MyBatis之架構(gòu)設(shè)計(jì)與實(shí)現(xiàn)

1插勤、ORM

1.1 概述

面向?qū)ο缶幊贪阉袑?shí)體看成對(duì)象(object),關(guān)系型數(shù)據(jù)庫(kù)則是采用實(shí)體之間的關(guān)系(relation)連接數(shù)據(jù),兩者的設(shè)計(jì)哲學(xué)是不一樣的伶选。這就導(dǎo)致一種困境:當(dāng)使用面向?qū)ο蟮木幊陶Z(yǔ)言來(lái)進(jìn)行應(yīng)用開(kāi)發(fā)時(shí)隐岛,從項(xiàng)目一開(kāi)始就采用的是面向?qū)ο蟮乃枷耄ǚ治雒睢⒃O(shè)計(jì)、編程等)聚凹,但到了持久層數(shù)據(jù)庫(kù)訪問(wèn)時(shí)割坠,又必須重返關(guān)系數(shù)據(jù)庫(kù)的數(shù)據(jù)模型。

為了跨越面向?qū)ο蟪绦蛟O(shè)計(jì)語(yǔ)言和關(guān)系數(shù)據(jù)庫(kù)之間的鴻溝妒牙,產(chǎn)生了ORM框架彼哼。

簡(jiǎn)單說(shuō),ORM 就是通過(guò)實(shí)例對(duì)象的語(yǔ)法湘今,完成關(guān)系型數(shù)據(jù)庫(kù)的操作的技術(shù)敢朱,是"對(duì)象-關(guān)系映射"(Object/Relational Mapping) 的縮寫。

ORM 把數(shù)據(jù)庫(kù)映射成對(duì)象:

1.png

基于此摩瞎,應(yīng)用程序不再直接訪問(wèn)底層數(shù)據(jù)庫(kù)拴签,而是以面向?qū)ο蟮姆绞絹?lái)操作持久化對(duì)象,而ORM框架則將這些面向?qū)ο蟮牟僮鬓D(zhuǎn)換成底層的SQL操作愉豺。開(kāi)發(fā)者只使用面向?qū)ο缶幊搪ㄓ酰c數(shù)據(jù)對(duì)象直接交互,不用關(guān)心底層數(shù)據(jù)庫(kù)蚪拦。

1.2 JPA & Hibernate & MyBatis

JPA本身是一種ORM規(guī)范杖剪,并不是ORM產(chǎn)品。若應(yīng)用程序是面向JPA編程驰贷,那么應(yīng)用程序就可以在各種ORM框架之間切換盛嘿。

MyBatis沒(méi)有實(shí)現(xiàn)JPA,它和ORM框架的設(shè)計(jì)思路不完全一樣括袒。MyBatis是擁抱SQL次兆,而ORM則更靠近面向?qū)ο螅唤ㄗh寫SQL锹锰,實(shí)在要寫芥炭,則推薦你用框架自帶的類SQL代替。MyBatis是SQL映射框架而不是ORM框架恃慧,當(dāng)然ORM和MyBatis都是持久層框架园蝠。

最典型的ORM 框架是Hibernate,它是全自動(dòng)ORM框架痢士,而MyBatis是半自動(dòng)的彪薛。ORM是Object和Relation之間的映射,包括Object->Relation和Relation->Object兩方面。Hibernate是個(gè)完整的ORM框架善延,而MyBatis完成的是Relation->Object少态,也就是其所說(shuō)的Data Mapper Framework。

但是近些年來(lái)易遣,Hibernat在國(guó)內(nèi)的IT界已經(jīng)慢慢沒(méi)落彼妻,Mybatis則后來(lái)居上,如日中天训挡。造成這種局面的原因自然跟阿里澳骤、騰訊、網(wǎng)易等大廠的推廣有很大關(guān)系澜薄,不過(guò)個(gè)人認(rèn)為为肮,根本原因還是在于兩者的設(shè)計(jì)哲學(xué)不一樣。一言以蔽之:Hibernate什么都想管肤京,JDBC什么都不想管颊艳,一個(gè)管的太多,一個(gè)管的太少忘分,最終棋枕,反倒讓Mybatis這個(gè)管的不多不少的中庸者勝出了。

