MyBatis

MyBatis

概述

MyBatis是一个和数据库进行交互,持久层框架

传统JDBC

  1. 编写困难
  2. SQL语句以硬编码的形式写在程序中,产生耦合,不利于后期维护和升级

MyBatis

  1. 将重要的步骤,SQL语句的编写抽取出来可以进行人为定制,其它步骤交由框架自动完成
  2. SQL语句写在配置文件中,方便进行后期维护和升级
  3. 完全解决数据库的优化问题
  4. 既将Java代码与SQL语句解耦合,也不会失去自动化功能,半自动化的持久层框架
  5. MyBatis是一个轻量级的框架,底层是对原生JDBC的简单封装

流程

  1. 环境搭建

    • 创建一个Java工程
    • 创建测试库,测试表,以及封装数据的JavaBean,和操作数据库的dao接口
  2. 使用MyBatis操作数据库

    • 导包

      mybatis-3.5.2.jar
      mysql-connector-java-5.1.37-bin.jar
      log4j-1.2.17.jar(日志包用于调试,依赖于类路径下log4j.xml配置文件)

    • 写配置

      全局配置文件,指导MyBatis如何正确运行

      <?xml version="1.0" encoding="UTF-8" ?>
      <!DOCTYPE configuration
              PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
              "http://mybatis.org/dtd/mybatis-3-config.dtd">
      <configuration>
          <environments default="development">
              <environment id="development">
                  <transactionManager type="JDBC"/>
                  <!--配置连接池-->
                  <dataSource type="POOLED">
                      <property name="driver" value="com.mysql.jdbc.Driver"/>
                      <property name="url" value="jdbc:mysql://localhost:3306/mybatis_1015"/>
                      <property name="username" value="root"/>
                      <property name="password" value="1015"/>
                  </dataSource>
              </environment>
          </environments>
      
          <!--引入自定义的sql映射文件-->
          <mappers>
              <!--resource:默认从类路径下开始-->
              <mapper resource="EmployeeDao.xml"/>
          </mappers>
      </configuration>
      

      SQL映射文件,编写dao方法执行的SQL语句,相当于接口的实现类,配置之后需在全局配置文件中注册

      <?xml version="1.0" encoding="UTF-8" ?>
      <!DOCTYPE mapper
              PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
              "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
      <!--名称空间:接口全类名,即表示该配置文件作为哪个接口的实现类-->
      <mapper namespace="com.th1024.dao.EmployeeDao">
          <!--Employee getEmpById(Integer id);-->
          <!--
          select标签:定义一个查询操作
          id:实现的方法名
          resultType:方法返回值类型
          #{属性名}:获取传递的属性值
          -->
          <select id="getEmpById" resultType="com.th1024.bean.Employee">
              select * from t_employee where id = #{id}
          </select>
      </mapper>
      
    • 测试

      @Test
      public void test01() throws IOException {
          //根据全局配置文件创建一个SqlSessionFactory对象
          String resource = "mybatis-config.xml";
          InputStream inputStream = Resources.getResourceAsStream(resource);
          SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
          //获取和数据库的一次会话
          //SqlSession openSession = sqlSessionFactory.openSession();
          //获取dao接口的实现
          //EmployeeDao employeeDao = openSession.getMapper(EmployeeDao.class);
          //调用dao层的方法操作数据库
      
          try (SqlSession openSession = sqlSessionFactory.openSession()) {
              EmployeeDao employeeDao = openSession.getMapper(EmployeeDao.class);
              Employee employee = employeeDao.getEmpById(1);
              System.out.println(employee);
          }
      
      }
      

      细节

      1)SqlSessionFactory创建SqlSession对象,SqlSessionFactory只需创建一个对象,SqlSession是和数据库交互的一次会话,每次需要和数据库进行交互,就需要创建一个SqlSession对象

      2)SqlSession获取到的是接口的代理对象,由MyBatis自动创建

CRUD

使用MyBatis完成对数据库的增删改查操作

JavaBean

public class Employee {

    private Integer id;
    private String empName;
    private Integer gender;
    private String email;

Dao接口

public interface EmployeeDao {

