Mybatis學(xué)習(xí)記錄

1. mybatis的基本概念

MyBatis 是一款優(yōu)秀的持久層框架,它支持定制化 SQL、存儲過程以及高級映射。MyBatis 避免了幾乎所有的 JDBC 代碼和手動設(shè)置參數(shù)以及獲取結(jié)果集。MyBatis 可以使用簡單的 XML 或注解來配置和映射原生信息,將接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java對象)映射成數(shù)據(jù)庫中的記錄。

上面是mybatis官方介紹,從介紹我們可以得知mybatis有以下特點(diǎn):

它是一個(gè)持久化框架

它支持sql、存儲過程、高級映射

它支持手動設(shè)置參數(shù)并且分裝結(jié)果集

它支持xml和注解兩種配置方式

以下為mybatis內(nèi)的一些基本概念:

SqlSessionFactory:SqlSession類的工廠類

SqlSession:數(shù)據(jù)庫會話類,為用戶提供數(shù)據(jù)庫操作方法

Executor:數(shù)據(jù)庫操作的執(zhí)行器,SqlSession通過Executor操作數(shù)據(jù)庫

MappedStatement:是一個(gè)sql操作的首相

映射接口:具體的業(yè)務(wù)模塊接口,映射接口不需要有實(shí)現(xiàn)類,接口內(nèi)定義了一些列方法,每個(gè)方法對應(yīng)一個(gè)sql操作,方法名就是sql操作的id

映射文件:當(dāng)配置方式為xml時(shí),可以將sql寫在xml配置文件中,一個(gè)映射文件對應(yīng)一個(gè)映射接口

Cache:mybatis內(nèi)部緩存實(shí)現(xiàn)

Configuration:全局配置信息(以及配置信息解析的結(jié)果)存放處,該實(shí)例全局共享,該實(shí)例是SqlSessionFactory的屬性

2. mybatis如何構(gòu)建和執(zhí)行的

那mybatis是如果構(gòu)建和執(zhí)行的呢,先看一個(gè)小例子(這里以xml配置方式為例):

創(chuàng)建一個(gè)maven項(xiàng)目

引入mybatis和mysql連接工具依賴

org.mybatismybatis3.4.5mysqlmysql-connector-java6.0.6復(fù)制代碼

編寫mybatis配置文件

<?xml version="1.0"encoding="UTF-8"?><!DOCTYPE configuration

? ? ? ? PUBLIC "-//mybatis.org//DTD Config 3.0//EN"

? ? ? ? "http://mybatis.org/dtd/mybatis-3-config.dtd"><!-- 關(guān)閉一級緩存 -->復(fù)制代碼

編寫映射接口

publicinterfaceUserMapper{ListselectUser();}復(fù)制代碼

編寫映射xml文件(resources/mapper/UserMapper.xml)

<?xml version="1.0"encoding="UTF-8"?><!DOCTYPE mapper

? ? ? ? PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"

? ? ? ? "http://mybatis.org/dtd/mybatis-3-mapper.dtd">select * from user

編寫啟動類

publicclassApp{publicstaticvoidmain(String[] args)throwsException{? ? ? ? SqlSessionFactoryBuilder factoryBuilder =newSqlSessionFactoryBuilder();? ? ? ? InputStream resource = Resources.getResourceAsStream("config.xml");? ? ? ? SqlSessionFactory sessionFactory = factoryBuilder.build(resource);? ? ? ? SqlSession sqlSession = sessionFactory.openSession();/* 這里通過jdk的動態(tài)代理獲取UserMapper接口的代理類 */UserMapper userMapper = sqlSession.getMapper(UserMapper.class);? ? ? ? List list = userMapper.selectUser();? ? ? ? System.out.println(list.size());? ? }}

以上就是搭建純mybatis運(yùn)行環(huán)境的過程,程序配置過程不詳述,這里說一下mybatis的啟動構(gòu)建和執(zhí)行過程。

