使用orm框架我們更多的是使用其查詢功能,那么查詢海量數據則又離不開性能,那么這篇中我們就看下mybatis高級應用之延遲加載、一級緩存、二級緩存。使用時需要注意延遲加載必須使用resultmap,resulttype不具有延遲加載功能。
一、延遲加載
延遲加載已經是老生常談的問題,什么最大化利用數據庫性能之類之類的,也懶的列舉了,總是我一提到延遲加載腦子里就會想起來了hibernate get和load的區別。ok,廢話少說,直接看代碼。 先來修改配置項xml。
注意,編寫mybatis.xml時需要注意配置節點的先后順序,settings在最前面,否則會報錯。
1
2
3
4
|
<settings> <setting name= "lazyloadingenabled" value= "true" /> <setting name= "aggressivelazyloading" value= "false" /> </settings> |
前面提到延遲加載只能通過association、collection來實現,因為只有存在關聯關系映射的業務場景里你才需要延遲加載,也叫懶加載,也就是常說的用的時候再去加載。ok,那么我們來配一個association來實現:
我來編寫一個加載博客列表的同時加載出博客額作者, 主要功能點在id為blogauthorresumtmap這個resultmap上,其中使用了association,關鍵點是它的select屬性,該屬性也就是你需要懶加載調用的statment id。 當然需要懶加載的statement 返回值當然是resultmap
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
<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支持延遲加載功能,配置延遲加載關聯關系--> <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> <!--延遲加載查詢博客對應的作者方法--> <select id= "selectauthorbyid" parametertype= "int" resulttype= "author" > select id,name from t_author where id=#{value} </select> |
ok,來看測試結果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
@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(); } } |
從圖一中看出,執行selectblogauthor返回list<blog>對象時只執行了sql select id,title,category,author_id from t_blog,循環遍歷時才去執行select id,name from t_author where id=?。
二、一級緩存
了解緩存前我們先看一張圖片(圖片來源于傳智播客視頻圖片)。從圖中可以了解一級緩存是sqlsession級別、二級緩存是mapper級別。在操作數據庫時我們需要先構造sqlsession【默認實現是defaultsqlsession.java】,在對象中有一個數據結構【hashmap】來存儲緩存數據。不同的sqlsession區域是互不影響的。 如果同一個sqlsession之間,如果多次查詢之間執行了commit,則緩存失效,mybatis避免臟讀。
ok,在看mybatis一級緩存時,我總是覺的一級緩存有點雞肋,兩個查詢如果得到一樣的數據,你還會執行第二次么,果斷引用第一次的返回值了。 可能還沒了解到一級緩存的奧妙之處。一級緩存默認是開啟的,不需要額外設置,直接使用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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截圖來看,當我們第一次調用方法時執行了select id,name from t_author where id=? 此時緩存中還沒有該數據,則執行數據庫查詢,當再次執行時直接從緩存中讀取。
執行demo后我們來看下這個查詢過程保存到緩存的源碼,先看下defaultsqlsession.java。我們調用的selectone(),從代碼中看它是直接調用selectlist()然后判斷返回值size大小。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
@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方法,看到先構造mappedstatement對象,然后看到真正執行query()的是一個executor對象,在defaultsqlsession.java中executor是成員變量,再翻到org.apache.ibatis.executor包中看到executor實際是一個接口。ok,那么我們debug時發現其引用是cachingexecutor。再打開cachingexecutor.java
1
2
3
4
5
6
7
8
9
10
11
|
@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的兩個query()可以看到先去構造cachekey 再調用抽象類baseexecutor.query(),這個也是最關鍵的一步。
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
|
//先創建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); } //再執行查詢方法 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
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
|
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; } |
再看其中關鍵代碼queryfromdatabase
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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,看了一長串,終于是有點眉目了,我們看到finally中先刪除當前key緩存,然后再調用localcache.putobject把最新的結果集存入hashmap中。
三、二級緩存
了解二級緩存之前先來看副圖(圖片來自傳智播客視頻,非本人編寫),那么從圖中我們可以看出,mybatis二級緩存是mapper級別,也就是說不同的sqlmapper共享不同的內存區域,不同的sqlsession共享同一個內存區域,用mapper的namespace區別內存區域。
開啟mybatis二級緩存: 1、設置mybatis.xml,也就是說mybatis默認二級緩存是關閉的。
2、設置mapper。在mapper.xml內添加標簽:<cache/>
3、pojo實現接口serializable。實現該接口后也就說明二級緩存不僅可以存入內存中,還可以存入磁盤。
ok,看一個二級緩存demo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
@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 { } } |
運行demo可以看出二級緩存不同的地方在于cache hit ratio,發出sql查詢時先看是否命中緩存,第一次則是0.0 ,再次查詢時則直接讀取緩存數據,命中率是0.5。當然數據結構還是hashmap。
如果數據實時性要求比較高,可以設置select 語句的
如果數據的查詢實時性要求比較高,則設置select語句的usecache="false",則每次都直接執行sql。
1
2
3
|
<select id= "selectblogauthor" resultmap= "blogauthorresumtmap" usecache= "false" > select id,title,category,author_id from t_blog </select> |
以上這篇mybatis 延遲加載、一級緩存、二級緩存(詳解)就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支持服務器之家。
原文鏈接:http://www.cnblogs.com/sword-successful/p/7400685.html