2妒峦、架構(gòu)分析

Mybatis雖然體量不大重斑,實(shí)現(xiàn)簡(jiǎn)單,但是骨骼清奇肯骇,代碼優(yōu)雅窥浪,很適合作為入門的開(kāi)源框架來(lái)研究。

總體架構(gòu)如下:

2.png

2.1 接口層

MyBatis和數(shù)據(jù)庫(kù)的交互有兩種方式笛丙。

第一種是基于使用傳統(tǒng)的API方式漾脂。傳遞Statement Id 和查詢參數(shù)給 SqlSession 對(duì)象,使用 SqlSession對(duì)象完成和數(shù)據(jù)庫(kù)的交互胚鸯。MyBatis 提供了非常方便和簡(jiǎn)單的API骨稿,供用戶實(shí)現(xiàn)對(duì)數(shù)據(jù)庫(kù)的增刪改查數(shù)據(jù)操作,以及對(duì)數(shù)據(jù)庫(kù)連接信息和MyBatis 自身配置信息的維護(hù)操作姜钳。

3.png

第二種是使用Mapper接口坦冠。 MyBatis 將配置文件中的每一個(gè)<mapper> 節(jié)點(diǎn)抽象為一個(gè) Mapper 接口,而這個(gè)接口中聲明的方法和跟<mapper> 節(jié)點(diǎn)中的<select|update|delete|insert> 節(jié)點(diǎn)項(xiàng)對(duì)應(yīng)哥桥,即<select|update|delete|insert> 節(jié)點(diǎn)的id值為Mapper 接口中的方法名稱蓝牲,parameterType 值表示Mapper 對(duì)應(yīng)方法的入?yún)㈩愋停?code>resultMap 值則對(duì)應(yīng)了Mapper 接口表示的返回值類型或者返回結(jié)果集的元素類型泰讽。

4.png

根據(jù)MyBatis 的配置規(guī)范配置好后,通過(guò)SqlSession.getMapper(XXXMapper.class) 方法,MyBatis 會(huì)根據(jù)相應(yīng)的接口聲明的方法信息已卸,通過(guò)動(dòng)態(tài)代理機(jī)制生成一個(gè)Mapper 實(shí)例佛玄,我們使用Mapper 接口的某一個(gè)方法時(shí),MyBatis 會(huì)根據(jù)這個(gè)方法的方法名和參數(shù)類型累澡,確定Statement Id梦抢,底層還是通過(guò)SqlSession.select("statementId",parameterObject);或者SqlSession.update("statementId",parameterObject);等等來(lái)實(shí)現(xiàn)對(duì)數(shù)據(jù)庫(kù)的操作。

MyBatis 引用Mapper 接口這種調(diào)用方式愧哟,純粹是為了滿足面向接口編程的需要奥吩。(其實(shí)還有一個(gè)原因是在于,面向接口的編程蕊梧,使得用戶在接口上可以使用注解來(lái)配置SQL語(yǔ)句霞赫,這樣就可以脫離XML配置文件,實(shí)現(xiàn)“0配置”)肥矢。

2.2 數(shù)據(jù)處理層

數(shù)據(jù)處理層可以說(shuō)是MyBatis 的核心端衰,從大的方面上講,它要完成三個(gè)功能:

  • 通過(guò)傳入?yún)?shù)構(gòu)建動(dòng)態(tài)SQL語(yǔ)句

  • SQL語(yǔ)句的執(zhí)行

  • 封裝查詢結(jié)果集成List<E>

動(dòng)態(tài)語(yǔ)句生成可以說(shuō)是MyBatis框架非常優(yōu)雅的一個(gè)設(shè)計(jì)甘改,MyBatis 通過(guò)傳入的參數(shù)值旅东,使用 Ognl 來(lái)動(dòng)態(tài)地構(gòu)造SQL語(yǔ)句,使得MyBatis 有很強(qiáng)的靈活性和擴(kuò)展性十艾。

