一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - mybatis查詢語句的背后揭秘

mybatis查詢語句的背后揭秘

2021-07-29 11:33不懂是非 Java教程

這篇文章主要給大家介紹了關于mybatis查詢語句的相關資料,文中通過示例代碼介紹的非常詳細,對大家學習或者使用mybatis具有一定的參考學習價值,需要的朋友們下面來一起學習學習吧

一、前言

在先了解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類中有兩個重要屬性,也就是它的內部類,

mybatis查詢語句的背后揭秘

也可以很清楚的了解到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

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 成人免费播放器 | 91李宗精品72集在线观看 | 放荡女小洁的性日记 | 亚洲不卡高清免v无码屋 | 99热免费在线观看 | 欧美色成人tv在线播放 | 九九国产在线视频 | 美女扒开奶罩让男人吃奶 | 性吧有你 | 狠狠撸在线播放 | 亚洲视频免费在线观看 | 91久久国产综合精品 | 特黄特色一级aa毛片免费观看 | 欧美国产日本高清不卡 | 日韩欧美不卡片 | 欧乱色国产精品兔费视频 | 香蕉精品国产高清自在自线 | 亚洲娇小性hd | 丝瓜草莓香蕉绿巨人幸福宝 | xnxx动漫 | www.com日本| 欧美不卡一区二区三区免 | 久久国产综合精品欧美 | 日本在线播放视频 | 免费午夜网站 | 我的奶头被客人吸的又肿又红 | 国内精品伊人久久大香线焦 | 亚洲欧美日韩国产综合专区 | 四虎1515hhcom| 国产欧美又粗又猛又爽老 | 女仆色永久免费网站 | 亚洲第一色区 | 亚洲国产精品二区久久 | 韩国三级视频网站 | 欧美综合国产精品日韩一 | 日本www午夜色在线视频 | 91四虎国自产在线播放线 | 国产精品麻豆99久久 | 狠狠色综合久久久久尤物 | 女同学高中你下面好紧 | 性色欲情网站IWWW |