巧说 Mybatis

67 阅读4分钟

巧说 Mybatis

核心配置文件详解

MyBatis 核心配置文件模板

<?xml version="1.0" encoding="utf8"?>
<!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"></transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="org/mybatis/a.xml"/> 
    </mappers>
</configuration>

environment

<environments default="development">
    <environment id="development">
        <transactionManager type="JDBC"></transactionManager>
        <dataSource type="POOLED">
            <property name="driver" value="${jdbc.driver}"/>
            <property name="url" value="${jdbc.url}"/>
            <property name="username" value="${jdbc.username}"/>
            <property name="password" value="${jdbc.password}"/>
        </dataSource>
    </environment>

    <environment id="test">
        <!--
            type:   JDBC / MANAGER
        -->
        <transactionManager type="JDBC"></transactionManager>
        <!--
            type:   POOLED / UNPOOLED / JNDI
                JNDI: 表示使用上下文中的数据源
        -->
        <dataSource type="POOLED">
            <property name="driver" value="com.mysql.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://localhost:3306/mysqldemo"/>
            <property name="username" value="root"/>
            <property name="password" value="root"/>
        </dataSource>
    </environment>
</environments>

typeAliases

<typeAliases>
    <!--
        typeAlias 设置某个类型别名:
        属性:
            type:设置需要设置别名的类型
            alias:设置某个类型别名  
    -->
    <typeAlias type="com.lyq.mybatis.Entity.Category" alias="Category"/>
    <!--以包为单位,将包下所有的类型设置默认的类型别名,即类名且不区分大小写-->
    <package name="com.lyq.mybatis.Entity"/>

</typeAliases>

properties

引入jdbc.properties,使用${变量名}的形式引入

<properties resource="jdbc.properties" />

mappers

<mappers>
    <!--<mapper resource="mapper/CategoryMapper.xml"></mapper>-->
    <!--
        以包为单位引入映射文件:
            要求 1: mapper 接口所在的包要和映射文件所在的包一致
            要求 2:mapper 接口要和映射文件的名字一样
    -->
    <package name="com.lyq.mybatis.mapper"/>
</mappers>

MyBatis 获取参数值的方式(重点)

当Mapper接口方法的参数为单个的字面量

    <!--User getUserById(String username);-->
    <select id="getUserById" resultType="User">
    select * from users where userName = '${userName}
    select * from users where userName = #{userName}
    </select>

当Mapper接口方法的参数为多个

  • 此时 MyBatis 会将这些变量存放在一个Map中,以两种方式存储
    1. 以arg0,arg1...为键,以参数为值
    2. 以 param1, param2...为键,以参数为值
<!--User checkLogin(String userName, String password);-->
<select id="checkLogin" resultType="User">
    select * from users 
    where userName = #{arg0} and password = #{arg1}
</select>

mapper 接口方法的参数是实体类类型的参数

  • 只需要通过 #{} 以属性的方式访问属性值即可,但是需要注意 ${} 的单引号问题