參數(shù)映射指的是對(duì)于java 數(shù)據(jù)類型和jdbc數(shù)據(jù)類型之間的轉(zhuǎn)換:這里有包括兩個(gè)過(guò)程:查詢階段抵代,我們要將java類型的數(shù)據(jù),轉(zhuǎn)換成jdbc類型的數(shù)據(jù)忘嫉,通過(guò) preparedStatement.setXXX() 來(lái)設(shè)值荤牍;另一個(gè)就是對(duì)resultset查詢結(jié)果集的jdbcType 數(shù)據(jù)轉(zhuǎn)換成java 數(shù)據(jù)類型。

動(dòng)態(tài)SQL語(yǔ)句生成之后榄融,MyBatis 將執(zhí)行SQL語(yǔ)句参淫,并將可能返回的結(jié)果集轉(zhuǎn)換成List<E> 列表。MyBatis 在對(duì)結(jié)果集的處理中愧杯,支持結(jié)果集關(guān)系一對(duì)多和多對(duì)一的轉(zhuǎn)換涎才,并且有兩種支持方式,一種為嵌套查詢語(yǔ)句的查詢力九,還有一種是嵌套結(jié)果集的查詢耍铜。

2.3 框架支撐層

負(fù)責(zé)最基礎(chǔ)的功能支撐,包括連接管理跌前、事務(wù)管理棕兼、配置加載和緩存處理,這些都是共用的東西抵乓,將他們抽取出來(lái)作為最基礎(chǔ)的組件伴挚。為上層的數(shù)據(jù)處理層提供最基礎(chǔ)的支撐靶衍。

2.4 引導(dǎo)層

引導(dǎo)層是配置和啟動(dòng)MyBatis 配置信息的方式。MyBatis 提供兩種方式來(lái)引導(dǎo)MyBatis :基于XML配置文件的方式和基于Java API 的方式茎芋。

3颅眶、主要組件

5.png

從MyBatis代碼實(shí)現(xiàn)的角度來(lái)看,MyBatis的主要的核心組件有以下幾個(gè):

  • SqlSession:作為MyBatis工作的主要頂層API田弥,表示和數(shù)據(jù)庫(kù)交互的會(huì)話涛酗,完成必要數(shù)據(jù)庫(kù)增刪改查功能
  • Executor:MyBatis執(zhí)行器,是MyBatis 調(diào)度的核心偷厦,負(fù)責(zé)SQL語(yǔ)句的生成和查詢緩存的維護(hù)
  • StatementHandler:封裝了JDBC Statement操作商叹,負(fù)責(zé)對(duì)JDBC statement 的操作,如設(shè)置參數(shù)只泼、將Statement結(jié)果集轉(zhuǎn)換成List集合
  • ParameterHandler:負(fù)責(zé)對(duì)用戶傳遞的參數(shù)轉(zhuǎn)換成JDBC Statement 所需要的參數(shù)
  • ResultSetHandler:負(fù)責(zé)將JDBC返回的ResultSet結(jié)果集對(duì)象轉(zhuǎn)換成List類型的集合
  • TypeHandler:負(fù)責(zé)java數(shù)據(jù)類型和jdbc數(shù)據(jù)類型之間的映射和轉(zhuǎn)換
  • MappedStatement:MappedStatement維護(hù)了一條<select|update|delete|insert>節(jié)點(diǎn)的封裝
  • SqlSource:負(fù)責(zé)根據(jù)用戶傳遞的parameterObject剖笙,動(dòng)態(tài)地生成SQL語(yǔ)句,將信息封裝到BoundSql對(duì)象中辜妓,并返回
  • BoundSql:表示動(dòng)態(tài)生成的SQL語(yǔ)句以及相應(yīng)的參數(shù)信息
  • Configuration:MyBatis所有的配置信息都維持在Configuration對(duì)象之中

4枯途、源碼解讀

下面深入源碼細(xì)節(jié),來(lái)看看Mybatis的各個(gè)組件如何相互協(xié)作籍滴,將上面架構(gòu)圖落地實(shí)現(xiàn)酪夷。

4.1 初始化過(guò)程

任何框架的初始化,無(wú)非是加載自己運(yùn)行時(shí)所需要的配置信息孽惰。

