当前位置: 首页 > news >正文

做logo用什么网站福建高速公路建设指挥部网站

做logo用什么网站,福建高速公路建设指挥部网站,wap网站建设免费,东莞废水处理 东莞网站建设文章目录 前言一、MyBatis 缓存概述二、一级缓存1_初识一级缓存2_一级缓存命中原则1_StatementId相同2_查询参数相同3_分页参数相同4_sql 语句5_环境 3_一级缓存的生命周期1_缓存的产生2_缓存的销毁3_网传的一些谣言 4_一级缓存核心源码5_总结 三、二级缓存1_开启二级缓存2_二级… 文章目录 前言一、MyBatis 缓存概述二、一级缓存1_初识一级缓存2_一级缓存命中原则1_StatementId相同2_查询参数相同3_分页参数相同4_sql 语句5_环境 3_一级缓存的生命周期1_缓存的产生2_缓存的销毁3_网传的一些谣言 4_一级缓存核心源码5_总结 三、二级缓存1_开启二级缓存2_二级缓存命中原则3_二级缓存的产生4_缓存失效情况5_二级缓存核心源码 五、缓存机制的注意事项六_总结 前言 MyBatis 是一个优秀的持久层框架它不仅简化了数据库交互的开发过程还提供了强大的缓存机制以提升性能。本文将详细介绍 MyBatis 的缓存机制包括一级缓存、二级缓存的工作原理、配置和注意事项。 一、MyBatis 缓存概述 为了减轻数据库的访问压力mybatis提供了缓存功能如果命中缓存将直接返回缓存中的结果实例不再需要查询数据库。 MyBatis 提供了两级缓存机制 一级缓存Local Cache也称为本地缓存是 SqlSession 级别的缓存。二级缓存Global Cache也称为全局缓存是 SqlSessionFactory 映射级别的缓存。 二、一级缓存 一级缓存是 SqlSession 级别的缓存在同一个 SqlSession 期间相同的查询结果会被缓存并复用减少数据库访问次数。它的作用范围是 SqlSession默认是开启的。 1_初识一级缓存 为了方便读者验证一级缓存的存在先将mybatis最基础的环境搭建完成。基础环境没有与sprig整合 所需的pom.xml依赖 dependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdversion1.18.34/version /dependency dependencygroupIdorg.mybatis/groupIdartifactIdmybatis/artifactIdversion3.5.11/version /dependency dependencygroupIdmysql/groupIdartifactIdmysql-connector-java/artifactIdversion8.0.28/version /dependency dependencygroupIdjunit/groupIdartifactIdjunit/artifactIdversion4.13.2/versionscopetest/scope /dependency首先是maybatis的基础配置文件 配置文件中包含了对 MyBatis 系统的核心设置包括获取数据库连接实例的数据源DataSource 以及决定事务作用域和控制方式的事务管理器TransactionManager。 这里是一个最简单的基础配置resources\mybaties.xml文件 ?xml version1.0 encodingUTF-8 ? !DOCTYPE configurationPUBLIC -//mybatis.org//DTD Config 3.0//ENhttps://mybatis.org/dtd/mybatis-3-config.dtd configuration!-- 控制台打印--settingssetting namelogImpl valueorg.apache.ibatis.logging.stdout.StdOutImpl//settingsenvironments defaultdevelopmentenvironment iddevelopment!-- 使用jdbc并配置数据源--transactionManager typeJDBC/dataSource typePOOLEDproperty namedriver valuecom.mysql.cj.jdbc.Driver/property nameurl valuejdbc:mysql://127.0.0.1:3306/bilibili/property nameusername valueroot/property namepassword value123456//dataSource/environment/environmentsmappersmapper resourcemappers/tempMapper.xml//mappers /configurationsql映射文件 resources/mappers/mapper.xml: ?xml version1.0 encodingUTF-8 ? !DOCTYPE mapperPUBLIC -//mybatis.org//DTD Mapper 3.0//ENhttps://mybatis.org/dtd/mybatis-3-mapper.dtd mapper namespacecom.shen.spring.dao.TempDaoselect idgetById parameterTypeint resultTypecom.shen.spring.entity.TempEntityselect * from temp where id #{id}/select /mapper测试使用的表及其数据所需sql drop table if exists temp; create table temp (id int auto_increment primary key ,value1 varchar(100) null ,value2 varchar(100) null ); insert into temp(value1, value2) VALUES (111111,aaaaaa); insert into temp(value1, value2) VALUES (222222,bbbbbb); insert into temp(value1, value2) VALUES (333333,cccccc); insert into temp(value1, value2) VALUES (444444,dddddd);对应的实体对象 import lombok.Data; import lombok.ToString;/*** author shenyang* version 1.0* info SpringBoot17* since 2024/7/22 下午3:41*/ Data ToString public class TempEntity {private Integer id;private String value1;private String value2; }对应的查询接口 import com.shen.spring.entity.TempEntity;/*** author shenyang* version 1.0* info SpringBoot17* since 2024/7/22 下午3:42*/ public interface TempDao {TempEntity getById(int id); }最后编写测试用例证明一级缓存的存在 package com.shen.spring;import com.shen.spring.entity.TempEntity; import lombok.extern.slf4j.Slf4j; 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 org.junit.Test;import java.io.IOException; import java.io.InputStream;/*** author shenyang* version 1.0* info SpringBoot17* since 2024/7/22 下午3:58*/ Slf4j public class TempDaoTest {Testpublic void test() throws IOException {//读取xml文件InputStream inputStream Resources.getResourceAsStream(mybaties.xml);//通过SqlSessionFactoryBuilder创建sqlSessionFactory SqlSessionFactory sqlSessionFactory new SqlSessionFactoryBuilder().build(inputStream);//拿到执行sql语句的一个会话SqlSession sqlSession sqlSessionFactory.openSession();// 第一次查询会从数据库获取数据TempEntity tempEntity1 sqlSession.selectOne(com.shen.spring.dao.TempDao.getById, 1);log.info({},tempEntity1);// 第二次查询会从缓存获取数据TempEntity tempEntity2 sqlSession.selectOne(com.shen.spring.dao.TempDao.getById, 1);log.info({},tempEntity2);System.out.println(tempEntity1 tempEntity2);} }输出结果 根据控制台输出结果可以发现第一次查询有sql语句日志输出代表查询了数据库。第二次没有sql语句输出没有查询数据库而是走的缓存。并且这两次查询都是返回同一个对象控制台结果打印true。这正验证了mybatis一级缓存的存在。 另外如果不了解SqlSession 这个mybatis提供的 java API 可以去官网上查看Mybatis-Java API。 最后给一个上述测试代码的时序图作为结尾吧 当 SqlSession 执行查询操作时首先会在缓存中查找是否有相同的查询结果。如果缓存中存在相同的结果直接返回。如果缓存中不存在执行数据库查询并将结果存入缓存中。 2_一级缓存命中原则 所谓的命中原则就是指:Mybatis是怎样判断某两次查询是完全相同的查询 为什么这么说呢因为如果两次查询时完全相同的查询且上一次查询时是有缓存的mybatis就不会查询数据库而是直接返回上一次查询的结果。 1_StatementId相同 先看第一个条件两次查询的StatementId必须相同,否则无法命中缓存即使两个查询语句、参数等完全一样。这个StatementId其实就是我们定义的dao层class内的方法名: 现在我们进行测试我的dao层和mapper.xml文件已经修改完成了这里就不附上完整步骤了 修改测试代码 Testpublic void test() throws IOException {InputStream inputStream Resources.getResourceAsStream(mybaties.xml);SqlSessionFactory sqlSessionFactory new SqlSessionFactoryBuilder().build(inputStream);SqlSession sqlSession sqlSessionFactory.openSession();TempEntity tempEntity1 sqlSession.selectOne(com.shen.spring.dao.TempDao.getById1, 1);log.info({},tempEntity1);TempEntity tempEntity2 sqlSession.selectOne(com.shen.spring.dao.TempDao.getById2, 1);log.info({},tempEntity2);System.out.println(tempEntity1 tempEntity2);sqlSession.close();//关闭sqlSession 关闭sql会话}输出结果 2_查询参数相同 要求两次查询的查询参数相同这里不做过多测试 。 但是这里还有一个问题这里的参数不是指SqlSession调用selectXXX()方法中的参数而是指最终执行sql语句中的参数。 也就是说要求传递给SQL的查询参数必须相同否则无法命中缓存。 比如如下例子 select idgetById3 parameterTypejava.util.Map resultTypecom.shen.spring.entity.TempEntityselect * from temp where id #{id} /select测试方法 Testpublic void test() throws IOException {InputStream inputStream Resources.getResourceAsStream(mybaties.xml);SqlSessionFactory sqlSessionFactory new SqlSessionFactoryBuilder().build(inputStream);SqlSession sqlSession sqlSessionFactory.openSession();MapString,Object params1 new HashMap();params1.put(id,1);params1.put(test,2);TempEntity tempEntity1 sqlSession.selectOne(com.shen.spring.dao.TempDao.getById3, params1);MapString,Object params2 new HashMap();params2.put(id,1);params2.put(test,2);log.info({},tempEntity1);TempEntity tempEntity2 sqlSession.selectOne(com.shen.spring.dao.TempDao.getById3, params2);log.info({},tempEntity2);System.out.println(tempEntity1 tempEntity2);}虽然这里的两次调用传递的Map对象不是同一个但是因为传递给sql中的参数是相同的所以可以查询到缓存所以只有一次sql查询的日志输出 3_分页参数相同 分页参数必须相同否则无法命中缓存。缓存的粒度是整个分页查询结果而不是结果中的每个对象。 注意这里的分页我们用的比较少这里的分页实际上是把数据库中的所有数据都查出来做个物理分页。而不是在数据库层面上用sql脚本进行分页。如下图这里的 RowBounds就是用来分页的。 这里的分页查询方法即使两次查询传递的不是同一个RowBounds的实例只要它们的传递参数相同就会命中缓存。反之参数不同就不会命中缓存。 select idlist resultTypecom.shen.spring.entity.TempEntityselect * from temp where 11 /select测试方法 //dao下添加 ListTempEntity list(); //测试方法 Test public void test2() throws IOException {InputStream inputStream Resources.getResourceAsStream(mybaties.xml);SqlSessionFactory sqlSessionFactory new SqlSessionFactoryBuilder().build(inputStream);SqlSession sqlSession sqlSessionFactory.openSession();RowBounds rowBounds1 new RowBounds(0,1);ListTempEntity tempEntity1 sqlSession //注意这里的参数列表要对上第三个参数的位置才是RowBounds .selectList(com.shen.spring.dao.TempDao.list, null,rowBounds1);log.info({},tempEntity1);RowBounds rowBounds2 new RowBounds(0,2);//两次参数不同ListTempEntity tempEntity2 sqlSession .selectList(com.shen.spring.dao.TempDao.list,null, rowBounds2);log.info({},tempEntity2);System.out.println(tempEntity1 tempEntity2); }结果如下 执行的sql也刚好验证了是查询了整个数据然后进行物理分页。 4_sql 语句 要求传递给JDBC的SQL语句必须完全相同。 看如下案例 mapper.xml文件,注意这两个查询的查询结果完全一样因为11必然是真不过Mybatis是不考虑这个的 select idgetById4 parameterTypejava.util.Map resultTypecom.shen.spring.entity.TempEntityif testtype 1select * from temp where id #{id}/ifif testtype 2select * from temp where 11 and id #{id}/if /selectdao层接口中的方法 TempEntity getById4(Map map);测试代码 Test public void test4() throws IOException {InputStream inputStream Resources.getResourceAsStream(mybaties.xml);SqlSessionFactory sqlSessionFactory new SqlSessionFactoryBuilder().build(inputStream);SqlSession sqlSession sqlSessionFactory.openSession();MapString,Object params1 new HashMap();params1.put(id,1);params1.put(type,1);TempEntity tempEntity1 sqlSession.selectOne(com.shen.spring.dao.TempDao.getById4, params1);MapString,Object params2 new HashMap();params2.put(id,1);params2.put(type,2);//根据动态sql会执行不同的sql语句log.info({},tempEntity1);TempEntity tempEntity2 sqlSession.selectOne(com.shen.spring.dao.TempDao.getById4, params2);log.info({},tempEntity2);System.out.println(tempEntity1 tempEntity2); }运行结果 5_环境 要求执行环境必须相同 其实我们在mapper.xml中 可以配置多个环境 并且在使用 SqlSessionFactoryBuilder().build(??) 方法构建 SqlSessionFactory 对象时其实还可以指定所使用环境。 public SqlSessionFactory build(InputStream inputStream, String environment) {return this.build((InputStream)inputStream, environment, (Properties)null); }不过这里我们无法进行测试因为环境的切换必然会导致创建不同的SqlSession对象。不过二级缓存中可以执行这个测试。 3_一级缓存的生命周期 学习到现在是不是一值有个疑惑 Mybatis一级缓存是什么时候产生的又是什么时候销毁的我们向下继续进行了解。 1_缓存的产生 我们的第一印象是 执行select/类型的statement的时候会产生缓存。 但是真的是这样么我们进行如下测试–使用update方法调用select\标签: Test public void testSelectAsUpdate() throws IOException {InputStream inputStream Resources.getResourceAsStream(mybaties.xml);SqlSessionFactory sqlSessionFactory new SqlSessionFactoryBuilder().build(inputStream);SqlSession sqlSession sqlSessionFactory.openSession();sqlSession.update(com.shen.spring.dao.TempDao.getById, 1);sqlSession.update(com.shen.spring.dao.TempDao.getById, 1); }执行结果 根据执行的结果可以知道产生缓存的实际上是SqlSession的select方法 2_缓存的销毁 缓存创建了之后又是如何被销毁的呢先说结论 当前的 SqlSession 关闭。执行 INSERT、UPDATE 或 DELETE 操作与表无关。调用了 SqlSession 的 clearCache() 方法主动清除缓存。调用了 SqlSession 的 Commit()、Rollback。 接下来进行验证 SqlSession 关闭缓存会被销毁 由于SqlSession会话关闭之后再次使用SqlSession会报错想要进行验证需要特殊的方法。 通过debug的方式查看这个LocalCache一个HashMap就是我们一级缓存存储的地方 了解了类图结构之后我们就可以通过反射的方式拿到一级缓存。 测试代码 Test public void test5() throws IOException, NoSuchFieldException, IllegalAccessException {InputStream inputStream Resources.getResourceAsStream(mybaties.xml);SqlSessionFactory sqlSessionFactory new SqlSessionFactoryBuilder().build(inputStream);SqlSession sqlSession sqlSessionFactory.openSession();// 第一次查询会从数据库获取数据TempEntity tempEntity1 sqlSession.selectOne(com.shen.spring.dao.TempDao.getById, 1);log.info({},tempEntity1);//从sqlSession的实现类DefaultSqlsession的成员变量中拿到cachingExecutorField executorField sqlSession.getClass().getDeclaredField(executor);executorField.setAccessible(true);CachingExecutor cachingExecutor (CachingExecutor) executorField.get(sqlSession);//从实例中获取//从CachingExecutor的成员变量中拿到SimpleExecutorField declaredField cachingExecutor.getClass().getDeclaredField(delegate);declaredField.setAccessible(true);SimpleExecutor simpleExecutor (SimpleExecutor) declaredField.get(cachingExecutor);//从SimpleExecutor中的父类中的成员变量中拿到PerpetualCache字段Field localCacheField simpleExecutor.getClass().getSuperclass().getDeclaredField(localCache);localCacheField.setAccessible(true);PerpetualCache perpetualCache (PerpetualCache) localCacheField.get(simpleExecutor);//最后从perpetualCache中拿到存储缓存的MapField cacheField perpetualCache.getClass().getDeclaredField(cache);cacheField.setAccessible(true);MapObject,Object map (MapObject, Object) cacheField.get(perpetualCache);//遍历结果SetMap.EntryObject, Object entries map.entrySet();int size1 entries.size();System.out.println(size1);if (size10){for (Map.EntryObject, Object entry : entries) {log.info(Map中数据结果为{} {},entry.getKey(),entry.getValue());}}sqlSession.close();//会话关闭后再次遍历int size2 entries.size();System.out.println(size2);if (size20){for (Map.EntryObject, Object entry : entries) {log.info(Map中数据结果为{} {},entry.getKey(),entry.getValue());}} }运行结果sqlSession关闭后缓存使用的map中没有元素了恰好证明了第一个结论 这里其他的情况并不像第一条这样比较特殊第二第三条感兴趣的可以自行进行验证参考我们之前的代码这里我就不一一验证了。 对第二条的 “与表无关” 进行一个解释这里的与表无关指的是即使我们执行 INSERT、UPDATE 或 DELETE 操作与Select/的不是同一张表也会清除缓存。 select idgetById parameterTypeint resultTypecom.shen.spring.entity.TempEntityselect * from temp where id #{id} /select select idgetById2 parameterTypeint resultTypecom.shen.spring.entity.TempEntityselect * from test where id #{id} /selectTest public void test9() throws IOException {//读取xml文件InputStream inputStream Resources.getResourceAsStream(mybaties.xml);//通过SqlSessionFactoryBuilder创建sqlSessionFactorySqlSessionFactory sqlSessionFactory new SqlSessionFactoryBuilder().build(inputStream);//拿到执行sql语句的一个会话SqlSession sqlSession sqlSessionFactory.openSession();// 第一次查询会从数据库获取数据TempEntity tempEntity1 sqlSession.selectOne(com.shen.spring.dao.TempDao.getById, 1);log.info({},tempEntity1);//执行更新操作并且与Session1操作的不是同一个表 这里是test表sqlSession.update(com.shen.spring.dao.TempDao.getById2, 1);// 第二次查询会从缓存获取数据TempEntity tempEntity2 sqlSession.selectOne(com.shen.spring.dao.TempDao.getById, 1);log.info({},tempEntity2);System.out.println(tempEntity1 tempEntity2); }运行结果 至于第三条在执行 SqlSession.clearCache()之后缓存会被清空第二次查询会查数据库。 3_网传的一些谣言 有些人总说Mybatis一级缓存会存在脏读问题这是不正确的仔细想想设计的人如果发现有这么大的问题会不会让一级缓存一直默认开启肯定是不能接受的。 相反我认为它反而解决了脏读的问题。我们看如下例子 根据如上图不难猜到网上的错误理解由于Session2已经将张三的年龄由18岁修改为20岁而Session1第二次查询由于查询的是缓存所以得到的张三年龄是18岁产生了脏读这里的脏读不是指的数据库中的那种查到了未提交的数据而是网上的错误理解即mybatis查询不到最新的数据也就是查询到了以前的脏数据。 首先强调由于关闭Session、执行Commit、执行RollBack都会清空Mybatis一级缓存所以实际上Mybatis一级缓存的生命周期是在数据库事务的生命周期之内的。 我们先回顾一下数据库事务中的脏读问题如下图 数据库中的脏读读未提交概念如下事务1读取了事务2修改但是尚未提交的数据如果事务2发生回滚则事务1读取的数据就变成了错误数据也称为脏数据。 我们看看两次的对比 所以这里我们可以发现反而mybatis解决了数据库事务的脏读问题即使数据库事务发生脏读mybatis也不会发生脏读问题。 而且我们到现在也可以很容易看出来根据第一张图查不到Session2更新并提交的数据一级缓存甚至解决了不可重复读和幻读的问题。 总结 问题read uncommittedread committedrepeatable readserializableMyBatis 一级缓存丢失更新避免避免避免避免✘脏读避免避免避免避免避免不可重复读避免避免避免避免幻读避免避免避免 4_一级缓存核心源码 接下来笔者将带领大家了解Mybatis的核心源码加深对Mybatis一级缓存的理解。 还记得我们之前画的那张类图吗SqlSession调用 SelectOne方法其实调用的是它的默认实现也就是 DefaultSqlSession中的方法并且可以看到selectOne其实调用的是selectList方法 public T T selectOne(String statement, Object parameter) {//调用了selectList方法ListT list this.selectList(statement, parameter);if (list.size() 1) {return list.get(0);} else if (list.size() 1) {throw new TooManyResultsException(Expected one result (or null) to be returned by selectOne(), but found: list.size());} else {return null;} }在 selectList调用链的最深处可以看到这个方法 private E ListE selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {List var6;try {//将我们定义的mapper.xml中的内容转换成对象 即 com.shen.spring.dao.TempDao.getByIdMappedStatement ms this.configuration.getMappedStatement(statement);var6 this.executor.query(ms, this.wrapCollection(parameter), rowBounds, handler);} catch (Exception var10) {Exception e var10;throw ExceptionFactory.wrapException(Error querying database. Cause: e, e);} finally {ErrorContext.instance().reset();}return var6; }this.executor.query这段关键代码所在的位置在Executor的实现类CachingExecutor中 public E ListE query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {BoundSql boundSql ms.getBoundSql(parameterObject);//这段代码是Mybatis缓存生成key依赖的参数也就是我们一级缓存的命中原则CacheKey key this.createCacheKey(ms, parameterObject, rowBounds, boundSql);return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }这里的 CacheKey 创建策略源码在BaseExecutor.class中 public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSqif (this.closed) {throw new ExecutorException(Executor was closed.);} else {CacheKey cacheKey new CacheKey();//update是对传递的对像的hashcode进行混合运算//StatementIdcacheKey.update(ms.getId());//分页参数cacheKey.update(rowBounds.getOffset());cacheKey.update(rowBounds.getLimit());//sql语句cacheKey.update(boundSql.getSql());//拿到方法的参数对方法参数进行循环ListParameterMapping parameterMappings boundSql.getParameterMappings();TypeHandlerRegistry typeHandlerRegistry ms.getConfiguration().getTypeHandlerRegistry();Iterator var8 parameterMappings.iterator();while(var8.hasNext()) {ParameterMapping parameterMapping (ParameterMapping)var8.next();if (parameterMapping.getMode() ! ParameterMode.OUT) {String propertyName parameterMapping.getProperty();Object value;if (boundSql.hasAdditionalParameter(propertyName)) {value boundSql.getAdditionalParameter(propertyName);} else if (parameterObject null) {value null;} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {value parameterObject;} else {MetaObject metaObject this.configuration.newMetaObject(parameterObject);value metaObject.getValue(propertyName);}//方法参数cacheKey.update(value);}}除了 CacheKey 我们可以看到下面还有一个查询的方法this.query: public E ListE query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {//这个Cache 是Mybatis二级缓存的内容 我们没有开启肯定是个nullCache cache ms.getCache();if (cache ! null) {}//还是会走到此处 delegate是个装饰模式 Executor类型 实现类是SimpleExecutorSimpleExecutor extends BaseExecutorreturn this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }虽然 this.delegate.query中 delegate的实现类是SimpleExecutor 但是SimpleExecutor 中并没有query方法。 SimpleExecutor extends BaseExecutor 所以此方法在BaseExecutor 中。 public E ListE query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)throws SQLException {ErrorContext.instance().resource(ms.getResource()).activity(executing a query).object(if (this.closed) {throw new ExecutorException(Executor was closed.);} else {if (this.queryStack 0 ms.isFlushCacheRequired()) {this.clearLocalCache();}List list;try {this.queryStack;//此处为关键代码会查询一级缓存list resultHandler null ? (List)this.localCache.getObject(key) : null;if (list ! null) {this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);} else {//如果一级缓存不命中走此处查询数据库list this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, }} finally {--this.queryStack;}if (this.queryStack 0) {····略}return list;} }this.localCache是个 PerpetualCache类的实例内部是使用HashMap维护缓存结构的他的内部结构与getObject方法的代码如下 ublic class PerpetualCache implements Cache {private final String id;//用来维护缓存的HashMapprivate final MapObject, Object cache new HashMap();public PerpetualCache(String id) {this.id id;}public String getId() {return this.id;}public int getSize() {return this.cache.size();}public void putObject(Object key, Object value) {this.cache.put(key, value);}//getObject方法public Object getObject(Object key) {return this.cache.get(key);} }根据核心源码解读对类图的补充蓝色部分是主要的接口其余的类都是它们的实现类 缓存销毁中close方法的源码在BaseExecutor.class中 public void close(boolean forceRollback) {try {try {this.rollback(forceRollback);} finally {if (this.transaction ! null) {this.transaction.close();}}} catch (SQLException var11) {SQLException e var11;log.warn(Unexpected exception on closing transaction. Cause: e);} finally {this.transaction null;this.deferredLoads null;//清空缓存this.localCache null;this.localOutputParameterCache null;this.closed true;} }commit提交方法的源码也在 BaseExecutor.class中 public void commit(boolean required) throws SQLException {if (this.closed) {throw new ExecutorException(Cannot commit, transaction is already closed);} else {//清除缓存的代码this.clearLocalCache();this.flushStatements();if (required) {this.transaction.commit();}} }可以看到commit方法调用了this.clearLocalCache()方法清除缓存 public void clearLocalCache() {//当前会话未被关闭if (!this.closed) {//调用map的clear方法this.localCache.clear();this.localOutputParameterCache.clear();} }update更新操作也会调用clearLocalCache()方法清空缓存而 Insert、delect方法内部会调用update方法执行任务。 5_总结 一级缓存也叫本地缓存它默认会启用并且不能关闭。一级缓存存在于SqlSession的生命周期中即它是SqlSession级别的缓存。在同一个 SqlSession 中查询时MyBatis 会把执行的方法和参数通过算法生成缓存的键值将键值和查询结果存入一个Map对象中。如果同一个SqlSession 中执行的方法和参数完全一致那么通过算法会生成相同的键值当Map 缓存对象中己经存在该键值时则会返回缓存中的对象。 其设计理念即在一个Session内 不过期不更新不限制一般情况下Session生存时间很短执行Update会销毁缓存而不是更新缓存支持主动销毁缓存 不限制是指如果一直缓存是没有上限的可能撑爆jvm内存。 与Spring集成的时候SqlSession的相关操作权不在我们的手中所以有一些注意事项可以自己尝试 如果与Spring集成的时候如果没有开启事务在一个方法内每次请求Spring都会关闭旧的session再创建新的session此时一级缓存无效。 开启事务在一个事务内Spring通过ThreadLocal始终使用同一个Session所以此时一级缓存在事务内有效。 三、二级缓存 存在弊端实际使用的人比较少。 二级缓存存在于SqlSessionFactory 的生命周期中即它是SqlSessionFactory级别的缓存。在多个 SqlSession 之间共享缓存数据。 1_开启二级缓存 需要在MyBatis 的全局配置settings 中有一个参数cacheEnabled这个参数是二级缓存的全局开关默认值 是true 初始状态为启用状态。Mybatis.xml: settingssetting namecacheEnabled valuetrue/ /settingsMyBatis 的二级缓存是和命名空间绑定的即二级缓存需要配置在Mapper.xml 映射文件中。在保证二级缓存的全局配置开启的情况下给Mapper.xml 开启二级缓存只需要在Mapper. xml 中添加如下代码 cache /配置缓存刷新间隔、大小、读写策略等可选 cache evictionFIFO flushInterval60000 size512 readOnlytrue/eviction缓存回收策略默认值为 LRULeast Recently Used。flushInterval缓存刷新间隔时间毫秒。size缓存大小。readOnly是否只读。 返回值对象需要实现Serializable接口如TempEntity implements Serializable。 如果成功可以在控制台看到二级缓存的打印日志 2_二级缓存命中原则 先说结论与一级缓存的命中原则一模一样。一级缓存中的5条放在这里也是适用的。 可自行测试。测试的时候不要忘了使用同一个SqlSessionFactory创建不同的SqlSession进行测试并且进行第5条环境测试的时候别忘了这里的流不能读两次。 3_二级缓存的产生 满足一级缓存产生的条件。Close Session 或者 Commit Session。(不包括别的如rollback) select/标签中没有设置 userCache false。 自行验证哦。 4_缓存失效情况 二级缓存会在以下几种情况下被销毁 执行 INSERT、UPDATE 或 DELETE 操作。刷新缓存的参数flushCache没有设置成false(默认为true)配置了缓存刷新间隔时间时间到了缓存会自动失效。 rollback 不会销毁缓存。 eviction清除策略配置参数 LRU最近最少使用移除最长时间不被使用的对象。 — LinlHashMap。FIFO先进先出按对象进入缓存的顺序来移除它们。----- LinkedList。SOFT软引用基于GC和软引用规则移除对象。------SoftReference。Weak弱引用基于GC和弱引用规则移除对象。------WeakReference。 这些清除策略mybatis内都有对应的源码实现XXXXCache。 size引用数目配置参数 缓存引用的最大数目默认1024. 还有个需要注意的点MyBatis 的二级缓存是和命名空间mapper绑定的。如果存在两个不同的mapper.xml管理dao层操作。执行更新的是mapper1不会将mapper2管理的缓存清空。 比如下方示例TestMapper操作test表TempMapper操作temp表一个mapper下的sqlSession的更新不会导致整个SqlSessionFactory的失效 Test public void test8() throws IOException {//读取xml文件InputStream inputStream Resources.getResourceAsStream(mybaties.xml);//通过SqlSessionFactoryBuilder创建sqlSessionFactorySqlSessionFactory sqlSessionFactory new SqlSessionFactoryBuilder().build(inputStream);//拿到执行sql语句的第一个会话SqlSession sqlSession1 sqlSessionFactory.openSession();// 第一次查询会从数据库获取数据 操作temp表TempEntity tempEntity1 sqlSession1.selectOne(com.shen.spring.dao.TempDao.getById, 1);log.info({},tempEntity1);//关闭会话并保存二级缓存sqlSession1.commit();//执行更新操作sqlSession1.update(com.shen.spring.dao.TestDao.getById, 1);sqlSession1.commit();//拿到第二个会话SqlSession sqlSession2 sqlSessionFactory.openSession();// 第二次查询会从缓存获取数据TempEntity tempEntity2 sqlSession2.selectOne(com.shen.spring.dao.TempDao.getById, 1);log.info({},tempEntity2);System.out.println(tempEntity1 tempEntity2);//虽然命中缓存会但是会输出false由于序列化的关系 }返回结果虽然命中缓存会但是会输出false由于序列化的关系。输出两次sql日志 如果两个mapper操作同一个表也是一样的一个mapper更新也不会让另一个mapper销毁缓存。 无论是单线程、多线程、多实例的情况下都会产生脏读。一级缓存没有脏读问题 不能跨SqlSessionFactory的原因 缓存不能跨SqlSessionFactory public class DefaultSqlSessionFactory implements SqlSessionFactory {private final Configuration configuration; } public class Configuration {····protected final MapString, MappedStatement mappedStatements;protected final MapString, Cache caches;····public Configuration(Environment environment) {this();this.environment environment;}public Configuration() {···this.caches new StrictMap(Caches collection); }而且多个SqlSessionFactory会存在脏读可以使用自定义缓存redis解决。 5_二级缓存核心源码 我们之前查看一级缓存时发现过一段二级缓存的代码: Cache cache ms.getCache(); public E ListE query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {//这个Cache 是Mybatis二级缓存的内容 我们没有开启肯定是个nullCache cache ms.getCache();if (cache ! null) {this.flushCacheIfRequired(ms);//缓存刷新属性如果设置了进行的一些动作if (ms.isUseCache() resultHandler null) {//看看标签中是否禁用了缓存 resultHandler默认就是nullthis.ensureNoOutParams(ms, boundSql);//如果开启二级缓存要求我们没有输出参数不然方法里面会报错。ListE list (List)this.tcm.getObject(cache, key);//查询二级缓存if (list null) {//跟最下面的一样是一级缓存的过程list this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);this.tcm.putObject(cache, key, list);//查询出来结果放入二级缓存中}return list;}}//还是会走到此处 delegate是个装饰模式 Executor类型 实现类是SimpleExecutorSimpleExecutor extends BaseExecutorreturn this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }从中可以看到是先走二级缓存查询不到才走一级缓存的逻辑。并且 tcm 是存储二级缓存的对象 TransactionalCacheManager 类型结构如下 public class TransactionalCacheManager {private final MapCache, TransactionalCache transactionalCaches new HashMap();····public void putObject(Cache cache, CacheKey key, Object value) {//放入缓存的方法this.getTransactionalCache(cache).putObject(key, value);}//最终调用此方法private TransactionalCache getTransactionalCache(Cache cache) {//如果没有则新建一个return (TransactionalCache)MapUtil.computeIfAbsent(this.transactionalCaches, cache, TransactionalCache::new);} }TransactionalCacheManager 对象中维护了一个 HashMap 。key是一个java的Cache接口其实就是我们维护的一个个开启了二级缓存的Mapper文件所对应的缓存对象value是个TransactionalCache 就是针对一级缓存所使用的那些Cache的实现类的进一步包装(还是装饰模式)。 TransactionalCache 的功能就是将我们查询到的结果不是直接放到缓存对象中而是放入entriesToAddOnCommit 中。 public class TransactionalCache implements Cache {private static final Log log LogFactory.getLog(TransactionalCache.class);private final Cache delegate;private boolean clearOnCommit;//执行commit时清空缓存private final MapObject, Object entriesToAddOnCommit;//当我们执行commit时即将放入二级缓存中的对象private final SetObject entriesMissedInCache;//当前session未命中的缓存//更新操作会调用此方法public void clear() {//commit时会用到这个参数 this.clearOnCommit true;//仅仅清空本次执行的二级缓存让其无效this.entriesToAddOnCommit.clear();}//提交后会调用到此方法public void commit() {//如果clear方法被调用clearOnCommit会变成true执行清空二级缓存操作if (this.clearOnCommit) {//执行清空二级缓存的操作this.delegate.clear();}//调用此方法this.flushPendingEntries();this.reset();}private void flushPendingEntries() {Iterator var1 this.entriesToAddOnCommit.entrySet().iterator();//对map结构进行循环根据key和value让其放入了我们二级缓存最终保存的对象中while(var1.hasNext()) {Map.EntryObject, Object entry (Map.Entry)var1.next();this.delegate.putObject(entry.getKey(), entry.getValue());}var1 this.entriesMissedInCache.iterator();while(var1.hasNext()) {Object entry var1.next();if (!this.entriesToAddOnCommit.containsKey(entry)) {this.delegate.putObject(entry, (Object)null);}}} }上述类中的commit方法再SqlSession被close时也会被调用。 二级缓存的类图右侧是三个接口与一级缓存的类图一样 五、缓存机制的注意事项 一致性问题缓存会导致数据一致性问题尤其是在高并发环境下需要谨慎使用。数据更新当数据发生变化时需要及时更新缓存以免返回过期数据。缓存策略根据具体业务需求配置合理的缓存策略如缓存回收策略、刷新间隔等。序列化二级缓存中的对象需要实现序列化接口以确保缓存对象能够正确存储和读取。 六_总结 一级缓存也叫本地缓存它默认会启用并且不能关闭。一级缓存存在于SqlSession的生命周期中即它是SqlSession级别的缓存。在同一个 SqlSession 中查询时MyBatis 会把执行的方法和参数通过算法生成缓存的键值将键值和查询结果存入一个Map对象中。如果同一个SqlSession 中执行的方法和参数完全一致那么通过算法会生成相同的键值当Map 缓存对象中己经存在该键值时则会返回缓存中的对象。 二级缓存存在于SqlSessionFactory 的生命周期中即它是SqlSessionFactory级别的缓存也是 Mapper 映射级别的缓存需要配置并适用于多个 SqlSession 共享缓存数据。在使用缓存时需要注意数据的一致性问题根据实际需求选择合适的缓存策略和配置。 可以使用redis和mybatis整合让二级缓存到redis中而不是jvm中。 通过合理使用 MyBatis 缓存机制可以显著提高系统的性能降低数据库的负载是优化 MyBatis 应用性能的重要手段之一。
http://www.lakalapos1.cn/news/66193/

