MyBatis的通俗理解:SqlSession.getMapper()源碼解讀
什么是 MyBatis?
直接看官方文檔:https://mybatis.org/mybatis-3/zh/index.html
從上面我們了解到:
1、MyBatis 是一款優(yōu)秀的持久層框架
2、MyBatis 避免了幾乎所有的 JDBC 代碼和手動(dòng)設(shè)置參數(shù)以及獲取結(jié)果集。
原理解析
1、程序員和Mybatis 和數(shù)據(jù)的關(guān)系:人通過mybatis框架來(lái)操作數(shù)據(jù)庫(kù)。
2、思考問題并解決
問題1:首先我們必須告訴MyBatis要怎么操作數(shù)據(jù)庫(kù)?
我們把可以通過XML配置文件或者注解的方式,MyBatis提供了一個(gè)類Configuration, Mybatis 讀取XML配置文件后會(huì)將內(nèi)容放在一個(gè)Configuration類中,Configuration類會(huì)存在整個(gè)Mybatis生命周期,以便重復(fù)讀取。
問題2:想要Mybatis與數(shù)據(jù)庫(kù)打交道,就要有一個(gè)類似于JDBC的Connection對(duì)象,在MyBatis中叫SqlSesion,所以我們要有一個(gè)SqlSession。
Mybatis 讀取XML配置文件后會(huì)將內(nèi)容放在一個(gè)Configuration類中,SqlSessionFactoryBuilder會(huì)讀取Configuration類中信息創(chuàng)建SqlSessionFactory。SqlSessionFactory創(chuàng)建SqlSession。
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession=null;
try{
sqlSession=sqlSessionFactory.openSession();
//some code
sqlSession.commit();
} catch(Exception ex){
sqlSession.roolback();
} finally{
if(sqlSession!=null){
sqlSession.close();
}
}關(guān)于SqlSessionFactory的創(chuàng)建,Mybatis采用構(gòu)造模式來(lái)完成創(chuàng)建。
- 第一步:XMLConfigBuilder解析XML配置,讀出配置參數(shù),存入Configuration類中。
- 第二步:Configuration類創(chuàng)建SqlSessionFactory。(DefaultSqlSessionFactory的構(gòu)造函數(shù)傳入Configuration類)
深入了解:SqlSessionFactoryBuilder.builder(inputStream)
//該方法.builder中的主要內(nèi)容:
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
SqlSessionFactory localSqlSessionFactory = build(parser.parse());
//build(parser.parse())方法實(shí)則為:
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}問題3:SqlSession能干什么?
SqlSession用途主要有兩種
①. 獲取對(duì)應(yīng)的Mapper,讓映射器通過命名空間和方法名稱找到對(duì)應(yīng)的SQL,發(fā)送給數(shù)據(jù)庫(kù)執(zhí)行后返回結(jié)果。
RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class); Role role = roleMapper.getRole(1L);
②. 直接使用SqlSession,通過命名信息去執(zhí)行SQL返回結(jié)果,該方式是IBatis版本留下的,SqlSession通過Update、Select、Insert、Delete等方法操作。
Role role = (Role)sqlSession.select("com.mybatis.mapper.RoleMapper.getRole",1L);Mybatis底層利用JDK動(dòng)態(tài)代理技術(shù)實(shí)現(xiàn)該接口,底層最后還是使用的IBatis中SqlSession通過Update、Select、Insert、Delete等方法操作。
問題4:上面說(shuō)到Mybatis底層利用JDK動(dòng)態(tài)代理技術(shù)實(shí)現(xiàn)該接口,但是我們?cè)谑褂肕yBatis的時(shí)候,都是只寫接口不用寫實(shí)現(xiàn)類,為什么呢?
為什么要使用動(dòng)態(tài)代理?可以在不修改別代理對(duì)象代碼的基礎(chǔ)上,通過擴(kuò)展代理類,進(jìn)行一些功能的附加與增強(qiáng)。
我們先看看傳統(tǒng)的JDK動(dòng)態(tài)代理:
1、首先有一個(gè)接口
public interface Calculate {
void add(int i, int j);
}2、然后是接口的實(shí)現(xiàn)類
public class CalculateImp implements Calculate {
@Override
public void add(int i, int j) {
System.out.println("result = " + (i + j));
}
}3、代理類實(shí)現(xiàn)InvocationHandler
public class CalculateProxy implements InvocationHandler {
private Object target;
//總要讓我知道要代理誰(shuí)吧:構(gòu)造方法中把傳入一個(gè)代理類的實(shí)例
public CalculateProxy(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("====== Before() ======");
method.invoke(target, args);
System.out.println("====== After () ======");
return null;
}
}4、拿到代理對(duì)象,操作接口方法
public class test {
public static void main(String[] args) {
InvocationHandler handler = new CalculateProxy(new CalculateImp());
Calculate calculateProxy =
(Calculate) Proxy.newProxyInstance(Calculate.class.getClassLoader(),
new Class[]{Calculate.class},
handler);
calculateProxy.add(10,20);
}
}Proxy.newProxyInstance()方法有三個(gè)參數(shù):
1. 類加載器(Class Loader)
2. 需要實(shí)現(xiàn)的接口數(shù)組
3. InvocationHandler接口。所有動(dòng)態(tài)代理類的方法調(diào)用,都會(huì)交由InvocationHandler接口實(shí)現(xiàn)類里的invoke()方法去處理。這是動(dòng)態(tài)代理的關(guān)鍵所在。
回到我們之前的問題,我們并沒有接口實(shí)現(xiàn)類,那沒有實(shí)現(xiàn)類還為什么還能調(diào)用方法操作。其實(shí)是這樣的:
操作數(shù)據(jù)庫(kù)主要是通過SQL語(yǔ)句,那么只要找到SQL語(yǔ)句然后執(zhí)行不就可以!
通過例子分析:
BlogMapper mapper = session.getMapper(BlogMapper.class); Blog blog = mapper.selectBlog(1);
這里 mapper 可以調(diào)用selectBlog(1) 這個(gè)方法,說(shuō)明 mapper 是個(gè)對(duì)象,因?yàn)閷?duì)象才具有方法行為實(shí)現(xiàn)啊。BlogMapper接口是不能實(shí)例化的,更沒有具體方法實(shí)現(xiàn)。
我們并沒有定義一個(gè)類,讓它實(shí)現(xiàn)BlogMapper接口,而在這里它只是通過調(diào)用session.getMapper() 所得到的。
由此,我們可以推斷:肯定是session.getMapper() 方法內(nèi)部產(chǎn)生了BlogMapper的實(shí)現(xiàn)類。有什么技術(shù)可以根據(jù)BlogMapper 接口生成了一個(gè)實(shí)現(xiàn)類呢?想到這里,對(duì)于有動(dòng)態(tài)代理 。
Mapper 接口的注冊(cè)
我們既然能夠從SqlSession中得到BlogMapper接口的,那么我們肯定需要先在哪里把它放進(jìn)去了,然后 SqlSession 才能生成我們想要的代理類啊。
我們可以從getMapper()聯(lián)系,可能會(huì)有一個(gè)setMapper()或者addMapper()方法。確實(shí)是有!
configuration.addMapper(BlogMapper.class);
跟著這個(gè) addMapper 方法的代碼實(shí)現(xiàn)是這樣的:
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}我們看到這里 mapper 實(shí)際上被添加到 mapperRegistry (mapper注冊(cè)器)中。繼續(xù)跟進(jìn)代碼:
public class MapperRegistry {
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers
= new HashMap<Class<?>, MapperProxyFactory<?>>();
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) { // 只添加接口
if (hasMapper(type)) { // 不允許重復(fù)添加
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory<T>(type)); // 注意這里
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
}我們首先看到MapperRegistry類,有一個(gè)私有屬性knowMappers,它是一個(gè)HashMap 。
其Key 為當(dāng)前Class對(duì)象,value 為一個(gè)MapperProxyFactory實(shí)例
在MapperRegistry類的addMapper()方法中,knownMappers.put(type, new MapperProxyFactory<T>(type));相當(dāng)于把:諸如BlogMapper之類的Mapper接口被添加到了MapperRegistry 中的一個(gè)HashMap中。
并以 Mapper 接口的 Class 對(duì)象作為 Key , 以一個(gè)攜帶Mapper接口作為屬性的MapperProxyFactory實(shí)例作為value 。
MapperProxyFactory從名字來(lái)看,好像是一個(gè)工廠,用來(lái)創(chuàng)建Mapper Proxy的工廠。
上面我們已經(jīng)知道,Mapper 接口被到注冊(cè)到了MapperRegistry中——放在其名為knowMappers 的HashMap屬性中,我們?cè)谡{(diào)用Mapper接口的方法的時(shí)候,是這樣的:
BlogMapper mapper = session.getMapper(BlogMapper.class);
這里,我們跟蹤一下session.getMapper() 方法的代碼實(shí)現(xiàn),這里 SqlSession 是一個(gè)接口,他有兩個(gè)實(shí)現(xiàn)類,
一個(gè)是DefaultSqlSession,另外一個(gè)是SqlSessionManager,
這里我們用的是DefaultSqlSession. 為什么是DefaultSqlSession呢?因?yàn)槲覀冊(cè)诔跏蓟疭qlSessionFactory的時(shí)候所調(diào)用的SqlSessionFactoryBuilder的build()方法里邊配置的就是DefaultSqlSession, 所以,我們進(jìn)入到DefaultSession類中,看看它對(duì)session.getMapper(BlogMapper.class)是怎么實(shí)現(xiàn)的:
public class DefaultSqlSession implements SqlSession {
private Configuration configuration;
@Override
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this); //最后會(huì)去調(diào)用MapperRegistry.getMapper
}
}如代碼所示,這里的 getMapper 調(diào)用了 configuration.getMapper , 這一步操作其實(shí)最終是調(diào)用了MapperRegistry,而此前我們已經(jīng)知道,MapperRegistry是存放了一個(gè)HashMap的,我們繼續(xù)跟蹤進(jìn)去看看,那么這里的get,肯定是從這個(gè)hashMap中取數(shù)據(jù)。
我們來(lái)看看代碼:
public class MapperRegistry {
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();// Mapper 映射
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory =
(MapperProxyFactory<T>) knownMappers.get(type);
try {
return mapperProxyFactory.newInstance(sqlSession); // 重點(diǎn)看這里
} catch (Exception e) {
}
}
}我們調(diào)用的session.getMapper(BlogMapper.class);最終會(huì)到達(dá)上面這個(gè)方法,這個(gè)方法,根據(jù)BlogMapper的class對(duì)象,以它為key在knowMappers 中找到了對(duì)應(yīng)的value —— MapperProxyFactory(BlogMapper) 對(duì)象,然后調(diào)用這個(gè)對(duì)象的newInstance()方法。
根據(jù)這個(gè)名字,我們就能猜到這個(gè)方法是創(chuàng)建了一個(gè)對(duì)象,代碼是這樣的:
public class MapperProxyFactory<T> { //映射器代理工廠
private final Class<T> mapperInterface;
private Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
// 刪除部分代碼,便于閱讀
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
//使用了JDK自帶的動(dòng)態(tài)代理生成映射器代理類的對(duì)象
return (T) Proxy.newProxyInstance(
mapperInterface.getClassLoader(),
new Class[] { mapperInterface },
mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}看到這里,就清楚了,最終是通過Proxy.newProxyInstance產(chǎn)生了一個(gè)BlogMapper的代理對(duì)象。
Mybatis 為了完成 Mapper 接口的實(shí)現(xiàn),運(yùn)用了代理模式。
具體是使用了JDK動(dòng)態(tài)代理,這個(gè)Proxy.newProxyInstance方法生成代理類的三個(gè)要素是:
- ClassLoader —— 指定當(dāng)前接口的加載器即可
- 當(dāng)前被代理的接口是什么 —— 這里就是 BlogMapper
- 代理類是什么 —— 這里就是 MapperProxy
代理模式中,代理類(MapperProxy)中才真正的完成了方法調(diào)用的邏輯。
我們貼出MapperProxy的代碼,如下:
public class MapperProxy<T> implements InvocationHandler, Serializable {// 實(shí)現(xiàn)了InvocationHandler
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//代理以后,所有Mapper的方法調(diào)用時(shí),都會(huì)調(diào)用這個(gè)invoke方法
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args); // 注意1
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
final MapperMethod mapperMethod = cachedMapperMethod(method); // 使用了緩存
//執(zhí)行CURD
return mapperMethod.execute(sqlSession, args); // 注意2
}
}我們調(diào)用的 Blog blog = mapper.selectBlog(1); 實(shí)際上最后是會(huì)調(diào)用這個(gè)MapperProxy的invoke方法。
這段代碼中,if 語(yǔ)句先判斷,我們想要調(diào)用的方法是否來(lái)自O(shè)bject類,這里的意思就是,如果我們調(diào)用toString()方法,那么是不需要做代理增強(qiáng)的,直接還調(diào)用原來(lái)的method.invoke()就行了。
只有調(diào)用selectBlog()之類的方法的時(shí)候,才執(zhí)行增強(qiáng)的調(diào)用——即mapperMethod.execute(sqlSession, args);這一句代碼邏輯。
而mapperMethod.execute(sqlSession, args);這句最終就會(huì)執(zhí)行增刪改查了,代碼如下:
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
if (SqlCommandType.INSERT == command.getType()) { //insert 處理,調(diào)用SqlSession的insert
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
} else if (SqlCommandType.UPDATE == command.getType()) { // update
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
} else if (SqlCommandType.DELETE == command.getType()) { // delete
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
} else if (SqlCommandType.SELECT == command.getType()) {
// 刪除部分代碼
} else {
throw new BindingException("Unknown execution method for: " + command.getName());
}
// 刪除部分代碼
return result;
}再往下一層,就是執(zhí)行JDBC那一套了,獲取鏈接,執(zhí)行,得到ResultSet,解析ResultSet映射成JavaBean。
總結(jié)一下各個(gè)過程
1、Mybatis 讀取XML配置文件后會(huì)將內(nèi)容放在一個(gè)Configuration類中,SqlSessionFactoryBuilder會(huì)讀取Configuration類中信息創(chuàng)建SqlSessionFactory。
2、在初始化SqlSessionFactory時(shí),Mapper 接口進(jìn)行注冊(cè),注冊(cè)在了名為 MapperRegistry 類的 HashMap中,key = Mapper class, value = 創(chuàng)建當(dāng)前Mapper的工廠。
3、SqlSessionFactory創(chuàng)建SqlSession。
4、SqlSession中可以通過getMapper()拿到代理對(duì)象,SqlSession.getMapper 運(yùn)用了 JDK動(dòng)態(tài)代理,產(chǎn)生了目標(biāo)Mapper接口的代理對(duì)象。
5. 動(dòng)態(tài)代理的 代理類是 MapperProxy ,這里邊mapperMethod.execute(sqlSession, args)最終完成了增刪改查方法的調(diào)用。
最后
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java 讀取網(wǎng)絡(luò)圖片存儲(chǔ)到本地并生成縮略圖
用Java做開發(fā)經(jīng)常需要處理圖片。本文就來(lái)看一下如何保存圖片到本地并生成縮略圖2021-05-05
巧用FutureTask 線程池輕松解決接口超時(shí)問題
這篇文章主要為大家介紹了使用FutureTask結(jié)合線程池輕松解決接口超時(shí)問題的巧妙用法,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11
布隆過濾器(Bloom Filter)的Java實(shí)現(xiàn)方法
下面小編就為大家?guī)?lái)一篇布隆過濾器(Bloom Filter)的Java實(shí)現(xiàn)方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來(lái)看看吧2016-12-12
Springboot2.x 使用 Log4j2 異步打印日志的實(shí)現(xiàn)
這篇文章主要介紹了Springboot2.x 使用 Log4j2 異步打印日志的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12
Java SpringMVC框架開發(fā)之?dāng)?shù)據(jù)導(dǎo)出Excel文件格式實(shí)例詳解
這篇文章主要介紹了Java基礎(chǔ)開發(fā)之?dāng)?shù)據(jù)導(dǎo)出Excel文件格式實(shí)例詳解,需要的朋友可以參考下2020-02-02
基于Java實(shí)現(xiàn)經(jīng)典蜘蛛紙牌游戲
《蜘蛛紙牌》(Ancient?Spider)?是由Oberon?Games開發(fā)的一款休閑益智類游戲。本文將利用Java語(yǔ)言實(shí)現(xiàn)這一經(jīng)典游戲,需要的可以參考一下2022-05-05
SpringBoot整合Hbase的實(shí)現(xiàn)示例
這篇文章主要介紹了SpringBoot整合Hbase的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12