    public Employee getEmpById(Integer id);
    public int updateEmp(Employee employee);
    public int insertEmp(Employee employee);
    public boolean deleteEmp(Integer id);

}

SQL映射文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--名称空间:接口全类名,即表示该配置文件作为哪个接口的实现类-->
<mapper namespace="com.th1024.dao.EmployeeDao">
    <!--
    public Employee getEmpById(Integer id);
    传入单个参数:
    基本类型:#{随便写}
    JavaBean:#{属性名}
    resultType:返回值类型
    -->
    <select id="getEmpById" resultType="com.th1024.bean.Employee">
        select * from t_employee where id = #{id}
    </select>

    <!--public int updateEmp(Employee employee);
    增删改不用写返回值类型,如果返回值类型为数值型(int,long)自动封装影响的行数,如果返回值类型为布尔型,影响0行则封装false,超过0行封装true
    #{属性名}:自动从传入的对象中获取对应属性的值
    -->
    <update id="updateEmp">
        update t_employee set empname = #{empName}, gender = #{gender}, email = #{email} where id = #{id}
    </update>

    <!--public int insertEmp(Employee employee);-->
    <insert id="insertEmp">
        insert into t_employee(`empname`,`gender`,`email`) values (#{empName},#{gender},#{email})
    </insert>

    <!--public boolean deleteEmp(Integer id);-->
    <delete id="deleteEmp">
        delete from t_employee where id = #{id}
    </delete>

</mapper>

之前在创建全局配置文件和SQL映射文件时,都是直接将其放在类路径下,现在可以在工程中创建一个源码文件夹,将SQL映射文件放于对应包名目录下,MyBatis可以自动识别

MyBatis


全局配置文件的部分细节

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!--使用properties标签引入外部配置文件-->
    <properties resource="dbconfig.properties"></properties>

    <!--进行一些mybatis运行时的设置-->
    <settings>
        <!--设置数据库列名以驼峰命名规则映射到JavaBean属性-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        
        <!--设置分步查询,按需加载-->
        <!--lazyLoadingEnabled:延迟加载-->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!--aggressiveLazyLoading:在3.4.1之后的版本默认为false-->
        <!--<setting name="aggressiveLazyLoading" value="false"/>-->
    </settings>
    
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <!--配置连接池-->
            <dataSource type="POOLED">
                <!--${}取值-->
                <property name="driver" value="${driverClass}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>

    <!--引入自定义的sql映射文件-->
    <mappers>
        <!--resource:默认从类路径下开始-->
        <!--<mapper resource="EmployeeDao.xml"/>-->
        <!--批量注册,name:dao所在的包名-->
        <package name="com.th1024.dao"/>
    </mappers>
</configuration>

SQL映射文件的部分细节

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--名称空间:接口全类名,即表示该配置文件作为哪个接口的实现类-->
<mapper namespace="com.th1024.dao.EmployeeDao">
    <!--
    public Employee getEmpById(Integer id);
    传入单个参数:
    基本类型:#{随便写}
    JavaBean:#{属性名}
    resultType:返回值类型
    -->
    <select id="getEmpById" resultType="com.th1024.bean.Employee">
        select * from t_employee where id = #{id}
    </select>

    <!--
    public Employee getEmpByIdAndEmpName(Integer id,String empName);
    传入多个参数:无法使用#{属性名}进行取值
    传入多个参数时,mybatis将参数自动封装到一个map中,key为参数的位置索引,例如#{0},#{1}或#{param1},#{param2}...#{paramN}
    利用注解@Param(),可以指定封装参数时所用的key
    -->
    <select id="getEmpByIdAndEmpName" resultType="com.th1024.bean.Employee">
        select * from t_employee where id = #{id} and empname = #{empName}
    </select>

    <!--
    public Employee getEmpByMap(Map<String,Object> map);
    传入参数为map:#{key}

    拓展:所有的参数都以map形式封装,因此只需正确使用key就可以成功取值
    public Employee getEmpByMap(@Param("id") Integer id,String empName,Employee employee);
    取值:
    id -> #{id}
    empName -> #{1}或#{param2}
    employee中的email属性 -> #{param3.email}
    -->
    <select id="getEmpByMap" resultType="com.th1024.bean.Employee">
        select * from t_employee where id = #{id} and empname = #{empName}
    </select>

    <!--
    #{}和${}的区别
    id = #{id} and empname = #{empName} -> select * from t_employee where id = ? and empname = ?:参数预编译,参数位置用占位符?代替,安全
    id = ${id} and empname = #{empName} -> select * from t_employee where id = 3 and empname = ?:直接进行拼串,存在sql注入问题
    一般使用#{}取值,在不支持预编译的时候(例如需要传入表名)使用${}
    -->
    <!--public List<Employee> getAllEmps();
    返回值类型为集合,resultType写的是集合的泛型-->
    <select id="getAllEmps" resultType="com.th1024.bean.Employee">
        select * from t_employee
    </select>

    <!--public Map<String,Object> getEmpByIdReturnMap(Integer id);
    返回值类型map,封装一条记录-->
    <select id="getEmpByIdReturnMap" resultType="map">
        select * from t_employee where id = #{id}
    </select>

    <!--public Map<Integer,Employee> getAllEmpsReturnMap();
    返回类型为map,封装多条记录,resultType写的是该map的value的泛型-->
    <select id="getAllEmpsReturnMap" resultType="com.th1024.bean.Employee">
        select * from t_employee
    </select>

    <!--public int updateEmp(Employee employee);
    增删改不用写返回值类型,如果返回值类型为数值型(int,long)自动封装影响的行数,如果返回值类型为布尔型,影响0行则封装false,超过0行封装true
    #{属性名}:自动从传入的对象中获取对应属性的值
    -->
    <update id="updateEmp">
        update t_employee set empname = #{empName}, gender = #{gender}, email = #{email} where id = #{id}
    </update>

    <!--public int insertEmp(Employee employee);-->
    <!--
    useGeneratedKeys:获取自增主键
    keyProperty:将获取到的自增主键的值赋值给哪个属性
    -->
    <insert id="insertEmp" useGeneratedKeys="true" keyProperty="id">
        insert into t_employee(`empname`,`gender`,`email`) values (#{empName},#{gender},#{email})
    </insert>

    <!--public boolean deleteEmp(Integer id);-->
    <delete id="deleteEmp">
        delete from t_employee where id = #{id}
    </delete>

</mapper>

resultMap自定义封装规则

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.th1024.dao.CatDao">
    <!--public Cat getCatById(Integer id);
    resultMap:使用自定义结果集映射
    -->
    <select id="getCatById" resultMap="mycat">
        select * from t_cat where id = #{id}
    </select>

    <!--
    JavaBean对象属性名和数据库列名不一致
    1. 在sql语句中起别名
    2. 使用自定义结果集
    -->
    <!--resultMap:自定义结果集映射,id:唯一标识,type:指定为哪个对象进行映射-->
    <resultMap id="mycat" type="com.th1024.bean.Cat">
        <!--指定主键列的映射规则,column:数据库列名,property:属性名-->
        <id column="id" property="id"/>
        <!--指定普通列映射规则-->
        <result column="c_name" property="name"/>
        <result column="c_age" property="age"/>
        <result column="c_gender" property="gender"/>
    </resultMap>

</mapper>

联合查询

创建两个JavaBean以及两张数据库表,利用MyBatis实现联合查询

JavaBean

public class Key {

    private Integer id;
    private String keyName;
    private Lock lock;
public class Lock {

    private Integer id;
    private String lockName;
    //对应多把钥匙
    private List<Key> keys;

Dao

public interface KeyDao {

    public Key getKeyById(Integer id);
}
public interface LockDao {

    public Lock getLockById(Integer id);
}

SQL映射文件

KeyDao.xml

<!--public Key getKeyById(Integer id);-->
<select id="getKeyById" resultMap="mykey">
    SELECT k.`id`,k.`key_name`,k.`lock_id`,l.`id` lid,l.`lock_name` FROM t_key k
    LEFT JOIN t_lock l ON k.`lock_id` = l.`id`
    WHERE k.`id` = #{id}
</select>

<resultMap id="mykey" type="com.th1024.bean.Key">
    <id property="id" column="id"/>
    <result property="keyName" column="key_name"/>
    <!--使用association标签完成联合查询出的对象封装,javaType:要封装的对象类型-->
    <association property="lock" javaType="com.th1024.bean.Lock">
        <id property="id" column="lid"/>
        <result property="lockName" column="lock_name"/>
    </association>
</resultMap>

LockDao.xml

<!--public Key getLockById(Integer id);-->
<select id="getLockById" resultMap="mylock">
    SELECT l.`id`,l.`lock_name`,k.`id` kid,k.`key_name`,k.`lock_id`
    FROM t_lock l LEFT JOIN t_key k ON l.`id`=k.`lock_id` WHERE l.`id`=#{id}
</select>

<resultMap id="mylock" type="com.th1024.bean.Lock">
    <id property="id" column="id" />
    <result property="lockName" column="lock_name"/>
    <!--collection标签:定义集合类型元素的封装,property:指定集合属性,ofType:指定集合内元素的类型-->
    <collection property="keys" ofType="com.th1024.bean.Key">
        <!--标签体中指定元素封装规则-->
        <id property="id" column="kid"/>
        <result property="keyName" column="key_name"/>
    </collection>
</resultMap>

分步查询,即在一次查询中分两步进行查询,可以完成对JavaBean属性的封装

全局配置文件中的配置

<!--设置分步查询,按需加载-->
<!--lazyLoadingEnabled:延迟加载-->
<setting name="lazyLoadingEnabled" value="true"/>
<!--aggressiveLazyLoading:在3.4.1之后的版本默认为false-->
<!--<setting name="aggressiveLazyLoading" value="false"/>-->

KeyDao.xml

<!--public Key getKeyByIdSimple(Integer id);-->
<select id="getKeyByIdSimple" resultMap="mykey01">
    select * from t_key where id = #{id}
</select>

<!--分步查询,可能存在性能问题和事务问题,所以实际工作中一般采用连接查询-->
<resultMap id="mykey01" type="com.th1024.bean.Key">
    <id property="id" column="id"/>
    <result property="keyName" column="key_name"/>
    <!--
    通过select属性,可以实现mybatis自动调用属性指定的方法查询数据
    传入参数?使用column属性,将属性指定的列的值传入
    collection属性操作类似-->
    <association property="lock" select="com.th1024.dao.LockDao.getLockByIdSimple" column="lock_id"></association>
</resultMap>

LockDao.xml

<!--public Lock getLockByIdSimple(Integer id);-->
<select id="getLockByIdSimple" resultType="com.th1024.bean.Lock">
    select * from t_lock where id = #{id}
</select>

动态SQL

通过MyBatis的if, choose, when, otherwise, trim, where, set, foreach等标签,可组合成非常灵活的SQL语句,提高SQL语句的准确性

Dao

public interface TeacherDao {

    public Teacher getTeacherById(Integer id);

    public List<Teacher> getTeacherByConditions(Teacher teacher);

    public List<Teacher> getTeacherIn(@Param("ids") List<Integer> ids);

    public List<Teacher> getTeacherByConditionsChoose(Teacher teacher);

    public Integer updateTeacher(Teacher teacher);

}

基本:where标签和if标签

if标签可以进行判断,如果结果为true则拼接上标签体内的SQL语句

where标签相当于SQL语句中的where,并可以去除if标签体内的SQL语句的多余的and或or

去除字符串操作同样可以使用trim标签,不过实际开发中建议使用where标签

<!--public List<Teacher> getTeacherByConditions(Teacher teacher);-->
<select id="getTeacherByConditions" resultType="com.th1024.bean.Teacher">
    select * from t_teacher
    <!--<where>-->
    <!--
    trim标签:截取字符串
    prefix:为整体添加一个前缀
    prefixOverrides:去除位于sql语句前面的多余字符串
    suffix:为整体添加一个后缀
    suffixOverrides:去除位于sql语句后面的多余的字符串

    推荐仍然使用where标签,并将所有的and写在sql语句的前面
    -->
    <trim prefix="where" prefixOverrides="and" suffixOverrides="and">
        <if test="id!=null">
            id > #{id} and
        </if>
        <!--name!=null && name!="" OGNL表达式,可以编写强大的判断条件-->
        <if test="name!=null &amp;&amp; !name.equals(&quot;&quot;)">
            name like #{name} and
        </if>
        <if test="birth!=null">
            birth &lt; #{birth} and
        </if>
    </trim>
    <!--</where>-->
</select>

foreach标签,遍历指定集合

<!--public List<Teacher> getTeacherIn(List<Integer> ids);-->
<select id="getTeacherIn" resultType="com.th1024.bean.Teacher">
    select * from t_teacher where id in
    <!--
    foreach:遍历指定集合
    collection:指定要遍历的集合
    separator:指定元素之间的分隔符
    item:为当前遍历出的元素指定一个变量名
    open:拼接到sql语句时以什么字符串开始
    close:拼接到sql语句时以什么字符串结束
    index:索引
        如果遍历的集合为一个List,则index为当前索引,item为当前遍历的元素的值
        如果遍历的集合是一个Map,则index为key,item为value
    -->
    <foreach collection="ids" close=")" item="id_item" open="(" separator=",">
        #{id_item}
    </foreach>
</select>

choose标签,查询条件中那个只要有任意一个查询条件满足即可,类似Java的Switch语句

<!--public List<Teacher> getTeacherByConditionsChoose(Teacher teacher);-->
<select id="getTeacherByConditionsChoose" resultType="com.th1024.bean.Teacher">
    <include refid="selectSql"></include>
    <where>
        <choose>
            <when test="id!=null">
                id = #{id}
            </when>
            <when test="name!=null and !name.equals(&quot;&quot;)">
                name = #{name}
            </when>
            <when test="birth!=null">
                birth&lt;#{birth}
            </when>
            <otherwise>
                1=1
            </otherwise>
        </choose>
    </where>
</select>

sql标签,抽取可重用的sql语句,配合include标签使用

<!--抽取可重用的sql语句,配合include标签使用-->
<sql id="selectSql">select * from t_teacher</sql>

set标签

<!--public Integer updateTeacher(Teacher teacher);-->
<update id="updateTeacher">
    update t_teacher
    <set>
        <if test="name!=null and !name.equals(&quot;&quot;)">
            name = #{name},
        </if>
        <if test="course!=null and !course.equals(&quot;&quot;)">
            course = #{course},
        </if>
        <if test="address!=null and !address.equals(&quot;&quot;)">
            address = #{address},
        </if>
        <if test="birth!=null">
            birth = #{birth}
        </if>
    </set>
    <where>
        id = #{id}
    </where>
</update>

缓存

MyBatis有缓存机制,可以暂时存储一些数据(存储在一个Map中),加快系统的查询速度

一级缓存,属于线程级别的缓存,SqlSession级别,默认存在

二级缓存,属于全局范围的缓存,namespace级别,需要进行配置才可以使用

一级缓存失效的几种情况

  1. 不同的SqlSession对应不同的一级缓存
  2. 同一个SqlSession但是查询条件不同
  3. 同一个SqlSession两次查询期间执行了任意一次增删改等操作
  4. 同一个SqlSession两次查询期间手动清空了缓存

SqlSession提交或关闭之后,一级缓存中的数据会转移到二级缓存中

如何使用二级缓存?

全局配置文件中配置二级缓存开启

<!--配置开启二级缓存-->
<setting name="cacheEnabled" value="true"/>

配置某个SQL映射文件,让其使用二级缓存

<!--使用二级缓存-->
<cache></cache>

缓存原理

MyBatis

上一篇:MySQL单表查询、聚合函数、多表连接查询、子查询


下一篇:Spring Boot学习笔记-员工管理系统(二)