相关文章:

  • 杭州 企业 建网站WordPress自定义古腾堡
  • 湖州培训网站建设网络营销网站推广营销案例
  • 网站快速备案公司做设计灵感的网站
  • 网站服务器网络小制作简单又漂亮
  • 李洋网站建设国内红酒网站建设
  • 花垣网站建设建站程序选择
  • 做国外网站做什么内容好潍坊在线网站建设
  • 网站开发前端后端书籍wordpress弹出服务协议窗口
  • 网站开发实训报告深圳住房与城乡建设部网站
  • 教育房地产 网站建设百度贴吧网页版登录
  • 教育公司网站模板wordpress使用文档
  • 做竞价网站英语网站建设
  • 网站开发年度总结工作wordpress 证书
  • html做的网站怎么发布wordpress resize
  • 晚上必看正能量网站短视频买一个域名多少钱一个
  • 深圳模板网站建设旅游信息网站开发
  • 青岛网站快速排名优化浙江省建设工程协会网站
  • 织梦网站管理安装西安网站制作开发公司哪家好
  • 东莞网站搜索排名做购物网站之前做些什么
  • 网站后台文章编辑器在线可以做翻译的网站
  • 网站建设与管理结课论文申请公司邮箱
  • 网站建设完整教程视频教程动漫设计与制作培训学院
  • 网站提升权重肇庆网站推广排名
  • 国外响应式网站模板东营市
  • 卖网站模板加强网站建设管理办法
  • 部门网站建设怎么做做pc端网站要成本么
  • 营销型网站建设哪家便宜wordpress 导入图片
  • 哪些网站容易做wordpress设置上传文件大小
  • 京东网站建设框架图多用户商城系统哪里有
  • 广州电子商城网站网络品牌策划