Java笔记之Mybatis(八):缓存机制

0.说在前面

  项目基于前面的mybatis_demo2

1.概念

  Mybatis有一级缓存和二级缓存两种缓存机制;

  缓存机制是对查询操作来说的;

  一级缓存是SqlSession级别的缓存,每个SqlSession的对象都有自己的数据区域存储缓存数据,不同的SqlSession对象缓存数据的数据区域互不干扰;

  二级缓存是Mapper级别的缓存,操作同一个Mapper配置文件中的SQL语句的SqlSession对象共用二级缓存,也就是说二级缓存是跨SqlSession的;

2.一级缓存

修改Employee.xml文件,添加修改操作

<?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.mybatis.demo2.pojo">
    <select id="getEmployeesByDeptId" parameterType="int" resultType="Employee">
        select * from t_employee where dept_id=#{value}
    </select>
    
    <select id="getEmployeeById" parameterType="int" resultMap="employeeMap">
        select * from t_employee where emp_id=#{value}
    </select>    
    <resultMap type="Employee" id="employeeMap">
        <association property="department" javaType="Department" select="getDepartmentByDeptId" column="dept_id"></association>
    </resultMap>
    
    <update id="updateEmployeeById" parameterType="Employee">
        update t_employee set emp_name=#{empName} where emp_id=#{empId}
    </update>
</mapper>

新建MybatisTest3类

package com.mybatis.demo2.test;

import java.io.IOException;
import java.io.InputStream;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import com.mybatis.demo2.pojo.Employee;

public class MybatisTest3 {

    public static void main(String[] args) throws IOException {
        //加载mybatis的配置文件
        InputStream inputStream = Resources.getResourceAsStream("mybatisConfig.xml");
        //获取SqlSessionFactory对象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //使用SqlSessionFactory对象创建SqlSession对象
        SqlSession sqlSession = sqlSessionFactory.openSession();
        System.out.println("======同一个SqlSession对象======");
        //第一次查询
        Employee employee= sqlSession.selectOne("getEmployeeById",1);
        System.out.println(employee.getEmpName());
        //第二次查询
        Employee employee2 = sqlSession.selectOne("getEmployeeById",1);
        System.out.println(employee2.getEmpName());
        //判断是否是同一个对象
        System.out.println("employee==employee2:"+(employee==employee2));
        //进行修改操作并提交
        employee.setEmpName(employee.getEmpName()+"temp");
        sqlSession.update("updateEmployeeById", employee);
        sqlSession.commit();
        //第三次查询
        Employee employee3=sqlSession.selectOne("getEmployeeById",1);
        System.out.println(employee3.getEmpName());
        System.out.println("employee==employee3:"+(employee==employee3));
        System.out.println("======新建的不同的SqlSession对象======");
        //第四次查询
        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        Employee employee4 = sqlSession2.selectOne("getEmployeeById",1);
        System.out.println(employee4.getEmpName());
        System.out.println("employee3==employee4:"+(employee3==employee4));
    }

}

运行MybatisTest3类,结果如下

======同一个SqlSession对象======
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 1344645519.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@5025a98f]
DEBUG [main] - ==>  Preparing: select * from t_employee where emp_id=? 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
张三temp
张三temp
employee==employee2:true
DEBUG [main] - ==>  Preparing: update t_employee set emp_name=? where emp_id=? 
DEBUG [main] - ==> Parameters: 张三temptemp(String), 1(Integer)
DEBUG [main] - <==    Updates: 1
DEBUG [main] - Committing JDBC Connection [com.mysql.jdbc.JDBC4Connection@5025a98f]
DEBUG [main] - ==>  Preparing: select * from t_employee where emp_id=? 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
张三temptemp
employee==employee3:false
======新建的不同的SqlSession对象======
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 1883919084.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@704a52ec]
DEBUG [main] - ==>  Preparing: select * from t_employee where emp_id=? 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
张三temptemp
employee3==employee4:false

