1、二级缓存使用
在 MyBatis 中默认二级缓存是不开启的,如果要使用需手动开启。在 mybatis-config.xml
配置文件中设置 cacheEnabled = 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>
<!--对应的设置都在 Configuration 配置类保存着,直接点进源码查看即可-->
<settings>
<!--开启二级缓存-->
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="multipleResultSetsEnabled" value="true"/>
<setting name="useColumnLabel" value="true"/>
<setting name="useGeneratedKeys" value="false"/>
<setting name="autoMappingBehavior" value="PARTIAL"/>
<!--修改默认 SQL 执行器,默认是 SIMPLE,但是修改成 REUSE 会好一点点,这也是数据库优化的一个关键之处-->
<setting name="defaultExecutorType" value="REUSE"/>
<setting name="defaultStatementTimeout" value="25"/>
<setting name="safeRowBoundsEnabled" value="false"/>
<setting name="mapUnderscoreToCamelCase" value="false"/>
<!--修改作用域,查看 LocalCacheScope 枚举值,修改成 STATEMENT 之后一级缓存失效,
但是不一定没用一级缓存,只是作用域缩小了而已,后面的嵌套、子查询就用到了一级缓存-->
<setting name="localCacheScope" value="SESSION"/>
<setting name="jdbcTypeForNull" value="OTHER"/>
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>
<plugins>
<plugin interceptor="org.apache.ibatis.gwmtest.plugin.MyPageInterceptor"></plugin>
</plugins>
<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/gwmdb?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="itsme999"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/PersonMapper.xml"/>
</mappers>
</configuration>
或者通过 Configuration 配置方式修改,调用 configuration.setCacheEnabled(true) 代码如下:
public class MyBatisCacheTest {
private static SqlSessionFactory sqlSessionFactory;
private static Configuration configuration;
private static JdbcTransaction jdbcTransaction;
private static Connection connection;
private static final String resource = "/mybatis-config.xml";
private static MappedStatement mappedStatement;
private static SqlSession sqlSession;
private static SqlSession sqlSession2;
static {
try {
InputStream inputStream = MyBatisCacheTest.class.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
configuration = sqlSessionFactory.getConfiguration();
configuration.setCacheEnabled(true);
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/gwmdb?useSSL=false&allowPublicKeyRetrieval=true", "root", "itsme999");
jdbcTransaction = new JdbcTransaction(connection);
String statement = "org.apache.ibatis.gwmtest.dao.PersonMapper.getPerson";
mappedStatement = configuration.getMappedStatement( statement);
// 注意这里设置了自动提交
sqlSession = sqlSessionFactory.openSession(true);
sqlSession2 = sqlSessionFactory.openSession(true);
} catch (Exception e) {
e.printStackTrace();
}
}
}
记得打开开关之后,该如何使用二级缓存?有两种方式:注解式、xml 配置、注解+配置结合体,提前准备两个文件: PersonMapper.xml 和 PersonMapper 接口。
1、使用注解缓存—@CacheNamespace
直接在 PersonMapper 接口上面添加注解 @CacheNamespace
,通过 @CacheNamespace
设置缓存命名空间,这个注解一加上,就相当于在 PersonMapper 接口文件中准备好一个二级缓存,代码如下:
@CacheNamespace
public interface PersonMapper {
@Select("select id,user_name as userName,age as age from test_user where id = #{myId}")
Person getPerson(@Param("myId") Integer id);
}
使用代码如下:
public static void main(String[] args) throws Exception {
PersonMapper mapper = sqlSession.getMapper(PersonMapper.class);
Person person = mapper.getPerson(1);
sqlSession.commit();
Person person1 = mapper.getPerson(1);
System.out.println("person==person1 = " + (person == person1));
}
这里一定要注意二级缓存必须要显示提交才能够命中。否则二级缓存命中率为 0.0 为什么?后面源码中会分析,最终输出结果如下:
2023-02-16 14:31:37,201 DEBUG [main] -org.apache.ibatis.transaction.jdbc.JdbcTransaction Opening JDBC Connection
2023-02-16 14:31:37,216 DEBUG [main] -org.apache.ibatis.datasource.pooled.PooledDataSource Created connection 19986569.
2023-02-16 14:31:37,222 DEBUG [main] -org.apache.ibatis.gwmtest.dao.PersonMapper.getPerson ==> Preparing: select id,user_name as userName,age as age from test_user where id = ?
2023-02-16 14:31:37,276 DEBUG [main] -org.apache.ibatis.gwmtest.dao.PersonMapper.getPerson ==> Parameters: 1(Integer)
2023-02-16 14:31:37,350 DEBUG [main] -org.apache.ibatis.gwmtest.dao.PersonMapper.getPerson <== Total: 1
2023-02-16 14:31:37,364 WARN [main] -org.apache.ibatis.io.SerialFilterChecker As you are using functionality that deserializes object streams, it is recommended to define the JEP-290 serial filter. Please refer to https://docs.oracle.com/pls/topic/lookup?ctx=javase15&id=GUID-8296D8E8-2B93-4B9A-856E-0A65AF9B8C66
2023-02-16 14:31:37,369 DEBUG [main] -org.apache.ibatis.gwmtest.dao.PersonMapper Cache Hit Ratio [org.apache.ibatis.gwmtest.dao.PersonMapper]: 0.5
person==person1 = false
会看到 Cache Hit Ratio 命中率为 0.5,说明已经命中二级缓存。但是发现最终:person==person1 = false
这个是因为反序列化重新生成一个 Person 类型实例。肯定是完全两个不同的实例。就比如网络传输 Person,接受端生成的 Person 能和你发送端 Person 实例一样嘛。
注意此时是在 PersonMapper 加上@CacheNamespace 注解,相当于二级缓存是在 PersonMapper 接口上准备好了。并且测试时查询使用的也是 @Select
注解方式,并且测试使用的时同一个 SqlSession,现在准备弄两个不同 SqlSession 在测试一下,代码如下:
public static void main(String[] args) throws Exception {
PersonMapper mapper = sqlSession.getMapper(PersonMapper.class);
PersonMapper mapper1 = sqlSession2.getMapper(PersonMapper.class);
Person person = mapper.getPerson(1);
sqlSession.commit();
Person person1 = mapper1.getPerson(1);
mapper.getPerson(1);
System.out.println("person==person1 = " + (person == person1));
}
运行结果:
2023-02-16 15:11:27,441 DEBUG [main] -org.apache.ibatis.transaction.jdbc.JdbcTransaction Opening JDBC Connection
2023-02-16 15:11:27,453 DEBUG [main] -org.apache.ibatis.datasource.pooled.PooledDataSource Created connection 19986569.
2023-02-16 15:11:27,460 DEBUG [main] -org.apache.ibatis.gwmtest.dao.PersonMapper.getPerson ==> Preparing: select id,user_name as userName,age as age from test_user where id = ?
2023-02-16 15:11:27,511 DEBUG [main] -org.apache.ibatis.gwmtest.dao.PersonMapper.getPerson ==> Parameters: 1(Integer)
2023-02-16 15:11:27,572 DEBUG [main] -org.apache.ibatis.gwmtest.dao.PersonMapper.getPerson <== Total: 1
2023-02-16 15:11:27,586 WARN [main] -org.apache.ibatis.io.SerialFilterChecker As you are using functionality that deserializes object streams, it is recommended to define the JEP-290 serial filter. Please refer to https://docs.oracle.com/pls/topic/lookup?ctx=javase15&id=GUID-8296D8E8-2B93-4B9A-856E-0A65AF9B8C66
2023-02-16 15:11:27,592 DEBUG [main] -org.apache.ibatis.gwmtest.dao.PersonMapper Cache Hit Ratio [org.apache.ibatis.gwmtest.dao.PersonMapper]: 0.5
2023-02-16 15:11:27,592 DEBUG [main] -org.apache.ibatis.gwmtest.dao.PersonMapper Cache Hit Ratio [org.apache.ibatis.gwmtest.dao.PersonMapper]: 0.6666666666666666
person==person1 = false
发现还是可以命中二级缓存,这就可以在多线程下不同 SqlSession 同时访问二级缓存提高查询效率。
现在准备在 PesonMapper 接口中再添加一个查询方法 getPerson2(),该方法不再使用 @Select
方式,代码如下:
@CacheNamespace
public interface PersonMapper {
@Select("select id,user_name as userName,age as age from test_user where id = #{666}")
Person getPerson(Integer id);
Person getPerson2(Integer id);
}
然后再 PesonMapper.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="org.apache.ibatis.gwmtest.dao.PersonMapper">
<select id="getPerson2" resultType="org.apache.ibatis.gwmtest.entity.Person">
select id,user_name as userName,age as age from test_user where id = #{666}
</select>
</mapper>
然后测试代码如下:
public static void ma333in(String[] args) throws Exception {
PersonMapper mapper = sqlSession.getMapper(PersonMapper.class);
PersonMapper mapper1 = sqlSession2.getMapper(PersonMapper.class);
Person person = mapper.getPerson2(1);
sqlSession.commit();
Person person1 = mapper1.getPerson2(1);
System.out.println("person==person1 = " + (person == person1));
}
输出结果:
2023-02-16 15:22:07,088 DEBUG [main] -org.apache.ibatis.transaction.jdbc.JdbcTransaction Opening JDBC Connection
2023-02-16 15:22:07,102 DEBUG [main] -org.apache.ibatis.datasource.pooled.PooledDataSource Created connection 1487470647.
2023-02-16 15:22:07,108 DEBUG [main] -org.apache.ibatis.gwmtest.dao.PersonMapper.getPerson2 ==> Preparing: select id,user_name as userName,age as age from test_user where id = ?
2023-02-16 15:22:07,159 DEBUG [main] -org.apache.ibatis.gwmtest.dao.PersonMapper.getPerson2 ==> Parameters: 1(Integer)
2023-02-16 15:22:07,200 DEBUG [main] -org.apache.ibatis.gwmtest.dao.PersonMapper.getPerson2 <== Total: 1
2023-02-16 15:22:07,202 DEBUG [main] -org.apache.ibatis.transaction.jdbc.JdbcTransaction Opening JDBC Connection
2023-02-16 15:22:07,210 DEBUG [main] -org.apache.ibatis.datasource.pooled.PooledDataSource Created connection 1796371666.
2023-02-16 15:22:07,210 DEBUG [main] -org.apache.ibatis.gwmtest.dao.PersonMapper.getPerson2 ==> Preparing: select id,user_name as userName,age as age from test_user where id = ?
2023-02-16 15:22:07,211 DEBUG [main] -org.apache.ibatis.gwmtest.dao.PersonMapper.getPerson2 ==> Parameters: 1(Integer)
2023-02-16 15:22:07,212 DEBUG [main] -org.apache.ibatis.gwmtest.dao.PersonMapper.getPerson2 <== Total: 1
person==person1 = false
发现没有命中二级缓存,因为不同 SqlSession,所以一级缓存也没有命中。为什么没有命中二级缓存,明明在开启了二级缓存,但是你要知道二级缓存是在哪里开启的?是在 PersonMapper 接口上,注意是接口文件上,PersonMapper.xml 资源文件中压根没有开启二级缓存,所以你使用 PersonMapper.xml 配置中的方法肯定命中不了二级缓存。那么要怎么才能够让他使用到 PersonMapper 接口上二级缓存呢?可以使用 <cache-ref> 标签,代码如下:
<?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="org.apache.ibatis.gwmtest.dao.PersonMapper">
<cache-ref namespace="org.apache.ibatis.gwmtest.dao.PersonMapper"/>
<select id="getPerson2" resultType="org.apache.ibatis.gwmtest.entity.Person">
select id,user_name as userName,age as age from test_user where id = #{666}
</select>
</mapper>
<cache-ref> 标签中 namespace 属性可以引用到 PersonMapper 接口上配置二级缓存。最后测试可以命中二级缓存。
以上就是通过 @CacheNamespace 注解使用二级缓存的步骤。通知也要注意 Mapper 接口和 Mapper 配置文件中准备的二级缓存不是同一个,要使用同一个就需要互相进行引入。 <cache-ref> 标签就是用于配置文件中引入接口中配置的二级缓存。
2、使用 xml 配置缓存
直接在 PersonMapper.xml 配置中加上 <cache/> 标签,相当于在 PersonMapper.xml 配置中准备好一个二级缓存,和上面注解式的不一样,上面是在 PersonMapper 接口中准备二级缓存(两个缓存是一样的,可以互相引用)配置如下:
<?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="org.apache.ibatis.gwmtest.dao.PersonMapper">
<select id="getPerson2" resultType="org.apache.ibatis.gwmtest.entity.Person">
select id,user_name as userName,age as age from test_user where id = #{666}
</select>
</mapper>
对应 PersonMapper 接口代码如下:
public interface PersonMapper {
@Select("select id,user_name as userName,age as age from test_user where id = #{666}")
Person getPerson(Integer id);
Person getPerson2(Integer id);
}
注意此时不要用 @CacheNamespace 注解,因为在 xml 中已经通过 <cache/> 标签准备好二级缓存,所以这里不能再去构建一个二级缓存,会冲突报错,错误如下:
Caused by: java.lang.IllegalArgumentException: Caches collection already contains value for org.apache.ibatis.gwmtest.dao.PersonMapper
at org.apache.ibatis.session.Configuration$StrictMap.put(Configuration.java:1021)
at org.apache.ibatis.session.Configuration$StrictMap.put(Configuration.java:977)
at org.apache.ibatis.session.Configuration.addCache(Configuration.java:713)
at org.apache.ibatis.builder.MapperBuilderAssistant.useNewCache(MapperBuilderAssistant.java:140)
at org.apache.ibatis.builder.annotation.MapperAnnotationBuilder.parseCache(MapperAnnotationBuilder.java:190)
at org.apache.ibatis.builder.annotation.MapperAnnotationBuilder.parse(MapperAnnotationBuilder.java:121)
at org.apache.ibatis.binding.MapperRegistry.addMapper(MapperRegistry.java:72)
at org.apache.ibatis.session.Configuration.addMapper(Configuration.java:848)
at org.apache.ibatis.builder.xml.XMLMapperBuilder.bindMapperForNamespace(XMLMapperBuilder.java:432)
at org.apache.ibatis.builder.xml.XMLMapperBuilder.parse(XMLMapperBuilder.java:97)
at org.apache.ibatis.builder.xml.XMLConfigBuilder.mapperElement(XMLConfigBuilder.java:378)
at org.apache.ibatis.builder.xml.XMLConfigBuilder.parseConfiguration(XMLConfigBuilder.java:120)
提示你已经存在缓存,你可以直接引用,但是你不要再重新准备,可以使用 @CacheNamespaceRef 注解引入,在此之前先接着测试下能不能命中二级缓存。
定义好了 PersonMapper 接口和 PersonMapper.xml 之后,开始测试,先测试 getPerson() 方法,注意这个方法是在 PersonMapper 接口上的,要知道测试的 PersonMapper 接口上面没有 @CacheNamespace 和 @CacheNamespaceRef 注解修饰,测试代码如下:
public static void main(String[] args) throws Exception {
PersonMapper mapper = sqlSession.getMapper(PersonMapper.class);
PersonMapper mapper1 = sqlSession2.getMapper(PersonMapper.class);
Person person = mapper.getPerson(1);
sqlSession.commit();
Person person1 = mapper1.getPerson(1);
mapper.getPerson(1);
System.out.println("person==person1 = " + (person == person1));
}
输出结果:
2023-02-16 15:43:42,638 DEBUG [main] -org.apache.ibatis.transaction.jdbc.JdbcTransaction Opening JDBC Connection
2023-02-16 15:43:42,652 DEBUG [main] -org.apache.ibatis.datasource.pooled.PooledDataSource Created connection 255944888.
2023-02-16 15:43:42,658 DEBUG [main] -org.apache.ibatis.gwmtest.dao.PersonMapper.getPerson ==> Preparing: select id,user_name as userName,age as age from test_user where id = ?
2023-02-16 15:43:42,703 DEBUG [main] -org.apache.ibatis.gwmtest.dao.PersonMapper.getPerson ==> Parameters: 1(Integer)
2023-02-16 15:43:42,760 DEBUG [main] -org.apache.ibatis.gwmtest.dao.PersonMapper.getPerson <== Total: 1
2023-02-16 15:43:42,763 DEBUG [main] -org.apache.ibatis.transaction.jdbc.JdbcTransaction Opening JDBC Connection
2023-02-16 15:43:42,770 DEBUG [main] -org.apache.ibatis.datasource.pooled.PooledDataSource Created connection 1935972447.
2023-02-16 15:43:42,770 DEBUG [main] -org.apache.ibatis.gwmtest.dao.PersonMapper.getPerson ==> Preparing: select id,user_name as userName,age as age from test_user where id = ?
2023-02-16 15:43:42,771 DEBUG [main] -org.apache.ibatis.gwmtest.dao.PersonMapper.getPerson ==> Parameters: 1(Integer)
2023-02-16 15:43:42,772 DEBUG [main] -org.apache.ibatis.gwmtest.dao.PersonMapper.getPerson <== Total: 1
person==person1 = false
发现没有使用到二级缓存,因为在 PersonMapper 接口上压根就没有定义二级缓存,直接去连接两次数据库查询,那怎么才能让他使用到二级缓存并且命中呢?上面说过在使用 @CacheNamespace 注解准备缓存直接冲突,所以这里应该使用 CacheNamespaceRef 注解,来引用在 PersonMapper.xml 中配置的二级缓存。代码如下:
@CacheNamespaceRef(PersonMapper.class)
public interface PersonMapper {
@Select("select id,user_name as userName,age as age from test_user where id = #{666}")
Person getPerson(Integer id);
Person getPerson2(Integer id);
}
测试结果:
2023-02-16 15:49:48,135 DEBUG [main] -org.apache.ibatis.transaction.jdbc.JdbcTransaction Opening JDBC Connection
2023-02-16 15:49:48,146 DEBUG [main] -org.apache.ibatis.datasource.pooled.PooledDataSource Created connection 294184992.
2023-02-16 15:49:48,153 DEBUG [main] -org.apache.ibatis.gwmtest.dao.PersonMapper.getPerson ==> Preparing: select id,user_name as userName,age as age from test_user where id = ?
2023-02-16 15:49:48,212 DEBUG [main] -org.apache.ibatis.gwmtest.dao.PersonMapper.getPerson ==> Parameters: 1(Integer)
2023-02-16 15:49:48,264 DEBUG [main] -org.apache.ibatis.gwmtest.dao.PersonMapper.getPerson <== Total: 1
2023-02-16 15:49:48,281 WARN [main] -org.apache.ibatis.io.SerialFilterChecker As you are using functionality that deserializes object streams, it is recommended to define the JEP-290 serial filter. Please refer to https://docs.oracle.com/pls/topic/lookup?ctx=javase15&id=GUID-8296D8E8-2B93-4B9A-856E-0A65AF9B8C66
2023-02-16 15:49:48,285 DEBUG [main] -org.apache.ibatis.gwmtest.dao.PersonMapper Cache Hit Ratio [org.apache.ibatis.gwmtest.dao.PersonMapper]: 0.5
person==person1 = false
发现又可以命中二级缓存。然后在测试 getPerson2() 方法,最终也是可以命中二级缓存的。
以上就是对 Mybatis 二级缓存的具体使用步骤。