先是創(chuàng)建SqlSessionFactoryBuilder實(shí)例,改實(shí)例的唯一作用就是用來構(gòu)建SqlSessionFactory的,一但創(chuàng)建了SqlSessionFactory實(shí)例SqlSessionFactoryBuilder實(shí)例就沒用了。構(gòu)建SqlSessionFactory的過程如下:

加載mybatis配置文件

(XMLConfigBuilder.parse)解析配置文件:解析過程是將xml配置文件內(nèi)的所有配置標(biāo)簽都解析并包括

<properties/>

<settings/>

<typeAliases/>

<plugins/>

<objectFactory/>

<objectWrapperFactory/>

<reflectorFactory/>

<environments/>

<databaseIdProvider/>

<typeHandlers/>

<mappers/>

解析每個(gè)標(biāo)簽調(diào)用不同的方法處理該標(biāo)簽的配置,例如解析標(biāo)簽是會把內(nèi)配置的所有映射記錄解析將mapper記錄添加到Configuration的MapperRegistry中去,并且將對應(yīng)mapper配置文件里的所有的sql操作解析成MapperStatement(XMLMapperBuilder.parse),同時(shí)也會解析resultMap和緩存配置。

解析xml配置文件最終會將所有配置信息放到Configuration實(shí)例中去,該實(shí)例是全局共享的,后續(xù)獲取Mapper接口代理、獲取MapperStatement、獲取Executor都會從這個(gè)Configuration實(shí)例中獲取。

解析完之后創(chuàng)建DefaultSqlSessionFactory實(shí)例,這里創(chuàng)建DefaultSqlSessionFactory實(shí)例比價(jià)簡單就是調(diào)用一個(gè)參數(shù)為Configuration的構(gòu)造函數(shù)即可,因?yàn)樗械男畔⒍家呀?jīng)存放到Configuration實(shí)例中去了

獲取SqlSession會話對象,調(diào)用SqlSessionFactory.open()方法即可,該方法最終會調(diào)用SqlSessionFactory.openSessionFromDataSource方法根據(jù)Configuration配置信息創(chuàng)建一個(gè)SqlSession實(shí)例。

有了SqlSession實(shí)例后,獲取映射接口的代理類,例如這里的sqlSession.getMapper(UserMapper.class),這里其實(shí)就是通過jdk的動態(tài)代理獲取得到UserMapper接口的代理類,實(shí)際代理的InvocationHandler是MapperProxy,在MapperProxy.invoke方法中會攔截映射接口的方法調(diào)用,然后創(chuàng)建(可能會被緩存)MapperMethod實(shí)例通過執(zhí)行MapperMethod.execute方法執(zhí)行sql操作,接著會調(diào)用SqlSession內(nèi)的一系列方法如selectList、insert、query等,根據(jù)調(diào)用的接口和方法組合的全限定名例如:com.test.UserMapper.getUser來獲取MappedStatement,最后通過Executor來作sql的操作(當(dāng)然其內(nèi)部也有些封裝執(zhí)行操作,詳情可看Executor的實(shí)現(xiàn)類BaseExecutor、CachingExecutor的源碼)。

Executor執(zhí)行sql的操作的過程,會將sql執(zhí)行的結(jié)果例如是insert、update、delete操作會返回執(zhí)行的影響的條數(shù),如果是query操作會將結(jié)果封裝成對應(yīng)的sql配置文件配置的類型(如pojo類型、map、resultMap等)返回List或者單個(gè)對象并返回。這里mybatis大量使用了范型。

以上就是Mybatis大致的啟動構(gòu)建和執(zhí)行過程,只能將主要的節(jié)點(diǎn)描述,很多細(xì)節(jié)還需閱讀源碼。

下圖為mybatis啟動示意圖:

3. mybatis的緩存