总结:

  (1).从第一次和第二次查询日志可以看出,第一次查询时会执行SQL语句进行查询返回结果,第二次查询就不会再执行SQL语句,而是直接返回结果.从两次返回的结果对象的比较来看,是同一个对象.

  (2).执行完修改操作并提交之后再执行相同的查询操作,会再次执行SQL语句并返回结果,与第一次的结果比较可以看出不是同一个对象.新增和删除操作只要做了提交也会有相同的效果,由此可见,SqlSession对象在执行了commit操作之后会清空它的一级缓存数据区域,以此保证缓存的数据都是最新的,避免脏读的发生.

  (3)第四次查询操作换了一个新的SqlSession对象执行相同的查询,从日志中可以看出重新执行了SQL语句,从与第三次查询的结果进行比较可以看出不是同一个对象,由此可以看出不同的SqlSession对象之间是互不干扰的,一级缓存是基于同一个SqlSession对象的.

3.二级缓存

新建包com.mybatis.demo2.mapper

在包下新建DepartmentMapper接口

package com.mybatis.demo2.mapper;

import com.mybatis.demo2.pojo.Department;

public interface DepartmentMapper {
    public Department getDepartmentById(int id);
    
    public Department getDepartmentByDeptId(int deptId);
}

在包下新建EmployeeMapper接口

package com.mybatis.demo2.mapper;

import com.mybatis.demo2.pojo.Employee;

public interface EmployeeMapper {
    
    public Employee getEmployeeById(int id);
    
    public void updateEmployeeById(Employee employee);
    
    public Employee getDepartmentByDeptId(int deptId);
}

在包下新建DepartmentMapper.xml文件,mapper标签中的namespace属性的值为对应的同名接口的完整包名+接口名,下同

<?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.mybatis.demo2.mapper.DepartmentMapper">
    <select id="getDepartmentById" parameterType="int" resultMap="departmentResultMap">
        select * from t_department where dept_id=#{value}
    </select>
    <resultMap type="Department" id="departmentResultMap">
        <collection property="employees" ofType="Employee" javaType="java.util.ArrayList"
        select="com.mybatis.demo2.mapper.EmployeeMapper.getEmployeesByDeptId" column="dept_id"></collection>
    </resultMap>
    
    <select id="getDepartmentByDeptId" parameterType="int" resultType="Department">
        select * from t_department where dept_id=#{value}
    </select>
</mapper>

在包下新建EmployeeMapper.xml文件,其中使用<cache></cache>标签表示对该Mapper文件开启二级缓存,没有该标签的Mapper文件并不会开启二级缓存.

<?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.mybatis.demo2.mapper.EmployeeMapper">
    <cache></cache>
    <select id="getEmployeesByDeptId" parameterType="int" resultType="Employee">
        select * from t_employee where dept_id=#{value}
    </select>
    
    <select id="getEmployeeById" parameterType="int" resultMap="employeeMap">
        select * from t_employee where emp_id=#{value}
    </select>    
    <resultMap type="Employee" id="employeeMap">
        <association property="department" javaType="Department" select="com.mybatis.demo2.mapper.DepartmentMapper.getDepartmentByDeptId" column="dept_id"></association>
    </resultMap>
    
    <update id="updateEmployeeById" parameterType="Employee">
        update t_employee set emp_name=#{empName} where emp_id=#{empId}
    </update>
</mapper>

修改mybatisConfig.xml文件,添加DepartmentMapper.xml和EmployeeMapper.xml文件的SQL映射配置,设置<setting name="cacheEnabled" value="true"/>开启二级缓存