<!--int insertUserInfo(User user);     -->
<insert id="insertUserInfo" parameterType="User">
    insert into users values(#{userName}, #{password}, #{phone})
</insert>

特殊 SQL 的执行

模糊查询

<select id="getUserByLike" resultType="User">
    select * from users where userName like '%${userName}%'
</select>

批量删除

<delete id="deleteMore" parameterType="string">
    delete from users where id in (${id})
</delete>

获取自增主键

<!--
    useGeneratedKeys: 设置当前标签中的 sql 使用自增主键
    keyProperty: 将自增的主键的值赋值给传输到映射文件中参数的某个属性
-->
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
    insert into users values(null, #{userName}, #{password}, #{phone})
</insert>

自定义映射 resultMap

解决字段名和数据库字段名不一致问题

  • 为字段起别名
  • 添加 MyBatis 全局配置
<settings>
    <!--将下划线自动映射为驼峰   emp_name -> empName -->
    <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
  • 通过 resultMap 映射(一般用来处理:多对一 和 一对多 关系)
<mapper namespace="com.lyq.mybatis.mapper.EmpMapper">
    <resultMap id="empResultMap" type="Emp">
        <!--
               association :用来处理 多对一 映射关系
               collection  :用来处理 一对多 映射关系
               id          : 设置主键映射关系
               result      : 设置普通字段映射关系
               type        : 设置映射关系中的实体类类型
               property    : 该字段是 sql 语句查询出的字段名
               column      : 该字段是数据库表的字段名
        -->
        <id property="eid" column="eid"></id>
        <result property="empName" column="emp_name"/>
        <result property="age" column="age"/>
        <result property="sex" column="sex"/>
        <result property="email" column="email"/>
    </resultMap>
    <select id="getAllEmp" resultMap="empResultMap">
        select * from t_emp
    </select>
</mapper>

多对一映射关系

1. 级联属性赋值

```
<!-- 处理多对一映射关系方式一:级联属性赋值 -->
<resultMap id="empAndDeptResultMapOne" type="Emp">
    <id property="eid" column="eid"></id>
    <result property="empName" column="emp_name"></result>
    <result property="age" column="age"></result>
    <result property="sex" column="sex"></result>
    <result property="email" column="email"></result>
    <result property="dept.did" column="did"></result>
    <result property="dept.deptName" column="dept_name"></result>
</resultMap>
<select id="getEmpAndDept" resultMap="empAndDeptResultMapOne">
    select * from t_emp left join t_dept on t_emp.did = t_dept.did where t_emp.eid = #{eid}
</select>
```

2. 使用 association标签

```
    <resultMap id="empAndDeptResultMapTwo" type="Emp">
        <id property="eid" column="eid"></id>
        <result property="empName" column="emp_name"></result>
        <result property="age" column="age"></result>
        <result property="sex" column="sex"></result>
        <result property="email" column="email"></result>
        <association property="dept" javaType="Dept">
            <id property="did" column="did"></id>
            <result property="deptName" column="dept_name"></result>
        </association>
    </resultMap>
    <select id="getEmpAndDept" resultMap="empAndDeptResultMapTwo">
        select * from t_emp left join t_dept on t_emp.did = t_dept.did where t_emp.eid = #{eid}
    </select>
   ```
   

3. 分步查询

<!--Emp getEmpAndDeptByStepOne(@Param("eid") Integer eid);-->
<select id="getEmpAndDeptByStepOne" resultMap="empAndDeptByStepResultMapTwo">
    select * from t_emp where eid = #{eid}
</select>
<resultMap id="empAndDeptByStepResultMapTwo" type="Emp">
    <id property="eid" column="eid"></id>
    <result property="empName" column="emp_name"></result>
    <result property="age" column="age"></result>
    <result property="sex" column="sex"></result>
    <result property="email" column="email"></result>
    <!--
        select : 设置分布查询的 sql 的唯一标识(mapper接口的全类名.方法名)
        column : 设置分布查询的条件
    -->
    <association
            property="dept"
            select="com.lyq.mybatis.mapper.DeptMapper.getEmpAndDeptByStepTwo"
            column="did"
    ></association>
</resultMap>



<mapper namespace="com.lyq.mybatis.mapper.DeptMapper">
    <!--Dept getEmpAndDeptByStepTwo(@Param("did") Integer did);-->
    <select id="getEmpAndDeptByStepTwo" resultType="Dept">
        select * from t_dept where did = #{did}
    </select>
</mapper>
    

一对多映射关系

1. collection 方式

<select id="getDeptAndEmpt" resultMap="deptAndEmpResultMap">
    select * from t_dept left join t_emp on t_dept.did = t_emp.did where t_dept.did = #{did}
</select>
<resultMap id="deptAndEmpResultMap" type="Dept">
    <id property="did" column="did"></id>
    <result property="deptName" column="dept_name"></result>
    <collection property="emps" ofType="Emp">
        <id property="eid" column="eid"></id>
        <result property="empName" column="emp_name"></result>
        <result property="age" column="age"></result>
        <result property="sex" column="sex"></result>
        <result property="email" column="email"></result>
    </collection>
</resultMap>

2. 分步查询方式

延迟加载

全局配置文件

<!-- 设置 MyBatis 的全局配置 -->
<settings>
 <!--将下划线自动映射为驼峰   emp_name -> empName -->
 <setting name="mapUnderscoreToCamelCase" value="true"/>
 <!-- 延迟加载开启 -->
 <setting name="lazyLoadingEnabled" value="true"/>
</settings>


mapper文件
<resultMap id="empAndDeptByStepResultMapTwo" type="Emp">
 <id property="eid" column="eid"></id>
 <result property="empName" column="emp_name"></result>
 <result property="age" column="age"></result>
 <result property="sex" column="sex"></result>
 <result property="email" column="email"></result>
 <!--
     select : 设置分布查询的 sql 的唯一标识(mapper接口的全类名.方法名)
     column : 设置分布查询的条件
     fetchType : 当开启了全局的延迟加载之后,可通过此属性手动控制延迟加载的效果
         lazy: 标识延迟加载
         eager: 表示立即加载
 -->
 <association
         property="dept"
         select="com.lyq.mybatis.mapper.DeptMapper.getEmpAndDeptByStepTwo"
         column="did"
         fetchType="eager"
 ></association>
</resultMap>

动态 SQL

多条件查询 - if 标签

使用 if 标签,可以动态添加 sql 字段名进行查询

<mapper namespace="com.lyq.mybatis.mapper.DynamicSQLMapper">
    <!--List<Emp> getEmpByCondition(Emp emp);-->
    <select id="getEmpByCondition" resultType="Emp">
        select * from t_emp where 1=1
        <if test="empName != null and empName != ''">
            emp_name = #{empName}
        </if>
        <if test="age != null and age != ''">
            and age = #{age}
        </if>
        <if test="sex != null and sex != ''">
            and sex = #{sex}
        </if>
        <if test="email != null and email != ''">
            and email = #{email}
        </if>
    </select>
</mapper>

多条件查询 - where 标签

<select id="getEmpByCondition" resultType="Emp">
    select * from t_emp
    <where>
        <if test="empName != null and empName != ''">
            emp_name = #{empName}
        </if>
        <if test="age != null and age != ''">
             age = #{age}
        </if>
        <if test="sex != null and sex != ''">
             sex = #{sex}
        </if>
        <if test="email != null and email != ''">
             email = #{email}
        </if>
    </where>
</select>

trim 标签

  • prefix: 将trim标签中内容前面添加指定内容
  • suffix: 将trim标签中内容后面添加指定内容
  • prefixOverrides: 将trim标签中内容前面去除指定内容
  • suffixOverrides: 将trim标签中内容后面去除指定内容

forEach 标签

  • collection :集合名称
  • item:集合中的每一项
  • separator :分隔符号
  • open:括号
  • close:括号
<delete id="deleteMoreByArray">
    delete from t_emp where eid in
    <foreach collection="eids" item="eid" separator="," close=")" open="(">
        #{eid}
    </foreach>
</delete>
<insert id="insertMoreByList">
    insert into t_emp values
    <foreach collection="emps" item="emp" separator=",">
        (null, #{emp.empName}, #{emp.age}, #{emp.sex}, #{emp.email}, null)
    </foreach>
</insert>

sql 标签

<sql id="testSql">
    eid, emp_name
</sql>
<select id="test">
    select <include refid="testSql"></include> from t_emp
</select>

MyBatis 的缓存

一级缓存

一级缓存是sqlSession级别的,通过同一个sqlSession查询的数据会被缓存,下次查询相同的数据,就会从缓存中直接获取,不会在次访问数据库

使一级缓存失效的几种情况

  • 不同的 SqlSession 对应不同的一级缓存
  • 同一个 SqlSession 但是查询条件不同
  • 同一个 SqlSession 两次查询期间执行了任何一次增删操作
  • 同一个 SqlSession 两次查询期间手动清除缓存

二级缓存

二级缓存是SqlSessionFactory级别,通过同一个 SqlSessionFactory 创建的 SqlSession 查询的结果会被缓存;此后若是再次执行相同的查询语句,结果就会从缓存中获取

二级缓存开启条件:

  1. 在核心配置文件中,设置全局配置属性 cacheEnabled = "true", 默认为 true, 不需要设置
  2. 在映射文件中设置标签 <cache/>
  3. 二级缓存必须在 SqlSession 关闭或提交之后有效
  4. 查询的数据缩转换的实体类类型必须实现序列化的接口

使二级缓存失效的情况:

  • 两次查询之间执行了任意的增删改,会让一级缓存和二级缓存同时失效

二级缓存相关配置

在 mapper 配置文件中添加的 cache 标签中可以设置的属性:

  • eviction 属性:缓存回收策略

    • LRU
    • FIFO
    • SOFT :软引用
    • WEAK :弱引用
    • 默认的是 LRU
  • flushInterval 属性:刷新间隔,单位是毫秒,默认没有间隔

  • size 属性:引用数目,代表缓存最多可以存储多少个对象

  • readOnly 属性:只读,true / false

    • true:会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改
    • false:会返回缓存对象的拷贝(通过序列化)。安全

MyBatis 缓存查询顺序

  1. 先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的程序,可以拿来直接使用
  2. 如果二级缓存没有命中,在查询一级缓存
  3. 如果一级缓存也没有命中,就查询数据库
  4. SqlSession 关闭之后,一级缓存中的数据会写入二级缓存

MyBatis 分页插件

  1. 安装依赖
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.2.0</version>
</dependency>
  1. 配置分页插件

在 MyBatis 的核心配置文件mybatis-config.xml中配置插件

<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
  1. 使用分页插件
/**
 * limit index pageSize
 * index:当前页起始索引
 * pageSize:每页显示的条数
 * pageNum:当前页的页码
 * index = (pageNum - 1) * pageSize
 * @throws Exception
 */
@Test
public void testPageHelper() throws Exception{
    InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
    SqlSession sqlSession = sqlSessionFactory.openSession(true);
    EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
    // 开启分页
    // 第一个参数是:从第几页开始
    // 第二个参数是:每一页多少条数据
    PageHelper.startPage(1, 4);
    List<Emp> allEmp = mapper.getAllEmp();
    for (Emp emp : allEmp) {
        System.out.println(emp);
    }

}