一 、 Mybatis概覽
說到mybatis就不得不說ORM框架,即對象-關(guān)系映射(Object-Relational Mapping),面向?qū)ο蟮拈_發(fā)方法是當今企業(yè)級應用開發(fā)環(huán)境中的主流開發(fā)方法,關(guān)系數(shù)據(jù)庫是企業(yè)級應用環(huán)境中永久存放數(shù)據(jù)的主流數(shù)據(jù)存儲系統(tǒng)。對象和關(guān)系數(shù)據(jù)是業(yè)務實體的兩種表現(xiàn)形式,業(yè)務實體在內(nèi)存中表現(xiàn)為對象,在數(shù)據(jù)庫中表現(xiàn)為關(guān)系數(shù)據(jù)。內(nèi)存中的對象之間存在關(guān)聯(lián)和繼承關(guān)系,而在數(shù)據(jù)庫中,關(guān)系數(shù)據(jù)無法直接表達多對多關(guān)聯(lián)和繼承關(guān)系。因此,對象-關(guān)系映射(ORM)系統(tǒng)一般以中間件的形式存在,主要實現(xiàn)程序?qū)ο蟮疥P(guān)系數(shù)據(jù)庫數(shù)據(jù)的映射。
1. Mybatis的框架設計
MyBatis本是apache的一個開源項目iBatis, 2010年這個項目由apache software foundation遷移到了google code,并且改名為MyBatis 。iBATIS一詞來源于“internet”和“abatis”的組合,是一個基于Java的持久層框架