MyBatis使用 org.apache.ibatis.session.Configuration 對(duì)象作為一個(gè)所有配置信息的容器晚岭,Configuration對(duì)象的組織結(jié)構(gòu)和XML配置文件的組織結(jié)構(gòu)幾乎完全一樣(當(dāng)然,Configuration對(duì)象的功能并不限于此勋功,它還負(fù)責(zé)創(chuàng)建一些MyBatis內(nèi)部使用的對(duì)象坦报,如Executor等)。

現(xiàn)在就從使用MyBatis的簡(jiǎn)單例子入手狂鞋,深入分析一下MyBatis是怎樣完成初始化的片择,都初始化了什么。

String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
List list = sqlSession.selectList("com.foo.bean.BlogMapper.queryAllBlogInfo");

上述語(yǔ)句的作用是執(zhí)行com.foo.bean.BlogMapper.queryAllBlogInfo 定義的SQL語(yǔ)句骚揍,返回一個(gè)List結(jié)果集字管。總的來(lái)說(shuō)信不,上述代碼經(jīng)歷了mybatis初始化 -->創(chuàng)建SqlSession -->執(zhí)行SQL語(yǔ)句并返回結(jié)果三個(gè)過(guò)程嘲叔。

初始化的基本過(guò)程如下圖所示:

6.png

SqlSessionFactoryBuilder相關(guān)的代碼如下所示:

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties)
{
    try
    {
        //1. 創(chuàng)建XMLConfigBuilder對(duì)象用來(lái)解析XML配置文件,生成Configuration對(duì)象
        XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      
        //2. 將XML配置文件內(nèi)的信息解析成Java對(duì)象Configuration對(duì)象
        Configuration config = parser.parse();
      
        //3. 根據(jù)Configuration對(duì)象創(chuàng)建出SqlSessionFactory對(duì)象
        return build(config);
      
    } catch (Exception e) {
        throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
        //...
    }
}
//從此處可以看出抽活,MyBatis內(nèi)部通過(guò)Configuration對(duì)象來(lái)創(chuàng)建SqlSessionFactory,
//用戶也可以自己通過(guò)API構(gòu)造好Configuration對(duì)象硫戈,調(diào)用此方法創(chuàng)建SqlSessionFactory
public SqlSessionFactory build(Configuration config)
{
    return new DefaultSqlSessionFactory(config);
}

上述的初始化過(guò)程中,涉及到了以下幾個(gè)對(duì)象:

  • SqlSessionFactoryBuilder : SqlSessionFactory的構(gòu)造器下硕,用于創(chuàng)建SqlSessionFactory丁逝,采用了Builder設(shè)計(jì)模式
  • Configuration :該對(duì)象是mybatis-config.xml文件中所有mybatis配置信息
  • SqlSessionFactory:SqlSession工廠類汁胆,以工廠形式創(chuàng)建SqlSession對(duì)象,采用了Factory工廠設(shè)計(jì)模式
  • XmlConfigParser :負(fù)責(zé)將mybatis-config.xml配置文件解析成Configuration對(duì)象霜幼,共SqlSessonFactoryBuilder使用沦泌,創(chuàng)建SqlSessionFactory

XMLConfigBuilder的parse()方法返回了Configuration對(duì)象。那么parse()方法是如何處理XML文件辛掠,生成Configuration對(duì)象的呢?

XMLConfigBuilder會(huì)將XML配置文件的信息轉(zhuǎn)換為Document對(duì)象释牺,而XML配置定義文件DTD轉(zhuǎn)換成XMLMapperEntityResolver對(duì)象萝衩,然后將二者封裝到XpathParser對(duì)象中,XpathParser的作用是提供根據(jù)Xpath表達(dá)式獲取基本的DOM節(jié)點(diǎn)Node信息的操作没咙。如下圖所示:

7.jpg
8.jpg

