1. mybatis的基本概念
MyBatis 是一款優(yōu)秀的持久層框架,它支持定制化 SQL稠曼、存儲(chǔ)過(guò)程以及高級(jí)映射。MyBatis 避免了幾乎所有的 JDBC 代碼和手動(dòng)設(shè)置參數(shù)以及獲取結(jié)果集堤如。MyBatis 可以使用簡(jiǎn)單的 XML 或注解來(lái)配置和映射原生信息蒲列,將接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java對(duì)象)映射成數(shù)據(jù)庫(kù)中的記錄。
上面是mybatis官方介紹搀罢,從介紹我們可以得知mybatis有以下特點(diǎn):
它是一個(gè)持久化框架
它支持sql蝗岖、存儲(chǔ)過(guò)程、高級(jí)映射
它支持手動(dòng)設(shè)置參數(shù)并且分裝結(jié)果集
它支持xml和注解兩種配置方式
以下為mybatis內(nèi)的一些基本概念:
SqlSessionFactory:SqlSession類的工廠類
SqlSession:數(shù)據(jù)庫(kù)會(huì)話類榔至,為用戶提供數(shù)據(jù)庫(kù)操作方法
Executor:數(shù)據(jù)庫(kù)操作的執(zhí)行器抵赢,SqlSession通過(guò)Executor操作數(shù)據(jù)庫(kù)
MappedStatement:是一個(gè)sql操作的首相
映射接口:具體的業(yè)務(wù)模塊接口,映射接口不需要有實(shí)現(xiàn)類唧取,接口內(nèi)定義了一些列方法铅鲤,每個(gè)方法對(duì)應(yīng)一個(gè)sql操作,方法名就是sql操作的id
映射文件:當(dāng)配置方式為xml時(shí)枫弟,可以將sql寫在xml配置文件中邢享,一個(gè)映射文件對(duì)應(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)閉一級(jí)緩存 -->復(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
編寫啟動(dòng)類
publicclassApp{publicstaticvoidmain(String[] args)throwsException{? ? ? ? SqlSessionFactoryBuilder factoryBuilder =newSqlSessionFactoryBuilder();? ? ? ? InputStream resource = Resources.getResourceAsStream("config.xml");? ? ? ? SqlSessionFactory sessionFactory = factoryBuilder.build(resource);? ? ? ? SqlSession sqlSession = sessionFactory.openSession();/* 這里通過(guò)jdk的動(dòng)態(tài)代理獲取UserMapper接口的代理類 */UserMapper userMapper = sqlSession.getMapper(UserMapper.class);? ? ? ? List list = userMapper.selectUser();? ? ? ? System.out.println(list.size());? ? }}
以上就是搭建純mybatis運(yùn)行環(huán)境的過(guò)程,程序配置過(guò)程不詳述韩容,這里說(shuō)一下mybatis的啟動(dòng)構(gòu)建和執(zhí)行過(guò)程款违。
先是創(chuàng)建SqlSessionFactoryBuilder實(shí)例,改實(shí)例的唯一作用就是用來(lái)構(gòu)建SqlSessionFactory的群凶,一但創(chuàng)建了SqlSessionFactory實(shí)例SqlSessionFactoryBuilder實(shí)例就沒(méi)用了插爹。構(gòu)建SqlSessionFactory的過(guò)程如下:
加載mybatis配置文件
(XMLConfigBuilder.parse)解析配置文件:解析過(guò)程是將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)簽是會(huì)把內(nèi)配置的所有映射記錄解析將mapper記錄添加到Configuration的MapperRegistry中去请梢,并且將對(duì)應(yīng)mapper配置文件里的所有的sql操作解析成MapperStatement(XMLMapperBuilder.parse)赠尾,同時(shí)也會(huì)解析resultMap和緩存配置。
解析xml配置文件最終會(huì)將所有配置信息放到Configuration實(shí)例中去毅弧,該實(shí)例是全局共享的萍虽,后續(xù)獲取Mapper接口代理、獲取MapperStatement形真、獲取Executor都會(huì)從這個(gè)Configuration實(shí)例中獲取杉编。
解析完之后創(chuàng)建DefaultSqlSessionFactory實(shí)例,這里創(chuàng)建DefaultSqlSessionFactory實(shí)例比價(jià)簡(jiǎn)單就是調(diào)用一個(gè)參數(shù)為Configuration的構(gòu)造函數(shù)即可咆霜,因?yàn)樗械男畔⒍家呀?jīng)存放到Configuration實(shí)例中去了
獲取SqlSession會(huì)話對(duì)象邓馒,調(diào)用SqlSessionFactory.open()方法即可,該方法最終會(huì)調(diào)用SqlSessionFactory.openSessionFromDataSource方法根據(jù)Configuration配置信息創(chuàng)建一個(gè)SqlSession實(shí)例蛾坯。
有了SqlSession實(shí)例后光酣,獲取映射接口的代理類,例如這里的sqlSession.getMapper(UserMapper.class)脉课,這里其實(shí)就是通過(guò)jdk的動(dòng)態(tài)代理獲取得到UserMapper接口的代理類救军,實(shí)際代理的InvocationHandler是MapperProxy财异,在MapperProxy.invoke方法中會(huì)攔截映射接口的方法調(diào)用,然后創(chuàng)建(可能會(huì)被緩存)MapperMethod實(shí)例通過(guò)執(zhí)行MapperMethod.execute方法執(zhí)行sql操作唱遭,接著會(huì)調(diào)用SqlSession內(nèi)的一系列方法如selectList戳寸、insert、query等拷泽,根據(jù)調(diào)用的接口和方法組合的全限定名例如:com.test.UserMapper.getUser來(lái)獲取MappedStatement疫鹊,最后通過(guò)Executor來(lái)作sql的操作(當(dāng)然其內(nèi)部也有些封裝執(zhí)行操作,詳情可看Executor的實(shí)現(xiàn)類BaseExecutor司致、CachingExecutor的源碼)拆吆。
Executor執(zhí)行sql的操作的過(guò)程,會(huì)將sql執(zhí)行的結(jié)果例如是insert脂矫、update枣耀、delete操作會(huì)返回執(zhí)行的影響的條數(shù),如果是query操作會(huì)將結(jié)果封裝成對(duì)應(yīng)的sql配置文件配置的類型(如pojo類型庭再、map奕枢、resultMap等)返回List或者單個(gè)對(duì)象并返回。這里mybatis大量使用了范型佩微。
以上就是Mybatis大致的啟動(dòng)構(gòu)建和執(zhí)行過(guò)程缝彬,只能將主要的節(jié)點(diǎn)描述,很多細(xì)節(jié)還需閱讀源碼哺眯。
下圖為mybatis啟動(dòng)示意圖:
3. mybatis的緩存
mybatis內(nèi)置了兩種緩存谷浅,一種是一級(jí)緩存(默認(rèn)開(kāi)啟),一種是二級(jí)緩存(默認(rèn)開(kāi)啟)奶卓,一級(jí)緩存是會(huì)話級(jí)別的也就是一個(gè)SqlSession實(shí)例內(nèi)的緩存一疯,而二級(jí)緩存是namespace級(jí)別的,所謂namespace就是一個(gè)映射接口的范圍夺姑,也就是說(shuō)如果開(kāi)啟了二級(jí)緩存那么多個(gè)會(huì)話如果調(diào)用的是同一個(gè)映射接口那么是有可能命中二級(jí)緩存的墩邀。下面詳細(xì)描述。
一級(jí)緩存:在上一部分我們知道對(duì)于SqlSession里的一系列操作方法盏浙,實(shí)際上最終會(huì)調(diào)用Executor執(zhí)行器內(nèi)的方法來(lái)進(jìn)行sql操作眉睹,Executor在mybatis種提供了幾個(gè)實(shí)現(xiàn)類,在不開(kāi)啟二級(jí)緩存的情況下默認(rèn)使用SimpleExecutor實(shí)現(xiàn)類废膘,SimpleExecutor是集成的BaseExecutor抽象類竹海,大部分的方法已在BaseExecutor實(shí)現(xiàn),我們關(guān)注BaseExecutor丐黄,當(dāng)作查詢操作的時(shí)候最終會(huì)執(zhí)行BaseExecutor.query方法斋配,在BaseExecutor類的152行有這樣的代碼list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;這里就是一級(jí)緩存實(shí)現(xiàn)的地方,即一級(jí)緩存是保存在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等方法后一級(jí)緩存會(huì)被清空甩卓。我們可以看到鸠匀,一級(jí)緩存只提供了簡(jiǎn)單的緩存更新的策略,如果使用一個(gè)SqlSession實(shí)例作同一個(gè)查詢不管查詢多少此其結(jié)果都不會(huì)變猛频,這就有可能出現(xiàn)臟數(shù)據(jù),所以需要斟酌使用一級(jí)緩存蛛勉,如果對(duì)數(shù)據(jù)實(shí)時(shí)性要求高可以在mybatis配置文件配置標(biāo)簽里設(shè)置<setting name="localCacheScope" value="STATEMENT"/>來(lái)關(guān)閉一級(jí)緩存鹿寻。
二級(jí)緩存:二級(jí)緩存是默認(rèn)開(kāi)啟的,如果要關(guān)閉可以在mybatis配置文件配置標(biāo)簽里設(shè)置<setting name="cacheEnabled" value="false"/>诽凌,開(kāi)啟二級(jí)緩存后SqlSession內(nèi)的Executor為CachingExecutor毡熏,實(shí)際CachingExecutor是使用裝飾器模式將包了一層,具體sql操作委托給其他的Executor執(zhí)行(其實(shí)默認(rèn)是委托給SimpleExecutor)侣诵,CachingExecutor只做二級(jí)緩存的處理痢法。源碼CachingExecutor第95行,在執(zhí)行查詢之前先從MappedStatement中獲取cache(如果對(duì)應(yīng)mapper映射文件中未配置那么此處的cache是空的杜顺,其實(shí)這里的cache在mybatis啟動(dòng)構(gòu)建解析配置文件的時(shí)候就已經(jīng)創(chuàng)建好了财搁,這個(gè)cache實(shí)例是和namespace一一對(duì)應(yīng)的)。如果部位空那么就從cache中獲取值躬络。但是這里不是直接從cache中獲取值而是通過(guò)CacheExecutor內(nèi)部的TransactionalCacheManager來(lái)獲取尖奔,之所以這樣是為了保證事務(wù)成功或失敗后緩存的正常保存和清理。例如這里如果開(kāi)啟二級(jí)緩存做一次查詢其實(shí)沒(méi)發(fā)真正保存緩存穷当,此時(shí)緩存是保存在TransactionalCache中的提茁,TransactionalCache內(nèi)保存了所有本次事務(wù)操作需有需要緩存的值,只有調(diào)用SqlSession.commit方法后將commit傳遞到TransactionalCache.commit才能真正保存緩存到namespace的cache實(shí)例中馁菜。在作insert茴扁、update、delete時(shí)二級(jí)緩存也會(huì)被清除汪疮,想比一級(jí)緩存二級(jí)緩存有淘汰策略峭火,默認(rèn)策略上LRU(淘汰最急最少使用),可以在映射配置文件的配置標(biāo)簽中自定義智嚷,除此之外還有:
FIFO:先進(jìn)先出:按對(duì)象進(jìn)入緩存的順序來(lái)移除它們
SOFT:軟引用:移除基于垃圾回收器狀態(tài)和軟引用規(guī)則的對(duì)象
WEAK:弱引用:更積極地移除基于垃圾收集器狀態(tài)和弱引用規(guī)則的對(duì)象
例如:
復(fù)制代碼
Cache:Cache是mybatis在一二級(jí)緩存是對(duì)緩存的抽象躲胳,Cache接口有一系列的實(shí)現(xiàn)類,這些實(shí)現(xiàn)類使用裝飾器模式來(lái)實(shí)現(xiàn)對(duì)不能緩存功能的包裝和功能疊加纤勒。
4. mybatis的插件系統(tǒng)
MyBatis 允許你在已映射語(yǔ)句執(zhí)行過(guò)程中的某一點(diǎn)進(jìn)行攔截調(diào)用坯苹。默認(rèn)情況下,MyBatis 允許使用插件來(lái)攔截的方法調(diào)用包括:
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)
以上是官方的對(duì)plugin的介紹摇天,本質(zhì)上plugin在sql操作的執(zhí)行周期中作用的粹湃,可以作用的點(diǎn)包括Executor恐仑、ParameterHandler、ResultSetHandler为鳄、StatementHandler內(nèi)部的一系列方法裳仆。mybatis通過(guò)動(dòng)態(tài)代理實(shí)現(xiàn)對(duì)作用點(diǎn)前后的自定義操作。在Configuration中有個(gè)interceptorChain屬性孤钦,即插件作用鏈歧斟,在Configuration中newParameterHandler、newResultSetHandler偏形、newStatementHandler静袖、newExecutor這些方法都會(huì)調(diào)用InterceptorChain.pluginAll方法通過(guò)動(dòng)態(tài)代理的方式將每個(gè)插件穿起來(lái),生成插件動(dòng)態(tài)代理鏈?zhǔn)峭ㄟ^(guò)插件工具類Plugin來(lái)實(shí)現(xiàn)俊扭,調(diào)用Plugin.wrap這個(gè)靜態(tài)方法來(lái)創(chuàng)建代理類队橙,代理InvocationHandler類就是Plugin(Plugin本身實(shí)現(xiàn)了InvocationHandler接口),當(dāng)然在創(chuàng)建插件代理類的過(guò)程中還會(huì)判斷插件類的簽名信息即插件類的@Intercepts注解配置信息萨惑,該配置信息里配置了該插件的作用點(diǎn)(實(shí)際上就是作用的函數(shù)調(diào)用點(diǎn))捐康。例如我們想把查詢出來(lái)為L(zhǎng)ist<Map>類型的結(jié)果內(nèi)部的Map字段轉(zhuǎn)成駝峰形式(如:user_name轉(zhuǎn)成userName)我們可以使用插件來(lái)實(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通過(guò)日志工廠獲取日志對(duì)象,mybatis本身不提供日志實(shí)現(xiàn)姐仅,具體的日志交給第三方日志框架來(lái)作倾鲫。可以在mybatis配置文件配置具體日志實(shí)現(xiàn)萍嬉,我門以log4j2為例:
復(fù)制代碼
配置了mybatis的log實(shí)現(xiàn)以后乌昔,需要引入相對(duì)應(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)類通過(guò)裝飾器模式來(lái)實(shí)現(xiàn)緩存的功能的疊加
動(dòng)態(tài)代理模式:在映射接口代理和插件方面mybatis使用jdk的動(dòng)態(tài)代理模式是為映射接口提供代理類磕道,為插件系統(tǒng)提供代理生成插件鏈
工廠模式:mybatis為每個(gè)映射接口生成一個(gè)代理工廠MapperProxyFactory,每次獲取映射接口代理是通過(guò)代理工廠獲取
組合模式:SqlNode的各個(gè)子類使用組合模式實(shí)現(xiàn)sql拼接
單例模式:如LogFacotry
模版方法模式:如抽象類BaseExecutor和其子類就是用該模式行冰。模板類定義一個(gè)操作中的算法的骨架溺蕉,而將一些步驟延遲到子類中。使得子類可以不改變一個(gè)算法的結(jié)構(gòu)即可重定義該算法的某些特定步驟悼做。
7. myabtis集成到spring
mybatis集成到spring需要添加mybatis-spring依賴疯特,這個(gè)依賴包是mybatis和spring對(duì)接依賴包。添加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 --><!-- 注冊(cè)掃描映射接口bean -->
從spring配置可以知道m(xù)ybatis-spring主要做了一下幾件事:
配置sqlSessionFactory工廠bean肛走,該bean是一個(gè)工廠bean(可以理解為這個(gè)工廠bean就是SqlSessionFactory的bean漓雅,當(dāng)注入的時(shí)候工廠bean會(huì)自動(dòng)點(diǎn)用getObject方法獲取得到SqlSessionFactory實(shí)例)
配置sqlSessionTemplate會(huì)話模版,它是SqlSession的子類,它相當(dāng)于全局的會(huì)話代理類它內(nèi)部也是通過(guò)代理的方式sql操作委托給別的SqlSession邻吞。因?yàn)樗梢宰鳛槿值腟qlSession所以它是線程安全的组题,之所以線程安全的是因?yàn)樗型ㄟ^(guò)SqlSessionTemplate調(diào)用的諸如selectList、update的方法都會(huì)委托給SqlSessionTemplate內(nèi)部的sqlSessionProxy抱冷,而sqlSessionProxy是一個(gè)SqlSession的代理崔列,其InvocationHandler是SqlSessionInterceptor,在SqlSessionInterceptor.invoke中每次都會(huì)從TransactionSynchronizationManager中獲取SqlSession旺遮,而在TransactionSynchronizationManager中使用ThreadLocal實(shí)現(xiàn)線程安全赵讯。(這里大概描述詳情看源碼SqlSessionTemplate、SqlSessionUtils)
注冊(cè)掃描映射接口bean:MapperScannerConfigurer實(shí)現(xiàn)了BeanDefinitionRegistryPostProcessor接口耿眉,在bean初始化的時(shí)候會(huì)調(diào)用postProcessBeanDefinitionRegistry边翼,MapperScannerConfigurer.postProcessBeanDefinitionRegistry方法內(nèi)就是掃描注冊(cè)映射接口bean的過(guò)程。掃描注冊(cè)映射接口后跷敬,才可以被注入到其他的Component中讯私。
8. mybatis集成springboot自動(dòng)化配置
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è)自動(dòng)化配置包里。自動(dòng)化配置依賴會(huì)讀取mybatis相關(guān)的配置屬性桶癣,然后自動(dòng)配置我們上面提到的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í)群號(hào):938837867 暗號(hào):555 里面會(huì)分享一些資深架構(gòu)師錄制的視頻錄像:有Spring,MyBatis间雀,Netty源碼分析悔详,高并發(fā)、高性能惹挟、分布式茄螃、微服務(wù)架構(gòu)的原理,JVM性能優(yōu)化连锯、分布式架構(gòu)等這些成為架構(gòu)師必備