mybatis內(nèi)置了兩種緩存,一種是一級緩存(默認(rèn)開啟),一種是二級緩存(默認(rèn)開啟),一級緩存是會話級別的也就是一個(gè)SqlSession實(shí)例內(nèi)的緩存,而二級緩存是namespace級別的,所謂namespace就是一個(gè)映射接口的范圍,也就是說如果開啟了二級緩存那么多個(gè)會話如果調(diào)用的是同一個(gè)映射接口那么是有可能命中二級緩存的。下面詳細(xì)描述。

一級緩存:在上一部分我們知道對于SqlSession里的一系列操作方法,實(shí)際上最終會調(diào)用Executor執(zhí)行器內(nèi)的方法來進(jìn)行sql操作,Executor在mybatis種提供了幾個(gè)實(shí)現(xiàn)類,在不開啟二級緩存的情況下默認(rèn)使用SimpleExecutor實(shí)現(xiàn)類,SimpleExecutor是集成的BaseExecutor抽象類,大部分的方法已在BaseExecutor實(shí)現(xiàn),我們關(guān)注BaseExecutor,當(dāng)作查詢操作的時(shí)候最終會執(zhí)行BaseExecutor.query方法,在BaseExecutor類的152行有這樣的代碼list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;這里就是一級緩存實(shí)現(xiàn)的地方,即一級緩存是保存在BaseExecutor內(nèi)部屬性localCache中,而localCache其實(shí)就是個(gè)PerpetualCache而該類是mybatis緩存的一個(gè)實(shí)現(xiàn)類,下鉆到PerpetualCache內(nèi)可以發(fā)現(xiàn)其內(nèi)部有個(gè)類型為Map的cache屬性其中key為CacheKey值就是查詢結(jié)果。當(dāng)執(zhí)行了update、commit等方法后一級緩存會被清空。我們可以看到,一級緩存只提供了簡單的緩存更新的策略,如果使用一個(gè)SqlSession實(shí)例作同一個(gè)查詢不管查詢多少此其結(jié)果都不會變,這就有可能出現(xiàn)臟數(shù)據(jù),所以需要斟酌使用一級緩存,如果對數(shù)據(jù)實(shí)時(shí)性要求高可以在mybatis配置文件配置標(biāo)簽里設(shè)置<setting name="localCacheScope" value="STATEMENT"/>來關(guān)閉一級緩存。

二級緩存:二級緩存是默認(rèn)開啟的,如果要關(guān)閉可以在mybatis配置文件配置標(biāo)簽里設(shè)置<setting name="cacheEnabled" value="false"/>,開啟二級緩存后SqlSession內(nèi)的Executor為CachingExecutor,實(shí)際CachingExecutor是使用裝飾器模式將包了一層,具體sql操作委托給其他的Executor執(zhí)行(其實(shí)默認(rèn)是委托給SimpleExecutor),CachingExecutor只做二級緩存的處理。源碼CachingExecutor第95行,在執(zhí)行查詢之前先從MappedStatement中獲取cache(如果對應(yīng)mapper映射文件中未配置那么此處的cache是空的,其實(shí)這里的cache在mybatis啟動構(gòu)建解析配置文件的時(shí)候就已經(jīng)創(chuàng)建好了,這個(gè)cache實(shí)例是和namespace一一對應(yīng)的)。如果部位空那么就從cache中獲取值。但是這里不是直接從cache中獲取值而是通過CacheExecutor內(nèi)部的TransactionalCacheManager來獲取,之所以這樣是為了保證事務(wù)成功或失敗后緩存的正常保存和清理。例如這里如果開啟二級緩存做一次查詢其實(shí)沒發(fā)真正保存緩存,此時(shí)緩存是保存在TransactionalCache中的,TransactionalCache內(nèi)保存了所有本次事務(wù)操作需有需要緩存的值,只有調(diào)用SqlSession.commit方法后將commit傳遞到TransactionalCache.commit才能真正保存緩存到namespace的cache實(shí)例中。在作insert、update、delete時(shí)二級緩存也會被清除,想比一級緩存二級緩存有淘汰策略,默認(rèn)策略上LRU(淘汰最急最少使用),可以在映射配置文件的配置標(biāo)簽中自定義,除此之外還有:

