概要:
1,2级缓存处理
动态化SQL
一、1,2级缓存处理
知识点:
1级缓存使用场景
2级缓存使用场景
1、1级缓存使用场景 订单表与会员表是存在一对多的关系,为了尽可能减少join 查询,进行了分阶段查询,即先查询出订单表,在根据member_id 字段查询出会员表,最后进行数据整合 。如果订单表中存在重复的member_id,就会出现很多没必要的重复查询。
针对这种情况myBatis 通过1级缓存来实现,在同一次查询会话中如果出现相同的语句及参数,就会从缓存中取出不在走数据库查询。
1级缓存只能作用于查询会话 中 所以也叫做==会话缓存==。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 public interface UserMapper { User getUser (Integer id) ; User selectById (Integer id) ; @Update("select * from user where id = #{id}") int updateById (Integer id) ; }@Slf4j public class MainTest { private SqlSession sqlSession = null ; private SqlSession sqlSession2 = null ; @Before public void before () { String resource = "mybatis-config.xml" ; InputStream inputStream = MainTest.class.getClassLoader().getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); sqlSession = sqlSessionFactory.openSession(); sqlSession2 = sqlSessionFactory.openSession(); } @Test public void test00 () { User user = (User)sqlSession.selectOne("com.study.mapper.UserMapper.selectById" , 1 ); System.out.println(user); } @Test public void test01 () { UserMapper mapper = sqlSession.getMapper(UserMapper.class); User user1 = mapper.getUser(1 ); log.info("首次查询======================" ); User user2 = mapper.getUser(1 ); User user3 = mapper.selectById(1 ); System.out.println(user1); System.out.println(user2); System.out.println(user3); System.out.println(user2==user1); System.out.println(user3==user1); } @Test public void test02 () { UserMapper mapper = sqlSession.getMapper(UserMapper.class); UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class); User user1 = mapper.getUser(1 ); log.info("首次查询======================" ); User user2 = mapper2.getUser(1 ); System.out.println(user1); System.out.println(user2); System.out.println(user2==user1); } @Test public void test03 () { UserMapper mapper = sqlSession.getMapper(UserMapper.class); UserMapper2 mapper2 = sqlSession.getMapper(UserMapper2.class); User user1 = mapper.getUser(1 ); log.info("首次查询结束======================" ); User user2 = mapper2.getUser(1 ); System.out.println(user1); System.out.println(user2); System.out.println(user2==user1); } @Test public void test04 () { UserMapper mapper = sqlSession.getMapper(UserMapper.class); User user1 = mapper.getUser(1 ); log.info("首次查询======================" ); sqlSession.clearCache(); User user2 = mapper.getUser(1 ); System.out.println(user1); System.out.println(user2); System.out.println(user2==user1); } @Test public void test05 () { UserMapper mapper = sqlSession.getMapper(UserMapper.class); User user1 = mapper.getUser(1 ); log.info("首次查询======================" ); mapper.updateById(1 ); User user2 = mapper.getUser(1 ); System.out.println(user1); System.out.println(user2); System.out.println(user2==user1); }
一级缓存的使用条件:
1.必须是相同的SQL和参数 2.必须是相同的statement 即同一个mapper实例接口中的同一个方法 3.必须是相同的会话 4.查询语句中间没有执行session.clearCache() 方法 5.查询语句中间没有执行 insert update delete 方法 (无论变动记录是否与 缓存数据有无关系)
6.必须是相同的namespace 即同一个mapper 7、同一个Mapper实例(Mapper类型相同的不同实例也不行) 1 2 3 UserMapper mapper = sqlSession.getMapper(UserMapper.class); UserMapper2 mapper2 = sqlSession.getMapper(UserMapper2.class);
一级缓存源码解析:
缓存获取 :
1 2 3 4 5 6 7 8 mapper.mapper.selectById(23 ) >org.apache.ibatis.binding.MapperProxy#invoke >org.apache.ibatis.binding.MapperMethod#execute >org.apache.ibatis.session.defaults.DefaultSqlSession#selectOne() >org.apache.ibatis.session.defaults.DefaultSqlSession#selectList() >org.apache.ibatis.executor.CachingExecutor#query()//尝试获取二级缓存 >org.apache.ibatis.executor.BaseExecutor#query() 142L >org.apache.ibatis.cache.impl.PerpetualCache#getObject 55L//尝试获取一级缓存
缓存的存储:
1 2 3 4 5 6 mapper.mapper.selectById(23 ) org.apache.ibatis.session.defaults.DefaultSqlSession#selectList() org.apache.ibatis.executor.CachingExecutor#query() org.apache.ibatis.executor.BaseExecutor#query() 142L org.apache.ibatis.executor.BaseExecutor#queryFromDatabase org.apache.ibatis.cache.impl.PerpetualCache#putObject
通过对clearCache 作为入口我们可能追踪到 一级缓存的实现PerpetualCache
1 2 3 4 org.apache.ibatis.session.defaults.DefaultSqlSession#clearCache org.apache.ibatis.executor.CachingExecutor#clearLocalCache org.apache.ibatis.executor.BaseExecutor#clearLocalCache org.apache.ibatis.cache.impl.PerpetualCache#clear
8.一级缓存是线程不安全的 提问:
在查询时另一个会话并发去修改查询的数据,一级缓存是否会生效?如果生效是否就会导致数据不正确?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 @Test public void testThread () { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); new Thread(new Runnable() { @Override public void run () { User user1 = userMapper.getUser(1 ); System.out.println("线程一:" ); System.out.println(user1); try { Thread.sleep(2000 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程一:" ); System.out.println(user1); } }).start(); try { Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(new Runnable() { @Override public void run () { User user2 = userMapper.getUser(1 ); user2.setUsername("周树人" ); System.out.println("线程二:" ); System.out.println(user2); sqlSession.close(); } }).start();
答:多次查询相同id一级缓存获取到的对象都是同一个应用,并没有做深克隆。所以当线程2查询某个缓存对象后并且人为修改了这个对象,那么也会同步影响线程一原本的对象,存在脏读 问题
2、2级缓存使用场景: 业务系统中存在很多的静态数据如,字典表、菜单表、权限表等,这些数据的特性是不会轻易修改但又是查询的热点数据。一级缓存针对的是同一个会话当中相同SQL,并不适合这情热点数据的缓存场景。为了解决这个问题引入了二级缓存,它脱离于会话之外。
2级缓存示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @CacheNamespace() public interface LabelMapper {@Select("select * from t_label where id =#{id}") Label getById (Integer id) ; } 属性说明:@CacheNamespace( implementation = PerpetualCache.class,// 缓存实现 Cache接口 实现类 eviction = LruCache.class,// 缓存算法 flushInterval =60000,// 刷新间隔时间 毫秒 size =1024,// 最大缓存引用对象 readWrite =true,// 是否可写 blocking = false// 是否阻塞,用来防止缓存击穿 ) 或者 <cache readOnly="false" blocking="true" flushInterval="60000" size="1024" eviction="LRU" />
bolocking 示意图
1、2级缓存使用条件: 1、当会话提交或关闭之后才会填充二级缓存 2、必须是在同一个命名空间之下 3、必须是相同的statement 即同一个mapper 接口中的同一个方法 4、必须是相同的SQL语句和参数 5、如果readWrite=true(readOnly=”false”) ,实体对像必须实现Serializable 接口 true:表示深克隆,实体需实现序列化,2次查询出来对象不是同一个,readWrite=false时,两次查询获取对象是同一个
6、mapper.xml 的<cache/>和 Mapper.java 的@CacheNamespace 是两个缓存空间,且不能共存 2、2级缓存清除条件: 1、xml中配置的update 不能清空 @CacheNamespace 中的缓存数据 2、只有修改会话提交之后 才会执行清空操作 3、任何一种增删改操作 都会清空整个namespace 中的缓存 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 <cache readOnly="true" blocking="true" flushInterval="60000" size="1024" eviction="LRU" />@Test public void test00 () { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); User user = userMapper.getUser(1 ); System.out.println("第一次会话结束" ); sqlSession.close(); UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class); User user2 = userMapper2.getUser(1 ); System.out.println(user==user2); } @Test public void test01 () { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); User user = userMapper.getUser(1 ); System.out.println("第一次会话结束" ); UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class); User user2 = userMapper2.getUser(1 ); System.out.println(user==user2); } @Test public void test02 () { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); User user = userMapper.getUser(1 ); System.out.println("第一次会话结束" ); sqlSession.close(); UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class); User user1 = new User(); user1.setUsername("lao li" ); user1.setId(1 ); userMapper2.updateUserById(user1); sqlSession2.close(); User user2 = sqlSessionFactory.openSession().getMapper(UserMapper.class).getUser(1 ); System.out.println(user==user2); }
2级缓存源码解析:
清除缓存!
1 2 3 4 org.apache.ibatis.session.defaults.DefaultSqlSession#selectList() 147L org.apache.ibatis.executor.CachingExecutor#query()81L org.apache.ibatis.executor.CachingExecutor#query()95L org.apache.ibatis.executor.CachingExecutor#flushCacheIfRequired() 164L //清除缓存
获取缓存关键源码!
1 2 3 4 5 6 7 8 org.apache.ibatis.cache.TransactionalCacheManager#getObject org.apache.ibatis.cache.decorators.TransactionalCache#getObject org.apache.ibatis.cache.decorators.SynchronizedCache#getObject org.apache.ibatis.cache.decorators.LoggingCache#getObject org.apache.ibatis.cache.decorators.SerializedCache#getObject org.apache.ibatis.cache.decorators.ScheduledCache#getObject org.apache.ibatis.cache.decorators.LruCache#getObject org.apache.ibatis.cache.impl.PerpetualCache#getObject
保存2级缓存 !
1 2 3 4 5 6 7 8 9 10 11 org.apache.ibatis.executor.CachingExecutor#close org.apache.ibatis.cache.TransactionalCacheManager#commit org.apache.ibatis.cache.decorators.TransactionalCache#flushPendingEntries org.apache.ibatis.cache.decorators.SynchronizedCache#putObject org.apache.ibatis.cache.decorators.LoggingCache#putObject org.apache.ibatis.cache.decorators.SerializedCache#putObject org.apache.ibatis.cache.decorators.ScheduledCache#putObject org.apache.ibatis.cache.decorators.LruCache#putObject org.apache.ibatis.cache.impl.PerpetualCache#putObject
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 public class TransactionalCache implements Cache { private Cache delegate; private Map<Object, Object> entriesToAddOnCommit; @Override public Object getObject (Object key) { Object object = delegate.getObject(key); if (object == null ) { entriesMissedInCache.add(key); } if (clearOnCommit) { return null ; } else { return object; } } @Override public void putObject (Object key, Object object) { entriesToAddOnCommit.put(key, object); } @Override public void clear () { clearOnCommit = true ; entriesToAddOnCommit.clear(); } public void commit () { if (clearOnCommit) { delegate.clear(); } flushPendingEntries(); reset(); } public void rollback () { unlockMissedEntries(); reset(); } }
3、如何理解 TransacionCache TransacionCache的生命周期是会话(事务)级别
TransacionCache:事务级别,sqlSession提交时会触发TransacionCache的commit
二级缓存:Mapper级别(应用级别)
一级缓存:SqlSession级别(会话级别)
问题:
为什么存在 TransactionalCache,且二级缓存必须是会话提交后 才生效
为了防止事务回滚的场景,别的会话脏读了二级缓存的的内容
所以当本次会话未关闭前,二级缓存新增对象都暂存在entriesToAddOnCommit 本地中
当事务提交时,才触发entriesToAddOnCommit 持久化到应用生命周期的delegate 这个实际cache中
而且每个会话期间,获取二级缓存都是从delegate 这个实际cache中获取,这样就避免了脏读(读未提交)问题.
4、二级缓存生命周期示意图
二、动态化SQL
基本命令使用
if
choose (when, otherwise)
trim (where, set)
foreach
示例说明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 <trim prefix ="where" prefixOverrides ="and|or" > <if test ="id != null" > and id = #{id} </if > <if test ="name != null" > and name = #{name} </if > </trim > trim属性说明: * prefix="where"// 前缀 * prefixOverrides="and|or"// 前缀要替换的词 * suffix=""// 添加后缀 * suffixOverrides=""// 后缀要替换的词<where > 元素说明: 在where 包裹的SQL前会自动添加 where 字符 并去掉首尾多佘的 and|or 字符 相当于下配置:<trim prefix ="where" prefixOverrides ="and|or" suffixOverrides ="and|or" > <set > 元素说明: 在set包裹的SQL前会自动添加 set 字符并去掉首尾多佘的 , 字符。<update id ="updateUser" parameterType ="User" > UPDATE users <set > <if test ="username != null" > username=#{username},</if > <if test ="password != null" > password=#{password},</if > <if test ="email != null" > email=#{email},</if > </set > WHERE id=#{id}</update > <sql > 元素说明:在同一个mapper多个statement 存在多个相同的sql 片段时,可以通过<sql > 元素声明,在通过 <include > 元素进行引用 声明sql 段<sql id ="files" > id ,name ,createTime</sql > 引用<include refid ="files" /> <bind > 变量使用 有时需要进行一些额外 逻辑运行,通过 声明<bind > 元素,并在其value 属性中添加运算脚本,如下示例 自动给likeName 加上了% 分号,然后就可以用#{likeName} 来使用带%分号的like 运算。<bind name ="likeName" value ="'%'+ _parameter.getName() +'%'" > </bind > 内置变量 _databaseid 数据库标识ID _parameter 当前参数变理
使用别的解析模板 以上的if trim where 等逻辑符都是 myBatis 自带的XMLLanguageDriver 所提供的解释语言,除非此之外 我们还可以使用 MyBatis-Velocity 或 mybatis-freemarker 等外部 解释器来编写动态脚本。
mybatis-freemarker 使用 引入mybatis 包:
1 2 3 4 5 <dependency > <groupId > org.mybatis.scripting</groupId > <artifactId > mybatis-freemarker</artifactId > <version > 1.1.2</version > </dependency >
1 2 3 4 5 6 7 8 9 10 11 添加sql 语句<select id ="selectByIds" resultType ="com.tuling.mybatis.dao.User" lang ="org.mybatis.scripting.freemarker.FreeMarkerLanguageDriver" > select * from user where id in(${ids?join(',')})</select > lang="org.mybatis.scripting.freemarker.FreeMarkerLanguageDriver" 指定解析器
添加接口方法 List selectByIds(@Param(“ids”) List ids);