之后XMLConfigBuilder調(diào)用parse()方法:會(huì)從XPathParser中取出 <configuration>節(jié)點(diǎn)對(duì)應(yīng)的Node對(duì)象猩谊,然后解析此Node節(jié)點(diǎn)的子Node:

  private void parseConfiguration(XNode root) {
    try {
      //1.首先處理properties 節(jié)點(diǎn) 
      propertiesElement(root.evalNode("properties")); //issue #117 read properties first
      //2.處理typeAliases
      typeAliasesElement(root.evalNode("typeAliases"));
      //3.處理插件
      pluginElement(root.evalNode("plugins"));
      //4.處理objectFactory
      objectFactoryElement(root.evalNode("objectFactory"));
      //5.objectWrapperFactory
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      //6.settings
      settingsElement(root.evalNode("settings"));
      //7.處理environments
      environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631
      //8.database
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      //9. typeHandlers
      typeHandlerElement(root.evalNode("typeHandlers"));
      //10 mappers
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

在上述代碼中,還有一個(gè)非常重要的地方祭刚,就是解析XML配置文件子節(jié)點(diǎn)<mappers>的方法mapperElements(root.evalNode("mappers")), 它將解析我們配置的Mapper.xml配置文件牌捷,Mapper配置文件是MyBatis的核心,MyBatis的特性和理念都體現(xiàn)在此Mapper的配置和設(shè)計(jì)上涡驮。

以上述的environmentsElement(root.evalNode("environments"));暗甥,看看XML的子節(jié)點(diǎn)是如何解析出來(lái),設(shè)置到Configuration對(duì)象中的:

/*
   解析environments節(jié)點(diǎn)捉捅,并將結(jié)果設(shè)置到Configuration對(duì)象中
   注意:創(chuàng)建envronment時(shí)撤防,如果SqlSessionFactoryBuilder指定了特定的環(huán)境(即數(shù)據(jù)源);
         則返回指定環(huán)境(數(shù)據(jù)源)的Environment對(duì)象棒口,否則返回默認(rèn)的Environment對(duì)象寄月;
         這種方式實(shí)現(xiàn)了MyBatis可以連接多數(shù)據(jù)源
*/
private void environmentsElement(XNode context) throws Exception
{
    if (context != null)
    {
        if (environment == null)
        {
            environment = context.getStringAttribute("default");
        }
        for (XNode child : context.getChildren())
        {
            String id = child.getStringAttribute("id");
            if (isSpecifiedEnvironment(id))
            {
                //1.創(chuàng)建事務(wù)工廠 TransactionFactory
                TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
                DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
                //2.創(chuàng)建數(shù)據(jù)源DataSource
                DataSource dataSource = dsFactory.getDataSource();
                //3. 構(gòu)造Environment對(duì)象
                Environment.Builder environmentBuilder = new Environment.Builder(id)
                .transactionFactory(txFactory)
                .dataSource(dataSource);
                //4. 將創(chuàng)建的Envronment對(duì)象設(shè)置到configuration 對(duì)象中
                configuration.setEnvironment(environmentBuilder.build());
            }
        }
    }
}

至此,Mybatis的初始化流程有了一個(gè)比較清晰的脈絡(luò)无牵,時(shí)序圖如下:

9.jpg

4.2 從一次select 查詢語(yǔ)句說(shuō)起

下面用一個(gè)查詢的實(shí)例漾肮,來(lái)分析系Mybatis的架構(gòu)設(shè)計(jì)。

//1.加載mybatis的配置文件茎毁,初始化mybatis克懊,創(chuàng)建出SqlSessionFactory,是創(chuàng)建SqlSession的工廠
InputStream inputStream = Resources.getResourceAsStream("mybatisConfig.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(inputStream);

//2. 從SqlSession工廠 SqlSessionFactory中創(chuàng)建一個(gè)SqlSession充岛,進(jìn)行數(shù)據(jù)庫(kù)操作
SqlSession sqlSession = factory.openSession();

//3.使用SqlSession查詢
Map<String,Object> params = new HashMap<String,Object>();

params.put("min_salary",10000);
//4.查詢工資低于10000的員工
List<Employee> result = sqlSession.selectList("test.EmployeesMapper.selectByMinSalary",params);

4.2.1 SqlSession

SqlSession sqlSession = factory.openSession();

MyBatis使用SqlSession來(lái)封裝一次數(shù)據(jù)庫(kù)的會(huì)話訪問(wèn)保檐,通過(guò)SqlSession實(shí)現(xiàn)事務(wù)控制與數(shù)據(jù)查詢。

10.jpg

List<Employee> result = sqlSession.selectList("test.EmployeesMapper.selectByMinSalary",params);

上述的test.EmployeesMapper.selectByMinSalary是配置在Mapper.xml 的Statement ID崔梗,params 是傳遞的查詢參數(shù)夜只。來(lái)看一下sqlSession.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 {
      //1.根據(jù)Statement Id,在mybatis 配置對(duì)象Configuration中查找和配置文件相對(duì)應(yīng)的MappedStatement  
      MappedStatement ms = configuration.getMappedStatement(statement);
      //2. 將查詢?nèi)蝿?wù)委托給MyBatis 的執(zhí)行器 Executor
      List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
      return result;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

MyBatis在初始化的時(shí)候蒜魄,會(huì)將MyBatis的配置信息全部加載到內(nèi)存中扔亥,使用org.apache.ibatis.session.Configuration實(shí)例來(lái)維護(hù)场躯。使用者可以使用sqlSession.getConfiguration()方法來(lái)獲取。MyBatis的配置文件中配置信息的組織格式和內(nèi)存中對(duì)象的組織格式幾乎完全對(duì)應(yīng)的旅挤。假設(shè)上述例子中的sql配置如下:

  <select id="selectByMinSalary" resultMap="BaseResultMap" parameterType="java.util.Map" >
    select 
        EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY
        from LOUIS.EMPLOYEES
        <if test="min_salary != null">
            where SALARY < #{min_salary,jdbcType=DECIMAL}
        </if>
  </select>

加載到內(nèi)存中會(huì)生成一個(gè)對(duì)應(yīng)的MappedStatement對(duì)象踢关,然后會(huì)以key=test.EmployeesMapper.selectByMinSalary ,value為MappedStatement對(duì)象的形式維護(hù)到Configuration的一個(gè)Map中粘茄。當(dāng)以后需要使用的時(shí)候签舞,只需要通過(guò)Id值來(lái)獲取就可以了。

從上述的代碼中我們可以看到SqlSession的職能:

  • 根據(jù)Statement ID, 在mybatis配置對(duì)象Configuration中獲取到對(duì)應(yīng)的MappedStatement對(duì)象
  • 然后調(diào)用mybatis執(zhí)行器來(lái)執(zhí)行具體的操作

4.2.2 Executor

/**
* BaseExecutor 類部分代碼
*/
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
      
    // 1.根據(jù)具體傳入的參數(shù)柒瓣,動(dòng)態(tài)地生成需要執(zhí)行的SQL語(yǔ)句儒搭,用BoundSql對(duì)象表示  
    BoundSql boundSql = ms.getBoundSql(parameter);
    // 2.為當(dāng)前的查詢創(chuàng)建一個(gè)緩存Key
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
 }
 
  @SuppressWarnings("unchecked")
  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 {
        // 3.緩存中沒(méi)有值,直接從數(shù)據(jù)庫(kù)中讀取數(shù)據(jù)  
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      deferredLoads.clear(); 
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        clearLocalCache(); 
      }
    }
    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 {
        
      //4. 執(zhí)行查詢芙贫,返回List 結(jié)果搂鲫,然后    將查詢的結(jié)果放入緩存之中
      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;
  }
/**
*SimpleExecutor類的doQuery()方法實(shí)現(xiàn)
*/
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      //5. 根據(jù)既有的參數(shù),創(chuàng)建StatementHandler對(duì)象來(lái)執(zhí)行查詢操作
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      //6. 創(chuàng)建java.Sql.Statement對(duì)象磺平,傳遞給StatementHandler對(duì)象
      stmt = prepareStatement(handler, ms.getStatementLog());
      //7. 調(diào)用StatementHandler.query()方法魂仍,返回List結(jié)果集
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

上述的Executor.query()方法幾經(jīng)轉(zhuǎn)折,最后會(huì)創(chuàng)建一個(gè)StatementHandler對(duì)象拣挪,然后將必要的參數(shù)傳遞給StatementHandler擦酌,使用StatementHandler來(lái)完成對(duì)數(shù)據(jù)庫(kù)的查詢,最終返回List結(jié)果集媒吗。

從上面的代碼中我們可以看出仑氛,Executor的功能和作用是:

  • 根據(jù)傳遞的參數(shù),完成SQL語(yǔ)句的動(dòng)態(tài)解析闸英,生成BoundSql對(duì)象锯岖,供StatementHandler使用;

  • 為查詢創(chuàng)建緩存甫何,以提高性能出吹;

  • 創(chuàng)建JDBC的Statement連接對(duì)象,傳遞給StatementHandler對(duì)象辙喂,返回List查詢結(jié)果捶牢。

4.2.3 StatementHandler

接著上面的Executor第六步,看下prepareStatement() 方法的實(shí)現(xiàn):

    /**
     * SimpleExecutor類的doQuery()方法實(shí)現(xiàn)
     */
    public <E> List<E> doQuery (MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler
            , BoundSql boundSql) throws SQLException {
        Statement stmt = null;
        try {
            Configuration configuration = ms.getConfiguration();
            StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds,
                    resultHandler, boundSql);
            // 1.準(zhǔn)備Statement對(duì)象巍耗,并設(shè)置Statement對(duì)象的參數(shù)
            stmt = prepareStatement(handler, ms.getStatementLog());

            // 2. StatementHandler執(zhí)行query()方法秋麸,返回List結(jié)果
            return handler.<E>query(stmt, resultHandler);

        } finally {
            closeStatement(stmt);
        }
    }

private Statement prepareStatement (StatementHandler handler, Log statementLog) throws SQLException {
        Statement stmt;
        Connection connection = getConnection(statementLog);
        stmt = handler.prepare(connection);

        //對(duì)創(chuàng)建的Statement對(duì)象設(shè)置參數(shù),即設(shè)置SQL 語(yǔ)句中 ? 設(shè)置為指定的參數(shù)
        handler.parameterize(stmt);

        return stmt;
    }

StatementHandler 的parameterize(statement) 方法的實(shí)現(xiàn):

  /**
   *ParameterHandler類的setParameters(PreparedStatement ps) 實(shí)現(xiàn)
   * 對(duì)某一個(gè)Statement進(jìn)行設(shè)置參數(shù)
   */
  public void setParameters(PreparedStatement ps) throws SQLException {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
      for (int i = 0; i < parameterMappings.size(); i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
        if (parameterMapping.getMode() != ParameterMode.OUT) {
          Object value;
          String propertyName = parameterMapping.getProperty();
          if (boundSql.hasAdditionalParameter(propertyName)) {
            value = boundSql.getAdditionalParameter(propertyName);
          } else if (parameterObject == null) {
            value = null;
          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            value = parameterObject;
          } else {
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            value = metaObject.getValue(propertyName);
          }
          
          // 每一個(gè)Mapping都有一個(gè)TypeHandler炬太,根據(jù)TypeHandler來(lái)對(duì)preparedStatement進(jìn)行設(shè)置參數(shù)
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) jdbcType = configuration.getJdbcTypeForNull();
          // 設(shè)置參數(shù)
          typeHandler.setParameter(ps, i + 1, value, jdbcType);
        }
      }
    }
  }

