剛開始使用Mybaits的同學(xué)有沒有這樣的疑惑,為什么我們沒有編寫Mapper的實(shí)現(xiàn)類,卻能調(diào)用Mapper的方法呢?本篇文章我?guī)Т蠹乙黄饋斫鉀Q這個(gè)疑問
上一篇文章我們獲取到了DefaultSqlSession,接著我們來看第一篇文章測(cè)試用例后面的代碼
EmployeeMapper employeeMapper = sqlSession.getMapper(Employee.class); List
<Employee> allEmployees = employeeMapper.getAll();
為 Mapper 接口創(chuàng)建代理對(duì)象
我們先從 DefaultSqlSession 的 getMapper 方法開始看起,如下:
1 public <T> T getMapper(Class<T> type) { 2 return
configuration.<T>getMapper(type,this); 3 } 4 5 // Configuration 6 public
<T> T getMapper(Class<T> type, SqlSession sqlSession) { 7 return
mapperRegistry.getMapper(type, sqlSession); 8 } 9 10 // MapperRegistry 11
public <T> T getMapper(Class<T> type, SqlSession sqlSession) { 12 // 從
knownMappers 中獲取與 type 對(duì)應(yīng)的 MapperProxyFactory 13 final MapperProxyFactory<T>
mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); 14 if
(mapperProxyFactory ==null) { 15 throw new BindingException("Type " + type + "
is not known to the MapperRegistry."); 16 } 17 try { 18 // 創(chuàng)建代理對(duì)象 19 return
mapperProxyFactory.newInstance(sqlSession);20 } catch (Exception e) { 21 throw
new BindingException("Error getting mapper instance. Cause: " + e, e); 22 } 23
}
這里最重要就是兩行代碼,第13行和第19行,我們接下來就分析這兩行代碼
獲取MapperProxyFactory
根據(jù)名稱看,可以理解為Mapper代理的創(chuàng)建工廠,是不是Mapper的代理對(duì)象由它創(chuàng)建呢?我們先來回顧一下knownMappers
集合中的元素是何時(shí)存入的。這要在我前面的文章中找答案,MyBatis 在解析配置文件的 <mappers> 節(jié)點(diǎn)的過程中,會(huì)調(diào)用 MapperRegistry
的 addMapper 方法將 Class 到 MapperProxyFactory 對(duì)象的映射關(guān)系存入到
knownMappers。有興趣的同學(xué)可以看看我之前的文章,我們來回顧一下源碼:
private void bindMapperForNamespace() { // 獲取映射文件的命名空間 String namespace =
builderAssistant.getCurrentNamespace();if (namespace != null) { Class<?>
boundType =null; try { // 根據(jù)命名空間解析 mapper 類型 boundType =
Resources.classForName(namespace); } catch (ClassNotFoundException e) { } if
(boundType !=null) { // 檢測(cè)當(dāng)前 mapper 類是否被綁定過 if (!
configuration.hasMapper(boundType)) { configuration.addLoadedResource(
"namespace:" + namespace); // 綁定 mapper 類 configuration.addMapper(boundType);
} } } }// Configuration public <T> void addMapper(Class<T> type) { // 通過
MapperRegistry 綁定 mapper 類 mapperRegistry.addMapper(type); } // MapperRegistry
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 { /* * 將
type 和 MapperProxyFactory 進(jìn)行綁定,MapperProxyFactory 可為 mapper 接口生成代理類*/
knownMappers.put(type,new MapperProxyFactory<T>(type)); MapperAnnotationBuilder
parser= new MapperAnnotationBuilder(config, type); // 解析注解中的信息 parser.parse();
loadCompleted= true; } finally { if (!loadCompleted) {
knownMappers.remove(type); } } } }
在解析Mapper.xml的最后階段,獲取到Mapper.xml的namespace,然后利用反射,獲取到namespace的Class,并創(chuàng)建一個(gè)
MapperProxyFactory的實(shí)例,namespace的Class作為參數(shù),最后將namespace的Class為key,
MapperProxyFactory的實(shí)例為value存入knownMappers。
注意,我們這里是通過映射文件的命名空間的Class當(dāng)做knownMappers的Key。然后我們看看
getMapper方法的13行,是通過參數(shù)Employee.class也就是Mapper接口的Class來獲取
MapperProxyFactory,所以我們明白了為什么要求xml配置中的namespace要和和對(duì)應(yīng)的Mapper接口的全限定名了
生成代理對(duì)象
我們看第19行代碼?return mapperProxyFactory.newInstance(sqlSession);,
很明顯是調(diào)用了MapperProxyFactory的一個(gè)工廠方法,我們跟進(jìn)去看看
public class MapperProxyFactory<T> { //存放Mapper接口Class private final Class<T>
mapperInterface;private final Map<Method, MapperMethod> methodCache = new
ConcurrentHashMap();public MapperProxyFactory(Class<T> mapperInterface) { this
.mapperInterface = mapperInterface; } public Class<T> getMapperInterface() {
return this.mapperInterface; } public Map<Method, MapperMethod>
getMethodCache() {return this.methodCache; } protected T
newInstance(MapperProxy<T> mapperProxy) { //生成mapperInterface的代理類 return
Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this
.mapperInterface}, mapperProxy); }public T newInstance(SqlSession sqlSession) {
/* * 創(chuàng)建 MapperProxy 對(duì)象,MapperProxy 實(shí)現(xiàn)了 InvocationHandler 接口,代理邏輯封裝在此類中 *
將sqlSession傳入MapperProxy對(duì)象中,第二個(gè)參數(shù)是Mapper的接口,并不是其實(shí)現(xiàn)類*/ MapperProxy<T>
mapperProxy =new MapperProxy(sqlSession, this.mapperInterface, this
.methodCache);return this.newInstance(mapperProxy); } }
上面的代碼首先創(chuàng)建了一個(gè) MapperProxy 對(duì)象,該對(duì)象實(shí)現(xiàn)了 InvocationHandler
接口。然后將對(duì)象作為參數(shù)傳給重載方法,并在重載方法中調(diào)用 JDK 動(dòng)態(tài)代理接口為 Mapper接口 生成代理對(duì)象。
這里要注意一點(diǎn),MapperProxy這個(gè)InvocationHandler
創(chuàng)建的時(shí)候,傳入的參數(shù)并不是Mapper接口的實(shí)現(xiàn)類,我們以前是怎么創(chuàng)建JDK動(dòng)態(tài)代理的?先創(chuàng)建一個(gè)接口,然后再創(chuàng)建一個(gè)接口的實(shí)現(xiàn)類,最后創(chuàng)建一個(gè)InvocationHandler并將實(shí)現(xiàn)類傳入其中作為目標(biāo)類,創(chuàng)建接口的代理類,然后調(diào)用代理類方法時(shí)會(huì)回調(diào)InvocationHandler的invoke方法,最后在invoke方法中調(diào)用目標(biāo)類的方法,但是我們這里調(diào)用Mapper接口代理類的方法時(shí),需要調(diào)用其實(shí)現(xiàn)類的方法嗎?不需要,我們需要調(diào)用對(duì)應(yīng)的配置文件的SQL,所以這里并不需要傳入Mapper的實(shí)現(xiàn)類到MapperProxy中,那Mapper接口的代理對(duì)象是如何調(diào)用對(duì)應(yīng)配置文件的SQL呢?下面我們來看看。
Mapper代理類如何執(zhí)行SQL?
上面一節(jié)中我們已經(jīng)獲取到了EmployeeMapper的代理類,并且其InvocationHandler為MapperProxy,那我們接著看Mapper接口方法的調(diào)用
List<Employee> allEmployees = employeeMapper.getAll();
知道JDK動(dòng)態(tài)代理的同學(xué)都知道,調(diào)用代理類的方法,最后都會(huì)回調(diào)到InvocationHandler的Invoke方法,那我們來看看這個(gè)InvocationHandler(MapperProxy)
public class MapperProxy<T> implements InvocationHandler, Serializable {
private final SqlSession sqlSession; private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache; public
MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method,
MapperMethod> methodCache) { this.sqlSession = sqlSession; this.mapperInterface
= mapperInterface; this.methodCache = methodCache; } public Object invoke
(Object proxy, Method method, Object[] args)throws Throwable { // 如果方法是定義在
Object 類中的,則直接調(diào)用 if (Object.class.equals(method.getDeclaringClass())) { try {
return method.invoke(this, args); } catch (Throwable var5) { throw
ExceptionUtil.unwrapThrowable(var5); } }else { // 從緩存中獲取 MapperMethod
對(duì)象,若緩存未命中,則創(chuàng)建 MapperMethod 對(duì)象 MapperMethod mapperMethod = this
.cachedMapperMethod(method);// 調(diào)用 execute 方法執(zhí)行 SQL return mapperMethod.execute(
this.sqlSession, args); } } private MapperMethod cachedMapperMethod(Method
method) { MapperMethod mapperMethod= (MapperMethod)this.methodCache.get(method);
if (mapperMethod == null) { //
創(chuàng)建一個(gè)MapperMethod,參數(shù)為mapperInterface和method還有Configuration mapperMethod = new
MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());
this.methodCache.put(method, mapperMethod); } return mapperMethod; } }
如上,回調(diào)函數(shù)invoke邏輯會(huì)首先檢測(cè)被攔截的方法是不是定義在 Object 中的,比如 equals、hashCode
方法等。對(duì)于這類方法,直接執(zhí)行即可。緊接著從緩存中獲取或者創(chuàng)建 MapperMethod 對(duì)象,然后通過該對(duì)象中的 execute 方法執(zhí)行
SQL。我們先來看看如何創(chuàng)建MapperMethod
創(chuàng)建 MapperMethod 對(duì)象
public class MapperMethod { //
包含SQL相關(guān)信息,比喻MappedStatement的id屬性,(mapper.EmployeeMapper.getAll) private final
SqlCommand command;//包含了關(guān)于執(zhí)行的Mapper方法的參數(shù)類型和返回類型。 private final MethodSignature
method;public MapperMethod(Class<?> mapperInterface, Method method,
Configuration config) {// 創(chuàng)建 SqlCommand 對(duì)象,該對(duì)象包含一些和 SQL 相關(guān)的信息 this.command = new
SqlCommand(config, mapperInterface, method);// 創(chuàng)建 MethodSignature
對(duì)象,從類名中可知,該對(duì)象包含了被攔截方法的一些信息 this.method = new MethodSignature(config,
mapperInterface, method); } }
MapperMethod包含SqlCommand 和MethodSignature 對(duì)象,我們來看看其創(chuàng)建過程
① 創(chuàng)建 SqlCommand 對(duì)象
public static class SqlCommand { //
name為MappedStatement的id,也就是namespace.methodName(mapper.EmployeeMapper.getAll)
private final String name; //SQL的類型,如insert,delete,update private final
SqlCommandType type;public SqlCommand(Configuration configuration, Class<?>
mapperInterface, Method method) {//
拼接Mapper接口名和方法名,(mapper.EmployeeMapper.getAll) String statementName =
mapperInterface.getName() + "." + method.getName(); MappedStatement ms = null;
//檢測(cè)configuration是否有key為mapper.EmployeeMapper.getAll的MappedStatement if
(configuration.hasStatement(statementName)) {//獲取MappedStatement ms =
configuration.getMappedStatement(statementName); } else if (!
mapperInterface.equals(method.getDeclaringClass())) { String parentStatementName
= method.getDeclaringClass().getName() + "." + method.getName(); if
(configuration.hasStatement(parentStatementName)) { ms=
configuration.getMappedStatement(parentStatementName); } }// 檢測(cè)當(dāng)前方法是否有對(duì)應(yīng)的
MappedStatement 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 { // 設(shè)置 name 和 type 變量
name = ms.getId(); type = ms.getSqlCommandType(); if (type ==
SqlCommandType.UNKNOWN) {throw new BindingException("Unknown execution method
for: " + name); } } } } public boolean hasStatement(String statementName,
boolean validateIncompleteStatements) { //
檢測(cè)configuration是否有key為statementName的MappedStatement return this
.mappedStatements.containsKey(statementName); }
通過拼接接口名和方法名,在configuration獲取對(duì)應(yīng)的MappedStatement,并設(shè)置設(shè)置 name 和 type 變量,代碼很簡(jiǎn)單
② 創(chuàng)建 MethodSignature 對(duì)象
MethodSignature?包含了被攔截方法的一些信息,如目標(biāo)方法的返回類型,目標(biāo)方法的參數(shù)列表信息等。下面,我們來看一下
MethodSignature 的構(gòu)造方法。
public static class MethodSignature { private final boolean returnsMany;
private final boolean returnsMap; private final boolean returnsVoid; private
final boolean returnsCursor; private final Class<?> returnType; private final
String mapKey;private final Integer resultHandlerIndex; private final Integer
rowBoundsIndex;private final ParamNameResolver paramNameResolver; public
MethodSignature(Configuration configuration, Class<?> mapperInterface, Method
method) {// 通過反射解析方法返回類型 Type resolvedReturnType =
TypeParameterResolver.resolveReturnType(method, mapperInterface);if
(resolvedReturnTypeinstanceof Class<?>) { this.returnType = (Class<?>)
resolvedReturnType; }else if (resolvedReturnType instanceof ParameterizedType) {
this.returnType = (Class<?>) ((ParameterizedType)
resolvedReturnType).getRawType(); }else { this.returnType =
method.getReturnType(); }// 檢測(cè)返回值類型是否是 void、集合或數(shù)組、Cursor、Map 等 this.returnsVoid
=void.class.equals(this.returnType); this.returnsMany =
configuration.getObjectFactory().isCollection(this.returnType) || this
.returnType.isArray();this.returnsCursor = Cursor.class.equals(this.returnType);
// 解析 @MapKey 注解,獲取注解內(nèi)容 this.mapKey = getMapKey(method); this.returnsMap = this
.mapKey !=null; /* * 獲取 RowBounds 參數(shù)在參數(shù)列表中的位置,如果參數(shù)列表中 * 包含多個(gè) RowBounds
參數(shù),此方法會(huì)拋出異常*/ this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class
);// 獲取 ResultHandler 參數(shù)在參數(shù)列表中的位置 this.resultHandlerIndex =
getUniqueParamIndex(method, ResultHandler.class); // 解析參數(shù)列表 this
.paramNameResolver =new ParamNameResolver(configuration, method); } }
執(zhí)行 execute 方法
前面已經(jīng)分析了 MapperMethod 的初始化過程,現(xiàn)在 MapperMethod 創(chuàng)建好了。那么,接下來要做的事情是調(diào)用 MapperMethod 的
execute 方法,執(zhí)行 SQL。傳遞參數(shù)sqlSession和method的運(yùn)行參數(shù)args
return mapperMethod.execute(this.sqlSession, args);
我們?nèi)apperMethod 的execute方法中看看
MapperMethod
public Object execute(SqlSession sqlSession, Object[] args) { Object result; //
根據(jù) SQL 類型執(zhí)行相應(yīng)的數(shù)據(jù)庫操作 switch (command.getType()) { case INSERT: { //
對(duì)用戶傳入的參數(shù)進(jìn)行轉(zhuǎn)換,下同 Object param = method.convertArgsToSqlCommandParam(args); //
執(zhí)行插入操作,rowCountResult 方法用于處理返回值 result =
rowCountResult(sqlSession.insert(command.getName(), param));break; } case UPDATE
: { Object param= method.convertArgsToSqlCommandParam(args); // 執(zhí)行更新操作 result =
rowCountResult(sqlSession.update(command.getName(), param));break; } case DELETE
: { Object param= method.convertArgsToSqlCommandParam(args); // 執(zhí)行刪除操作 result =
rowCountResult(sqlSession.delete(command.getName(), param));break; } case SELECT
:// 根據(jù)目標(biāo)方法的返回類型進(jìn)行相應(yīng)的查詢操作 if (method.returnsVoid() && method.hasResultHandler())
{ executeWithResultHandler(sqlSession, args); result= null; } else if
(method.returnsMany()) {// 執(zhí)行查詢操作,并返回多個(gè)結(jié)果 result = executeForMany(sqlSession,
args); }else if (method.returnsMap()) { // 執(zhí)行查詢操作,并將結(jié)果封裝在 Map 中返回 result =
executeForMap(sqlSession, args); }else if (method.returnsCursor()) { //
執(zhí)行查詢操作,并返回一個(gè) Cursor 對(duì)象 result = executeForCursor(sqlSession, args); } else {
Object param= method.convertArgsToSqlCommandParam(args); // 執(zhí)行查詢操作,并返回一個(gè)結(jié)果
result = sqlSession.selectOne(command.getName(), param); } break; case FLUSH: //
執(zhí)行刷新操作 result = sqlSession.flushStatements(); break; default: throw new
BindingException("Unknown execution method for: " + command.getName()); } return
result; }
如上,execute 方法主要由一個(gè) switch 語句組成,用于根據(jù) SQL
類型執(zhí)行相應(yīng)的數(shù)據(jù)庫操作。我們先來看看是參數(shù)的處理方法convertArgsToSqlCommandParam是如何將方法參數(shù)數(shù)組轉(zhuǎn)化成Map的
public Object convertArgsToSqlCommandParam(Object[] args) { return
paramNameResolver.getNamedParams(args); }public Object getNamedParams(Object[]
args) {final int paramCount = names.size(); if (args == null || paramCount == 0
) {return null; } else if (!hasParamAnnotation && paramCount == 1) { return
args[names.firstKey()]; }else { //創(chuàng)建一個(gè)Map,key為method的參數(shù)名,值為method的運(yùn)行時(shí)參數(shù)值 final
Map<String, Object> param =new ParamMap<Object>(); int i = 0; for
(Map.Entry<Integer, String> entry : names.entrySet()) { // 添加 <參數(shù)名, 參數(shù)值> 鍵值對(duì)到
param 中 param.put(entry.getValue(), args[entry.getKey()]); final String
genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1); if (!
names.containsValue(genericParamName)) { param.put(genericParamName,
args[entry.getKey()]); } i++; } return param; } }
我們看到,將Object[] args轉(zhuǎn)化成了一個(gè)Map<參數(shù)名, 參數(shù)值> ,接著我們就可以看查詢過程分析了,如下
// 執(zhí)行查詢操作,并返回一個(gè)結(jié)果 result = sqlSession.selectOne(command.getName(), param);
我們看到是通過sqlSession來執(zhí)行查詢的,并且傳入的參數(shù)為command.getName()和param,也就是
namespace.methodName(mapper.EmployeeMapper.getAll)和方法的運(yùn)行參數(shù)。
查詢操作我們下一篇文章單獨(dú)來講
?
熱門工具 換一換