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