FIFO:先進(jìn)先出:按對象進(jìn)入緩存的順序來移除它們

SOFT:軟引用:移除基于垃圾回收器狀態(tài)和軟引用規(guī)則的對象

WEAK:弱引用:更積極地移除基于垃圾收集器狀態(tài)和弱引用規(guī)則的對象

例如:

復(fù)制代碼

Cache:Cache是mybatis在一二級緩存是對緩存的抽象,Cache接口有一系列的實(shí)現(xiàn)類,這些實(shí)現(xiàn)類使用裝飾器模式來實(shí)現(xiàn)對不能緩存功能的包裝和功能疊加。

4. mybatis的插件系統(tǒng)

MyBatis 允許你在已映射語句執(zhí)行過程中的某一點(diǎn)進(jìn)行攔截調(diào)用。默認(rèn)情況下,MyBatis 允許使用插件來攔截的方法調(diào)用包括:

Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)

ParameterHandler (getParameterObject, setParameters)

ResultSetHandler (handleResultSets, handleOutputParameters)

StatementHandler (prepare, parameterize, batch, update, query)

以上是官方的對plugin的介紹,本質(zhì)上plugin在sql操作的執(zhí)行周期中作用的,可以作用的點(diǎn)包括Executor、ParameterHandler、ResultSetHandler、StatementHandler內(nèi)部的一系列方法。mybatis通過動態(tài)代理實(shí)現(xiàn)對作用點(diǎn)前后的自定義操作。在Configuration中有個(gè)interceptorChain屬性,即插件作用鏈,在Configuration中newParameterHandler、newResultSetHandler、newStatementHandler、newExecutor這些方法都會調(diào)用InterceptorChain.pluginAll方法通過動態(tài)代理的方式將每個(gè)插件穿起來,生成插件動態(tài)代理鏈?zhǔn)峭ㄟ^插件工具類Plugin來實(shí)現(xiàn),調(diào)用Plugin.wrap這個(gè)靜態(tài)方法來創(chuàng)建代理類,代理InvocationHandler類就是Plugin(Plugin本身實(shí)現(xiàn)了InvocationHandler接口),當(dāng)然在創(chuàng)建插件代理類的過程中還會判斷插件類的簽名信息即插件類的@Intercepts注解配置信息,該配置信息里配置了該插件的作用點(diǎn)(實(shí)際上就是作用的函數(shù)調(diào)用點(diǎn))。例如我們想把查詢出來為List<Map>類型的結(jié)果內(nèi)部的Map字段轉(zhuǎn)成駝峰形式(如:user_name轉(zhuǎn)成userName)我們可以使用插件來實(shí)現(xiàn)。

@Intercepts({@Signature(? ? ? ? type= ResultSetHandler.class,? ? ? ? method ="handleResultSets",? ? ? ? args = {Statement.class})})publicclassMyPluginimplementsInterceptor{@Override@SuppressWarnings("unchecked")publicObjectintercept(Invocation invocation)throwsThrowable{? ? ? ? List result = (List) invocation.proceed();if(result !=null&& result.size() >0) {if(result.get(0)instanceofMap) {? ? ? ? ? ? ? ? List reList =newArrayList();for(Map el : (List) result) {? ? ? ? ? ? ? ? ? ? Map map =newHashMap();for(String key : (Set) el.keySet()) {? ? ? ? ? ? ? ? ? ? ? ? map.put(getCamelKey(key), el.get(key));? ? ? ? ? ? ? ? ? ? }? ? ? ? ? ? ? ? ? ? reList.add(map);? ? ? ? ? ? ? ? }returnreList;? ? ? ? ? ? }? ? ? ? }returnresult;? ? }@OverridepublicObjectplugin(Object target){returnPlugin.wrap(target,this);? ? }@OverridepublicvoidsetProperties(Properties properties){? ? }privateStringgetCamelKey(String key){? ? ? ? String[] split = key.split("_");? ? ? ? String camelKey ="";for(inti =0; i < split.length; i++) {if(i !=0) camelKey += split[i].substring(0,1).toUpperCase() + split[i].substring(1, split[i].length());elsecamelKey += split[i];? ? ? ? }returncamelKey;? ? }}

