一级、二级缓存介绍:
1、一级缓存
1.1 mybatis的一级缓存基于SqlSession级别,默认是开启且无法关闭(但是我们可以手动设置不使用缓存,useCahe,flushCahe参数)。在同一个sqlSession中执行多次一样的查询,可以发现第后续多次查询并没有去查询数据库,而是直接命中了第一次的缓存。一级缓存可以
减少数据库压力,但可能会查询到脏数据(第一次查询后数据被修改了)。但是mybatis官方
仍然会这样设计的原因是什么?因为真实开发中,几乎没人会使用同一个sqlSession连续执行多次相同的查询。。。
1.2 一级缓存清除策略:遇到upate、insert、delete
2、二级缓存
2.1 二级缓存基于namespace级别,二级缓存默认关闭,也不建议使用。多个sqlSession对同一个namespace下的sql查询,后续查询命中第一次的缓存。
2.2 二级缓存问题:a、脏数据问题 b、数据失效问题
**a、**二级缓存开启时关联查询时,可能会有脏数据问题。如查询Blog时关联查询了Author,mybatis会在BLOG的namespace下缓存blog查询结果,同时会缓存一份Author的副本。这时如果Author的数据更新了,mybatis只刷新Author的namespace下缓存数据,而不会刷新Blog下的author缓存数据。
**b、**二级缓存开启时,如果遇到缓存刷新,mybatis会被该namespace下的所有缓存清除。如namespace下有10条sql,现在update了第一条sql触发了二级缓存刷新动作,二级缓存清空了这10条sql的缓存,这显然是不合理的。
2.3 二级缓存清除策略:遇到upate、insert、delete
缓存验证:
数据源
<?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>
<typeHandlers>
<typeHandler handler="com.study.mybatis.handler.ConverHandler"></typeHandler>
</typeHandlers>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="root123"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
</configuration>
@Mapper
public interface UserMapper extends BaseMapper<User> {
User queryUser(User user);
@Select("select * from user where id=#{id}")
User findById(int id);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org/DTD Mapper 3.0" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 指明当前xml对应的Mapper -->
<mapper namespace="com.study.mybatis.dao.UserMapper">
<select id="queryUser" parameterType="com.study.mybatis.pojo.User" resultType="com.study.mybatis.pojo.User">
select * from user where 1=1
<if test="id !=null and id !=''">and id = #{id}</if>
<if test="name !=null and name !=''">and name = #{name}</if>
<if test="password !=null and password != ''">and password = #{password}</if>
</select>
</mapper>
一级缓存验证demo
public class MybatisSourceDemo {
public static void main(String[] args) throws IOException {
User user = new User();
user.setId("1");
SqlSessionFactory sqlSessionFactory = getSqlSession();
SqlSession session1 = sqlSessionFactory.openSession();
try {
UserMapper mapper1 = session1.getMapper(UserMapper.class);
//第一次查询
User user1 = mapper1.queryUser(user);
System.out.println("user1="+user1);
//第二次查询
User user2 = mapper1.queryUser(user);
System.out.println("user2="+user2);
//第三次查询
User user3 = mapper1.queryUser(user);
System.out.println("user3="+user3);
}catch (Exception e){
}finally {
session1.commit();
session1.close();
}
}
private static SqlSessionFactory getSqlSession() throws IOException {
String resource = "com/study/mybatis/source/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory =
new SqlSessionFactoryBuilder().build(inputStream);
return sqlSessionFactory;
}
}
日志输出如下,可以看到同一个sqlSession执行多次相同查询,只执行了一次select,后续查询直接命中缓存。
14:23:54.627 [main] DEBUG com.study.mybatis.dao.UserMapper.queryUser - ==> Preparing: select * from user where 1=1 and id = ?
14:23:54.646 [main] DEBUG com.study.mybatis.dao.UserMapper.queryUser - ==> Parameters: 1...(String)
14:23:54.666 [main] DEBUG com.study.mybatis.dao.UserMapper.queryUser - <== Total: 1
user1=User(id=null, name=老王, password=12341234, remark=老王是各好邻居)
user2=User(id=null, name=老王, password=12341234, remark=老王是各好邻居)
user3=User(id=null, name=老王, password=12341234, remark=老王是各好邻居)
手动配置“不使用”一级缓存,使用useCache=“false” flushCahe=“true”,这两个要一起使用才会生效。
<select id="queryUser" parameterType="com.study.mybatis.pojo.User" resultType="com.study.mybatis.pojo.User" useCache="false" flushCache="true">
select * from user where 1=1
<if test="id !=null and id !=''">and id = #{id}</if>
<if test="name !=null and name !=''">and name = #{name}</if>
<if test="password !=null and password != ''">and password = #{password}</if>
</select>
验证输出结果,发现一级缓存已失效。
14:24:44.956 [main] DEBUG com.study.mybatis.dao.UserMapper.queryUser - ==> Preparing: select * from user where 1=1 and id = ?
14:24:44.979 [main] DEBUG com.study.mybatis.dao.UserMapper.queryUser - ==> Parameters: 1...(String)
14:24:44.994 [main] DEBUG com.study.mybatis.dao.UserMapper.queryUser - <== Total: 1
user1=User(id=null, name=老王, password=12341234, remark=老王是各好邻居)
14:24:44.995 [main] DEBUG com.study.mybatis.dao.UserMapper.queryUser - ==> Preparing: select * from user where 1=1 and id = ?
14:24:44.995 [main] DEBUG com.study.mybatis.dao.UserMapper.queryUser - ==> Parameters: 1...(String)
14:24:44.996 [main] DEBUG com.study.mybatis.dao.UserMapper.queryUser - <== Total: 1
user2=User(id=null, name=老王, password=12341234, remark=老王是各好邻居)
14:24:44.996 [main] DEBUG com.study.mybatis.dao.UserMapper.queryUser - ==> Preparing: select * from user where 1=1 and id = ?
14:24:44.996 [main] DEBUG com.study.mybatis.dao.UserMapper.queryUser - ==> Parameters: 1...(String)
14:24:44.997 [main] DEBUG com.study.mybatis.dao.UserMapper.queryUser - <== Total: 1
user3=User(id=null, name=老王, password=12341234, remark=老王是各好邻居)
二级缓存验证demo
未开启二级缓存日志如下,可以发现执行了三次查询
查询demo
public class MybatisSourceDemo {
public static void main(String[] args) throws IOException {
User user = new User();
user.setId("1");
SqlSessionFactory sqlSessionFactory = getSqlSession();//注意这里的sqlSession,一定要是同一个factory生成的
SqlSession session1 = sqlSessionFactory.openSession();
SqlSession session2= sqlSessionFactory.openSession();
SqlSession session3 = sqlSessionFactory.openSession();
try {
UserMapper mapper1 = session1.getMapper(UserMapper.class);
User user1 = mapper1.queryUser(user);
System.out.println("user1="+user1);
// User user2 = mapper1.queryUser(user);
// System.out.println("user2="+user2);
//
// User user3 = mapper1.queryUser(user);
// System.out.println("user3="+user3);
}catch (Exception e){
}finally {
session1.commit();//一定要先提交事务后,后面的查询才有缓存可用
session1.close();
}
try {
UserMapper mapper2 = session2.getMapper(UserMapper.class);
User user2 = mapper2.queryUser(user);
System.out.println("user2=" + user2);
}catch (Exception e){
}finally {
session2.commit();
session2.close();
}
try{
UserMapper mapper3 = session3.getMapper(UserMapper.class);
User user3 = mapper3.queryUser(user);
System.out.println("user3="+user3);
}catch (Exception e){
}finally {
session3.commit();
session3.close();
}
}
private static SqlSessionFactory getSqlSession() throws IOException {
String resource = "com/study/mybatis/source/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory =
new SqlSessionFactoryBuilder().build(inputStream);
return sqlSessionFactory;
}
}
14:25:54.632 [main] DEBUG com.study.mybatis.dao.UserMapper.queryUser - ==> Preparing: select * from user where 1=1 and id = ?
14:25:54.652 [main] DEBUG com.study.mybatis.dao.UserMapper.queryUser - ==> Parameters: 1...(String)
14:25:54.668 [main] DEBUG com.study.mybatis.dao.UserMapper.queryUser - <== Total: 1
user1=User(id=null, name=老王, password=12341234, remark=老王是各好邻居)
14:25:54.669 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@5bd03f44]
14:25:54.669 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@5bd03f44]
14:25:54.669 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - Returned connection 1540374340 to pool.
14:25:54.669 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Opening JDBC Connection
14:25:54.670 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - Checked out connection 1540374340 from pool.
14:25:54.670 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@5bd03f44]
14:25:54.670 [main] DEBUG com.study.mybatis.dao.UserMapper.queryUser - ==> Preparing: select * from user where 1=1 and id = ?
14:25:54.670 [main] DEBUG com.study.mybatis.dao.UserMapper.queryUser - ==> Parameters: 1...(String)
14:25:54.671 [main] DEBUG com.study.mybatis.dao.UserMapper.queryUser - <== Total: 1
user2=User(id=null, name=老王, password=12341234, remark=老王是各好邻居)
14:25:54.671 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@5bd03f44]
14:25:54.673 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@5bd03f44]
14:25:54.673 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - Returned connection 1540374340 to pool.
14:25:54.673 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Opening JDBC Connection
14:25:54.673 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - Checked out connection 1540374340 from pool.
14:25:54.673 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@5bd03f44]
14:25:54.673 [main] DEBUG com.study.mybatis.dao.UserMapper.queryUser - ==> Preparing: select * from user where 1=1 and id = ?
14:25:54.674 [main] DEBUG com.study.mybatis.dao.UserMapper.queryUser - ==> Parameters: 1...(String)
14:25:54.674 [main] DEBUG com.study.mybatis.dao.UserMapper.queryUser - <== Total: 1
user3=User(id=null, name=老王, password=12341234, remark=老王是各好邻居)
开启二级缓存
mybatis配置文件添加
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
mapper.xml中添加
<mapper namespace="com.study.mybatis.dao.UserMapper">
<cache></cache>
<select id="queryUser" parameterType="com.study.mybatis.pojo.User" resultType="com.study.mybatis.pojo.User">
select * from user where 1=1
<if test="id !=null and id !=''">and id = #{id}</if>
<if test="name !=null and name !=''">and name = #{name}</if>
<if test="password !=null and password != ''">and password = #{password}</if>
</select>
</mapper>
再次测试,日志如下,可以发现Cache Hit Ratio [com.study.mybatis.dao.UserMapper这句日志,表示查询命中了xxx namespace下的缓存,命中率为xxx。这句日志是二级缓存才有的日志。
14:31:07.437 [main] DEBUG com.study.mybatis.dao.UserMapper.queryUser - ==> Preparing: select * from user where 1=1 and id = ?
14:31:07.457 [main] DEBUG com.study.mybatis.dao.UserMapper.queryUser - ==> Parameters: 1...(String)
14:31:07.483 [main] DEBUG com.study.mybatis.dao.UserMapper.queryUser - <== Total: 1
user1=User(id=null, name=老王, password=12341234, remark=老王是各好邻居)
14:31:07.489 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@5c30a9b0]
14:31:07.496 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@5c30a9b0]
14:31:07.496 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - Returned connection 1546693040 to pool.
14:31:07.498 [main] DEBUG com.study.mybatis.dao.UserMapper - Cache Hit Ratio [com.study.mybatis.dao.UserMapper]: 0.5
user2=User(id=null, name=老王, password=12341234, remark=老王是各好邻居)
14:31:07.499 [main] DEBUG com.study.mybatis.dao.UserMapper - Cache Hit Ratio [com.study.mybatis.dao.UserMapper]: 0.6666666666666666
user3=User(id=null, name=老王, password=12341234, remark=老王是各好邻居)
总结:
1、一级缓存是sqlSession级别的,默认开启且无法关闭,但是我们可以使用useCahe和flushCahe配置不使用一级缓存。
2、二级缓存是namespace级别的,默认开闭需要手动开启。如果要写demo测试二级缓存,要保证多个sqlsession为同一个工厂产出,同时要保证第一次的查询结果的缓存已提交事务。即使用二级缓存的的前提是它要先存在。