日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区

您的位置:首頁技術文章
文章詳情頁

Mybatis一級緩存和結合Spring Framework后失效的源碼探究

瀏覽:60日期:2023-07-16 14:24:24

1.在下面的案例中,執行兩次查詢控制臺只會輸出一次 SQL 查詢:

mybatis-config.xml<?xml version='1.0' encoding='UTF-8' ?><!DOCTYPE configurationPUBLIC '-//mybatis.org//DTD Config 3.0//EN''http://mybatis.org/dtd/mybatis-3-config.dtd'><configuration> <environments default='development'><environment id='development'> <transactionManager type='JDBC'/> <dataSource type='POOLED'><property name='driver' value='com.mysql.jdbc.Driver'/><property name='url' value='jdbc:mysql://localhost:3306/xxx?useUnicode=true&characterEncoding=utf-8&autoReconnect=true'/><property name='username' value='xxx'/><property name='password' value='xxx'/> </dataSource></environment> </environments> <mappers><mapper resource='com/hrh/mapper/PersonMapper.xml'/> </mappers></configuration>

PersonMapper.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='com.hrh.mapper.PersonMapper'> <resultMap type='com.hrh.bean.Person'><id column='id' property='id' jdbcType='BIGINT'/><result column='name' property='name' jdbcType='VARCHAR'/><result column='age' property='age' jdbcType='BIGINT'/> </resultMap> <sql id='Base_Column_List'> id, name, age </sql> <select resultType='com.hrh.bean.Person'>select<include refid='Base_Column_List'/>from tab_person </select></mapper>

public interface PersonMapper { List<Person> list();}

String resource = 'mybatis-config2.xml';InputStream inputStream = Resources.getResourceAsStream(resource);SqlSessionFactory sqlSessionFactory =new SqlSessionFactoryBuilder().build(inputStream);SqlSession sqlSession = sqlSessionFactory.openSession();//開啟會話PersonMapper mapper = sqlSession.getMapper(PersonMapper.class);mapper.list();mapper.list();

Mybatis一級緩存和結合Spring Framework后失效的源碼探究

之所以會出現這種情況,是因為 Mybatis 存在一級緩存導致的,下面 debug 探究下內部流程:

Mybatis一級緩存和結合Spring Framework后失效的源碼探究