<?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>
    <settings>
        <!-- 开启驼峰命名规则映射 -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <!-- 开启延时加载 -->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!-- 将积极加载改为消极加载,也就是按需加载 -->
        <setting name="aggressiveLazyLoading" value="false"/>
        <!-- 开启二级缓存 -->
        <setting name="cacheEnabled" value="true"/>
    </settings>
    <!-- 对类起别名 -->
    <typeAliases>
        <!-- 类的别名默认为类名 -->
        <package name="com.mybatis.demo2.pojo"/>
    </typeAliases>
    <environments default="development">
        <environment id="development">
            <!-- 使用JDBC事务管理 -->
            <transactionManager type="JDBC"></transactionManager>
            <!-- 定义数据库连接池 -->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/keeper?characterEncoding=utf-8"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <!-- 配置sql映射配置文件 -->
    <mappers>
        <mapper resource="com/mybatis/demo2/mapper/DepartmentMapper.xml"/>
        <mapper resource="com/mybatis/demo2/mapper/EmployeeMapper.xml"/>
        <mapper resource="com/mybatis/demo2/pojo/Employee.xml"/>
        <mapper resource="com/mybatis/demo2/pojo/Department.xml"/>
    </mappers>
</configuration>

Employee类和Department类都实现Serializable接口

package com.mybatis.demo2.pojo;

import java.io.Serializable;

public class Employee implements Serializable{
    private static final long serialVersionUID = 6343525615451290445L;
    private int empId;
    private String empName;
    private Department department;
    
    public int getEmpId() {
        return empId;
    }
    public void setEmpId(int empId) {
        this.empId = empId;
    }
    public String getEmpName() {
        return empName;
    }
    public void setEmpName(String empName) {
        this.empName = empName;
    }
    public Department getDepartment() {
        return department;
    }
    public void setDepartment(Department department) {
        this.department = department;
    }
}
package com.mybatis.demo2.pojo;

import java.io.Serializable;
import java.util.List;

public class Department implements Serializable{
    private static final long serialVersionUID = -6879322208163653319L;
    private int deptId;
    private String deptName;
    private List<Employee> employees;
    public int getDeptId() {
        return deptId;
    }
    public void setDeptId(int deptId) {
        this.deptId = deptId;
    }
    public String getDeptName() {
        return deptName;
    }
    public void setDeptName(String deptName) {
        this.deptName = deptName;
    }
    public List<Employee> getEmployees() {
        return employees;
    }
    public void setEmployees(List<Employee> employees) {
        this.employees = employees;
    } 
    
}

在包com.mybatis.demo2.test包下新建MybatisTest4类

package com.mybatis.demo2.test;

import java.io.IOException;
import java.io.InputStream;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import com.mybatis.demo2.mapper.EmployeeMapper;
import com.mybatis.demo2.pojo.Employee;

public class MybatisTest4 {

    public static void main(String[] args) throws IOException {
        //加载mybatis的配置文件
        InputStream inputStream = Resources.getResourceAsStream("mybatisConfig.xml");
        //获取SqlSessionFactory对象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //使用SqlSessionFactory对象创建SqlSession对象
        SqlSession sqlSession = sqlSessionFactory.openSession();
        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        SqlSession sqlSession3 = sqlSessionFactory.openSession();
        SqlSession sqlSession4 = sqlSessionFactory.openSession();
        System.out.println("======同一个SqlSession对象======");
        //第一次查询
        EmployeeMapper mapper1 = sqlSession.getMapper(EmployeeMapper.class);
        Employee employee1 = mapper1.getEmployeeById(1);
        System.out.println(employee1.getEmpName());
        //第二次查询
        EmployeeMapper mapper2 = sqlSession.getMapper(EmployeeMapper.class);
        Employee employee2 = mapper2.getEmployeeById(1);
        System.out.println(employee2.getEmpName());
        //比较是否是同一个对象
        System.out.println("employee1==employee2:"+(employee1==employee2));
        //关闭SqlSession,将对象中的数据写到EmployeeMapper二级缓存数据区域
        sqlSession.close();
        //第三次查询
        EmployeeMapper mapper3 = sqlSession2.getMapper(EmployeeMapper.class);
        Employee employee3 = mapper3.getEmployeeById(1);
        System.out.println(employee3.getEmpName());
        sqlSession2.close();
        //比较是否是同一个对象
        System.out.println("employee1==employee3:"+(employee1==employee3));
        //进行修改操作并提交
        employee1.setEmpName(employee1.getEmpName()+"666");
        EmployeeMapper mapper4 = sqlSession3.getMapper(EmployeeMapper.class);
        mapper4.updateEmployeeById(employee1);
        //执行提交操作,并且清除EmployeeMapper数据区域中的二级缓存
        sqlSession3.commit();
        //第四次查询
        EmployeeMapper mapper5 = sqlSession4.getMapper(EmployeeMapper.class);
        Employee employee4 = mapper5.getEmployeeById(1);
        System.out.println(employee4.getEmpName());
        System.out.println("employee1==employee4:"+(employee1==employee4));
        //执行关闭操作,将SqlSession对象中的数据写到EmployeeMapper的二级缓存数据区域
        sqlSession4.close();
    }

}