5. mybatis的日志系統(tǒng)

Mybatis 的內(nèi)置日志工廠提供日志功能,內(nèi)置日志工廠將日志交給以下其中一種工具作代理:

SLF4J

Apache Commons Logging

Log4j 2

Log4j

JDK logging

實(shí)際mybatis只提供了一個(gè)日志工廠LogFactory,mybatis通過日志工廠獲取日志對象,mybatis本身不提供日志實(shí)現(xiàn),具體的日志交給第三方日志框架來作??梢栽趍ybatis配置文件配置具體日志實(shí)現(xiàn),我門以log4j2為例:

復(fù)制代碼

配置了mybatis的log實(shí)現(xiàn)以后,需要引入相對應(yīng)的日志依賴包。

org.apache.logging.log4jlog4j-api2.11.1org.apache.logging.log4jlog4j-core2.11.1

然后配置日志框架的配置文件(每個(gè)日志框架的配置不同這里以log4j2為例)

<?xml version="1.0"encoding="UTF-8"?>

6. mybatis用到的設(shè)計(jì)模式

mybatis在實(shí)現(xiàn)的時(shí)候用了一些設(shè)計(jì)模式,如:

裝飾器模式:在緩存方面Cache緩存接口的各個(gè)實(shí)現(xiàn)類通過裝飾器模式來實(shí)現(xiàn)緩存的功能的疊加

動態(tài)代理模式:在映射接口代理和插件方面mybatis使用jdk的動態(tài)代理模式是為映射接口提供代理類,為插件系統(tǒng)提供代理生成插件鏈

工廠模式:mybatis為每個(gè)映射接口生成一個(gè)代理工廠MapperProxyFactory,每次獲取映射接口代理是通過代理工廠獲取

組合模式:SqlNode的各個(gè)子類使用組合模式實(shí)現(xiàn)sql拼接

單例模式:如LogFacotry

模版方法模式:如抽象類BaseExecutor和其子類就是用該模式。模板類定義一個(gè)操作中的算法的骨架,而將一些步驟延遲到子類中。使得子類可以不改變一個(gè)算法的結(jié)構(gòu)即可重定義該算法的某些特定步驟。

7. myabtis集成到spring

mybatis集成到spring需要添加mybatis-spring依賴,這個(gè)依賴包是mybatis和spring對接依賴包。添加spring依賴和mybatis-spring依賴

<!-- spring -->org.springframeworkspring-core5.0.4.RELEASEorg.springframeworkspring-beans5.0.4.RELEASEorg.springframeworkspring-context5.0.4.RELEASEorg.springframeworkspring-jdbc5.0.4.RELEASE<!-- mybatis-spring -->org.mybatismybatis-spring1.3.1

配置spring

<?xml version="1.0"encoding="UTF-8"?>

? ? ? ? http://www.springframework.org/schema/beans/spring-beans.xsd

? ? ? ? http://www.springframework.org/schema/context

? ? ? ? http://www.springframework.org/schema/context/spring-context.xsd"><!-- 屬性掃描 --><!-- 組件掃描 --><!-- 數(shù)據(jù)源 --><!-- 配置sqlSessionFactory工廠bean --><!-- 配置sqlSessionTemplate --><!-- 注冊掃描映射接口bean -->

從spring配置可以知道m(xù)ybatis-spring主要做了一下幾件事:

