目錄java
mybatis的基本概念
mybatis如何構建和執行的
mybatis的緩存
mybatis的插件系統
mybatis的日誌系統
mybatis用到的設計模式
myabtis集成到spring
mybatis集成springboot自動化配置mysql
MyBatis 是一款優秀的持久層框架,它支持定製化 SQL、存儲過程以及高級映射。MyBatis 避免了幾乎全部的 JDBC 代碼和手動設置參數以及獲取結果集。MyBatis 可使用簡單的 XML 或註解來配置和映射原生信息,將接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java對象)映射成數據庫中的記錄。算法
上面是mybatis官方介紹,從介紹咱們能夠得知mybatis有如下特色:spring
它是一個持久化框架
它支持sql、存儲過程、高級映射
它支持手動設置參數而且分裝結果集
它支持xml和註解兩種配置方式sql
如下爲mybatis內的一些基本概念:數據庫
SqlSessionFactory:SqlSession類的工廠類
SqlSession:數據庫會話類,爲用戶提供數據庫操做方法
Executor:數據庫操做的執行器,SqlSession經過Executor操做數據庫
MappedStatement:是一個sql操做的抽象
映射接口:具體的業務模塊接口,映射接口不須要有實現類,接口內定義了一些列方法,每一個方法對應一個sql操做,方法名就是sql操做的id
映射文件:當配置方式爲xml時,能夠將sql寫在xml配置文件中,一個映射文件對應一個映射接口
Cache:mybatis內部緩存實現
Configuration:全局配置信息(以及配置信息解析的結果)存放處,該實例全局共享,該實例是SqlSessionFactory的屬性apache
那mybatis是若是構建和執行的呢,先看一個小例子(這裏以xml配置方式爲例):設計模式
建立一個maven項目
引入mybatis和mysql鏈接工具依賴api
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>6.0.6</version>
</dependency>緩存
編寫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">
<configuration>
<properties resource="config.properties"/> <settings> <setting name="logImpl" value="LOG4J2"/> <!-- 關閉一級緩存 --> <setting name="localCacheScope" value="STATEMENT"/> </settings> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource> </environment> </environments> <mappers> <mapper resource="mapper/UserMapper.xml"/> </mappers>
</configuration>
編寫映射接口
public interface UserMapper {
List<Map> selectUser();
}
編寫映射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">
<mapper namespace="test.demos.mybatis.UserMapper">
<select id="selectUser" resultType="java.util.Map"> select * from user </select>
</mapper>
編寫啓動類
public class App {
public static void main(String[] args) throws Exception { SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder(); InputStream resource = Resources.getResourceAsStream("config.xml"); SqlSessionFactory sessionFactory = factoryBuilder.build(resource); SqlSession sqlSession = sessionFactory.openSession(); /* 這裏經過jdk的動態代理獲取UserMapper接口的代理類 */ UserMapper userMapper = sqlSession.getMapper(UserMapper.class); List<Map> list = userMapper.selectUser(); System.out.println(list.size()); }
}
以上就是搭建純mybatis運行環境的過程,程序配置過程不詳述,這裏說一下mybatis的啓動構建和執行過程。
先是建立SqlSessionFactoryBuilder實例,改實例的惟一做用就是用來構建SqlSessionFactory的,一但建立了SqlSessionFactory實例SqlSessionFactoryBuilder實例就沒用了。構建SqlSessionFactory的過程以下:
加載mybatis配置文件
(XMLConfigBuilder.parse)解析配置文件:解析過程是將xml配置文件內的全部配置標籤都解析幷包括
<properties/>
<settings/>
<typeAliases/>
<plugins/>
<objectFactory/>
<objectWrapperFactory/>
<reflectorFactory/>
<environments/>
<databaseIdProvider/>
<typeHandlers/>
<mappers/>
解析每一個標籤調用不一樣的方法處理該標籤的配置,例如解析標籤是會把內配置的全部映射記錄解析將mapper記錄添加到Configuration的MapperRegistry中去,而且將對應mapper配置文件裏的全部的sql操做解析成MapperStatement(XMLMapperBuilder.parse),同時也會解析resultMap和緩存配置。
解析xml配置文件最終會將全部配置信息放到Configuration實例中去,該實例是全局共享的,後續獲取Mapper接口代理、獲取MapperStatement、獲取Executor都會從這個Configuration實例中獲取。
解析完以後建立DefaultSqlSessionFactory實例,這裏建立DefaultSqlSessionFactory實例比價簡單就是調用一個參數爲Configuration的構造函數便可,由於全部的信息都已經存放到Configuration實例中去了
獲取SqlSession會話對象,調用SqlSessionFactory.open()方法便可,該方法最終會調用SqlSessionFactory.openSessionFromDataSource方法根據Configuration配置信息建立一個SqlSession實例。
有了SqlSession實例後,獲取映射接口的代理類,例如這裏的sqlSession.getMapper(UserMapper.class),這裏其實就是經過jdk的動態代理獲取獲得UserMapper接口的代理類,實際代理的InvocationHandler是MapperProxy,在MapperProxy.invoke方法中會攔截映射接口的方法調用,而後建立(可能會被緩存)MapperMethod實例經過執行MapperMethod.execute方法執行sql操做,接着會調用SqlSession內的一系列方法如selectList、insert、query等,根據調用的接口和方法組合的全限定名例如:com.test.UserMapper.getUser來獲取MappedStatement,最後經過Executor來做sql的操做(固然其內部也有些封裝執行操做,詳情可看Executor的實現類BaseExecutor、CachingExecutor的源碼)。
Executor執行sql的操做的過程,會將sql執行的結果例如是insert、update、delete操做會返回執行的影響的條數,若是是query操做會將結果封裝成對應的sql配置文件配置的類型(如pojo類型、map、resultMap等)返回List或者單個對象並返回。這裏mybatis大量使用了範型。
以上就是Mybatis大體的啓動構建和執行過程,只能將主要的節點描述,不少細節還需閱讀源碼。
下圖爲mybatis啓動示意圖:
mybatis內置了兩種緩存,一種是一級緩存(默認開啓),一種是二級緩存(默認開啓),一級緩存是會話級別的也就是一個SqlSession實例內的緩存,而二級緩存是namespace級別的,所謂namespace就是一個映射接口的範圍,也就是說若是開啓了二級緩存那麼多個會話若是調用的是同一個映射接口那麼是有可能命中二級緩存的。下面詳細描述。
一級緩存:在上一部分咱們知道對於SqlSession裏的一系列操做方法,實際上最終會調用Executor執行器內的方法來進行sql操做,Executor在mybatis種提供了幾個實現類,在不開啓二級緩存的狀況下默認使用SimpleExecutor實現類,SimpleExecutor是集成的BaseExecutor抽象類,大部分的方法已在BaseExecutor實現,咱們關注BaseExecutor,看成查詢操做的時候最終會執行BaseExecutor.query方法,在BaseExecutor類的152行有這樣的代碼list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;這裏就是一級緩存實現的地方,即一級緩存是保存在BaseExecutor內部屬性localCache中,而localCache其實就是個PerpetualCache而該類是mybatis緩存的一個實現類,下鑽到PerpetualCache內能夠發現其內部有個類型爲Map的cache屬性其中key爲CacheKey值就是查詢結果。當執行了update、commit等方法後一級緩存會被清空。咱們能夠看到,一級緩存只提供了簡單的緩存更新的策略,若是使用一個SqlSession實例做同一個查詢無論查詢多少此其結果都不會變,這就有可能出現髒數據,因此須要斟酌使用一級緩存,若是對數據實時性要求高能夠在mybatis配置文件配置標籤裏設置<setting name="localCacheScope" value="STATEMENT"/>來關閉一級緩存。
二級緩存:二級緩存是默認開啓的,若是要關閉能夠在mybatis配置文件配置標籤裏設置<setting name="cacheEnabled" value="false"/>,開啓二級緩存後SqlSession內的Executor爲CachingExecutor,實際CachingExecutor是使用裝飾器模式將包了一層,具體sql操做委託給其餘的Executor執行(其實默認是委託給SimpleExecutor),CachingExecutor只作二級緩存的處理。源碼CachingExecutor第95行,在執行查詢以前先從MappedStatement中獲取cache(若是對應mapper映射文件中未配置那麼此處的cache是空的,其實這裏的cache在mybatis啓動構建解析配置文件的時候就已經建立好了,這個cache實例是和namespace一一對應的)。若是部位空那麼就從cache中獲取值。可是這裏不是直接從cache中獲取值而是經過CacheExecutor內部的TransactionalCacheManager來獲取,之因此這樣是爲了保證事務成功或失敗後緩存的正常保存和清理。例如這裏若是開啓二級緩存作一次查詢其實沒發真正保存緩存,此時緩存是保存在TransactionalCache中的,TransactionalCache內保存了全部本次事務操做需有須要緩存的值,只有調用SqlSession.commit方法後將commit傳遞到TransactionalCache.commit才能真正保存緩存到namespace的cache實例中。在做insert、update、delete時二級緩存也會被清除,想比一級緩存二級緩存有淘汰策略,默認策略上LRU(淘汰最急最少使用),能夠在映射配置文件的配置標籤中自定義,除此以外還有:
FIFO:先進先出:按對象進入緩存的順序來移除它們
SOFT:軟引用:移除基於垃圾回收器狀態和軟引用規則的對象
WEAK:弱引用:更積極地移除基於垃圾收集器狀態和弱引用規則的對象
例如:
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
Cache:Cache是mybatis在一二級緩存是對緩存的抽象,Cache接口有一系列的實現類,這些實現類使用裝飾器模式來實現對不能緩存功能的包裝和功能疊加。
MyBatis 容許你在已映射語句執行過程當中的某一點進行攔截調用。默認狀況下,MyBatis 容許使用插件來攔截的方法調用包括:
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)
以上是官方的對plugin的介紹,本質上plugin在sql操做的執行週期中做用的,能夠做用的點包括Executor、ParameterHandler、ResultSetHandler、StatementHandler內部的一系列方法。mybatis經過動態代理實現對做用點先後的自定義操做。在Configuration中有個interceptorChain屬性,即插件做用鏈,在Configuration中newParameterHandler、newResultSetHandler、newStatementHandler、newExecutor這些方法都會調用InterceptorChain.pluginAll方法經過動態代理的方式將每一個插件穿起來,生成插件動態代理鏈是經過插件工具類Plugin來實現,調用Plugin.wrap這個靜態方法來建立代理類,代理InvocationHandler類就是Plugin(Plugin自己實現了InvocationHandler接口),固然在建立插件代理類的過程當中還會判斷插件類的簽名信息即插件類的@Intercepts註解配置信息,該配置信息裏配置了該插件的做用點(實際上就是做用的函數調用點)。例如咱們想把查詢出來爲List<Map>類型的結果內部的Map字段轉成駝峯形式(如:user_name轉成userName)咱們可使用插件來實現。
@Intercepts({@Signature(
type= ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})})
public class MyPlugin implements Interceptor {
@Override @SuppressWarnings("unchecked") public Object intercept(Invocation invocation) throws Throwable { List result = (List) invocation.proceed(); if (result != null && result.size() > 0) { if (result.get(0) instanceof Map) { List reList = new ArrayList(); for (Map el : (List<Map>) result) { Map map = new HashMap(); for (String key : (Set<String>) el.keySet()) { map.put(getCamelKey(key), el.get(key)); } reList.add(map); } return reList; } } return result; } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { } private String getCamelKey(String key) { String[] split = key.split("_"); String camelKey = ""; for (int i = 0; i < split.length; i++) { if (i != 0) camelKey += split[i].substring(0, 1).toUpperCase() + split[i].substring(1, split[i].length()); else camelKey += split[i]; } return camelKey; }
}
Mybatis 的內置日誌工廠提供日誌功能,內置日誌工廠將日誌交給如下其中一種工具做代理:
SLF4J
Apache Commons Logging
Log4j 2
Log4j
JDK logging
實際mybatis只提供了一個日誌工廠LogFactory,mybatis經過日誌工廠獲取日誌對象,mybatis自己不提供日誌實現,具體的日誌交給第三方日誌框架來做。能夠在mybatis配置文件配置具體日誌實現,我門以log4j2爲例:
<configuration>
<settings>
<setting name="logImpl" value="LOG4J2"/>
</settings>
</configuration>
配置了mybatis的log實現之後,須要引入相對應的日誌依賴包。
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.11.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.11.1</version>
</dependency>
而後配置日誌框架的配置文件(每一個日誌框架的配置不一樣這裏以log4j2爲例)
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="info" name="RoutingTest">
<Appenders> <Console name="Console" target="SYSTEM_OUT"> <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/> </Console> </Appenders> <Loggers> <Root level="debug"> <AppenderRef ref="Console"/> </Root> </Loggers>
</Configuration>
mybatis在實現的時候用了一些設計模式,如:
裝飾器模式:在緩存方面Cache緩存接口的各個實現類經過裝飾器模式來實現緩存的功能的疊加
動態代理模式:在映射接口代理和插件方面mybatis使用jdk的動態代理模式是爲映射接口提供代理類,爲插件系統提供代理生成插件鏈
工廠模式:mybatis爲每一個映射接口生成一個代理工廠MapperProxyFactory,每次獲取映射接口代理是經過代理工廠獲取
組合模式:SqlNode的各個子類使用組合模式實現sql拼接
單例模式:如LogFacotry
模版方法模式:如抽象類BaseExecutor和其子類就是用該模式。模板類定義一個操做中的算法的骨架,而將一些步驟延遲到子類中。使得子類能夠不改變一個算法的結構便可重定義該算法的某些特定步驟。
mybatis集成到spring須要添加mybatis-spring依賴,這個依賴包是mybatis和spring對接依賴包。添加spring依賴和mybatis-spring依賴
<!-- spring -->
<dependency>
<groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>5.0.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>5.0.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.0.4.RELEASE</version>
</dependency>
<!-- mybatis-spring -->
<dependency>
<groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.1</version>
</dependency>
配置spring
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 屬性掃描 --> <context:property-placeholder location="config.properties"/> <!-- 組件掃描 --> <context:component-scan base-package="test.demos.mybatis"/> <!-- 數據源 --> <bean id="dataSource" class="com.mysql.cj.jdbc.MysqlDataSource"> <property name="url" value="${url}"/> <property name="user" value="${username}"/> <property name="password" value="${password}"/> <property name="databaseName" value="test"/> </bean> <!-- 配置sqlSessionFactory工廠bean --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="configLocation" value="classpath:config.xml"/> <property name="dataSource" ref="dataSource"/> </bean> <!-- 配置sqlSessionTemplate --> <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate"> <constructor-arg ref="sqlSessionFactory"/> </bean> <!-- 註冊掃描映射接口bean --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="test.demos.mybatis"/> </bean>
</beans>
從spring配置能夠知道mybatis-spring主要作了一下幾件事:
配置sqlSessionFactory工廠bean,該bean是一個工廠bean(能夠理解爲這個工廠bean就是SqlSessionFactory的bean,當注入的時候工廠bean會自動點用getObject方法獲取獲得SqlSessionFactory實例)
配置sqlSessionTemplate會話模版,它是SqlSession的子類,它至關於全局的會話代理類它內部也是經過代理的方式sql操做委託給別的SqlSession。由於它能夠做爲全局的SqlSession因此它是線程安全的,之因此線程安全的是由於全部經過SqlSessionTemplate調用的諸如selectList、update的方法都會委託給SqlSessionTemplate內部的sqlSessionProxy,而sqlSessionProxy是一個SqlSession的代理,其InvocationHandler是SqlSessionInterceptor,在SqlSessionInterceptor.invoke中每次都會從TransactionSynchronizationManager中獲取SqlSession,而在TransactionSynchronizationManager中使用ThreadLocal實現線程安全。(這裏大概描述詳情看源碼SqlSessionTemplate、SqlSessionUtils)
註冊掃描映射接口bean:MapperScannerConfigurer實現了BeanDefinitionRegistryPostProcessor接口,在bean初始化的時候會調用postProcessBeanDefinitionRegistry,MapperScannerConfigurer.postProcessBeanDefinitionRegistry方法內就是掃描註冊映射接口bean的過程。掃描註冊映射接口後,才能夠被注入到其餘的Component中。
mybatis集成springboot須要添加一個start
<dependency>
<groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.0</version>
</dependency>
其實mybatis-spring-boot-starter只是個空的依賴,mybatis-spring-boot-starter依賴了mybatis-spring-boot-autoconfigure,主要的代碼在這個自動化配置包裏。自動化配置依賴會讀取mybatis相關的配置屬性,而後自動配置咱們上面提到的mybatis相關的組件。配置例子:
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相關內容,只作配置樣例介紹。