运行MybatisTest4类,结果如下

======同一个SqlSession对象======
DEBUG [main] - Cache Hit Ratio [com.mybatis.demo2.mapper.EmployeeMapper]: 0.0
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 1335298403.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f970963]
DEBUG [main] - ==>  Preparing: select * from t_employee where emp_id=? 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
张三temp
DEBUG [main] - Cache Hit Ratio [com.mybatis.demo2.mapper.EmployeeMapper]: 0.0
张三temp
employee1==employee2:true
DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f970963]
DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f970963]
DEBUG [main] - Returned connection 1335298403 to pool.
DEBUG [main] - Cache Hit Ratio [com.mybatis.demo2.mapper.EmployeeMapper]: 0.3333333333333333
张三temp
employee1==employee3:false
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Checked out connection 1335298403 from pool.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f970963]
DEBUG [main] - ==>  Preparing: update t_employee set emp_name=? where emp_id=? 
DEBUG [main] - ==> Parameters: 张三temp666(String), 1(Integer)
DEBUG [main] - <==    Updates: 1
DEBUG [main] - Committing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f970963]
DEBUG [main] - Cache Hit Ratio [com.mybatis.demo2.mapper.EmployeeMapper]: 0.25
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 802581203.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@2fd66ad3]
DEBUG [main] - ==>  Preparing: select * from t_employee where emp_id=? 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
张三temp666
employee1==employee4:false
DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@2fd66ad3]
DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@2fd66ad3]
DEBUG [main] - Returned connection 802581203 to pool.

总结:

  (1).第一次查询由于没有二级缓存信息,执行查询的SQL语句,返回查询结果,此时SqlSession对象并未关闭,所以数据并未写到Mapper的二级缓存数据区域中;

  (2).从日志中可以看出,第二次查询没有执行SQL语句,就返回了结果,该结果的数据并不是从Mapper的二级缓存中取出的,而是从当前的SqlSession的一级缓存中取出的数据;

  (3).从日志中可以看到,前两次的查询二级缓存命中率都是0.0,原因就是以上两条,关闭当前的SqlSession对象,对象中的数据就会写入到Mapper的二级缓存数据区域中;

  (4).第三次查询使用新的SqlSession对象进行查询,也没有执行SQL语句,说明是从二级缓存中获取的数据.这时的命中率为0.3333333333333333,原因是执行了三次查询,只有这一次命中了二级缓存中的数据,1/3;

  (5).执行数据的修改和提交操作,将Mapper二级缓存数据区域中的数据清空,同样的,执行新增和删除操作并提交也会将二级缓存数据区域中的数据清空;

  (6).第四次查询再次执行了SQL语句,可以证实第(5)条;

  (7).第一次和第三次查询可以看出Mapper的二级缓存是跨SqlSession的;

 

posted @ 2020-04-02 14:47  安徒生敲代码  阅读(218)  评论(0编辑  收藏  举报