Mybatis源碼學習(15)-binding模塊之MapperMethod類
標簽: MapperMethod SqlCommand MethodSignature binding Mybatis
一、概述
1、Mybatis操作數據
??在Mybatis中,進行數據操作時,有兩種方式,分別是:1、使用傳統的MyBatis提供的API;2、使用Mapper接口,即面向接口編程。
- 傳統的Mybatis工作模式
示例:
@Test
public void testApi() {
SqlSession sqlSession = mySqlSessionFactory.openSession(true);//自動提交
Map<String, Object> param = new HashMap<String, Object>();
param.put("field1", "a1");
List<Map<String, Object>> list = sqlSession.selectList("TestMapper.queryList", param);
System.out.println(list.toString());
}
2. 面向接口編程
示例:
@Test
public void testMapper() {
SqlSession sqlSession = mySqlSessionFactory.openSession(true);//自動提交
TestMapper testMapper = sqlSession.getMapper(TestMapper.class);
Map<String, Object> param = new HashMap<String, Object>();
param.put("field1", "a1");
List<Map<String, Object>> list = testMapper.queryList(param);
System.out.println(list.toString());
}
2、面向接口編程簡介
??Mybatis的binding模塊就是用來實現面向接口編程的功能。面向接口編程,其實就是通過建立Mapper接口與XML配置文件的一一對應關系,從而實現通過操作對應接口來實現操作數據庫SQL語句實現增刪改查功能。
二、binding模塊結構
Mybatis的binding模塊的包目錄:org.apache.ibatis.binding。具體包結構如下圖所示:
其中,Mybatis的binding模塊就是用到了動態代理。
- MapperMethod
定義Mapper接口中對應方法的信息以及對應SQL語句的信息,用來完成參數轉換以及SQL語句的執行功能,可以看成連接Mapper接口以及映射配置文件中定義的SQL語句的橋梁。 - MapperProxy
MapperProxy實現了lnvocationHandler接口,是代理對象。 - MapperProxyFactory
負責創建代理對象 - MapperRegistry
Mapper接口及其對應的代理對象工廠的注冊中心
三、MapperMethod類
??定義Mapper接口中對應方法的信息以及對應SQL語句的信息,用來完成參數轉換以及SQL語句的執行功能,可以看成連接Mapper接口以及映射配置文件中定義的SQL語句的橋梁。
- 字段、構造函數
在MapperMethod類中,定義了command、method兩個字段,這兩個字段均是內部類對象。其中command定義SQL命令的概要信息,主要包括了SQL命令的名稱和類型;method定義Mapper接口中方法的相關信息。構造函數主要是實現command、method兩個對象的初始化。
private final SqlCommand command; //定義SQL命令的概要信息,主要包括了SQL命令的名稱和類型
private final MethodSignature method;//定義Mapper接口中方法的相關信息
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
- 內部類 SqlCommand
MapperMethod內部類,用來定義SQL命令的概要信息,主要包括了SQL命令的名稱和類型。
public static class SqlCommand {
private final String name;//定義SQL命令的名稱,由Mapper接口的全限定類名與對應的方法名稱組成的。
private final SqlCommandType type;//定義SQL命令的類型,SqlCommandType是枚舉類型,可選值為:UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH;
/**
* 構造函數,完成對字段屬性name、type的初始化。
* 同時驗證了Configuration.mappedStatements中是否有對應的MappedStatement對象(在解析XML時初始化該對象)。
* @param configuration
* @param mapperInterface
* @param method
*/
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
final String methodName = method.getName();
final Class<?> declaringClass = method.getDeclaringClass();
MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
configuration);
if (ms == null) {
if (method.getAnnotation(Flush.class) != null) {
name = null;
type = SqlCommandType.FLUSH;
} else {
throw new BindingException("Invalid bound statement (not found): "
+ mapperInterface.getName() + "." + methodName);
}
} else {
name = ms.getId();
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
}
public String getName() {
return name;
}
public SqlCommandType getType() {
return type;
}
/**
* 解析SqlCommand實例對應的MappedStatement對象,并返回。
* 對應的MappedStatement對象在解析Mapper對應的XML時,進行初始化,并存儲在了Configuration.mappedStatements中。
* @param mapperInterface
* @param methodName
* @param declaringClass
* @param configuration
* @return
*/
private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
Class<?> declaringClass, Configuration configuration) {
String statementId = mapperInterface.getName() + "." + methodName;
if (configuration.hasStatement(statementId)) {
return configuration.getMappedStatement(statementId);
} else if (mapperInterface.equals(declaringClass)) {//當declaringClass==mapperInterface時,說明methodName對應方法的MappedStatement對象不存在,且不可能是在在超類中,所以直接返回null。
return null;
}
//下面循環是處理當methodName對應的方法在超類中的情況
for (Class<?> superInterface : mapperInterface.getInterfaces()) {
if (declaringClass.isAssignableFrom(superInterface)) {//判斷declaringClass是superInterface的父類,如果是繼續查找對應的父類,直到查找到對應的MappedStatement或者檢索完所有的父類
MappedStatement ms = resolveMappedStatement(superInterface, methodName,
declaringClass, configuration);
if (ms != null) {
return ms;
}
}
}
return null;
}
}
- 內部類 MethodSignature
定義Mapper接口中方法的相關信息。其中ParamNameResolver類后續再詳細分析,現在只需要了解是用來處理方法參數即可。
public static class MethodSignature {
private final boolean returnsMany;//返回值類型是否為Collection類型或是數組類型
private final boolean returnsMap;//返回值類型是否為Map類型
private final boolean returnsVoid;//返回值類型是否為void類型
private final boolean returnsCursor;//返回值是否為Cursor類型
private final Class<?> returnType;//返回值類型
private final String mapKey;//如果返回值類型是Map,則該字段記錄了作為key的列名
private final Integer resultHandlerIndex;//用來標記該方法參數列表中ResultHandler類型參數的位置
private final Integer rowBoundsIndex;//用來標記該方法參數列表中RowBounds類型參數的位置
private final ParamNameResolver paramNameResolver;//該方法對應的ParamNameResolver對象,主要用來處理方法的參數
public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
if (resolvedReturnType instanceof Class<?>) {
this.returnType = (Class<?>) resolvedReturnType;
} else if (resolvedReturnType instanceof ParameterizedType) {
this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();
} else {
this.returnType = method.getReturnType();
}
this.returnsVoid = void.class.equals(this.returnType);
this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
this.returnsCursor = Cursor.class.equals(this.returnType);
this.mapKey = getMapKey(method);
this.returnsMap = this.mapKey != null;
this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
this.paramNameResolver = new ParamNameResolver(configuration, method);
}
/**
* 負責將args[]數組(用戶傳入的實參列表)轉換成SQL語句對應的參數列表
* @param args
* @return
*/
public Object convertArgsToSqlCommandParam(Object[] args) {
return paramNameResolver.getNamedParams(args);
}
public boolean hasRowBounds() {
return rowBoundsIndex != null;
}
public RowBounds extractRowBounds(Object[] args) {
return hasRowBounds() ? (RowBounds) args[rowBoundsIndex] : null;
}
public boolean hasResultHandler() {
return resultHandlerIndex != null;
}
public ResultHandler extractResultHandler(Object[] args) {
return hasResultHandler() ? (ResultHandler) args[resultHandlerIndex] : null;
}
public String getMapKey() {
return mapKey;
}
public Class<?> getReturnType() {
return returnType;
}
public boolean returnsMany() {
return returnsMany;
}
public boolean returnsMap() {
return returnsMap;
}
public boolean returnsVoid() {
return returnsVoid;
}
public boolean returnsCursor() {
return returnsCursor;
}
/**
* 查找指定類型的參數在參數列表中的位置,且該paramType在參數中只能有一個,否則會拋出BindingException異常。
* @param method
* @param paramType
* @return
*/
private Integer getUniqueParamIndex(Method method, Class<?> paramType) {
Integer index = null;
final Class<?>[] argTypes = method.getParameterTypes();
for (int i = 0; i < argTypes.length; i++) {
if (paramType.isAssignableFrom(argTypes[i])) {
if (index == null) {
index = i;
} else {
throw new BindingException(method.getName() + " cannot have multiple " + paramType.getSimpleName() + " parameters");
}
}
}
return index;
}
private String getMapKey(Method method) {
String mapKey = null;
if (Map.class.isAssignableFrom(method.getReturnType())) {
final MapKey mapKeyAnnotation = method.getAnnotation(MapKey.class);
if (mapKeyAnnotation != null) {
mapKey = mapKeyAnnotation.value();
}
}
return mapKey;
}
}
- 內部類 ParamMap
重寫了HashMap類,主要重寫了get()方法,在原來的基礎上,添加了當獲取不存在的key值時,直接保存BindingException異常。
public static class ParamMap<V> extends HashMap<String, V> {
private static final long serialVersionUID = -2212268410512043556L;
@Override
public V get(Object key) {
if (!super.containsKey(key)) {
throw new BindingException("Parameter '" + key + "' not found. Available parameters are " + keySet());
}
return super.get(key);
}
}
- execute()方法
代理對象調用invoke()方法時,最終是調用了execute()方法,是最核心的代碼,是實現操作SQL語句的邏輯的地方。這個方法主要用來處理參數、處理返回結果,真正操作數據的操作還是交給了sqlSession對應的方法。
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
- convertArgsToSqlCommandParam()方法
負責將args[]數組(用戶傳入的實參列表)轉換成SQL語句對應的參數列表。內部是通過paramNameResolver的getNamedParams()方法實現。
public Object convertArgsToSqlCommandParam(Object[] args) {
return paramNameResolver.getNamedParams(args);
}
- 返回結果處理
/**
* 對數字或布爾類型的返回結果進行轉換
* @param rowCount
* @return
*/
private Object rowCountResult(int rowCount) {
final Object result;
if (method.returnsVoid()) {
result = null;
} else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) {
result = rowCount;
} else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) {
result = (long)rowCount;
} else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) {
result = rowCount > 0;
} else {
throw new BindingException("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType());
}
return result;
}
/**
* 處理需要通過回調ResultHandler處理結果集的情況
* @param sqlSession
* @param args
*/
private void executeWithResultHandler(SqlSession sqlSession, Object[] args) {
MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(command.getName());
if (!StatementType.CALLABLE.equals(ms.getStatementType())
&& void.class.equals(ms.getResultMaps().get(0).getType())) {
throw new BindingException("method " + command.getName()
+ " needs either a @ResultMap annotation, a @ResultType annotation,"
+ " or a resultType attribute in XML so a ResultHandler can be used as a parameter.");
}
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
sqlSession.select(command.getName(), param, rowBounds, method.extractResultHandler(args));
} else {
sqlSession.select(command.getName(), param, method.extractResultHandler(args));
}
}
/**
* 處理對應方法的返回值為數組或是Collection接口實現類
* @param <E>
* @param sqlSession
* @param args
* @return
*/
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
} else {
result = sqlSession.<E>selectList(command.getName(), param);
}
// issue #510 Collections & arrays support
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
if (method.getReturnType().isArray()) {
return convertToArray(result);
} else {
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
return result;
}
/**
* 處理返回值為Cursor的方法
* @param <T>
* @param sqlSession
* @param args
* @return
*/
private <T> Cursor<T> executeForCursor(SqlSession sqlSession, Object[] args) {
Cursor<T> result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.<T>selectCursor(command.getName(), param, rowBounds);
} else {
result = sqlSession.<T>selectCursor(command.getName(), param);
}
return result;
}
/**
* 主要負責將結果對象轉換成Collection集合對象和數組對象
* @param <E>
* @param config
* @param list
* @return
*/
private <E> Object convertToDeclaredCollection(Configuration config, List<E> list) {
Object collection = config.getObjectFactory().create(method.getReturnType());
MetaObject metaObject = config.newMetaObject(collection);
metaObject.addAll(list);
return collection;
}
/**
* 主要負責將結果對象轉換成Collection集合對象和數組對象
* @param <E>
* @param list
* @return
*/
@SuppressWarnings("unchecked")
private <E> Object convertToArray(List<E> list) {
Class<?> arrayComponentType = method.getReturnType().getComponentType();
Object array = Array.newInstance(arrayComponentType, list.size());
if (arrayComponentType.isPrimitive()) {
for (int i = 0; i < list.size(); i++) {
Array.set(array, i, list.get(i));
}
return array;
} else {
return list.toArray((E[])array);
}
}
/**
* 主要負責將結果對象轉換成Map對象
* @param <K>
* @param <V>
* @param sqlSession
* @param args
* @return
*/
private <K, V> Map<K, V> executeForMap(SqlSession sqlSession, Object[] args) {
Map<K, V> result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.<K, V>selectMap(command.getName(), param, method.getMapKey(), rowBounds);
} else {
result = sqlSession.<K, V>selectMap(command.getName(), param, method.getMapKey());
}
return result;
}
智能推薦
mybatis源碼解析之基礎模塊-Transaction
MyBatis源碼解析之基礎模塊—Transaction 前文回顧 上一篇,咱們一起學習了Mybatis的DataSource模塊相關源碼,掌握了三種數據源工廠的邏輯,同時也掌握非池化數據源的連接創建,池化數據源如何從空閑列表獲取連接并放到活躍連接列表,及連接的歸還到空閑隊列的邏輯。 下面跟隨筆者的思路,咱們繼續學習另一個重要模塊——Transaction模塊。...
mybatis源碼解析之基礎模塊-log
MyBatis源碼解析之基礎模塊—Log 前文回顧 上一章節我們一起學習了DataSource源碼邏輯。本次我們學習MyBatis的Log日志處理模塊。 背景描述 只要做過技術開發的小伙伴都應該明白日志的重要性。這是用于追蹤線上運行情況及排查問題的利器。如果沒有有效規范的日志輸出,碰到問題特別是線上問題將會陷入一片迷茫,而且線上環境又不能隨意調整。而日志中很重要的一部分還是與數據變更相...
mybatis源碼解析之基礎模塊-Plugin
MyBatis源碼解析之基礎模塊—Plugin 前文回顧 上一章節我們一起學習了Mapper接口綁定的源碼邏輯。本次我們學習MyBatis的Plugin數據源模塊。 架構設計 Plugin模塊所在包路徑為org.apache.ibatis.plugin,對應的類架構設計圖如下: 源碼解讀 Signature Signature注解類主要定義了三個屬性,通過這些屬性定位對應要攔截的方法。...
mybatis源碼解析之基礎模塊-TypeHandler
MyBatis源碼解析之基礎模塊—TypeHandler 前文回顧 上一章節我們一起分析了Mybatis的Plugin模塊的源碼。掌握了如何配置攔截器注解,如何自定義攔截器以及攔截器的執行過程。 在使用Mybatis的過程中,基本上我們都要在xml中編寫相應的sql語句以及對應的java屬性與字段的轉換。那么對于數據庫與java之間的轉換,Mybatis是怎么做的呢? 接下來本章節我們...
猜你喜歡
freemarker + ItextRender 根據模板生成PDF文件
1. 制作模板 2. 獲取模板,并將所獲取的數據加載生成html文件 2. 生成PDF文件 其中由兩個地方需要注意,都是關于獲取文件路徑的問題,由于項目部署的時候是打包成jar包形式,所以在開發過程中時直接安照傳統的獲取方法沒有一點文件,但是當打包后部署,總是出錯。于是參考網上文章,先將文件讀出來到項目的臨時目錄下,然后再按正常方式加載該臨時文件; 還有一個問題至今沒有解決,就是關于生成PDF文件...
電腦空間不夠了?教你一個小秒招快速清理 Docker 占用的磁盤空間!
Docker 很占用空間,每當我們運行容器、拉取鏡像、部署應用、構建自己的鏡像時,我們的磁盤空間會被大量占用。 如果你也被這個問題所困擾,咱們就一起看一下 Docker 是如何使用磁盤空間的,以及如何回收。 docker 占用的空間可以通過下面的命令查看: TYPE 列出了docker 使用磁盤的 4 種類型: Images:所有鏡像占用的空間,包括拉取下來的鏡像,和本地構建的。 Con...
requests實現全自動PPT模板
http://www.1ppt.com/moban/ 可以免費的下載PPT模板,當然如果要人工一個個下,還是挺麻煩的,我們可以利用requests輕松下載 訪問這個主頁,我們可以看到下面的樣式 點每一個PPT模板的圖片,我們可以進入到詳細的信息頁面,翻到下面,我們可以看到對應的下載地址 點擊這個下載的按鈕,我們便可以下載對應的PPT壓縮包 那我們就開始做吧 首先,查看網頁的源代碼,我們可以看到每一...
Linux C系統編程-線程互斥鎖(四)
互斥鎖 互斥鎖也是屬于線程之間處理同步互斥方式,有上鎖/解鎖兩種狀態。 互斥鎖函數接口 1)初始化互斥鎖 pthread_mutex_init() man 3 pthread_mutex_init (找不到的情況下首先 sudo apt-get install glibc-doc sudo apt-get install manpages-posix-dev) 動態初始化 int pthread_...