從上述的代碼可以看到,StatementHandler 的parameterize(Statement) 方法調(diào)用了 ParameterHandler的setParameters(statement) 方法灸蟆,根據(jù)我們輸入的參數(shù),對(duì)statement對(duì)象的 ? 占位符進(jìn)行賦值亲族。

StatementHandler 的List<E> query(Statement statement, ResultHandler resultHandler)方法的實(shí)現(xiàn):

  /**
   * PreParedStatement類的query方法實(shí)現(xiàn)
   */
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    // 1.調(diào)用preparedStatemnt炒考。execute()方法可缚,然后將resultSet交給ResultSetHandler處理  
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    //2. 使用ResultHandler來(lái)處理ResultSet
    return resultSetHandler.<E> handleResultSets(ps);
  }
/**  
*ResultSetHandler類的handleResultSets()方法實(shí)現(xiàn)
*/
public List<Object> handleResultSets(Statement stmt) throws SQLException {
    final List<Object> multipleResults = new ArrayList<Object>();
 
    int resultSetCount = 0;
    ResultSetWrapper rsw = getFirstResultSet(stmt);
 
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);
    
    while (rsw != null && resultMapCount > resultSetCount) {
      ResultMap resultMap = resultMaps.get(resultSetCount);
      
      //將resultSet
      handleResultSet(rsw, resultMap, multipleResults, null);
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }
 
    String[] resultSets = mappedStatement.getResulSets();
    if (resultSets != null) {
      while (rsw != null && resultSetCount < resultSets.length) {
        ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
        if (parentMapping != null) {
          String nestedResultMapId = parentMapping.getNestedResultMapId();
          ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
          handleResultSet(rsw, resultMap, null, parentMapping);
        }
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
      }
    }
 
    return collapseSingleResultList(multipleResults);
  }