接口層
Mybatis和數(shù)據(jù)庫交互的兩種方式:
-
使用傳統(tǒng)的Mybatis提供的api
傳遞查詢語句和查詢參數(shù)給SqlSession對象,使用SqlSession對象完成和數(shù)據(jù)庫的交互;Mybatis提供了非常方便的api,供用戶實現(xiàn)對數(shù)據(jù)庫的增刪改查。
mybatis api具體的SqlSession提供的接口如下:
public interface SqlSession extends Closeable { <T> T selectOne(String var1); <T> T selectOne(String var1, Object var2); <E> List<E> selectList(String var1); <E> List<E> selectList(String var1, Object var2); <E> List<E> selectList(String var1, Object var2, RowBounds var3); <K, V> Map<K, V> selectMap(String var1, String var2); <K, V> Map<K, V> selectMap(String var1, Object var2, String var3); <K, V> Map<K, V> selectMap(String var1, Object var2, String var3, RowBounds var4); <T> Cursor<T> selectCursor(String var1); <T> Cursor<T> selectCursor(String var1, Object var2); <T> Cursor<T> selectCursor(String var1, Object var2, RowBounds var3); void select(String var1, Object var2, ResultHandler var3); void select(String var1, ResultHandler var2); void select(String var1, Object var2, RowBounds var3, ResultHandler var4); int insert(String var1); int insert(String var1, Object var2); int update(String var1); int update(String var1, Object var2); int delete(String var1); int delete(String var1, Object var2); //事務提交 void commit(); void commit(boolean var1); //事務回滾 void rollback(); void rollback(boolean var1); List<BatchResult> flushStatements(); //關(guān)閉連接 void close(); void clearCache(); Configuration getConfiguration(); <T> T getMapper(Class<T> var1); //連接數(shù)據(jù)庫 Connection getConnection(); }
上述使用MyBatis 的方法,是創(chuàng)建一個和數(shù)據(jù)庫打交道的SqlSession對象,然后根據(jù)Statement Id 和參數(shù)來操作數(shù)據(jù)庫,這種方式固然很簡單和實用,但是它不符合面向?qū)ο笳Z言的概念和面向接口編程的編程習慣。由于面向接口的編程是面向?qū)ο蟮拇筅厔?,MyBatis 為了適應這一趨勢,增加了第二種使用MyBatis 支持接口(Interface)調(diào)用方式。
-
使用Mapper接口
MyBatis 將配置文件中的每一個<mapper> 節(jié)點抽象為一個 Mapper 接口,而這個接口中聲明的方法和跟<mapper> 節(jié)點中的<select|update|delete|insert> 節(jié)點項對應,即<select|update|delete|insert> 節(jié)點的id值為Mapper 接口中的方法名稱,parameterType 值表示Mapper 對應方法的入?yún)㈩愋?,而resultMap 值則對應了Mapper 接口表示的返回值類型或者返回結(jié)果集的元素類型。<?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="org.cloud.dao.RandomGener_Dao"> <resultMap type="org.cloud.model.RandomId" id="getId_List"> <id column="id" property="id"/> </resultMap> <select id="selectUnusingId" resultType="Integer"> SELECT id FROM randomID where tag = 0 limit 1 for UPDATE </select> <update id="updateId" parameterType="Integer"> UPDATE randomID set tag=1 where id = #{id} </update> </mapper>
然后編寫對應接口文件
public interface RandomGener_Dao {
//搜索tag不為1的id
Integer selectUnusingId();
//更新tag 為1
void updateId(@Param("id") Integer id);
}
根據(jù)MyBatis 的配置規(guī)范配置好后,通過SqlSession.getMapper(XXXMapper.class) 方法,MyBatis 會根據(jù)相應的接口聲明的方法信息,通過動態(tài)代理機制生成一個Mapper 實例,我們使用Mapper 接口的某一個方法時,MyBatis 會根據(jù)這個方法的方法名和參數(shù)類型,確定Statement Id,底層還是通過SqlSession.select("statementId",parameterObject);或者SqlSession.update("statementId",parameterObject); 等等來實現(xiàn)對數(shù)據(jù)庫的操作。
數(shù)據(jù)處理層
數(shù)據(jù)處理層是Mybatis 的核心,它主要完成三個功能:
- 通過傳入?yún)?shù)構(gòu)建動態(tài)sql語句
動態(tài)語句生成可以說是MyBatis框架非常優(yōu)雅的一個設計,MyBatis 通過傳入的參數(shù)值,使用 Ognl 來動態(tài)地構(gòu)造SQL語句,使得MyBatis 有很強的靈活性和擴展性。參數(shù)映射指的是對于java 數(shù)據(jù)類型和jdbc數(shù)據(jù)類型之間的轉(zhuǎn)換:這里有包括兩個過程:查詢階段,我們要將java類型的數(shù)據(jù),轉(zhuǎn)換成jdbc類型的數(shù)據(jù),通過preparedStatement.setXXX()來設值;另一個就是對resultset查詢結(jié)果集的jdbcType 數(shù)據(jù)轉(zhuǎn)換成java 數(shù)據(jù)類型。 - sql語句的執(zhí)行
- 封裝查詢結(jié)果轉(zhuǎn)化為List<E>
框架支撐層
- 事務管理機制
- 連接池管理機制
- 緩存機制
為了提高數(shù)據(jù)利用率和減小服務器和數(shù)據(jù)庫的壓力,MyBatis 會對于一些查詢提供會話級別的數(shù)據(jù)緩存,會將對某一次查詢,放置到SqlSession 中,在允許的時間間隔內(nèi),對于完全相同的查詢,MyBatis 會直接將緩存結(jié)果返回給用戶,而不用再到數(shù)據(jù)庫中查找。
引導層
引導層是配置和啟動MyBatis 配置信息的方式。MyBatis 提供兩種方式來引導MyBatis :基于XML配置文件的方式和基于Java API 的方式,我們將在下面介紹spring整合mybatis的配置信息。
2. Mybatis的主要構(gòu)件及相互關(guān)系
mybatis的主要核心部件:
| 組件 | 說明 |
|---|---|
| SqlSession | 每一個SqlSession實例代表應用程序和數(shù)據(jù)庫的一次連接 ,每次打開一個SqlSession都會綁定一個事務管理實例,執(zhí)行器實例。 |
| Executor | MyBatis執(zhí)行器,是MyBatis 調(diào)度的核心,負責SQL語句的生成和查詢緩存的維護 |
| MappedStatement | MappedStatement維護了一條<select update delete insert>節(jié)點的封裝 |
| Configuration | MyBatis所有的配置信息都維持在Configuration對象之中。 |
二、Spring整合mybatis
1. 配置Mybatis
-
在pom中添加mybatis包
<!--mybatis 包--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.2</version> </dependency> <!--mybatis 整合spring的適配包--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.1</version> </dependency> <!--數(shù)據(jù)庫連接池--> <dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.1.2</version> </dependency> <!--java mysql驅(qū)動版本要與mysql數(shù)據(jù)庫的版本對應--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.11</version> </dependency> -
在spring容器的配置文件中整合mybatis
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="jdbcUrl" value="${db.url}"></property> <property name="driverClass" value="${db.driver}"></property> <property name="user" value="${db.user}"></property> <property name="password" value="${db.pw}"></property> </bean> <!--整合 mybatis--> <bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!--指定mybatis的全局配置文件--> <property name="configLocation" value="classpath:mybatis-config.xml"></property> <property name="mapperLocations" value="classpath:mapper/*.xml"></property> <property name="dataSource" ref="dataSource"></property> <!--<property name="mapperLocations" value="classpath*:mapper/*.xml"></property>--> </bean> <!--配置掃描器 將mybatis接口實現(xiàn)加入到ioc容器中--> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.hust.dao" ></property> </bean> <!--事務控制 為了便于管理數(shù)據(jù)庫--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!--配置數(shù)據(jù)源--> <property name="dataSource" ref="dataSource"/> <!--開啟基于注解的事務 或者是xml配置事務--> </bean> <!--配置一個可以批量執(zhí)行的sqlSession--> <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <constructor-arg name="sqlSessionFactory" ref="sessionFactory"/> <constructor-arg name="executorType" value="BATCH"/> </bean>Spring 中的 DataSourceTransactionManager用來管理事務,它允許mybatis參與到Spring的事務管理中,而不是為mybatis創(chuàng)建一個新的特定的事務管理器。要注意, 為事務管理器指定的 DataSource 必須和用來創(chuàng)建 SqlSessionFactoryBean 的 是同一個數(shù)據(jù)源,否則事務管理器就無法工作了。
-
mybatis逆向生成工具
mybatsi逆向生成工具可以根據(jù)數(shù)據(jù)庫中的的字段,創(chuàng)建相應的pojo類以及其對應的mapper.xml文件,mapper中包含一些常用的CRUD方法。
首先在pom中加入依賴<!--mybatis 逆向生成工具--> <dependency> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-core</artifactId> <version>1.3.5</version> </dependency>然后添加mbg.xml配置文件
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"> <generatorConfiguration> <properties resource="db.properties"></properties> <!--配置數(shù)據(jù)庫連接信息--> <context id="DB2Tables" targetRuntime="MyBatis3"> <!--不讓生成注釋--> <commentGenerator> <property name="suppressAllComments" value="true"></property> </commentGenerator> <!--idea 將&當成了特殊符號 這里需要轉(zhuǎn)義 & = &--> <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/gm?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT&useSSL=false" userId="root" password="root"> </jdbcConnection> <javaTypeResolver > <property name="forceBigDecimals" value="false" /> </javaTypeResolver> <!--指定javabean的生成位置--> <javaModelGenerator targetPackage="com.hust.bean" targetProject=".\src\main\java"> <property name="enableSubPackages" value="true" /> <property name="trimStrings" value="true" /> </javaModelGenerator> <!--指定sql映射文件的位置--> <sqlMapGenerator targetPackage="mapper" targetProject=".\src\main\resources"> <property name="enableSubPackages" value="true" /> </sqlMapGenerator> <!--指定dao接口的生成位置--> <javaClientGenerator type="XMLMAPPER" targetPackage="com.hust.dao" targetProject=".\src\main\java"> <property name="enableSubPackages" value="true" /> </javaClientGenerator> <!--指定每個表的生成策略--> <table tableName="mg_employee" domainObjectName="Employee" ></table> <table tableName="mg_inventory" domainObjectName="Inventory"></table> <table tableName="mg_sale" domainObjectName="Sale"></table> <table tableName="mg_user" domainObjectName="User"></table> </context> </generatorConfiguration>
配置文件中的注釋對各個節(jié)點的作用進行了詳細的說明
編寫一個測試類生成對應的pojo類和mapper文件
public class Mbg {
public static void main(String[] args) throws Exception {
List<String> warnings = new ArrayList<>();
boolean overwrite = true;
File configFile = new File("E:mbg.xml");
ConfigurationParser cp = new ConfigurationParser(warnings);
Configuration configuration = cp.parseConfiguration(configFile);
//是否覆蓋已有的文件
DefaultShellCallback callback = new DefaultShellCallback(overwrite);
MyBatisGenerator generator = new MyBatisGenerator(configuration,callback,warnings);
generator.generate(null);
Logger logger = LogManager.getLogger("info");
logger.info("mybatis逆向生成工具完成任務");
}
}
三、pageHelper分頁工具
pagehelper是一款由國人編寫的開源免費的mybatis第三方物理插件,我們可以在github上看到它的源碼,下面我們介紹一下mybatis的基本使用以及它的實現(xiàn)原理。
1. PageHelper的配置
引入maven依賴
<!--分頁工具-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.0.0</version>
</dependency>
將pagehelper整合到mybatis中
<!--配置分頁插件-->
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!--指定數(shù)據(jù)庫-->
<property name="dialect" value="mysql" />
<!-- 該參數(shù)默認為false -->
<!-- 設置為true時,會將RowBounds第一個參數(shù)offset當成pageNum頁碼使用 -->
<!-- 和startPage中的pageNum效果一樣 -->
<property name="offsetAsPageNum" value="false" />
<!-- 該參數(shù)默認為false -->
<!-- 設置為true時,使用RowBounds分頁會進行count查詢 -->
<property name="rowBoundsWithCount" value="true" />
<!-- 設置為true時,如果pageSize=0或者RowBounds.limit = 0就會查詢出全部的結(jié)果 -->
<!-- (相當于沒有執(zhí)行分頁查詢,但是返回結(jié)果仍然是Page類型) <property name="pageSizeZero" value="true"/> -->
<!-- 3.3.0版本可用 - 分頁參數(shù)合理化,默認false禁用 -->
<!-- 啟用合理化時,如果pageNum<1會查詢第一頁,如果pageNum>pages會查詢最后一頁 -->
<!-- 禁用合理化時,如果pageNum<1或pageNum>pages會返回空數(shù)據(jù) -->
<property name="reasonable" value="true" />
<!-- 3.5.0版本可用 - 為了支持startPage(Object params)方法 -->
<!-- 增加了一個`params`參數(shù)來配置參數(shù)映射,用于從Map或ServletRequest中取值 -->
<!-- 可以配置pageNum,pageSize,count,pageSizeZero,reasonable,不配置映射的用默認值 -->
<!-- 不理解該含義的前提下,不要隨便復制該配置 <property name="params" value="pageNum=start;pageSize=limit;"/> -->
</plugin>
</plugins>
2. PageHelper原理分析
pagehelper通過mybatis的plugin實現(xiàn)了interceptor接口

實現(xiàn)原理:
pageHelper會使用ThreadLocal獲取到同一線程中的變量信息,各個線程之間的Threadlocal不會相互干擾,也就是Thread1中的ThreadLocal1之后獲取到Tread1中的變量的信息,不會獲取到Thread2中的信息
所以在多線程環(huán)境下,各個Threadlocal之間相互隔離,可以實現(xiàn),不同thread使用不同的數(shù)據(jù)源或不同的Thread中執(zhí)行不同的SQL語句
所以,PageHelper利用這一點通過攔截器獲取到同一線程中的預編譯好的SQL語句之后將SQL語句包裝成具有分頁功能的SQL語句,并將其再次賦值給下一步操作,所以實際執(zhí)行的SQL語句就是有了分頁功能的SQL語句
關(guān)于pagehelper的更詳細的實現(xiàn),可以查看這篇博文
