一、前言
在先了解mybatis查詢之前,先大致了解下以下代碼的為查詢做了哪些鋪墊,在這里我們要事先了解,myabtis會默認使用defaultsqlsessionfactory作為sqlsessionfactory的實現類,而sqlsession的默認實現類為defaultsqlsession
1
2
3
4
5
|
public static sqlsessionfactory getsessionfactory() throws ioexception { reader reader = resources.getresourceasreader( "mybatis/mybatis-config.xml" ); sqlsessionfactorybuilder builder = new sqlsessionfactorybuilder(); return builder.build(reader); } |
獲取mybatis的配置文件流,交給sqlsessionfactorybuilder進行解析,在這里只會涉及到一部分,具體,請大家移步mybatis源碼進行分析
解析大致步驟(以下說的配置文件,是mybatis配置數據庫連接信息的那個配置文件,不是mapper.xml文件)
解析配置文件的核心類在xmlconfigbuilder類中,
代碼如下
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
|
public configuration parse() { if (parsed) { throw new builderexception( "each xmlconfigbuilder can only be used once." ); } parsed = true ; parseconfiguration(parser.evalnode( "/configuration" )); return configuration; } private void parseconfiguration(xnode root) { try { // 解析properties節點信息 propertieselement(root.evalnode( "properties" )); // 解析settings節點配置信息,其中二級緩存的總開關就是這里配置,當然mybatis默認是開啟的,詳細見configuration類中的cacheenabled屬性 properties settings = settingsasproperties(root.evalnode( "settings" )); loadcustomvfs(settings); loadcustomlogimpl(settings); // 解析別名 typealiaseselement(root.evalnode( "typealiases" )); // 解析插件 pluginelement(root.evalnode( "plugins" )); // 這個節點一般不進行配置,myabtis也提供了一個默認實現類defaultobjectfactory,除非自定義對象工廠實現,才需配置 objectfactoryelement(root.evalnode( "objectfactory" )); objectwrapperfactoryelement(root.evalnode( "objectwrapperfactory" )); reflectorfactoryelement(root.evalnode( "reflectorfactory" )); settingselement(settings); // read it after objectfactory and objectwrapperfactory issue #631 environmentselement(root.evalnode( "environments" )); databaseidproviderelement(root.evalnode( "databaseidprovider" )); // 處理java類型和數據庫類型的轉換,mybatis提供了許多默認實現,詳細見typehandlerregistry類,如果需自定義,可在此節點中進行配置 typehandlerelement(root.evalnode( "typehandlers" )); // 這也是一個核心的配置,mapperelement方法會對mapper.xml文件內容進行一個解析 mapperelement(root.evalnode( "mappers" )); } catch (exception e) { throw new builderexception( "error parsing sql mapper configuration. cause: " + e, e); } } |
解析mapper.xml文件 的類xmlmapperbuilder,
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public void parse() { // 也就是檢測配置文件配置的mapper節點有沒有加載到configuration類中,防止重復加載 if (!configuration.isresourceloaded(resource)) { configurationelement(parser.evalnode( "/mapper" )); configuration.addloadedresource(resource); // 這個是綁定,mapper接口的,當處理成功,在configuration類中的mapper注冊器中,會添加一個mapper bindmapperfornamespace(); } parsependingresultmaps(); // 解析resultmap節點 parsependingcacherefs(); // 解析緩存節點,如<cache-ref/> parsependingstatements(); // 解析select|update等節點,并封裝成mappedstatement類 } |
其中bindmapperfornamespace()方法的操作會導致以下結果
在configuration類中的mapperregistry屬性中添加一個mapper,結果存儲在mapperregistry類的一個map中,key為mapper的class value為一個代理工廠,負責產生mapper接口代理類。
二、查詢操作
當我們使用要使用mybatis進行查詢操作,無非大致就是兩種方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
/** * 通過mapper接口形式查詢數據 */ @test public void testselectbymapper() throws ioexception { sqlsession sqlsession = mybatisutil.getsessionfactory().opensession(); usermapper mapper = sqlsession.getmapper(usermapper. class ); user user = mapper.selectbyprimarykey( 10 ); system.out.println(user); sqlsession.close(); } /** * 通過mapper接口的全限定名來進行查詢 * @throws ioexception */ @test public void testselectbystring() throws ioexception { sqlsessionfactory sessionfactory = mybatisutil.getsessionfactory(); sqlsession sqlsession = sessionfactory.opensession(); user user = sqlsession.selectone( "com.mybatis.demo.mybatisdemo.mapper.usermapper.selectbyprimarykey" , 10 ); system.out.println(user); sqlsession.close(); } |
先來看第一種的分析,當我們點擊getmapper進去,它會去調用configuration類中getmapper方法,就如上面介紹的解析出mapper節點后,會存儲在configuration類中的mapper注冊器中,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
// defaultsqlsession類 public <t> t getmapper( class <t> type) { return configuration.<t>getmapper(type, this ); } //configuration類 public <t> t getmapper( class <t> type, sqlsession sqlsession) { return mapperregistry.getmapper(type, sqlsession); } // 最終獲取mapper對象的方法,其主要是創建一個mapper代理工廠,我們都知道mybatis的mapper接口是沒有實現類的, // 但是我們直接查詢是能獲取數據,這里起作用的就是代理(采用的是jdk動態代理) public <t> t getmapper( class <t> type, sqlsession sqlsession) { final mapperproxyfactory<t> mapperproxyfactory = (mapperproxyfactory<t>) knownmappers.get(type); if (mapperproxyfactory == null ) { throw new bindingexception( "type " + type + " is not known to the mapperregistry." ); } try { return mapperproxyfactory.newinstance(sqlsession); } catch (exception e) { throw new bindingexception( "error getting mapper instance. cause: " + e, e); } } |
然后最終會經過代理類mapperproxy的invoke方法,進行返回結果。在這里為了更好的能理解這個類,舉個例子,步驟如下
先創建一個接口,再使用一個類去實現java的jdk代理的核心接口invocationhandler,
1
2
3
4
5
|
public interface testmapper { user findbyuserid(integer id); } |
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
|
public class mapperproxytest implements invocationhandler { private class <?> target; public mapperproxytest( class <?> target) { this .target = target; } public object getproxyinstances(){ return proxy.newproxyinstance(thread.currentthread().getcontextclassloader(), new class []{target}, this ); } @override public object invoke(object proxy, method method, object[] args) throws throwable { if (object. class .equals(method.getdeclaringclass())) { return method.invoke( this , args); } user user = new user(); user.setpassword( "123" ); user.setusername( "李四" ); user.setaddress( "123" ); user.setregistertime( new date()); user.setcellphone( "1111111" ); user.setage( 25 ); return user; } } |
測試類
1
2
3
4
5
6
7
8
|
public class mappertest { public static void main(string[] args){ mapperproxytest proxytest = new mapperproxytest(testmapper. class ); testmapper testmapper = (testmapper) proxytest.getproxyinstances(); system.out.println(testmapper.findbyuserid( 10 )); } } |
執行結果
user{id=null, username='李四', password='123', age=25, address='123', cellphone='1111111', registertime=sat mar 09 15:02:16 cst 2019}
由上面例子也可以看出最終結果是在invoke方法內,同理在mybatis中的mapperproxy的invoke方法也是負責返回最終結果的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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); } // 交給了mppermethod類去處理 final mappermethod mappermethod = cachedmappermethod(method); return mappermethod.execute(sqlsession, args); } |
mappermethod類中有兩個重要屬性,也就是它的內部類,
也可以很清楚的了解到sqlcommand是用來存儲當前執行方法的信息,如全限定名,還有該方法是屬于select|update|delete|insert|flush的哪一種,
對于methodsignature,則是紀錄該方法的一些信息,如返回值類型,參數等信息,paramnameresolver處理mapper接口中的參數,下面代碼中有一個大致的介紹,以后會做一個詳細的介紹,這里只貼下代碼,只針對select做介紹
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
|
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()) { // 返回值為void類型,但是有resulthandler參數,并且只能有一個,不然會報錯 executewithresulthandler(sqlsession, args); result = null ; } else if (method.returnsmany()) { // 處理返回值類型為集合類型或者數組類型 result = executeformany(sqlsession, args); } else if (method.returnsmap()) { //處理返回值類型為map類型 result = executeformap(sqlsession, args); } else if (method.returnscursor()) { //返回值是否為cursor類型 result = executeforcursor(sqlsession, args); } else { //其他類型 object param = method.convertargstosqlcommandparam(args); result = sqlsession.selectone(command.getname(), param); if (method.returnsoptional() && (result == null || !method.getreturntype().equals(result.getclass()))) { result = optional.ofnullable(result); } } 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; } |
這里只介紹select部分中常用返回多個實例對象的情況,也就是返回值為集合類型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
private <e> object executeformany(sqlsession sqlsession, object[] args) { list<e> result; // 將mapper接口的參數名稱和args整成一個map結構,最后在會將值賦給sql中對應的變量 // 在3.5版本中,默認的mapper結構(假如沒使用@param注解或者處于jdk1.8版本中在代碼編譯時加上 -parameters 參數),結構為 // param1 -> args[0] param2 -> args[1] // arg0 -> args[0] arg1 -> args[1] mybatis之前有些版本不是arg0 而是0 1 。。數字代替。 object param = method.convertargstosqlcommandparam(args); if (method.hasrowbounds()) { // 處理參數中帶有rowbounds參數 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; } |
從上面知道,最終還是回到了sqlsession里面,
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(); } } |
mappedstatement存儲的其實就是對每一個select|update|delete|insert 標簽的解析結果
關于mappedstatement是怎么解析得來的,又是怎么存儲在configuration中,可沿著以下路線進行查看
sqlsessionfactorybuilder ---> build方法
xmlconfigbuilder ----> parse、parseconfiguration、mapperelement方法
xmlmapperbuilder ----> parse、parsependingstatements、parsestatementnode
mapperbuilderassistant ----> addmappedstatement
這里不做過多介紹,詳情見源碼
在selectlist中executor的默認實現類是,simpleexecutor,不過它還由configuration類中的一個屬性決定最后的類型,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public executor newexecutor(transaction transaction, executortype executortype) { executortype = executortype == null ? defaultexecutortype : executortype; executortype = executortype == null ? executortype.simple : executortype; executor executor; if (executortype.batch == executortype) { executor = new batchexecutor( this , transaction); } else if (executortype.reuse == executortype) { executor = new reuseexecutor( this , transaction); } else { executor = new simpleexecutor( this , transaction); } // 如果cacheenabled為true,其實這個屬性默認為true的, // 則由cachingexecutor進行包裝,也就是常說的裝飾設計模式 if (cacheenabled) { executor = new cachingexecutor(executor); } executor = (executor) interceptorchain.pluginall(executor); return executor; } |
最后回到selectlist中來,由此可見,調用了cachingexecutor類中的query方法來執行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
@override 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.query(ms, parameterobject, rowbounds, resulthandler, key, boundsql); tcm.putobject(cache, key, list); // issue #578 and #116 } return list; } } return delegate.query(ms, parameterobject, rowbounds, resulthandler, key, boundsql); } |
關于二級緩存,相信熟悉的都清楚,要想使用它,需要動兩個地方,
一個是mybatis的配置文件,將cacheenabled設置為true,其實mybatis對這個屬性的默認值就是true,所以二級緩存的總開關是打開的。
第二個就是在mpper.xml文件中使用 <cache/> 或<cache-ref/>
這里對緩存不做介紹。
然后調用了baseexecutor的query方法,這個類起的作用就是對一級緩存進行了操作,最終調用了simpleexecutor的doquery方法進行查詢。
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對服務器之家的支持。
原文鏈接:https://www.cnblogs.com/qm-article/p/10542187.html