(1)mapper.list() 會進入 MapperProxy#invoke():參數proxy是一個代理對象(每個 Mapper 接口都會被轉換成一個代理對象),里面包含會話 sqlSession、接口信息、方法信息;method是目標方法(當前執行的方法),它里面包含了所屬的哪個類(接口)、方法名、返回類型(List、Map、void 或其他)、參數類型等;args是參數;

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { if (Object.class.equals(method.getDeclaringClass())) {return method.invoke(this, args); } else if (isDefaultMethod(method)) {return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } //從方法緩存methodCache中獲取到方法的信息:比如方法名、類型(select、update等)、返回類型 //如果獲取中沒有MapperMethod,則創建一個并放入methodCache中 final MapperMethod mapperMethod = cachedMapperMethod(method); //執行查詢SQL并返回結果 return mapperMethod.execute(sqlSession, args); }

Mybatis一級緩存和結合Spring Framework后失效的源碼探究

cacheMapperMethod:MapperMethod 包含方法名、類型(select、update等)、返回類型等信息

private MapperMethod cachedMapperMethod(Method method) { //緩存中獲取 MapperMethod mapperMethod = methodCache.get(method); //沒有則創建一個對象并放入緩存中供下次方便取用 if (mapperMethod == null) { mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()); methodCache.put(method, mapperMethod); } return mapperMethod; }

(2)MapperMethod#execute()根據 SQL 類型進入不同的查詢方法

public Object execute(SqlSession sqlSession, Object[] args) { //返回結果 Object result; //判斷語句類型 switch (command.getType()) { case INSERT: {//插入語句 Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.insert(command.getName(), param));break; } case UPDATE: {//更新語句Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.update(command.getName(), param));break; } case DELETE: {//刪除語句Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.delete(command.getName(), param));break; } case SELECT://查詢語句//返回空的查詢if (method.returnsVoid() && method.hasResultHandler()) { executeWithResultHandler(sqlSession, args); result = null; //返回List的查詢} else if (method.returnsMany()) { result = executeForMany(sqlSession, args); //返回Map的查詢} else if (method.returnsMap()) { result = executeForMap(sqlSession, args); //返回游標的查詢} else if (method.returnsCursor()) { result = executeForCursor(sqlSession, args);} else { Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(command.getName(), param);}break; case FLUSH:result = sqlSession.flushStatements();break; default:throw new BindingException('Unknown execution method for: ' + command.getName()); } if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) { throw new BindingException('Mapper method ’' + command.getName() + ' attempted to return null from a method with a primitive return type (' + method.getReturnType() + ').'); } return result; }

(3)上面的案例是 select 語句,返回結果是List集合,所以進入 MapperMethod#executeForMany():

private <E> Object executeForMany(SqlSession sqlSession, Object[] args) { List<E> result; //獲取參數 Object param = method.convertArgsToSqlCommandParam(args); //是否有分頁查詢 if (method.hasRowBounds()) { RowBounds rowBounds = method.extractRowBounds(args); result = sqlSession.<E>selectList(command.getName(), param, rowBounds); } else { result = sqlSession.<E>selectList(command.getName(), param); } // issue #510 Collections & arrays support //如果list中的泛型跟結果類型不一致,進行轉換 if (!method.getReturnType().isAssignableFrom(result.getClass())) { if (method.getReturnType().isArray()) {return convertToArray(result); } else {return convertToDeclaredCollection(sqlSession.getConfiguration(), result); } } return result; }

(4)selectList執行了DefaultSqlSession#selectList():

public <E> List<E> selectList(String statement, Object parameter) { return this.selectList(statement, parameter, RowBounds.DEFAULT); }

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { //SQL執行的信息:resource(xxMapper.xml)、id、sql、返回類型等 MappedStatement ms = configuration.getMappedStatement(statement); //執行查詢 return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); } catch (Exception e) { throw ExceptionFactory.wrapException('Error querying database. Cause: ' + e, e); } finally { ErrorContext.instance().reset(); } }

Mybatis一級緩存和結合Spring Framework后失效的源碼探究

(5)接下來調用緩存執行器的方法:CachingExecutor#query()

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { //獲取到執行SQL BoundSql boundSql = ms.getBoundSql(parameterObject); //將SQL包裝成一個緩存對對象,該對象和結果集組成鍵值對存儲到緩存中,方便下次直接從緩存中拿而不需要再次查詢 //createCacheKey:調用BaseExecutor#createCacheKey CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { //獲取緩存 Cache cache = ms.getCache(); if (cache != null) { flushCacheIfRequired(ms); if (ms.isUseCache() && resultHandler == null) {ensureNoOutParams(ms, boundSql);@SuppressWarnings('unchecked')List<E> list = (List<E>) tcm.getObject(cache, key);if (list == null) { list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); tcm.putObject(cache, key, list); // issue #578 and #116}return list; } } //沒有緩存連接查詢 return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }

(6)接下來執行 BaseExecutor#query():從下面可以看到將結果緩存到localCache 中了

public <E> List<E> 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(ms.getId()); if (closed) { throw new ExecutorException('Executor was closed.'); } //如果不是嵌套查詢(默認為0),且 <select> 的 flushCache=true 時清空緩存 if (queryStack == 0 && ms.isFlushCacheRequired()) { clearLocalCache(); } List<E> list; try { //嵌套查詢層數+1 queryStack++; //從localCache緩存中獲取 list = resultHandler == null ? (List<E>) localCache.getObject(key) : null; if (list != null) {handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else {//連接查詢list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } } finally { queryStack--; } //下面是延遲加載邏輯 if (queryStack == 0) { for (DeferredLoad deferredLoad : deferredLoads) {deferredLoad.load(); } // issue #601 deferredLoads.clear(); if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {// issue #482clearLocalCache(); } } return list; }

private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { List<E> list; //緩存中添加占位符 localCache.putObject(key, EXECUTION_PLACEHOLDER); try { //連接查詢獲取到數據結果 list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); } finally { //刪除占位符 localCache.removeObject(key); } //將結果緩存起來 localCache.putObject(key, list); //處理存儲過程 if (ms.getStatementType() == StatementType.CALLABLE) { localOutputParameterCache.putObject(key, parameter); } return list; }

2.但當 Spring Framework + Mybatis 時,情況就不一樣了,每次查詢都會連接數據庫查詢,控制臺都會打印 SQL 出來,如下案例:

@Servicepublic class PersonService { @Autowired PersonMapper personMapper; public List<Person> getList() {personMapper.list();personMapper.list();return personMapper.list(); }}

@Configuration@ComponentScan('com.hrh')@MapperScan('com.hrh.mapper')public class MyBatisConfig { @Bean public SqlSessionFactoryBean sqlSessionFactory() throws Exception {SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();factoryBean.setDataSource(dataSource());factoryBean.setMapperLocations(resolveMapperLocations());return factoryBean; } public Resource[] resolveMapperLocations() {ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();List<String> mapperLocations = new ArrayList<>();mapperLocations.add('classpath*:com/hrh/mapper/*Mapper*.xml');List<Resource> resources = new ArrayList();if (mapperLocations != null) { for (String mapperLocation : mapperLocations) {try { Resource[] mappers = resourceResolver.getResources(mapperLocation); resources.addAll(Arrays.asList(mappers));} catch (IOException e) { // ignore} }}return resources.toArray(new Resource[resources.size()]); } @Bean public DataSource dataSource() {DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();driverManagerDataSource.setDriverClassName('com.mysql.jdbc.Driver');driverManagerDataSource.setUsername('xxx');driverManagerDataSource.setPassword('xxx');driverManagerDataSource.setUrl('jdbc:mysql://localhost:3306/xxx?useUnicode=true&characterEncoding=utf-8&autoReconnect=true');return driverManagerDataSource; }}

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyBatisConfig.class);PersonService bean = context.getBean(PersonService.class);bean.getList();

Mybatis一級緩存和結合Spring Framework后失效的源碼探究

下面debug進入的步驟跟上面的(1)、(2)、(3)是一致的,但第四步卻是進入SqlSessionTemplate#selectList()中【SqlSessionTemplate是mybatis-spring-xx.jar的,上文的DefaultSqlSession是屬于mybatis-xx.jar的】:

public <E> List<E> selectList(String statement, Object parameter) { return this.selectList(statement, parameter, RowBounds.DEFAULT); }

接下來的selectList() 會被方法攔截:method.invoke() 會執行到 DefaultSqlSession#selectList(),重新回到上文的第四步并且繼續下去,也就是在上文的(1)~(6)中插入了前后文,在其中做了關閉會話的操作;

private class SqlSessionInterceptor implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //得到會話 SqlSession sqlSession = getSqlSession( SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator); try {//執行方法查詢Object result = method.invoke(sqlSession, args);if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) { // force commit even on non-dirty sessions because some databases require // a commit/rollback before calling close() sqlSession.commit(true);//在關閉會話前提交和回滾}return result; } catch (Throwable t) {//有異常拋出異常并結束會話Throwable unwrapped = unwrapThrowable(t);if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) { // release the connection to avoid a deadlock if the translator is no loaded. See issue #22 closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); sqlSession = null; Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped); if (translated != null) { unwrapped = translated; }}throw unwrapped; } finally {//關閉會話if (sqlSession != null) { closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);} } } }總結:

Mybatis 的一級緩存是會話級別的緩存(單線程的,特別雞肋),Mybatis 每創建一個 SqlSession 會話對象,就表示打開一次數據庫會話,在一次會話中,應用程序很可能在短時間內反復執行相同的查詢語句,如果不對數據進行緩存,則每查詢一次就要執行一次數據庫查詢,這就造成數據庫資源的浪費。又因為通過 SqlSession 執行的操作,實際上由 Executor 來完成數據庫操作的,所以在 Executor 中會建立一個簡單的緩存,即一級緩存;將每次的查詢結果緩存起來,再次執行查詢的時候,會先查詢一級緩存(默認開啟的),如果命中,則直接返回,否則再去查詢數據庫并放入緩存中。

一級緩存的生命周期與 SqlSession 的生命周期相同,因此當 Mybatis 和Spring Framework 的集成包中擴展了一個 SqlSessionTemplate 類(它是一個代理類,增強了查詢方法),所有的查詢經過 SqlSessionTemplate 代理攔截后再進入到 DefaultSqlSession#selectList() 中,結束查詢后把會話SqlSession 關了,所以導致了緩存失效。

那為什么要這么操作呢?

原始的 Mybatis 有暴露 SqlSession 接口,因此有 close 方法暴露出來供你選擇使用,你可以選擇關與不關,但在Mybatis 和Spring Framework 的集成包中,SqlSession 是交給了Spring Framework 管理的,沒有暴露出來,為了穩妥決定,直接給你關了。

到此這篇關于Mybatis一級緩存和結合Spring Framework后失效的源碼探究的文章就介紹到這了,更多相關Mybatis一級緩存Spring Framework失效內容請搜索好吧啦網以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持好吧啦網!

標簽: Spring
相關文章:
日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区
成人福利av| 久久丁香四色| 91看片一区| 日韩中文在线电影| 久久久久国产一区二区| 欧美亚洲国产一区| 伊人久久成人| 日韩精品一级中文字幕精品视频免费观看| 精品一区免费| aa国产精品| 亚洲精品欧美| 国产精品毛片视频| 国产精品久久观看| 91精品一区二区三区综合在线爱| 亚洲精品中文字幕乱码| 亚洲香蕉网站| 六月天综合网| 欧美视频一区| 国产成人精品免费视| 久久久久国产| 丝袜脚交一区二区| 日韩二区在线观看| 精品欧美日韩精品| 99久久www免费| 亚洲欧洲日韩| 久久99高清| 亚洲精品97| 日本不卡一区二区三区| 欧美精品99| 精品捆绑调教一区二区三区| 欧美日韩国产在线一区| 久久成人国产| 国产激情一区| 99国产精品一区二区| 亚洲色图网站| 精品美女视频 | 日韩精品首页| 亚洲综合婷婷| 精品欠久久久中文字幕加勒比| 99久久婷婷这里只有精品| 丝袜美腿亚洲一区| 精品国产一区二| 欧美特黄一区| 国产亚洲字幕| 日韩大片在线播放| 日韩欧美2区| 日韩不卡视频在线观看| 视频一区日韩精品| 日本久久黄色| 综合激情视频| 日产精品一区| 人人精品久久| 日韩一区二区中文| 日本成人一区二区| 999国产精品999久久久久久| 日韩欧美久久| 精品日韩视频| 日韩不卡一区二区三区| 97精品在线| 蜜臀久久99精品久久久画质超高清| 久久成人av| 六月天综合网| 日韩一区二区在线免费| 日韩成人av影视| 午夜精品婷婷| 日韩成人精品一区二区| 视频一区中文字幕精品| 久久激情中文| 精品一区二区三区中文字幕视频 | 丝袜a∨在线一区二区三区不卡| 久久麻豆视频| 一区二区电影| 免费不卡中文字幕在线| 精品国产欧美日韩| 青青青国产精品| 国产偷自视频区视频一区二区| 国产成人精品一区二区三区免费| 综合国产精品| 宅男在线一区| 亚洲精品成人图区| 国产欧美自拍| 蜜臀av一区二区在线免费观看| 亚洲a一区二区三区| 精品久久久久久久| 欧美日韩一区二区三区四区在线观看 | 99在线精品免费视频九九视| 美女视频一区在线观看| 亚洲欧洲日本mm| 日韩精彩视频在线观看| 蜜臀久久99精品久久一区二区| 久久精品国产精品亚洲毛片| 日本中文字幕一区二区视频| 欧美精品自拍| 欧美激情 亚洲a∨综合| 日韩1区2区3区| 蜜桃av一区二区在线观看| 欧美日韩一二| 日韩一区二区三区免费| 国产高潮在线| 久久97久久97精品免视看秋霞| 欧美一区网站| 日本vs亚洲vs韩国一区三区二区| 免费日韩av片| 中文日韩欧美| 在线成人动漫av| 色爱av综合网| 日韩一区三区| 久久久夜夜夜| 欧美日韩国产v| 日韩av一级| 亚洲小说欧美另类婷婷| 精品日韩毛片| 在线精品国产亚洲| 国产亚洲精品美女久久| 国产精品videossex久久发布| 黄色欧美在线| 国产欧美精品| 亚洲欧洲一区| 亚洲视频播放| 蜜桃视频在线观看一区| 亚洲永久精品唐人导航网址| 蜜桃视频在线观看一区| 亚洲免费资源| 黄色在线网站噜噜噜| 国产日韩欧美一区二区三区| 久久中文字幕一区二区三区| 蜜桃精品在线| 亚洲色图网站| 久久伊人久久| 亚洲综合激情在线| 国产字幕视频一区二区| 在线精品小视频| 亚洲一卡久久| 亚洲香蕉久久| 国产日韩中文在线中文字幕 | 欧美成人久久| 日韩成人综合| 玖玖玖国产精品| 欧美日韩伊人| 亚洲成人不卡| 综合亚洲视频| 麻豆成全视频免费观看在线看| 久久伊人久久| 久久久一二三| 视频一区免费在线观看| 午夜电影一区| 国产精品最新| 亚洲综合电影| 国产伦理久久久久久妇女| 国产a亚洲精品| 日韩中文字幕麻豆| 美女国产精品久久久| 亚洲韩日在线| 国产精品v日韩精品v欧美精品网站| 日本国产精品| 99在线观看免费视频精品观看| 日本在线观看不卡视频| 久久wwww| 久久裸体视频| 涩涩涩久久久成人精品| 麻豆国产欧美一区二区三区| 成人羞羞在线观看网站| 美女精品在线| 国产精品videossex久久发布| 日韩高清中文字幕一区二区| 蜜桃免费网站一区二区三区| 国产精品一区二区中文字幕| 国产精品蜜芽在线观看| 国产亚洲亚洲| 久久精品伊人| 国产精品色网| 久久久久九九精品影院| 国产综合激情| 国产精品羞羞答答在线观看| 91精品精品| 日韩国产欧美一区二区三区| 色偷偷偷在线视频播放| 在线午夜精品| 久久只有精品| 男人的天堂亚洲一区| 精品一区二区三区在线观看视频| 欧美精选一区二区三区| 久久国际精品| 欧美福利专区| 久久精品二区亚洲w码| 久久av在线| 韩国女主播一区二区三区| 免费在线观看日韩欧美| 国产成年精品| 日韩专区视频网站| 91精品一区二区三区综合| 欧美亚洲三级| 狠狠色狠狠色综合日日tαg| 美女性感视频久久| 亚洲三级在线| 136国产福利精品导航网址| 国产日产高清欧美一区二区三区 | 国产中文在线播放| 国产午夜一区| 美女网站久久|