從上述代碼我們可以看出,StatementHandler 的List<E> query(Statement statement, ResultHandler resultHandler)方法的實(shí)現(xiàn)斋枢,是調(diào)用了ResultSetHandler的handleResultSets(Statement) 方法帘靡。ResultSetHandler的handleResultSets(Statement)方法會(huì)將Statement語(yǔ)句執(zhí)行后生成的resultSet 結(jié)果集轉(zhuǎn)換成List<E> 結(jié)果集:

以上我們可以總結(jié)StatementHandler對(duì)象主要完成三個(gè)工作:

  • 對(duì)預(yù)編譯SQL語(yǔ)句中的占位符賦值
  • 執(zhí)行SQL
  • 將數(shù)據(jù)庫(kù)返回的resultSet封裝成List
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市瓤帚,隨后出現(xiàn)的幾起案子描姚,更是在濱河造成了極大的恐慌,老刑警劉巖戈次,帶你破解...
    沈念sama閱讀 218,386評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件轰胁,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡朝扼,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門霎肯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)擎颖,“玉大人,你說(shuō)我怎么就攤上這事观游÷酰” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,704評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵懂缕,是天一觀的道長(zhǎng)允跑。 經(jīng)常有香客問(wèn)我,道長(zhǎng)搪柑,這世上最難降的妖魔是什么聋丝? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,702評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮工碾,結(jié)果婚禮上弱睦,老公的妹妹穿的比我還像新娘。我一直安慰自己渊额,他們只是感情好况木,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著旬迹,像睡著了一般火惊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上奔垦,一...
    開(kāi)封第一講書(shū)人閱讀 51,573評(píng)論 1 305
  • 那天屹耐,我揣著相機(jī)與錄音,去河邊找鬼宴倍。 笑死张症,一個(gè)胖子當(dāng)著我的面吹牛仓技,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播俗他,決...
    沈念sama閱讀 40,314評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼脖捻,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了兆衅?” 一聲冷哼從身側(cè)響起地沮,我...
    開(kāi)封第一講書(shū)人閱讀 39,230評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎羡亩,沒(méi)想到半個(gè)月后摩疑,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,680評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡畏铆,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評(píng)論 3 336
  • 正文 我和宋清朗相戀三年雷袋,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片辞居。...
    茶點(diǎn)故事閱讀 39,991評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡楷怒,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出瓦灶,到底是詐尸還是另有隱情鸠删,我是刑警寧澤,帶...
    沈念sama閱讀 35,706評(píng)論 5 346
  • 正文 年R本政府宣布贼陶,位于F島的核電站刃泡,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏碉怔。R本人自食惡果不足惜烘贴,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望撮胧。 院中可真熱鬧庙楚,春花似錦、人聲如沸趴樱。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,910評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)叁征。三九已至纳账,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間捺疼,已是汗流浹背疏虫。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,038評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人卧秘。 一個(gè)月前我還...
    沈念sama閱讀 48,158評(píng)論 3 370
  • 正文 我出身青樓呢袱,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親翅敌。 傳聞我的和親對(duì)象是個(gè)殘疾皇子羞福,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容