问题:查询=》连接数据库=》消耗资源!
解决方案:
在一次sqlSession会话中有效,如下从开启到关闭sqlSession
@Test public void testGetBlogList() { try (SqlSession sqlSession = MybatisUtils.getSqlSession()) { BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); List<Blog> blogList = mapper.getBlogList(); for (Blog blog : blogList) { System.out.println(blog); } } }
一级缓存默认开启,我们只需要测试即可
在核心配置文件mybatis-config.xml
<settings> <!-- <setting name="logImpl" value="STDOUT_LOGGING"/>--> <setting name="logImpl" value="LOG4J"/> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings>
package com.happy.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor @AllArgsConstructor public class User { private int id; private String name; private String pwd; }
package com.happy.dao; import com.happy.pojo.User; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import java.util.List; public interface UserMapper { @Select("select * from user") List<User> getUserList(); //规范最好给参数取名字 User getUserById(@Param("id") int id); }
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.happy.dao.UserMapper"> <select id="getUserById" parameterType="int" resultType="user"> select * from user where id = #{id} </select> </mapper>
package com.happy.dao; import com.happy.pojo.User; import com.happy.utils.MybatisUtils; import org.apache.ibatis.session.SqlSession; import org.junit.Test; import java.util.List; public class UserMapperTest { @Test public void testGetUserList() { try (SqlSession sqlSession = MybatisUtils.getSqlSession()) { UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> userList = mapper.getUserList(); for (User user : userList) { System.out.println(user); } } } @Test public void testGetUserById() { try (SqlSession sqlSession = MybatisUtils.getSqlSession()) { UserMapper mapper = sqlSession.getMapper(UserMapper.class); System.out.println("============第1次查询============="); User user1 = mapper.getUserById(3); System.out.println(user1); System.out.println("============第2次查询============="); User user2 = mapper.getUserById(3); System.out.println(user2); } } }
查询不同的东西
增删该操作DML操作,可能会改变原来的数据,所以必定会刷新缓存。
即在两次查询之间,加入修改操作后(即使是修改其他行数据),缓存也会刷新。
查询不同的mapper.xml
手动清楚缓存。
默认情况下,只启用了本地的会话缓存即一级缓存,它仅仅对一个会话中的数据进行缓存
。
二级缓存产生的原因:
一级缓存中的数据被再次保存到二级缓存中。
要启用全局的二级缓存,只需要在你的sql映射文件中添加一行:
<cache/>
二级缓存只作用于cache标签所在的映射文件中的语句,如果你混合使用Java API和XML映射文件,在共用接口中的语句将不会被默认缓存、你需要使用@CacheNamespaceRef注解指定缓存作用域。
这些属性可以通过cache元素的属性来修改,比如:
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
上面这个更高级的配置策略:
创建了一个FIFO缓存,
每隔60秒刷新,
最多可以存储结果对象或列表512个应用
而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同的线程中调用者产生冲突。
可用的清除策略有:
虽然默认为true
<settings> <setting name="cacheEnabled" value="true"/> </settings>
在要使用二级缓存的mapper.xml文件里开启,也可以自定义一些参数
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
@Test public void testGetUserByIdByCache2() { SqlSession sqlSession1=null; SqlSession sqlSession2=null; try { sqlSession1 = MybatisUtils.getSqlSession(); sqlSession2 = MybatisUtils.getSqlSession(); UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class); UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class); System.out.println("============第1次查询============="); User user1 = mapper1.getUserById(1); System.out.println(user1); System.out.println("============第2次查询============="); User user2 = mapper1.getUserById(1); System.out.println(user2); // 注意这里在其他sqlsession使用前必须关闭,如果不关闭sqlsession,不会把对象引用放到二级缓存共享给其他sqlsession sqlSession1.commit(); // sqlSession1.close(); System.out.println("============第3次查询,使用sqlsession2============="); User user3 = mapper2.getUserById(1); System.out.println(user3); System.out.println("============判断两个对象是否相同============="); System.out.println(user1 == user3); } catch (Exception e){ }finally { sqlSession1.close(); sqlSession2.close(); } }
注意:
readOnly="true"/>
user类实现Serializable接口
,否则需要。实现以下接口
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // package org.apache.ibatis.cache; import java.util.concurrent.locks.ReadWriteLock; public interface Cache { String getId(); void putObject(Object var1, Object var2); Object getObject(Object var1); Object removeObject(Object var1); void clear(); int getSize(); default ReadWriteLock getReadWriteLock() { return null; } }
mybatis已经实现的缓存策略如下:默认使用LRU,清除最不常使用。
要在程序中使用ehcache
<dependency> <groupId>org.mybatis.caches</groupId> <artifactId>mybatis-ehcache</artifactId> <version>1.2.1</version> </dependency>
除了上述自定义缓存方式,可以通过实现你自己的缓存,或为其他第三方缓存方案创建适配器,来完全覆盖缓存行为。
<!-- 开启二级缓存,在这个mapper文件中有效--> <cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true" type="org.mybatis.caches.ehcache.EhcacheCache" />
在mapper中指定使用我们的ehcache缓存实现。
<?xml version="1.0" encoding="UTF-8"?> <ehcache> <!-- 配置缓存文件的路劲 java.io.tmpdir,表示临时文件夹,windows表示在C:\Documents and Settings\Administrator\Local Setting\Temp --> <diskStore path="../temp/ehcache"/> <!-- 设定缓存的默认数据过期策略 --> <!-- name:缓存名称 maxElementsInMemory:缓存最大个数 eternal:对象是否永久有效,一但设置了,timeout将不起作用 timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒),仅当eternal=false对象不是永久有效时使用 timeToLiveSeconds:设置对象在失效前允许存活时间,最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用 overflowToDisk:当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中 diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB maxElementsOnDisk:硬盘最大缓存个数 diskPersistent: 是否缓存虚拟机重启期数据 diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒 memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU。 最近使用(LRU)"策略,其它还有先入先出FIFO,最少使用LFU,较少使用LRU clearOnFlush:内存数量最大时是否清除 --> <defaultCache maxElementsInMemory="1000" eternal="false" overflowToDisk="true" timeToIdleSeconds="300" timeToLiveSeconds="600" diskPersistent="false" diskExpiryThreadIntervalSeconds="120"/> <cache name="userCache" maxElementsInMemory="1000" eternal="false" overflowToDisk="true" timeToIdleSeconds="300" timeToLiveSeconds="600" memoryStoreEvictionPolicy="LRU"/> </ehcache>
和mybatis自带的二级缓存没有区别,只是多了一些缓存持久化存放到本地。
1、先判断二级缓存是否开启,如果没开启,再判断一级缓存是否开启,如果没开启,直接查数据库
2、如果一级缓存关闭,即使二级缓存开启也没有数据,因为二级缓存的数据从一级缓存获取
3、一般不会关闭一级缓存
4、二级缓存默认不开启
5、如果二级缓存关闭,直接判断一级缓存是否有数据,如果没有就查数据库
6、如果二级缓存开启,先判断二级缓存有没有数据,如果有就直接返回;如果没有,就查询一级缓存,如果有就返回,没有就查询数据库;
综上:先查二级缓存,再查一级缓存,再查数据库;即使在一个sqlSession中,也会先查二级缓存;一个namespace中的查询更是如此;缓存执行顺序是:二级缓存–>一级缓存–>数据库
工作中一般使用redis作为数据库缓存,而不使用自定义缓存。