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的;