您好,登錄后才能下訂單哦!
使用ORM框架我們更多的是使用其查詢功能,那么查詢海量數(shù)據(jù)則又離不開(kāi)性能,那么這篇中我們就看下mybatis高級(jí)應(yīng)用之延遲加載、一級(jí)緩存、二級(jí)緩存。使用時(shí)需要注意延遲加載必須使用resultMap,resultType不具有延遲加載功能。
一、延遲加載
延遲加載已經(jīng)是老生常談的問(wèn)題,什么最大化利用數(shù)據(jù)庫(kù)性能之類之類的,也懶的列舉了,總是我一提到延遲加載腦子里就會(huì)想起來(lái)了Hibernate get和load的區(qū)別。OK,廢話少說(shuō),直接看代碼。 先來(lái)修改配置項(xiàng)xml。
注意,編寫(xiě)mybatis.xml時(shí)需要注意配置節(jié)點(diǎn)的先后順序,settings在最前面,否則會(huì)報(bào)錯(cuò)。
<settings> <setting name="lazyLoadingEnabled" value="true"/> <setting name="aggressiveLazyLoading" value="false"/> </settings>
前面提到延遲加載只能通過(guò)association、collection來(lái)實(shí)現(xiàn),因?yàn)橹挥写嬖陉P(guān)聯(lián)關(guān)系映射的業(yè)務(wù)場(chǎng)景里你才需要延遲加載,也叫懶加載,也就是常說(shuō)的用的時(shí)候再去加載。OK,那么我們來(lái)配一個(gè)association來(lái)實(shí)現(xiàn):
我來(lái)編寫(xiě)一個(gè)加載博客列表的同時(shí)加載出博客額作者, 主要功能點(diǎn)在id為blogAuthorResumtMap這個(gè)resultmap上,其中使用了association,關(guān)鍵點(diǎn)是它的select屬性,該屬性也就是你需要懶加載調(diào)用的statment id。 當(dāng)然需要懶加載的statement 返回值當(dāng)然是resultmap
<resultMap id="blogAuthorResumtMap" type="Blog"> <id column="id" property="id"/> <result column="title" property="title"/> <result column="category" property="category"/> <result column="author_id" property="author_id"/> <!--使用assocition支持延遲加載功能,配置延遲加載關(guān)聯(lián)關(guān)系--> <association property="author" javaType="Author" select="selectAuthorById" column="author_id"/> </resultMap> <!--要使用延遲記載的方法--> <select id="selectBlogAuthor" resultMap="blogAuthorResumtMap"> SELECT id,title,category,author_id FROM t_blog </select> <!--延遲加載查詢博客對(duì)應(yīng)的作者方法--> <select id="selectAuthorById" parameterType="int" resultType="Author"> SELECT id,name from t_author where id=#{value} </select>
OK,來(lái)看測(cè)試結(jié)果:
@Test public void getBlogAuthorByLazyloading(){ SqlSession sqlSession=null; try{ sqlSession=sqlSessionFactory.openSession(); List<Blog> list = sqlSession.selectList("com.autohome.mapper.Author.selectBlogAuthor"); for (Blog blog:list) { System.out.println("id:"+blog.getId()+",title:"+blog.getTitle()+",category:"+blog.getCategory()); System.out.println("author:"+blog.getAuthor().getName()); } }catch(Exception e){ e.printStackTrace(); }finally { sqlSession.close(); } }
從圖一中看出,執(zhí)行selectBlogAuthor返回List<Blog>對(duì)象時(shí)只執(zhí)行了SQL SELECT id,title,category,author_id from t_blog,循環(huán)遍歷時(shí)才去執(zhí)行select id,name from t_author where id=?。
二、一級(jí)緩存
了解緩存前我們先看一張圖片(圖片來(lái)源于傳智播客視頻圖片)。從圖中可以了解一級(jí)緩存是sqlsession級(jí)別、二級(jí)緩存是mapper級(jí)別。在操作數(shù)據(jù)庫(kù)時(shí)我們需要先構(gòu)造sqlsession【默認(rèn)實(shí)現(xiàn)是DefaultSqlSession.java】,在對(duì)象中有一個(gè)數(shù)據(jù)結(jié)構(gòu)【hashmap】來(lái)存儲(chǔ)緩存數(shù)據(jù)。不同的sqlsession區(qū)域是互不影響的。 如果同一個(gè)sqlsession之間,如果多次查詢之間執(zhí)行了commit,則緩存失效,mybatis避免臟讀。
OK,在看mybatis一級(jí)緩存時(shí),我總是覺(jué)的一級(jí)緩存有點(diǎn)雞肋,兩個(gè)查詢?nèi)绻玫揭粯拥臄?shù)據(jù),你還會(huì)執(zhí)行第二次么,果斷引用第一次的返回值了。 可能還沒(méi)了解到一級(jí)緩存的奧妙之處。一級(jí)緩存默認(rèn)是開(kāi)啟的,不需要額外設(shè)置,直接使用。
public void testCache(){ SqlSession sqlSession=null; try{ sqlSession=sqlSessionFactory.openSession(); Author author = sqlSession.selectOne("com.autohome.mapper.Author.selectAuthorById",1); System.out.println("作者信息 id:"+author.getId()+",name:"+author.getName()); author = sqlSession.selectOne("com.autohome.mapper.Author.selectAuthorById",1); System.out.println("作者信息2 id:"+author.getId()+",name:"+author.getName()); }catch(Exception e){ e.printStackTrace(); }finally { sqlSession.close(); } }
從DEBUG截圖來(lái)看,當(dāng)我們第一次調(diào)用方法時(shí)執(zhí)行了SELECT id,name from t_author where id=? 此時(shí)緩存中還沒(méi)有該數(shù)據(jù),則執(zhí)行數(shù)據(jù)庫(kù)查詢,當(dāng)再次執(zhí)行時(shí)直接從緩存中讀取。
執(zhí)行demo后我們來(lái)看下這個(gè)查詢過(guò)程保存到緩存的源碼,先看下DefaultSqlSession.java。我們調(diào)用的selectOne(),從代碼中看它是直接調(diào)用selectList()然后判斷返回值size大小。
@Override public <T> T selectOne(String statement) { return this.<T>selectOne(statement, null); } @Override public <T> T selectOne(String statement, Object parameter) { // Popular vote was to return null on 0 results and throw exception on too many. List<T> list = this.<T>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方法,看到先構(gòu)造MappedStatement對(duì)象,然后看到真正執(zhí)行query()的是一個(gè)executor對(duì)象,在DefaultSqlSession.java中executor是成員變量,再翻到org.apache.ibatis.executor包中看到executor實(shí)際是一個(gè)接口。OK,那么我們debug時(shí)發(fā)現(xiàn)其引用是CachingExecutor。再打開(kāi)CachingExecutor.java
@Override public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { 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(); } }
從CachingExecutor.java的兩個(gè)query()可以看到先去構(gòu)造CacheKey 再調(diào)用抽象類BaseExecutor.query(),這個(gè)也是最關(guān)鍵的一步。
//先創(chuàng)建CacheKey public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameterObject); CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); } //再執(zhí)行查詢方法 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, parameterObject, 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); }
BaseExecutor.java
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."); } if (queryStack == 0 && ms.isFlushCacheRequired()) { clearLocalCache(); } List<E> list; try { queryStack++; 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 #482 clearLocalCache(); } } return list; }
再看其中關(guān)鍵代碼queryFromDatabase
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; }
OK,看了一長(zhǎng)串,終于是有點(diǎn)眉目了,我們看到finally中先刪除當(dāng)前key緩存,然后再調(diào)用localCache.putObject把最新的結(jié)果集存入HashMap中。
三、二級(jí)緩存
了解二級(jí)緩存之前先來(lái)看副圖(圖片來(lái)自傳智播客視頻,非本人編寫(xiě)),那么從圖中我們可以看出,mybatis二級(jí)緩存是mapper級(jí)別,也就是說(shuō)不同的sqlmapper共享不同的內(nèi)存區(qū)域,不同的sqlsession共享同一個(gè)內(nèi)存區(qū)域,用mapper的namespace區(qū)別內(nèi)存區(qū)域。
開(kāi)啟mybatis二級(jí)緩存: 1、設(shè)置mybatis.xml,也就是說(shuō)mybatis默認(rèn)二級(jí)緩存是關(guān)閉的。
2、設(shè)置mapper。在mapper.xml內(nèi)添加標(biāo)簽:<cache/>
3、pojo實(shí)現(xiàn)接口Serializable。實(shí)現(xiàn)該接口后也就說(shuō)明二級(jí)緩存不僅可以存入內(nèi)存中,還可以存入磁盤。
OK,看一個(gè)二級(jí)緩存demo:
@Test public void testCache2(){ SqlSession sqlSession=null; SqlSession sqlSession2=null; try{ sqlSession=sqlSessionFactory.openSession(); sqlSession2=sqlSessionFactory.openSession(); Author author = sqlSession.selectOne("com.autohome.mapper.Author.selectAuthorById",1); System.out.println("作者信息 id:"+author.getId()+",name:"+author.getName()); sqlSession.close(); Author author2 = sqlSession2.selectOne("com.autohome.mapper.Author.selectAuthorById",1); System.out.println("作者信息2 id:"+author2.getId()+",name:"+author2.getName()); sqlSession2.close(); }catch(Exception e){ e.printStackTrace(); }finally { } }
運(yùn)行demo可以看出二級(jí)緩存不同的地方在于Cache Hit Ratio,發(fā)出sql查詢時(shí)先看是否命中緩存,第一次則是0.0 ,再次查詢時(shí)則直接讀取緩存數(shù)據(jù),命中率是0.5。當(dāng)然數(shù)據(jù)結(jié)構(gòu)還是HashMap。
如果數(shù)據(jù)實(shí)時(shí)性要求比較高,可以設(shè)置select 語(yǔ)句的
如果數(shù)據(jù)的查詢實(shí)時(shí)性要求比較高,則設(shè)置select語(yǔ)句的useCache="false",則每次都直接執(zhí)行sql。
<select id="selectBlogAuthor" resultMap="blogAuthorResumtMap" useCache="false"> SELECT id,title,category,author_id FROM t_blog </select>
以上這篇MyBatis 延遲加載、一級(jí)緩存、二級(jí)緩存(詳解)就是小編分享給大家的全部?jī)?nèi)容了,希望能給大家一個(gè)參考,也希望大家多多支持億速云。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。