目錄
一.一個核心問題
爲什麼通過mapper接口就可以執行數據庫操作(mapper接口沒有實現類型)。
Ans: 通過mapper接口動態代理的增強+ 配置文件解讀。
動態代理增強就是binding模塊的功能。
從表現來講,bingding的主要功能是將面向mapper接口編程轉換成session中對應的方法執行。
二 、binding模塊分析
2.1、binging 模塊核心組件關係如下圖:
- MapperRegistry : mapper 接口和對應動態代理工廠 的註冊中心。
- MapperProxyFactory : 用於生成mapper接口動態代理的實例對象;
- MapperProxy:實現了InvocationHandler接口,它是增強mapper接口的實現;
- MapperMethod:封裝了Mapper接口中對應方法的信息,以及對應的sql語句的信息;它是mapper接口與映射配置文件中sql語句的橋樑;
2.2、MapperRegistry解析
MapperRegistry是 Mapper 接口及其對應的代理對象工廠的註冊中心。
MapperRegistry中字段的含義和功能如下:
private final Configuration config;//Configuration對象,mybatis全局唯一的
//記錄了mapper接口與對應MapperProxyFactory之間的關係
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap();
public MapperRegistry(Configuration config) {
this.config = config;
}
在Mybatis初始化過程中會讀取配置文件以及mapper接口中的註解信息,並調用MapperRegistry.addMapper()方法將mapper接口的工廠類添加到mapper註冊中心(MapperRegistry.knownMappers 集合 )該集合的 key 是 Mapper 接口對應的 Class 對象, value 爲 MapperProxyFactory 工廠對象,可以爲 Mapper 接口創建代理對象, MapperProxyFactory 的實現馬上就會分析到。 MapperRegistry.addMapper()方法的 部分實現如下:
//將mapper接口的工廠類添加到mapper註冊中心
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 {
//實例化Mapper接口的代理工程類,並將信息添加至knownMappers
knownMappers.put(type, new MapperProxyFactory<T>(type));
//解析接口上的註解信息,並添加至configuration對象
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
在需要執行某sql語句時候,會先調用 MapperRegistry.getMapper()方法獲得Mapper接口的代理對象,例如:
TUserMapper mapper = sqlSession.getMapper(TUserMapper.class); mapper實際上是通過Jdk動態代理爲TUserMapper接口生產代理對象。其代碼如下所示:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//根據type獲取 其動態代理工廠類
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
//返回mapper接口的動態代理對象
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
2.3、MapperProxyFactory
MapperProxyFactory 主要負責創建代理對象, 其中核心字段的含義和功能如下:
//mapper接口的class對象
private final Class<T> mapperInterface;
//key是mapper接口中的某個方法的method對象,value是對應的MapperMethod,
// MapperMethod對象不記錄任何狀態信息,所以它可以在多個代理對象之間共享
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap();
MapperProxyFactory.newInstance(SqlSession sqlSession)方法實現了創建mapperInterface接口的代理對象的功能,其具體代碼如下:
public T newInstance(SqlSession sqlSession) {
//每次調用都會創建新的MapperProxy對象
final MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
//創建實現了mapper接口的動態代理對象
/**
* jdk動態代理獲得代理對象
* 參數一 被代理對象的類加載器
* 參數二 被代理對象的接口
* 參數三 代理對象
*/
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
2.4 MapperProxy
MapperProxy 實現了 lnvocationHandler 接 口,在介紹 JDK 動態代理,該接口的實現是代理對象的核心邏輯,這裏不再重複描述。 MapperProxy 中核心宇段的含義和功能 如下:
private final SqlSession sqlSession;//當前查詢的sqlSession對象
private final Class<T> mapperInterface;// Mapper接口對應的class對象
////key是mapper接口中的某個方法的method對象,value是對應的MapperMethod,
// MapperMethod對象不記錄任何狀態信息,所以它可以在多個代理對象之間共享
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;
}
MapperProxy. invoke()方法是代理對象執行的主要邏輯, 實現如下:
/**
*
* @param proxy 代理對象
* @param method 被代理對象中方法
* @param args 方法中餓入參
* @return 代理對象
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//如果目標方法繼承自 Object ,則直接調用目標方法
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (method.isDefault()) {// ..針對 Java7 以上版本對動態類型語言的支持
if (privateLookupInMethod == null) {
return invokeDefaultMethodJava8(proxy, method, args);
} else {
return invokeDefaultMethodJava9(proxy, method, args);
}
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
//methodCache 緩存中獲取MapperMethod 對象,如採緩存中沒有,則創建新的 MapperMethod 對象並添加到緩存中
final MapperMethod mapperMethod = cachedMapperMethod(method);
//調用 MapperMethod.execute()方法執行 SQL 語句
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
return methodCache.computeIfAbsent(method,
k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
2.5 MapperMethod
MapperMethod 中封裝了Mapper接口中對應方法的信息,以及對應SQL語句的信息。我們可以把MapperMethod的看着連接Mapper接口以及映射配置文件中定義的SQL語句的橋樑。
MapperMethod中各個字段的信息如下:
private final SqlCommand command;//從configuration中獲取方法的命名空間.方法名以及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);
}
2.5.1 SqlCommand
SqlCommand 是MapperMethod中定義的內部類,他使用name字段記錄SQL語句的名稱,使用 type 宇段( SqlCommandType 類型)記錄了 SQL 語句的類型。 SqlCommandType 是枚舉類 型,有效取值爲 UNKNOWN、 INSERT、 UPDATE、 DELETE、 SELECT、 FLUSH。
public static class SqlCommand {
//sql的名稱,命名空間+方法名稱
private final String name;
//獲取sql語句類型
private final SqlCommandType type;
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
final String methodName = method.getName();//獲取名稱
final Class<?> declaringClass = method.getDeclaringClass();
//從configuration中獲取mappedStatement
// 用於存儲mapper.xml文件中的select、insert、update和delete節點,同時還包
//含了這些節點的很多重要屬性;
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 {//如果如果mappedStatement不爲空
name = ms.getId();//獲取sql的名稱,命名空間+方法名稱
type = ms.getSqlCommandType();//獲取sql語句的類型
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
}
public String getName() {
return name;
}
public SqlCommandType getType() {
return type;
}
//從configuration中獲取mappedStatement
private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
Class<?> declaringClass, Configuration configuration) {
String statementId = mapperInterface.getName() + "." + methodName;//sql語句的id爲命名空間+方法名字
if (configuration.hasStatement(statementId)) {
return configuration.getMappedStatement(statementId);//從configuration中獲取mappedStatement
} else if (mapperInterface.equals(declaringClass)) {
return null;
}
for (Class<?> superInterface : mapperInterface.getInterfaces()) {
if (declaringClass.isAssignableFrom(superInterface)) {
MappedStatement ms = resolveMappedStatement(superInterface, methodName,
declaringClass, configuration);
if (ms != null) {
return ms;
}
}
}
return null;
}
}
2.5.2 ParamNameResolver
ParamNameResolver(Resolver [riˈzɑlvər] 解析器; ) 解析mapper接口方法中的入參;
在 MethodSignature 中, 會使用 ParamNameResolver 處理 Mapper 接口中定義的方法的參數 列表。 ParamNameResolver 使用 name 宇段( SortedMap<Integer, String>類型)記錄了參數在參 數列表中的位置索引與參數名稱之間的對應關係,其中 key 表示參數在參數列表中的索引位置, value 表示參數名稱,參數名稱可以通過@Param 註解指定,如果沒有指定@Param 註解,則使 用參數索引作爲其名稱。
2.5.3 MethodSignature
MethodSignature(Signature [ˈsɪɡnətʃər] 明顯特徵;)封裝mapper接口方法的相關信息(入參,返回類型);
MethodSignature 中核心內容如下:
private final boolean returnsMany;//返回參數是否爲集合類型或數組
private final boolean returnsMap;//返回參數是否爲map
private final boolean returnsVoid;//返回值爲空
private final boolean returnsCursor;//返回值是否爲遊標類型
private final boolean returnsOptional;//返回值是否爲Optional
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.returnsOptional = Optional.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 語句對應的參數列表,它是通過上面介紹的
public Object convertArgsToSqlCommandParam(Object[] args) {
return paramNameResolver.getNamedParams(args);
}
2.5.4 MapperMethod.execute()
介紹完 MapperMethod 中定義的內部類, 回到 MapperMethod 繼續分析。 MapperMethod 中 最核心的方法是 execute()方法,它會根據 SQL 語句的類型調用 SqISession 對應的方法完成數據 庫操作。 SqlSession 是 MyBatis 的核心組件之一,其具體實現後面會詳細介紹,這裏讀者暫時只 需知道它負責完成數據庫操作即可。
MapperMethod.execute()方法的具體實現如下:
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
//根據 SQL 語句的類型調用 SqlSession 對應的方法
switch (command.getType()) {
case INSERT: {
//使用 ParamNameResolver 處理 args[]數組(用戶傳入的實參列表),將用戶傳入的 實參與
// 指定參數名稱關聯起
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
}
case DELETE: {
}
case SELECT:
//處理返回值爲 void 且 ResultSet 通過 ResultHandler 處理的方法
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {///處理返回值爲集合或數組的方法
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {//處理返回值爲 Map 的方法
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {//處理返回值爲 Cursor 的方法
result = executeForCursor(sqlSession, args);
} else {//處理返回值爲單一對象的方法
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
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;
}
三 、數據庫執行流程分析
3.1 流程分析
@Test
public void testManyParamQuery(){
String resource = "mybatis-config.xml";
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(resource);
} catch (IOException e) {
e.printStackTrace();
}
// 1.讀取mybatis配置文件創SqlSessionFactory
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 2.獲取sqlSession 方法級別的
SqlSession sqlSession = sqlSessionFactory.openSession();
//// 3.獲取對應mapper
TUserMapper mapper = sqlSession.getMapper(TUserMapper.class);
String email = "qq.com";
Byte sex = 1;
// 第三種方式用對象
EmailSexBean esb = new EmailSexBean();
esb.setEmail(email);
esb.setSex(sex);
List<TUser> list3 = mapper.selectByEmailAndSex3(esb);
System.out.println("javabean:"+list3.size());
}
3.1.1 創建動態代理對象流程
//// 3.獲取對應mapper
TUserMapper mapper = sqlSession.getMapper(TUserMapper.class);
step1.SqlSession的唯一實現是DefaultSqlSession,getMapper方法如下:
@Override
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
step2.調用Configuration的getMapper方法:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
/*mapper接口的動態代理註冊中心*/
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
public MapperRegistry(Configuration config) {
this.config = config;
}
step3.調用MapperRegistry的getMapper方法:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//根據type獲取 其動態代理工廠類
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
//返回mapper接口的動態代理對象
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
step4.調用MapperProxyFactory的newInstance:
public T newInstance(SqlSession sqlSession) {
//每次調用都會創建新的MapperProxy對象
final MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
//創建實現了mapper接口的動態代理對象
/**
* jdk動態代理獲得代理對象
* 參數一 被代理對象的類加載器
* 參數二 被代理對象的接口
* 參數三 代理對象
*/
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
最後DEBUG可以看出來得到的mapper實際上是 mapper接口class對象的動態代理對象
3.1.2 執行查詢流程
List<TUser> list3 = mapper.selectByEmailAndSex3(esb);
step1.動態代理最終要調用的InvocationHandler——mapperProxy
/**
*
* @param proxy 代理對象
* @param method 被代理對象中方法
* @param args 方法中餓入參
* @return 代理對象
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//如果目標方法繼承自 Object ,則直接調用目標方法
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (method.isDefault()) {// ..針對 Java7 以上版本對動態類型語言的支持
if (privateLookupInMethod == null) {
return invokeDefaultMethodJava8(proxy, method, args);
} else {
return invokeDefaultMethodJava9(proxy, method, args);
}
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
//methodCache 緩存中獲取MapperMethod 對象,如採緩存中沒有,則創建新的 MapperMethod 對象並添加到緩存中
final MapperMethod mapperMethod = cachedMapperMethod(method);
//調用 MapperMethod.execute()方法執行 SQL 語句
return mapperMethod.execute(sqlSession, args);
}
step2.獲取MapperMethod對象
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
MapperMethod封裝了Mapper接口中對應方法的信息,以及對應的sql語句的信息,它是mapper接口與映射配置文件中sql語句的橋樑,MapperMethod對象不記錄任何狀態信息,所以它可以在多個代理對象之間共享。
- SqlCommand:從configuration中獲取方法的命名空間、方法名以及SQL語句的類型;
- MethodSignature:封裝mapper接口方法的相關信息(入參、返回類型);
- ParamNameResolver:解析mapper接口方法中的入參。
step2-1.獲取SqlCommand
step2-2.獲取MethodSignature
step3.調用MapperMethod.execute執行sql
return mapperMethod.execute(sqlSession, args);
四、總結
step1.通過動態代理創建了代理對象MapperProxy
step2.MapperProxy最後調用的是MapperMethod的方法執行
最後的核心落在了MapperMethod,其封裝了Mapper接口中對應方法的信息,以及對應的sql語句的信息,它是mapper接口與映射配置文件中sql語句的橋樑。
參考
- 1)享學課堂Lison老師筆記