第二章Mybatis进阶操作学习

特殊SQL的执行

模糊查询

<mapper namespace="com.lsc.mybatis.Mapper.SpecialSQLMapper">
        <!--    List<User> getUserByLike(@Param("mohu") String mohu);-->
       <select id="getUserByLike" resultType="User">-->
          	select * from t_user where username like "%${mohu}%"-->
       </select>-->
       <select id="getUserByLike" resultType="User">-->
        	select * from t_user where username like "%"#{mohu}"%"-->
       </select>-->
     
       <select id="getUserByLike" resultType="User">
         	 select * from t_user where username like concat('%',#{mohu},'%')
       </select>
</mapper>
 /**
     * 通过用户名模糊查询用户信息
     * @param mohu
     * @return
     */
    List<User> getUserByLike(@Param("mohu") String mohu);
  • 最主要是对于我们这种%%和我们的参数进行拼接
    • 第一种直接使用${},不会自动加’’
    • 第二种"%" “%”
    • 第三种利用concat函数进行拼接

批量操作

<mapper namespace="com.lsc.mybatis.Mapper.SpecialSQLMapper">
            <delete id="deleteMoreUser">
                delete from t_user where id in (${ids})
            </delete>
</mapper>
  /**
     * 批量删除
     * @param ids
     */
    int  deleteMoreUser(@Param("ids") String ids);

动态设置表名

/**
     * 动态设置表名,查询用户信息
     * @param tableName
     * @return
     */
    List<User> getUserList(@Param("tableName") String tableName);
 <mapper namespace="com.lsc.mybatis.Mapper.SpecialSQLMapper">
 <!--    List<User> getUserList(@Param("tableName") String tableName);-->
            <select id="getUserList" resultType="User">
                select * from ${tableName}
            </select>
</mapper>

添加功能获取自增的主键

场景模拟:
t_clazz(clazz_id,clazz_name)
t_student(student_id,student_name,clazz_id)
1、添加班级信息
2、获取新添加的班级的id
3、为班级分配学生,即将某学的班级id修改为新添加的班级的id

  • 所以有时候对新增数据的主键是很有必要的
 /**
     * 添加用户信息并获取自增的主键
     * @param user
     */
    void insertUser(User user);
 <mapper namespace="com.lsc.mybatis.Mapper.SpecialSQLMapper">
 <!--    void insertUser(User user);-->
            <insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
                insert into t_user values(null,#{username},#{pwd},#{age},#{gender},#{email})
            </insert>
</mapper>
  • useGeneratedKeys:设置使用自增的主键
  • keyProperty:因为增删改有统一的返回值是受影响的行数,因此只能将获取的自增的主键放在传输的参数user对象的某个属性中
 @Test
    public void insertUserTest(){
        SqlSession sqlSession= SqlSessionUtil.getSqlSession();
        SpecialSQLMapper specialSQLMapper=sqlSession.getMapper(SpecialSQLMapper.class);
        User user=new User(null,"小寿司","123",1,"男","xss@163.com");
        specialSQLMapper.insertUser(user);
        System.out.println(user);
    }
    //输出结果
    //User{id=13, username='小寿司', pwd='123', age=1, gender='男', email='xss@163.com'}
  • 我们进行执行的User的id是null.但是执行insert后的User中有了对应的id属性的值

自定义映射resultMap

resultMap:设置自定义的映射关系

  • ​ id:唯一标识
  • ​ type:处理映射关系的实体类的类型
  • ​ 常用的标签:
    • id:处理主键和实体类中属性的映射关系
      • column:设置映射关系中的字段名,必须是sql查询出的某个字段
      • property:设置映射关系中的属性的属性名,必须是处理的实体类类型中的属性名
    • result:处理普通字段和实体类中属性的映射关系
      • column:设置映射关系中的字段名,必须是sql查询出的某个字段
      • property:设置映射关系中的属性的属性名,必须是处理的实体类类型中的属性名
    • association:处理一对一的映射关系(处理实体类类型的属性)
      • property:设置需要处理映射关系的属性的属性名
      • javaType:设置要处理的属性的类型
    • collection:处理一对多的映射关系(处理集合类型的属性)
      • property:设置需要处理映射关系的属性的属性名
      • ofType:设置collection标签所处理的集合属性中存储数据的类型
  • 这里的一对一和一对多前面的表示属性,后面表示记录

处理字段和属性的映射关系

  • 在前面的例子中,我们的属性名称和字段名称是一样的,所以不需要特意设置,但是也会有属性名和字段名不一样的情况,就需要我们主动去设置

image-20230110165232648

/**
     * 通过Id查询对应的Emp数据
     * @param id
     * @return
     */
    Emp getEmpById(@Param("id")Integer id);

在SQL语句中使用别名

<mapper namespace="com.lsc.mybatis.mappers.EmpMapper">
    <!--   Emp getEmpById(@Param("id")Integer id);-->
    <select id="getEmpById" resultType="Emp">
        select emp_id as empId, emp_name as empName,age,gender,dept_id as deptId from t_emp where emp_id = #{id}
    </select>
</mapper>
  • 为查询的字段设置别名,和属性名保持一致

使用核心配置文件中的驼峰对应方法

mybatis对应的核心配置文件的设置

  <settings>
        <!--将下划线映射为驼峰-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>

    </settings>

EmpMapper.xml的配置

<mapper namespace="com.lsc.mybatis.mappers.EmpMapper">
    <!--   Emp getEmpById(@Param("id")Integer id);-->
    <select id="getEmpById" resultType="Emp">
        select * from  t_emp where emp_id= #{id}
    </select>
</mapper>
  • 当我们开启了这个配置,不需要取别名也能成功获取数据到对应的属性上
  • 字段符合MySQL的要求使用_,而属性符合java的要求使用驼峰
    • 此时可以在MyBatis的核心配置文件中设置一个全局配置,可以自动将下划线映射为驼峰
      • emp_id->empId , emp_name->empName dept_id->deptId

使用resultMap自定义映射处理

<mapper namespace="com.lsc.mybatis.mappers.EmpMapper">
    <resultMap id="empResultMap" type="Emp">
        <id column="emp_id" property="empId"></id>
        <result column="emp_name" property="empName"></result>
        <result column="age" property="age"></result>
        <result column="gender" property="gender"></result>
        <result column="dept_id" property="deptId"></result>
    </resultMap>
    <!-- Emp getEmpById(@Param("id")Integer id);-->
    <select id="getEmpById" resultMap="empResultMap">
        select * from  t_emp where emp_id= #{id}
    </select>
</mapper>
  • <resultMap id="empResultMap" type="Emp"> id表示resultMap的id标志 type表示表示返回的实体类型,Emp是别名
  • <id column="emp_id" property="empId"></id> id表示处理主键和实体类中属性的映射关系
    • column表示表中的主键名称, property表示实体类的属性名,表示对应
  • <result column="emp_name" property="empName"></result> result表示处理普通字段和实体类中属性的映射关系
    • column表示表中的字段名称, property表示实体类的属性名,表示对应

一对一映射处理

image-20230110211424156

public interface EmpMapper {
    /**
     * 通过Id查询对应的Emp数据
     * @param id
     * @return
     */
    Emp getEmpById(@Param("id")Integer id);
}

级联方式处理

<mapper namespace="com.lsc.mybatis.mappers.EmpMapper">
    <resultMap id="empResultMap" type="Emp">
        <id column="emp_id" property="empId"></id>
        <result column="emp_name" property="empName"></result>
        <result column="age" property="age"></result>
        <result column="gender" property="gender"></result>
        <result column="dept_id" property="dept.deptId"></result>
        <result column="dept_name" property="dept.deptName"></result>
    </resultMap>
    <!--  Emp getEmpById(@Param("id")Integer id);-->
    <select id="getEmpById" resultMap="empResultMap">
        select emp.*,dept.* from t_emp as emp left join t_dept as dept on emp.emp_id=dept.dept_id where emp_id=#{id}
    </select>
</mapper>
  • 利用联表查询到两张表中的所有数据 ,然后利用级联方式进行对应数据
    • <result column="dept_id" property="dept.deptId"></result>
    • <result column="dept_name" property="dept.deptName"></result>

association标签

 <resultMap id="empResultMapTwo" type="Emp">
        <id column="emp_id" property="empId"></id>
        <result column="emp_name" property="empName"></result>
        <result column="age" property="age"></result>
        <result column="gender" property="gender"></result>
       <association  javaType="Dept" property="dept">
           <id column="dept_id" property="deptId"></id>
           <result column="dept_name" property="deptName"></result>
       </association>
    </resultMap>
        <!--    Emp getEmpById(@Param("id")Integer id);-->
        <select id="getEmpById" resultMap="empResultMapTwo">
            select emp.*,dept.* from t_emp as emp left join t_dept as dept on emp.emp_id=dept.dept_id where emp_id=#{id}
        </select>
  • assocation标签就是专门处理这种一个属性对应表中一列记录的情况
    • <association javaType="Dept" property="dept">
      • javaType表示转换成的Java对象的类型 Dept是别名
      • property表示对应的属性名称

分步查询

EmpMapper

public interface EmpMapper {
    /**
     * 通过Id分步查询出对应的Emp数据
     * @param empId
     * @return
     */
    Emp getEmpAndDeptByStepOne(@Param("empId") Integer empId);
}

DeptMapper

public interface DeptMapper {
    /**
     * 根据DeptId查询出对应Dept数据
     * @param DeptId
     * @return
     */
    Dept getDept(@Param("DeptId") Integer DeptId);
}

DeptMapper.xml

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

EmpMapper.xml

<mapper namespace="com.lsc.mybatis.mappers.EmpMapper">

    <resultMap id="empResultMapThree" type="Emp">
        <id column="emp_id" property="empId"></id>
        <result column="emp_name" property="empName"></result>
        <result column="age" property="age"></result>
        <result column="gender" property="gender"></result>
        <association   property="dept" fetchType="lazy"
        select="com.lsc.mybatis.mappers.DeptMapper.getDept" column="dept_id">
        </association>
    </resultMap>
    <!--Emp getEmpAndDeptByStepOne(@Param("empId") Integer empId);-->
    <select id="getEmpAndDeptByStepOne" resultMap="empResultMapThree">
        select * from t_emp where emp_id = #{empId}
    </select>
</mapper>
  • property:设置需要处理映射关系的属性的属性名

  • select:设置分步查询的sql的唯一标识 也就是这个方法的全名成(包名+类名+方法名)

  • column:将查询出的某个字段作为分步查询的sql的条件

  • fetchType:在开启了延迟加载的环境中,通过该属性设置当前的分步查询是否使用延迟加载

    • fetchType=“eager(立即加载)|lazy(延迟加载)”

在核心配置文件中的设置

 <settings>
        <!--开启延迟加载-->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!--按需加载-->
        <setting name="aggressiveLazyLoading" value="false"/>
    </settings>

分步查询的优点:可以实现延迟加载

但是必须在核心配置文件中设置全局配置信息:

  • lazyLoadingEnabled:延迟加载的全局开关。当开启时,所有关联对象都会延迟加载
  • aggressiveLazyLoading:当开启时,任何方法的调用都会加载该对象的所有属性。否则,每个属性会按需加载
  • 两个属性这样设置,此时就可以实现按需加载,获取的数据是什么,就只会执行相应的sql

Mybatis仅支持association关联对象,和collection关联集合对象的延迟加载,association指的是一对一,collection指的是一对多(这里是指对象和表中记录的关系)

原理

  • 使用CGLIB创建目标对象的代理对象,当调用目标方法时候,进入拦截器方法,比如调用a.getB().getName()方法,拦截器的invoke方法发现a.getB()是null值,那么就会单独发送事先保存好的关联B对象的sql,把B查询上来,然后调用a.setB(b),,于是a的对象b属性有值了,接着完成a.getB().getName()方法的调用
  • 几乎所有延迟加载的原理都是这样

假设我们使用测试方法测试延迟加载,先调用一个查询,查询得出结果赋给对象a,里面包含未加载的null值项,假设这一项为类B的引用——b,并且假设类B拥有get方法getName()。当我们调用a.getB().getName()时,此时对象a中的成员对象b为null,无法调用其的getName()方法,此时Mybatis意识到要使用延迟加载来使此次调用不出Bug。

Mybatis使用CGLib生成目标对象a的代理对象,当我们在测试方法里调用a.getB()方法时,结果为null,不可行。于是,Mybatis调用拦截器方法,使用事先在resultMap中的association或collection标签里设定好的select查询语句来获取数据库中的数据并映射到对象b上,此时b不再为null,a.getB().getName()顺利调用,实现了按需加载、延迟加载,节省了宝贵的计算资源。

一对多映射处理

image-20230110211526982

/**
 * 获得部门信息和部门对应的所有人的信息
 * @param deptId
 * @return
 */
Dept getEmpAndDeptResultMap(@Param("deptId") Integer deptId);

collection

<mapper namespace="com.lsc.mybatis.mappers.DeptMapper">
<resultMap id="deptAndEmpResultMap" type="Dept">
        <id column="dept_id" property="deptId"></id>
        <result column="dept_name" property="deptName"></result>
        <!--
            ofType:设置集合类型的属性中存储的数据的类型
        -->
       <collection property="empList" ofType="Emp">
           <id property="empId" column="emp_id"></id>
           <result property="empName" column="emp_name"></result>
           <result property="gender" column="gender"></result>
           <result property="age" column="age"></result>
       </collection>
    </resultMap>
<!--    Dept getEmpAndDeptResultMap(@Param("deptId") Integer deptId);-->
     <select id="getEmpAndDeptResultMap" resultMap="deptAndEmpResultMap">
         select *
         from t_dept
         left join t_emp
         on t_dept.dept_id=t_emp.dept_id
         where t_dept.dept_id=#{deptId}
     </select>
</mapper>
  • connection处理一对多的这种映射关系,也就是说一个属性对应多条数据
    • ofType设置集合类型的属性中存储的数据的类型

分步查询

DeptMapper

 /**
     * 分步获得部门信息和部门对应的所有人的信息
     * @param deptId
     * @return
     */
    Dept getEmpAndDeptTwoStep(@Param("deptId") Integer deptId);

EmpMapper

  /**
     * 根据deptId来获取所有的Emp
     * @param deptId
     * @return
     */
     List<Emp> getDeptAndEmpByStepTwo(@Param("deptId") Integer deptId);
<mapper namespace="com.lsc.mybatis.mappers.DeptMapper">
<resultMap id="deptAndEmpResultMapByStep" type="Dept">
        <id column="dept_id" property="deptId"></id>
        <result column="dept_name" property="deptName"></result>
        <collection property="empList"
        select="com.lsc.mybatis.mappers.EmpMapper.getDeptAndEmpByStepTwo"
        column="dept_id"></collection>
    </resultMap>
<!--    Dept getEmpAndDeptTwoStep(@Param("deptId") Integer deptId);-->
    <select id="getEmpAndDeptTwoStep" resultMap="deptAndEmpResultMapByStep">
        select * from t_dept where dept_id=#{deptId}
    </select>
</mapper>

动态SQL

动态 sql 是Mybatis的强大特性之一,能够完成不同条件下不同的 sql 拼接。

  • 如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦

  • 它存在的意义是为了 解决 拼接SQL语句字符串时的痛点问题。

if标签

if标签可通过test属性的表达式进行判断,若表达式的结果为true,则标签中的内容会执行;反之标签中的内容不会执行

 <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 != ''">
            and age=#{age}
        </if>
        <if test="gender != null and gender != ''">
            and gender=#{gender}
        </if>
    </select>
  • 如果empName为null,那么开头会多出来一个and
  • 如果都为null,那么会只出现一个where

使用1=1解决and问题

DynamicMapper

 /**
     * 根据条件查询员工信息
     * @param emp
     * @return
     */
    List<Emp> getEmpByCondition(Emp emp);
<mapper namespace="com.lsc.mybatis.mappers.DynamicMapper">
    <!--    List<Emp> getEmpByCondition(Emp emp);-->
    <select id="getEmpByCondition" resultType="Emp">
        select * from t_emp where 1=1
        <if test="empName!=null and empName !=''">
            and emp_name=#{empName}
        </if>
        <if test="age != null and age != ''">
            and age=#{age}
        </if>
        <if test="gender != null and gender != ''">
            and gender=#{gender}
        </if>
    </select>
</mapper>

1

使用where标签解决问题

<mapper namespace="com.lsc.mybatis.mappers.DynamicMapper">
<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 != ''">
                  and age=#{age}
            </if>
            <if test="gender != null and gender != ''">
                  and gender=#{gender}
            </if>
        </where>
    </select>
</mapper>    
    

where和if一般结合使用:

  • 若where标签中的if条件都不满足,则where标签没有任何功能,即不会添加where关键字
  • 若where标签中的if条件满足,则where标签会自动添加where关键字,并将条件最前方多余的and去掉
  • 注意:where标签不能去掉条件最后多余的and

利用trim解决问题

trim用于去掉或添加标签中的内容
常用属性:

  • prefix:表示整个语句块,以prefix的值作为前缀
  • suffix:表示整个语句块,以suffix的值作为后缀
  • prefixOverrides:表示整个语句块要去除掉的前缀
  • suffixOverrides:表示整个语句块要去除掉的后缀

如果为内容为空串,那么都不会生效

<mapper namespace="com.lsc.mybatis.mappers.DynamicMapper">
<select id="getEmpByCondition" resultType="Emp">
        select * from t_emp
        <trim prefix="where" suffixOverrides="and">
            <if test="empName != null and empName != ''">
                emp_name = #{empName} and
            </if>
            <if test="age != null and age != ''">
                age = #{age} and
            </if>
            <if test="gender != null and gender != ''">
                gender = #{gender}
            </if>
        </trim>
    </select>
</mapper>
  • 在前面加一个 prefix=“where”

choose、when、otherwise

相当于java中的if…else if…else

DynamicMapper

 /**
     * 使用choose查询员工信息
     * @param emp
     * @return
     */
    List<Emp> getEmpByChoose(Emp emp);
<mapper namespace="com.lsc.mybatis.mappers.DynamicMapper">
<!--    List<Emp> getEmpByChoose(Emp emp);-->
    <select id="getEmpByChoose" resultType="Emp">
        select * from t_emp
        <where>
            <choose>
                <when test="empName != null and empName != ''">
                    emp_name=#{empName}
                </when>
                <when test="age !=null and age!=''">
                    age=#{age}
                </when>
                <when test="gender != null and gender !=''">
                    gender= #{gender}
                </when>
            </choose>
        </where>
    </select>
 </mapper>   

在这里插入图片描述

  • choose是when和otherwise的父标签
  • when只要满足了一个,后面的就不会执行,如果when都不满足,就执行ontherwise,很类似我们swtich case带break的结构

foreach

用于迭代的标签

/**
     * 批量添加员工信息
     * @param emps
     */
    void insertMoreEmp(@Param("emps") List<Emp> emps);

    /**
     * 批量删除
     * @param emplds
     */
    void deleteMoreEmp(@Param("emplds") Integer[] emplds);
<mapper namespace="com.lsc.mybatis.mappers.DynamicMapper">
<!--    void insertMoreEmp(@Param("emps") List<Emp> emps);-->
    <insert id="insertMoreEmp" >
        insert into t_emp values
        <foreach collection="emps" item="emp" separator=",">
            (null,#{emp.empName},#{emp.age},#{emp.gender},null)
        </foreach>
    </insert>
     <!--    void deleteMoreEmp(@Param("empIds") Integer[] empIds);-->
	<delete id="deleteMoreEmp">
        delete from t_emp where emp_id in
        <foreach collection="emplds" item="empId" separator="," open="(" close=")">
            #{empId}
        </foreach>
    </delete>
  <!--     一样的效果-->
   <!--    void deleteMoreEmp(@Param("empIds") Integer[] empIds);-->
    <delete id="deleteMoreEmp">
        delete from t_emp where
        <foreach collection="empIds" item="empId" separator="or">
            emp_id = #{empId}
        </foreach>
    </delete>
</mapper>
    

在这里插入图片描述

  • collection:设置要循环的数组或集合
  • item:用一个字符串表示数组或集合中的每一个数据
  • separator:设置每次循环的数据之间的分隔符
  • open:循环的所有内容以什么开始
  • close:循环的所有内容以什么结束

set标签

根据传入的用户对象属性来更新用户数据,可以使用标签来指定动态内容。

/**
     * 利用set标签更新
     * @param emp
     */
    void updateEmpBySet( Emp emp);
<mapper namespace="com.lsc.mybatis.mappers.DynamicMapper">
<!--    void updateEmpBySet(@Param("emp") Emp emp);-->
    <update id="updateEmpBySet">
        update t_emp
        <set>
            <if test="empName!=null and empName !=''">
                 emp_name=#{empName}
            </if>
            <if test="age != null and age != ''">
                  ,age=#{age}
            </if>
            <if test="gender != null and gender != ''">
                 ,gender=#{gender}
            </if>
        </set>
        where emp_id= #{empId}
    </update>
</mapper>

在这里插入图片描述

  • 跟where标签很相似

SQL片段

sql片段,可以记录一段公共sql片段,在使用的地方通过include标签进行引入

 <sql id="empColumns">
         emp_id,emp_name,age,gender,dept_id
</sql>
select <include refid="empColumns"></include> from t_emp

Mybatis缓存

  • 我们的浏览器也有缓存,当我们访问一些web资源,会把一些资源存储在本地,下次再次访问这些资源,就不需要从服务器获取,直接从本地获取
  • Mybatis查询的数据会被缓存,下次查询相同的数据,就会从缓存中直接获取,不会从数据库重新访问

CacheMapper接口

public interface CacheMapper {
    /**
     * 根据员工Id查询员工信息
     * @param empId
     * @return
     */
    Emp getEmpById(@Param("empId") Integer empId);

    /**
     * 添加员工信息
     * @param emp
     */
    void insertEmp(Emp emp);
}

Cachemapper.xml文件

<mapper namespace="com.lsc.mybatis.mappers.CacheMapper">
        <!--    Emp getEmpById(@Param("empId") Integer empId);-->
        <select id="getEmpById" resultType="Emp">
            select * from t_Emp where emp_id=#{empId}
        </select>
        <!--    void insertEmp(Emp emp);-->
        <insert id="insertEmp" >
            insert into t_emp values(null,#{empName},#{age},#{gender},null)
        </insert>
</mapper>

一级缓存

一级缓存是SqlSession级别的

 public void getEmpByIdTest() throws IOException {
        InputStream is= Resources.getResourceAsStream("mybatis_config.xml");
        SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(is);
        SqlSession sqlSession=sqlSessionFactory.openSession();
        CacheMapper cacheMapper=sqlSession.getMapper(CacheMapper.class);
        Emp emp1=cacheMapper.getEmpById(1);
        Emp emp2=cacheMapper.getEmpById(1);
        System.out.println(emp1);
        System.out.println(emp2);
//        SqlSession sqlSession1=sqlSessionFactory.openSession();

 }
DEBUG 01-11 23:01:39,518 ==>  Preparing: select * from t_Emp where emp_id=?  (BaseJdbcLogger.java:137) 
DEBUG 01-11 23:01:39,545 ==> Parameters: 1(Integer)  (BaseJdbcLogger.java:137) 
DEBUG 01-11 23:01:39,563 <==      Total: 1  (BaseJdbcLogger.java:137) 
Emp{empId=1, empName='lsc', gender='男', age=22, deptId=1}
Emp{empId=1, empName='lsc', gender='男', age=22, deptId=1}
  • 我们发现我们通过同一个sqlSession来执行了两次getEmpById,但是从结果中我们看出来的只从数据库中查询了一次
    • 说明了第二次查询是从缓存中获得数据

image-20230112030929050

  • 每个SqlSession中都持有Excutor,每个Excutor中有一个LocalCache。当用户发起询问时,MyBatis根据当前执行的语句生成MappedStatement,在Local Cache进行查询,如果缓存命中的话,直接返回结果给用户,如果缓存没有命中的话,查询数据库,结果写入Local Cache,最后返回结果给用户。
  • Mybatis一级缓存内部设计简单,只是一个没有容量限定的HashMap,在缓存的功能性上有所欠缺

使一级缓存失效的四种情况:

  • 不同的SqlSession对应不同的一级缓存
  • 同一个SqlSession但是查询条件不同
    • 缓存也是从数据库查出来的,如果缓存中都没有数据,那么肯定用不了缓存,需要从数据库查
  • 同一个SqlSession两次查询期间执行了任何一次增删改操作
    • 因为发生增删改,可能会导致缓存中的数据跟我们的数据库中不一样
  • 同一个SqlSession两次查询期间手动清空了缓存
   @Test
    public void getEmpByIdTest() throws IOException {
        InputStream is= Resources.getResourceAsStream("mybatis_config.xml");
        SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(is);

        SqlSession sqlSession1=sqlSessionFactory.openSession(true);
        CacheMapper cacheMapper1=sqlSession1.getMapper(CacheMapper.class);
        Emp emp1=cacheMapper1.getEmpById(1);

        SqlSession sqlSession2=sqlSessionFactory.openSession(true);
        CacheMapper cacheMapper2=sqlSession2.getMapper(CacheMapper.class);
        Emp emp2=cacheMapper2.getEmpById(1);

        System.out.println(emp1);
        System.out.println(emp2);
//        SqlSession sqlSession1=sqlSessionFactory.openSession();

}
//输出结果
DEBUG 01-11 23:13:45,519 ==>  Preparing: select * from t_Emp where emp_id=?  (BaseJdbcLogger.java:137) 
DEBUG 01-11 23:13:45,547 ==> Parameters: 1(Integer)  (BaseJdbcLogger.java:137) 
DEBUG 01-11 23:13:45,564 <==      Total: 1  (BaseJdbcLogger.java:137) 

DEBUG 01-11 23:13:45,600 ==>  Preparing: select * from t_Emp where emp_id=?  (BaseJdbcLogger.java:137) 
DEBUG 01-11 23:13:45,600 ==> Parameters: 1(Integer)  (BaseJdbcLogger.java:137) 
DEBUG 01-11 23:13:45,603 <==      Total: 1  (BaseJdbcLogger.java:137) 
    
Emp{empId=1, empName='lsc', gender='男', age=22, deptId=1}
Emp{empId=1, empName='lsc', gender='男', age=22, deptId=1}
  • 我们发现从不同的SqlSession中查询,一级缓存失效了
 @Test
    public void getEmpByIdTest() throws IOException {
        InputStream is= Resources.getResourceAsStream("mybatis_config.xml");
        SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(is);

        SqlSession sqlSession1=sqlSessionFactory.openSession(true);
        CacheMapper cacheMapper1=sqlSession1.getMapper(CacheMapper.class);
        Emp emp1=cacheMapper1.getEmpById(1);
        cacheMapper1.insertEmp(new Emp(null,"ABC",null,null,null));
        Emp emp2=cacheMapper1.getEmpById(1);
        System.out.println(emp1);
        System.out.println(emp2);
        sqlSession1.close();
        sqlSession2.close();
    }
DEBUG 01-11 23:20:30,168 ==>  Preparing: select * from t_Emp where emp_id=?  (BaseJdbcLogger.java:137) 
DEBUG 01-11 23:20:30,191 ==> Parameters: 1(Integer)  (BaseJdbcLogger.java:137) 
DEBUG 01-11 23:20:30,209 <==      Total: 1  (BaseJdbcLogger.java:137) 
    
DEBUG 01-11 23:20:30,211 ==>  Preparing: insert into t_emp values(null,?,?,?,null)  (BaseJdbcLogger.java:137) 
DEBUG 01-11 23:20:30,213 ==> Parameters: ABC(String), null, null  (BaseJdbcLogger.java:137) 
DEBUG 01-11 23:20:30,218 <==    Updates: 1  (BaseJdbcLogger.java:137) 
    
DEBUG 01-11 23:20:30,219 ==>  Preparing: select * from t_Emp where emp_id=?  (BaseJdbcLogger.java:137) 
DEBUG 01-11 23:20:30,219 ==> Parameters: 1(Integer)  (BaseJdbcLogger.java:137) 
DEBUG 01-11 23:20:30,221 <==      Total: 1  (BaseJdbcLogger.java:137) 
Emp{empId=1, empName='lsc', gender='男', age=22, deptId=1}
Emp{empId=1, empName='lsc', gender='男', age=22, deptId=1}
  • 我们在同一个sqlSession下,查询相同的数据,因为中间有一个插入操作,就导致了一级缓存失效

MyBatis的二级缓存

  • 二级缓存是SqlSessionFactory级别,通过同一个SqlSessionFactory创建的SqlSession查询的结果会被缓存;此后若再次执行相同的查询语句,结果就会从缓存中获取
  • 我们的一级缓存是默认开启的,但是我们的二级缓存需要我们手动配置

image-20230112031401956

二级缓存开启后,同一个namespace下的所有操作语句,都影响着同一个Cache,即二级缓存被多个SqlSession共享,是一个全局的变量。
当开启缓存后,数据的查询执行的流程就是 二级缓存 -> 一级缓存 -> 数据库。

二级缓存开启的条件

  • 在核心配置文件中,设置全局配置属性cacheEnabled=“true”,默认为true,不需要设置
  • 在映射文件中设置标签<cache/>
  • 二级缓存必须在SqlSession关闭或提交之后有效
  • 查询的数据所转换的实体类类型必须实现序列化的接口
  public void TestCacheTwo() throws IOException {
        InputStream is = Resources.getResourceAsStream("mybatis_config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);

        SqlSession sqlSession1 = sqlSessionFactory.openSession(true);
        CacheMapper mapper1 = sqlSession1.getMapper(CacheMapper.class);
        Emp emp1 = mapper1.getEmpById(1);
        System.out.println(emp1);
        sqlSession1.close();

        SqlSession sqlSession2 = sqlSessionFactory.openSession(true);
        CacheMapper mapper2 = sqlSession2.getMapper(CacheMapper.class);
        Emp emp2 = mapper2.getEmpById(1);
        System.out.println(emp2);
        sqlSession2.close();
    }
DEBUG 01-11 23:45:06,117 ==>  Preparing: select * from t_Emp where emp_id=?  (BaseJdbcLogger.java:137) 
DEBUG 01-11 23:45:06,143 ==> Parameters: 1(Integer)  (BaseJdbcLogger.java:137) 
DEBUG 01-11 23:45:06,161 <==      Total: 1  (BaseJdbcLogger.java:137) 
Emp{empId=1, empName='lsc', gender='男', age=22, deptId=1}

DEBUG 01-11 23:45:06,178 Cache Hit Ratio [com.lsc.mybatis.mappers.CacheMapper]: 0.5  (LoggingCache.java:60) 
Emp{empId=1, empName='lsc', gender='男', age=22, deptId=1}

二级缓存的相关配置

在mapper配置文件中添加的cache标签可以设置一些属性:
①eviction属性:缓存回收策略,默认的是 LRU。

  • LRU(Least Recently Used) – 最近最少使用的:移除最长时间不被使用的对象。
  • FIFO(First in First out) – 先进先出:按对象进入缓存的顺序来移除它们。
  • SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
  • WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。

②flushInterval属性:刷新间隔,单位毫秒

  • 默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新

③size属性:引用数目,正整数

  • 代表缓存最多可以存储多少个对象,太大容易导致内存溢出

④readOnly属性:只读, true/false

  • true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了 很重要的性能优势。

  • false:读写缓存;会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是false

MyBatis缓存查询的顺序

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

总结

  1. MyBatis的二级缓存相对于一级缓存来说,实现了SqlSession之间缓存数据的共享,同时粒度更加的细,能够到namespace级别,通过Cache接口实现类不同的组合,对Cache的可控性也更强。
  2. MyBatis在多表查询时,极大可能会出现脏数据,有设计上的缺陷,安全使用二级缓存的条件比较苛刻。
  3. 在分布式环境下,由于默认的MyBatis Cache实现都是基于本地的,分布式环境下必然会出现读取到脏数据,需要使用集中式缓存将MyBatis的Cache接口实现,有一定的开发成本,直接使用Redis,Memcached等分布式缓存可能成本更低,安全性也更高。

Mybatis逆向工程

正向工程:先创建Java实体类,由框架负责根据实体类生成数据库表。 Hibernate是支持正向工程的。
逆向工程:先创建数据库表,由框架负责根据数据库表,反向生成如下资源:

  • Java实体类
  • Mapper接口
  • Mapper映射文件

说白了就是自动生成代码,主要生成一些CURD的代码

创建逆向工程的步骤

添加依赖和插件

  <!--    打包方式-->
    <packaging>jar</packaging>
    <dependencies>
        <!-- Mybatis核心 -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.7</version>
        </dependency>
        <!-- junit测试 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <!-- MySQL驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.49</version>
        </dependency>
        <!-- log4j日志 -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
    </dependencies>
    <!-- 控制Maven在构建过程中相关配置 -->
    <build>
    <!-- 构建过程中用到的插件 -->
    <plugins>
        <!-- 具体插件,逆向工程的操作是以构建过程中插件形式出现的 -->
        <plugin>
            <groupId>org.mybatis.generator</groupId>
            <artifactId>mybatis-generator-maven-plugin</artifactId>
            <version>1.3.0</version>
            <!-- 插件的依赖 -->
            <dependencies>
                <!-- 逆向工程的核心依赖 -->
                <dependency>
                    <groupId>org.mybatis.generator</groupId>
                    <artifactId>mybatis-generator-core</artifactId>
                    <version>1.3.2</version>
                </dependency>
                <!-- MySQL驱动 -->
                <dependency>
                    <groupId>mysql</groupId>
                    <artifactId>mysql-connector-java</artifactId>
                    <version>8.0.16</version>
                </dependency>
            </dependencies>
        </plugin>
    </plugins>
    </build>

创建MyBatis的核心配置文件

创建逆向工程的配置文件

文件名必须是:generatorConfig.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
    <!--
    targetRuntime: 执行生成的逆向工程的版本
    MyBatis3Simple: 生成基本的CRUD(清新简洁版)
    MyBatis3: 生成带条件的CRUD(奢华尊享版)
    -->
    <context id="DB2Tables" targetRuntime="MyBatis3">
        <!-- 数据库的连接信息 -->
        <jdbcConnection driverClass="com.mysql.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/ssm"
                        userId="root"
                        password="123456">
        </jdbcConnection>
        <!-- javaBean的生成策略-->
        <javaModelGenerator targetPackage="com.lsc.mybatis.pojo"
                            targetProject=".\src\main\java">
            <property name="enableSubPackages" value="true" />
            <property name="trimStrings" value="true" />
        </javaModelGenerator>
        <!-- SQL映射文件的生成策略 -->
        <sqlMapGenerator targetPackage="com.lsc.mybatis.mapper"
                         targetProject=".\src\main\resources">
            <property name="enableSubPackages" value="true" />
        </sqlMapGenerator>
        <!-- Mapper接口的生成策略 -->
        <javaClientGenerator type="XMLMAPPER"
                             targetPackage="com.lsc.mybatis.mappers" targetProject=".\src\main\java">
            <property name="enableSubPackages" value="true" />
        </javaClientGenerator>
        <!-- 逆向分析的表 -->
        <!-- tableName设置为*号,可以对应所有表,此时不写domainObjectName -->
        <!-- domainObjectName属性指定生成出来的实体类的类名 -->
        <table tableName="t_emp" domainObjectName="Emp"/>
        <table tableName="t_dept" domainObjectName="Dept"/>
    </context>
</generatorConfiguration>
  • 配置数据库连接的信息
    • 驱动信息
    • URL
    • 用户名
    • 密码
  • 配置JavaBean的信息
    • 配置JavaBean生成下的目录
  • SQLXML文件配置的信息
  • Mapper接口的生成的配置信息
  • 配置表名和对应的类名的映射关系

执行MBG插件的generate目标

image-20230112002137441

最后的效果

image-20230112002255957

功能测试

public class EmpMapperTest {
    @Test
    public void mbgTest(){
        SqlSession sqlSession= SqlSessionUtil.getSqlSession();
        EmpMapper empMapper=sqlSession.getMapper(EmpMapper.class);
//        根据Id查询数据
        Emp emp=empMapper.selectByPrimaryKey(1);
        //select emp_id, emp_name, age, gender, dept_id from t_emp where emp_id = ?  
        System.out.println(emp);
//        查询所有数据
        List<Emp> empList = empMapper.selectByExample(null);
        empList.forEach(System.out::println);
        // select emp_id, emp_name, age, gender, dept_id from t_emp 
//        根据条件查询数据
        EmpExample example=new EmpExample();
        example.createCriteria().andEmpNameEqualTo("刘颂成").andAgeEqualTo(22);
        example.or().andGenderEqualTo("男");
        List<Emp> empList1 = empMapper.selectByExample(example);
        //select emp_id, emp_name, age, gender, dept_id from t_emp WHERE ( emp_name = ? and age = ? ) or( gender = ? ) 
        empList.forEach(System.out::println);
        
        Emp emp1 = new Emp(1,"小成",23,"男",null);
        //测试普通修改功能
        empMapper.updateByPrimaryKey(emp);
        //update t_emp set emp_name = ?, age = ?, gender = ?, dept_id = ? where emp_id = ?
        //测试选择性修改
        empMapper.updateByPrimaryKeySelective(emp);
        //update t_emp SET emp_name = ?, age = ?, gender = ? where emp_id = ?
    }
}

分页插件

分析分页需要哪些数据

当前在第一页

首页 上一页 2 3 4 5 6 下一页 末页

  • 假如上面是我们实际的需求

  • 我们要确定每页显示的条数,和当前页面的页码,这些数据是从前端传过来的

    • pageSize:每页显示的条数
    • pageNum:当前页的页码
  • 我们还需要知道总页面数,因为需要判断当前是不是最后一页,如果是最后一页,那么末页和下一页都不能使用

    • totalPage:总页数
    totalPage = count / pageSize;
    if(count % pageSize != 0){
    	totalPage += 1;
    }
    
  • 总页数=总记录数/pageSize

    • count:总记录数
  • 当前页的起始索引,因为我们要通过limit index,pageSize来查询当前页面需要的数据

    • index=(pageNum-1)*pageSize

例子
pageSize=4,pageNum=1,index=0 limit 0,4
pageSize=4,pageNum=3,index=8 limit 8,4
pageSize=4,pageNum=6,index=20 limit 8,4

使用步骤

添加依赖

<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>

配置分页插件

在MyBatis的核心配置文件中配置插件

<plugins>
<!--设置分页插件-->
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>

分页插件的使用

  @Test
    public void Test(){
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
        //查询功能之前开启分页功能
        Page<Object> page= PageHelper.startPage(5,4);//第一个参数表第几页 第二个参数表示每页有几条数据
        List<Emp> empList = mapper.selectByExample(null);
        PageInfo<Emp> pageInfo = new PageInfo<>(empList, 5);//这个5表示当前页面应该显示的页面导航页的集合的大小
        empList.forEach(System.out::println);
        System.out.println(pageInfo);
    }
PageInfo{pageNum=5, pageSize=4, size=4, startRow=17, endRow=20, total=37, pages=10, 
         
list=Page{count=true, pageNum=5, pageSize=4, startRow=16, endRow=20, total=37, pages=10, reasonable=false, pageSizeZero=false}
         [	Emp{empId=17, empName='a', age=null, gender='null', deptId=null},
          	Emp{empId=18, empName='a', age=null, gender='null', deptId=null},
          	Emp{empId=19, empName='aa', age=null, gender='null', deptId=null}, 
          	Emp{empId=20, empName='a', age=null, gender='null', deptId=null}
         ], 
prePage=4, nextPage=6, isFirstPage=false, isLastPage=false, hasPreviousPage=true, hasNextPage=true, navigatePages=5, navigateFirstPage=3, navigateLastPage=7, navigatepageNums=[3, 4, 5, 6, 7]}

  • ​ pageInfo里有我们分页需要的信息
  • pageNum表示这是第几页 pageSize表示每页有几条数据 表示这一页的起始记录行数和结尾记录行数 startRow=17, endRow=20 total表示总记录行数,pages表示总共有多少页
  • 上一页的页数和下一页的页数 prePage=4, nextPage=6,
  • isFirstPage=false, isLastPage=false表示是不是最后一页 是不是第一页
  • hasPreviousPage=true, hasNextPage=true 是否有上一页 是否有下一页
  • 表示应该显示的页数 navigatepageNums=[3, 4, 5, 6, 7]

没有开启分页插件

DEBUG 01-12 02:12:19,579 ==>  Preparing: select emp_id, emp_name, age, gender, dept_id from t_emp  (BaseJdbcLogger.java:137) 
DEBUG 01-12 02:12:19,615 ==> Parameters:   (BaseJdbcLogger.java:137) 
DEBUG 01-12 02:12:19,639 <==      Total: 37  (BaseJdbcLogger.java:137) 

开启了分页插件

DEBUG 01-12 02:05:24,147 Cache Hit Ratio [SQL_CACHE]: 0.0  (LoggingCache.java:60) 
DEBUG 01-12 02:05:24,195 ==>  Preparing: SELECT count(0) FROM t_emp  (BaseJdbcLogger.java:137) 
DEBUG 01-12 02:05:24,219 ==> Parameters:   (BaseJdbcLogger.java:137) 
DEBUG 01-12 02:05:24,233 <==      Total: 1  (BaseJdbcLogger.java:137) 
DEBUG 01-12 02:05:24,235 ==>  Preparing: select emp_id, emp_name, age, gender, dept_id from t_emp LIMIT ?, ?  (BaseJdbcLogger.java:137) 
DEBUG 01-12 02:05:24,236 ==> Parameters: 16(Long), 4(Integer)  (BaseJdbcLogger.java:137) 
DEBUG 01-12 02:05:24,237 <==      Total: 4  (BaseJdbcLogger.java:137)
  • 我们发现开启了分页插件,我们的sql语句发生了变化
  • 分页插件的基本原理就是使用Mybatis提供的插件接口,实现了自定义插件,在插件的拦截方法内拦截主执行是SQL,然后重写SQL,添加对应的物理分页语句和物理分页参数
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

库里不会投三分

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值