您好,登錄后才能下訂單哦!
今天小編給大家分享一下MyBatis的SqlSession.getMapper()源碼分析的相關知識點,內容詳細,邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。
1、MyBatis 是一款優秀的持久層框架
2、MyBatis 避免了幾乎所有的 JDBC 代碼和手動設置參數以及獲取結果集。
3、MyBatis 避免了幾乎所有的 JDBC 代碼和手動設置參數以及獲取結果集。
1、程序員和Mybatis 和數據的關系:人通過mybatis框架來操作數據庫。
2、思考問題并解決
問題1:首先我們必須告訴MyBatis要怎么操作數據庫?
我們把可以通過XML配置文件或者注解的方式,MyBatis提供了一個類Configuration, Mybatis 讀取XML配置文件后會將內容放在一個Configuration類中,Configuration類會存在整個Mybatis生命周期,以便重復讀取。
問題2:想要Mybatis與數據庫打交道,就要有一個類似于JDBC的Connection對象,在MyBatis中叫SqlSesion,所以我們要有一個SqlSession。
Mybatis 讀取XML配置文件后會將內容放在一個Configuration類中,SqlSessionFactoryBuilder會讀取Configuration類中信息創建SqlSessionFactory。SqlSessionFactory創建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(); } }
關于SqlSessionFactory的創建,Mybatis采用構造模式來完成創建。
第一步:XMLConfigBuilder解析XML配置,讀出配置參數,存入Configuration類中。
第二步:Configuration類創建SqlSessionFactory。(DefaultSqlSessionFactory的構造函數傳入Configuration類)
深入了解:SqlSessionFactoryBuilder.builder(inputStream)
//該方法.builder中的主要內容: XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties); SqlSessionFactory localSqlSessionFactory = build(parser.parse()); //build(parser.parse())方法實則為: public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); }
問題3:SqlSession能干什么?
SqlSession用途主要有兩種
①. 獲取對應的Mapper,讓映射器通過命名空間和方法名稱找到對應的SQL,發送給數據庫執行后返回結果。
RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class); Role role = roleMapper.getRole(1L);
②. 直接使用SqlSession,通過命名信息去執行SQL返回結果,該方式是IBatis版本留下的,SqlSession通過Update、Select、Insert、Delete等方法操作。
Role role = (Role)sqlSession.select("com.mybatis.mapper.RoleMapper.getRole",1L);
Mybatis底層利用JDK動態代理技術實現該接口,底層最后還是使用的IBatis中SqlSession通過Update、Select、Insert、Delete等方法操作。
問題4:上面說到Mybatis底層利用JDK動態代理技術實現該接口,但是我們在使用MyBatis的時候,都是只寫接口不用寫實現類,為什么呢?
為什么要使用動態代理?可以在不修改別代理對象代碼的基礎上,通過擴展代理類,進行一些功能的附加與增強。
我們先看看傳統的JDK動態代理:
1、首先有一個接口
public interface Calculate { void add(int i, int j); }
2、然后是接口的實現類
public class CalculateImp implements Calculate { @Override public void add(int i, int j) { System.out.println("result = " + (i + j)); } }
3、代理類實現InvocationHandler
public class CalculateProxy implements InvocationHandler { private Object target; //總要讓我知道要代理誰吧:構造方法中把傳入一個代理類的實例 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、拿到代理對象,操作接口方法
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()方法有三個參數:
1. 類加載器(Class Loader)
2. 需要實現的接口數組
3. InvocationHandler接口。所有動態代理類的方法調用,都會交由InvocationHandler接口實現類里的invoke()方法去處理。這是動態代理的關鍵所在。
回到我們之前的問題,我們并沒有接口實現類,那沒有實現類還為什么還能調用方法操作。其實是這樣的:
操作數據庫主要是通過SQL語句,那么只要找到SQL語句然后執行不就可以!
通過例子分析:
BlogMapper mapper = session.getMapper(BlogMapper.class); Blog blog = mapper.selectBlog(1);
這里 mapper 可以調用selectBlog(1) 這個方法,說明 mapper 是個對象,因為對象才具有方法行為實現啊。BlogMapper接口是不能實例化的,更沒有具體方法實現。
我們并沒有定義一個類,讓它實現BlogMapper接口,而在這里它只是通過調用session.getMapper() 所得到的。
由此,我們可以推斷:肯定是session.getMapper() 方法內部產生了BlogMapper的實現類。有什么技術可以根據BlogMapper 接口生成了一個實現類呢?想到這里,對于有動態代理 。
我們既然能夠從SqlSession中得到BlogMapper接口的,那么我們肯定需要先在哪里把它放進去了,然后 SqlSession 才能生成我們想要的代理類啊。
我們可以從getMapper()聯系,可能會有一個setMapper()或者addMapper()方法。確實是有!
configuration.addMapper(BlogMapper.class);
跟著這個 addMapper 方法的代碼實現是這樣的:
public <T> void addMapper(Class<T> type) { mapperRegistry.addMapper(type); }
我們看到這里 mapper 實際上被添加到 mapperRegistry (mapper注冊器)中。繼續跟進代碼:
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)) { // 不允許重復添加 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類,有一個私有屬性knowMappers,它是一個HashMap
。
其Key
為當前Class對象,value
為一個MapperProxyFactory實例
在MapperRegistry類的addMapper()方法中,knownMappers.put(type, new MapperProxyFactory<T>(type));相當于把:諸如BlogMapper
之類的Mapper接口被添加到了MapperRegistry
中的一個HashMap中。
并以 Mapper 接口的 Class 對象作為 Key , 以一個攜帶Mapper接口作為屬性的MapperProxyFactory
實例作為value 。
MapperProxyFactory從名字來看,好像是一個工廠,用來創建Mapper Proxy的工廠。
上面我們已經知道,Mapper 接口被到注冊到了MapperRegistry
中——放在其名為knowMappers 的HashMap屬性中,我們在調用Mapper接口的方法的時候,是這樣的:
BlogMapper mapper = session.getMapper(BlogMapper.class);
這里,我們跟蹤一下session.getMapper() 方法的代碼實現,這里 SqlSession 是一個接口,他有兩個實現類,
一個是DefaultSqlSession
,另外一個是SqlSessionManager
,
這里我們用的是DefaultSqlSession
. 為什么是DefaultSqlSession
呢?因為我們在初始化SqlSessionFactory的時候所調用的SqlSessionFactoryBuilder
的build()方法里邊配置的就是DefaultSqlSession
, 所以,我們進入到DefaultSession類中,看看它對session.getMapper(BlogMapper.class)
是怎么實現的:
public class DefaultSqlSession implements SqlSession { private Configuration configuration; @Override public <T> T getMapper(Class<T> type) { return configuration.<T>getMapper(type, this); //最后會去調用MapperRegistry.getMapper } }
如代碼所示,這里的 getMapper 調用了 configuration.getMapper , 這一步操作其實最終是調用了MapperRegistry
,而此前我們已經知道,MapperRegistry
是存放了一個HashMap的,我們繼續跟蹤進去看看,那么這里的get,肯定是從這個hashMap中取數據。
我們來看看代碼:
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); // 重點看這里 } catch (Exception e) { } } }
我們調用的session.getMapper(BlogMapper.class);
最終會到達上面這個方法,這個方法,根據BlogMapper
的class對象,以它為key
在knowMappers
中找到了對應的value
—— MapperProxyFactory(BlogMapper) 對象,然后調用這個對象的newInstance()
方法。
根據這個名字,我們就能猜到這個方法是創建了一個對象,代碼是這樣的:
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自帶的動態代理生成映射器代理類的對象 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
產生了一個BlogMapper的代理對象。
Mybatis 為了完成 Mapper 接口的實現,運用了代理模式。
具體是使用了JDK動態代理,這個Proxy.newProxyInstance
方法生成代理類的三個要素是:
ClassLoader —— 指定當前接口的加載器即可
當前被代理的接口是什么 —— 這里就是 BlogMapper
代理類是什么 —— 這里就是 MapperProxy
代理模式中,代理類(MapperProxy)中才真正的完成了方法調用的邏輯。
我們貼出MapperProxy的代碼,如下:
public class MapperProxy<T> implements InvocationHandler, Serializable {// 實現了InvocationHandler @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //代理以后,所有Mapper的方法調用時,都會調用這個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); // 使用了緩存 //執行CURD return mapperMethod.execute(sqlSession, args); // 注意2 } }
我們調用的 Blog blog = mapper.selectBlog(1);
實際上最后是會調用這個MapperProxy
的invoke
方法。
這段代碼中,if
語句先判斷,我們想要調用的方法是否來自Object類,這里的意思就是,如果我們調用toString()
方法,那么是不需要做代理增強的,直接還調用原來的method.invoke()就行了。
只有調用selectBlog()
之類的方法的時候,才執行增強的調用——即mapperMethod.execute(sqlSession, args);
這一句代碼邏輯。
而mapperMethod.execute(sqlSession, args);
這句最終就會執行增刪改查了,代碼如下:
public Object execute(SqlSession sqlSession, Object[] args) { Object result; if (SqlCommandType.INSERT == command.getType()) { //insert 處理,調用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; }
再往下一層,就是執行JDBC那一套了,獲取鏈接,執行,得到ResultSet,解析ResultSet映射成JavaBean。
以上就是“MyBatis的SqlSession.getMapper()源碼分析”這篇文章的所有內容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學習更多的知識,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。