配置sqlSessionFactory工廠bean,該bean是一個(gè)工廠bean(可以理解為這個(gè)工廠bean就是SqlSessionFactory的bean,當(dāng)注入的時(shí)候工廠bean會自動點(diǎn)用getObject方法獲取得到SqlSessionFactory實(shí)例)

配置sqlSessionTemplate會話模版,它是SqlSession的子類,它相當(dāng)于全局的會話代理類它內(nèi)部也是通過代理的方式sql操作委托給別的SqlSession。因?yàn)樗梢宰鳛槿值腟qlSession所以它是線程安全的,之所以線程安全的是因?yàn)樗型ㄟ^SqlSessionTemplate調(diào)用的諸如selectList、update的方法都會委托給SqlSessionTemplate內(nèi)部的sqlSessionProxy,而sqlSessionProxy是一個(gè)SqlSession的代理,其InvocationHandler是SqlSessionInterceptor,在SqlSessionInterceptor.invoke中每次都會從TransactionSynchronizationManager中獲取SqlSession,而在TransactionSynchronizationManager中使用ThreadLocal實(shí)現(xiàn)線程安全。(這里大概描述詳情看源碼SqlSessionTemplate、SqlSessionUtils)

注冊掃描映射接口bean:MapperScannerConfigurer實(shí)現(xiàn)了BeanDefinitionRegistryPostProcessor接口,在bean初始化的時(shí)候會調(diào)用postProcessBeanDefinitionRegistry,MapperScannerConfigurer.postProcessBeanDefinitionRegistry方法內(nèi)就是掃描注冊映射接口bean的過程。掃描注冊映射接口后,才可以被注入到其他的Component中。

8. mybatis集成springboot自動化配置

mybatis集成springboot需要添加一個(gè)start

org.mybatis.spring.bootmybatis-spring-boot-starter1.3.0

其實(shí)mybatis-spring-boot-starter只是個(gè)空的依賴,mybatis-spring-boot-starter依賴了mybatis-spring-boot-autoconfigure,主要的代碼在這個(gè)自動化配置包里。自動化配置依賴會讀取mybatis相關(guān)的配置屬性,然后自動配置我們上面提到的mybatis相關(guān)的組件。配置例子:

mybatis.mapper-locations=classpath:/mapper/**/*Mapper.xmlmybatis.typeAliasesPackage=com.test.*.modelmybatis.configuration.map-underscore-to-camel-case=truemybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImplmybatis.configuration.callSettersOnNulls=true

這里不將springboot相關(guān)內(nèi)容,只做配置樣例介紹。在此我向大家推薦一個(gè)架構(gòu)學(xué)習(xí)交流群。交流學(xué)習(xí)群號:938837867 暗號:555 里面會分享一些資深架構(gòu)師錄制的視頻錄像:有Spring,MyBatis,Netty源碼分析,高并發(fā)、高性能、分布式、微服務(wù)架構(gòu)的原理,JVM性能優(yōu)化、分布式架構(gòu)等這些成為架構(gòu)師必備

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1 Mybatis入門 1.1 單獨(dú)使用jdbc編程問題總結(jié) 1.1.1 jdbc程序 上邊使...
    哇哈哈E閱讀 3,410評論 0 38
  • 1. 簡介 1.1 什么是 MyBatis ? MyBatis 是支持定制化 SQL、存儲過程以及高級映射的優(yōu)秀的...
    笨鳥慢飛閱讀 6,219評論 0 4
  • MyBatis 理論篇 [TOC] 什么是MyBatis ?MyBatis是支持普通SQL查詢,存儲過程和高級映射...
    有_味閱讀 3,168評論 0 26
  • 夲圣閱讀 283評論 6 10
  • 白羊座:“你和那個(gè)xx最近好像 接觸挺多 總聽你談到她” 潛臺詞:“ 你沒理解錯(cuò)!姐不爽了 而且是相當(dāng)不爽!” 金...
    隨筆星文閱讀 501評論 0 2

友情鏈接更多精彩內(nèi)容