小編給大家分享一下MyBatis中Mapper生效的示例分析,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
一切都從最簡單的開始,所以先來回顧一下其基本的使用(不會吧不會吧,最基本的hello world別忘了)。
步驟:
1、首先我們要創建一個maven工程
2、添加MyBatis的依賴及MySQL依賴,如下:
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.6</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.49</version> </dependency>
3、再添加一個測試單元依賴吧,等會要通過測試單元進行測試
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency>
OK,到這一步項目的基本環境就搭建完畢了,下面就是正式的使用 MyBatis 框架相關的內容了。
在資源目錄下面創建下面兩個配置文件:
這里我們先準備數據庫連接信息的配置類:jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://127.0.0.1:3306/mybatistest?useUnicode=true&characterEncoding=utf-8 jdbc.username=root jdbc.password=root
接著就是最重要的一個配置類了:MyBatisConfig.xml
<?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="jdbc.properties"></properties>
<!-- 配置setting屬性-->
<settings>
<!-- 開啟了一個駝峰命名規則-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- 日志-->
<setting name="logImpl" value="STDOUT_LOGGING"></setting>
</settings>
<!-- 配置數據庫-->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<!-- 配置連接池 -->
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<!-- mappers中注冊我們所有寫的dao接口的實現(映射)文件-->
<mappers>
<mapper resource="/.../IUserMapper.xml"/>
<!-- 如果映射文件有十幾百個的話,可以用下面的全局注冊
<package name="文件所在包路徑"></package>
<package name="cn.liuliang.Dao"></package>
-->
</mappers>
</configuration>Mapper接口類
public interface IUserMapper {
/**
* 查詢所有用戶
* @return
*/
List<User> findAll();
}開始測試
public class MyBatisTest {
@Test
public void test01() throws IOException {
// 讀取配置文件
InputStream in= Resources.getResourceAsStream("MyBatisConfig.xml");
// 創建sqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
// 獲得會話
SqlSession session=sqlSessionFactory.openSession();
// 得到代理
IUserMapper iUserMapper =session.getMapper(IUserMapper.class);
// 查詢數據庫
List<User> userList= iUserMapper.findAll();
for (User user : userList) {
System.out.println(user);
}
}
}
SQL和結果都打印出來了?。
以后,只要是對數據庫的操作,我們就只需要編寫 Mapper 接口和其對應的 xml 文件就可以非??焖俨僮鲾祿?,對比以前原生JDBC操作什么拼接SQL、結果集映射、資源關閉一大堆操作讓我們開發人員來處理,也太雞肋了吧!所以對于這個 MyBatis 持久層框架我只想說(牛逼)。
下面就要全程高能哦!但其實也很簡單了,它就只是把原生操作的 JDBC 進行了封裝,暴露出按照它所定義的簡單規則走而已,多的不說了,你們有資格一睹 MyBatis 源碼的芳容了。

既然要分析源碼了,那么從什么地方入手呢!— 測試方法
通過測試方法,我們可以知道 MyBatis 會先加載資源文件(MyBatisConfig.xml),因為這文件是一切的開始,通過這個文件可以知道數據源、特性(日志,駝峰命名…)、Mapper 文件等一系列信息。
第一個類名出現了:SqlSessionFactory ,它的類圖如下:

簡單熟悉一下圖中出現的名字吧:
SqlSessionFactory接口:SqlSessionFactory 負責創建 SqlSession 對象,其中只包含了多個 openSession() 方法的重載,可以通過其參數指定事務的隔離級別、底層使用 Executor 的類型以及是否自動提交事務等方面的配置。
DefaultSqlSessionFactory類:一個具體的工廠,實現了 SqlSessionFactory 接口。它主要提供了兩種創建 DefaultSqlSession 對象的方式:
通過數據源獲取數據庫連接,并創建 Executor 對象及 DefaultSqlSession 。
通過用戶提供的數據連接對象,DefaultSqlSessionFactory 會使用該數據庫連接對象創建 Executor 對象及 DefaultSqlSession。
SqlSessionManager類:同時實現了 SqlSession 接口和 SqlSessionFactory 接口 ,也就同時提供了SqlSessionFactory 創建 SqlSession 以及 SqlSession 操縱數據庫的功能。
SqlSession接口:是mybatis的核心操作類,其中對數據庫的crud都封裝在這個中,是一個頂級接口,其中默認實現類是DefaultSqlSession這個類。
DefaultSqlSession類:默認 SqlSession 接口的 CRUD 實現類,且 DefaultSqlsession 不是線程安全的(對于線程安全,關注session和connnect的關系就好了)
好了開始分析,從第一行代碼入手:
// 讀取配置文件
InputStream in= Resources.getResourceAsStream("MyBatisConfig.xml");
// 創建sqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);SqlSessionFactoryBuilder # build
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
// ...
// 根據文件流,創建 XMLConfigBuilder 對象
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// 先解析 配置文件,然后構建出 SqlSessionFactory對象
return build(parser.parse());
// ...
}最終會創建一個 DefaultSqlSessionFactory 對象返回出去
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}流程如下:

在獲取到會話工廠之后,就是根據工廠獲得具體的會話了。
代碼入口:
// 獲得會話 SqlSession session=sqlSessionFactory.openSession();
調用:DefaultSqlSessionFactory # openSession()
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}最終來到:DefaultSqlSessionFactory # openSessionFromDataSource()
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
// 根據配置文件 configuration 獲取對應的會話環境(包括事物,數據源)
final Environment environment = configuration.getEnvironment();
// 獲取事物工廠
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// 根據數據源,配置事物,autoCommit:是否自動提交事物
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 根據配置獲取執行器(最終都是它執行對應的數據庫操作)
final Executor executor = configuration.newExecutor(tx, execType);
// 準備好上面的信息之后,都封裝到默認會話對象中返回出去
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}在獲取 SqlSession 對象的過程中,都是根據默認的會話工廠,從工廠中獲取對應的會話。這樣在我看來非常的不錯,因為獲取一個數據庫的操作會話是需要配置非常多的屬性的,包括數據源配置、事物配置等。但是有了這個創建會話工廠類之后,那么一切就變得簡單起來了,工廠囊括了所有的細節,只需要我們調一個對外的 API 我們就可以獲得對應的 SqlSession 對象(工廠幫我們做了細節),進而操作數據庫,讀了上面的代碼就是一個很好的提現?。
提一點:
配置文件(MyBatisConfig.xml)構造出默認會話工廠(SqlSessionFactory),工廠再創建出具體的操作數據庫會話(SqlSession)
在上面,已經分析了如何獲取一個會話的源碼,那我們得到一個會話之后,就是要根據具體的 Mapper 接口獲得對應的操作數據庫代理對象了,就是下面這段代碼:
// 得到代理 IUserMapper iUserMapper =session.getMapper(IUserMapper.class);
點進去看看
因為 session 對象是由 DefaultSqlSessionFactory 創建出來的 DefaultSqlSession,所以該代碼位于此類中
public <T> T getMapper(Class<T> type) {
// 根據配置類,獲取 Mapper
return configuration.getMapper(type, this);
}點進去:Configuration # getMapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 從 mapperRegistry 中獲取具體 Mapper
return mapperRegistry.getMapper(type, sqlSession);
}MapperRegistry:可以理解為 Mapper 接口的注冊中心,里面存放了所有 Mapper 接口相關屬性。
MapperRegistry# getMapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// knownMappers,一個Map,存放 Mapper 代理工廠
// 在初始化的時候根據配置文件已經將所有配置的 Mapper 接口注冊到此了
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
// 具體代理生成
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}點進具體代理:MapperProxyFactory # newInstance
public T newInstance(SqlSession sqlSession) {
// 根據 SqlSession 和 Mapper 接口生成代理對象
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
// 真正代理,如下
return newInstance(mapperProxy);
}
// 下面就是根據 JDK 原生 API 進行代理了,由此返回代理對象給用戶使用
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}以上就是 Mapper 接口被代理的全部流程了,其中先是根據會話去獲得對應的 Mapper 但其內部調用的是 Mapper 注冊中心(MapperRegistry)獲取,這里面有所有配置的 Mapper 接口,在 MapperRegistry 中維護了一個 Map 鍵為 Class 值是 MapperProxyFactory ,這樣就可以獲得要代理 Mapper 接口的代理工廠,最后通過這個工廠生成我們想要的 Mapper 返回用戶。
流程不復雜,就是里面出現了很多 MapperXXX 相關的類,那么下面我梳理一下這些類關系圖如下:

對于具體的代理執行類這一步就要到執行這一塊了,當用戶通過我們返回的代理類(Mapper 接口)執行對應方法時,就會走到圖中涉及的類。
按照慣例,來個流程圖吧!

上面的所有分析,都是為了等到一個具體的操作數據庫的一個橋梁,那就是 Mapper 代理了(iUserMapper)。
接下來就是分析最后一步了,真正操作數據庫,代碼如下:
// 查詢數據庫
List<User> userList= iUserMapper.findAll();
for (User user : userList) {
System.out.println(user);
}對于 iUserMapper 對象,我們知道他是代理去執行的,所以直接點進去的話根本行不通,那么我們可以通過 Debug 進去看看。

MapperProxy # invoke
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 方法的類為 object 直接通過原始 JDK 去執行
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
// 根據方法,獲得方法的執行器后再執行代理方法
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}我們先進入 MapperProxy # cachedInvoker 這個方法看看
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
try {
// 先查緩存,有就返回,沒有就創建
MapperMethodInvoker invoker = methodCache.get(method);
if (invoker != null) {
return invoker;
}
return methodCache.computeIfAbsent(method, m -> {
// ...
// 返回 PlainMethodInvoker 類型的 Mapper 方法執行器
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
// ...
});
} catch (RuntimeException re) {
Throwable cause = re.getCause();
throw cause == null ? re : cause;
}
}接著進入 PlainMethodInvoker# invoke 這個方法
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
// 調用 mapperMethod 對象的 execute 方法去真正執行了
return mapperMethod.execute(sqlSession, args);
}真正執行的開始 execute
MapperMethod # execute
public Object execute(SqlSession sqlSession, Object[] args) {
// 這里面內容比較多,我簡單分析一下
// 1)封裝參數
// 2)根據對應的執行類型(INSERT,UPDATE,DELETE,SELECT),執行對應的方法
// 3)根據參數,執行類型封裝對應的 sql
// 4)操作原生 JDBC API 執行數據庫操作
// 5)封裝結果集,返回出去
}我們 Debug 這個方法最后一步,看看結果:

到此,我們的 Mapper 接口及文件生效的原理,就全部過了一邊,是不是覺得不是很難呢!
在分析這一塊源碼時,本人理解的步驟就是:
一步步點進源碼看。
畫出流程圖,不清楚的就 Debug。
很重要一點,對很多出現類似名字的類,一定要畫出類圖,搞清楚關系在往下走(助于理解每個類的職責)。
最后,那就是寫點筆記了,畢竟好記性不如爛筆頭。

以上是“MyBatis中Mapper生效的示例分析”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。