流程原理分析系列:
MyBatis原理分析之擷取SqlSessionFactor
MyBatis原理分析之擷取SqlSession
MyBatis原理分析之擷取Mapper接口的代理對象
MyBatis原理分析之查詢單個對象
本篇博文是原理分析的第三篇。
當使用mapper接口進行CRUD時,其實是其代理對象在發揮作用,SQLsession擷取mapper接口的代理對象時序圖如下:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI0gTMx81dsQWZ4lmZf1GLlpXazVmcvwFciV2dsQXYtJ3bm9CX9s2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xCMy81dvRWYoNHLwEzX5xCMx8FesU2cfdGLwMzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5yMwYzM5MmMyMzMlljYxATYyYzX4MjN0ETM4AzLcZDMyIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLyM3Lc9CX6MHc0RHaiojIsJye.png)
【1】DefaultSqlSession
如下代碼所示,這裡其實是調用了configuration執行個體的方法。該方法是一個泛型方法,參數有
Class<T> type
表示你的接口Class對象,比如
UserMapper.class---interface com.jane.mapper.UserMapper
@Override
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
關于DefaultSqlSession更多詳情參考博文:MyBatis中SqlSessionFactory和SqlSession簡解
【2】Configuration
configuration有常量成員
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
執行個體,mapperRegistry 引用了目前configuration。
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
MapperRegistry屬性和構造方法如下:
public class MapperRegistry {
private final Configuration config;
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
public MapperRegistry(Configuration config) {
this.config = config;
}
//..
}
在MyBatis原理分析之擷取SqlSessionFactory一文中我們可以得知建立SqlSessionFactory時對所有的mapper(xml和接口)進行了解析并為每一個mapper接口建立了MapperProxyFactory對象放入knownMappers 中。
MapperRegistry的addMapper方法如下:
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放入目前解析的mapper對應的MapperProxyFactory執行個體
knownMappers.put(type, new MapperProxyFactory<>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
【3】MapperRegistry
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
代碼解釋如下:
- ① 從knownMappers中擷取目前class對象的mapperProxyFactory執行個體;
MyBatis原理分析之擷取Mapper接口的代理對象 - ② 如果不存在則抛出異常;
- ③ 如果存在則執行
來擷取目前mapperProxyFactory.newInstance(sqlSession)
的代理對象mapper
【4】MapperProxyFactory
MapperProxyFactory主要屬性和構造方法
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
//...
}
可以看到其除了接口的class對象外還維護了一個私有ConcurrentHashMap類型常量
methodCache
。
建立執行個體對象MapperProxy
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
代碼解釋如下:
- ① 根據
、sqlsession
(接口的class對象),以及類型的mapperInterface
建立methodCache
MapperProxy
- ② 為Mapper建立代理對象
這裡需要注意的是
MapperProxy
是一個
InvocationHandler
類型,需要實作
Object invoke(Object proxy, Method method, Object[] args)
方法。
MapperProxy的主要屬性和構造方法
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
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;
}
//...
}
InvocationHandler是什麼呢?InvocationHandler是由代理執行個體的調用處理程式實作的接口。也就是說一個類/接口的代理執行個體的調用處理程式必須實作InvocationHandler接口的invoke方法。可以了解為本文中MapperProxy就是Mapper代理執行個體的調用處理程式。
InvocationHandler的invoke方法如下:
Object invoke(Object proxy, Method method, Object[] args)
proxy:代理執行個體對象
method:目标方法
args:方法入參
Proxy是什麼?Proxy是專門完成代理的操作類,是所有動态代理類的父類,通過此類為一個或多個接口動态地生成實作類。使用 Proxy 生成一個動态代理時,往往并不會憑空産生一個動态代理,這樣沒有太大的意義。通常都是為指定的目标對象生成動态代理
//直接建立一個動态代理對象
static Object newProxyInstance( ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )
loader :定義代理類的類加載器
interfaces:被代理類實作的所有接口
h:代理執行個體的調用處理程式
該方法将會傳回一個代理對象,代理對象有代理調用處理程式--InvocationHandler
根據mapperProxy建立Mapper的代理對象
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
可以看下最終得到的Mapper的代理對象如下(
h 表示其是一個InvocationHandler也就是調用處理程式
):
每個代理執行個體都有一個關聯的調用處理程式InvocationHandler。在代理執行個體上調用方法時,方法調用将被編碼并發送到其調用處理程式的
invoke
方法。
關于java的動态代理更多參考博文:
Java中的代理模式與動(靜)态代理Java中動态代理使用與原理詳解
【5】MapperMethod
上面我們提到了
MapperProxyFactory
有常量成員
methodCache
,在類加載過程中就進行了初始化。
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
根據
MapperProxyFactory
生成
MapperProxy
執行個體時,将
ConcurrentHashMap
類型的methodCache 傳了過去。
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
MapperProxy
實作了
InvocationHandler
接口的
invoke
方法,那麼在使用
Mapper
進行
CRUD
時實際會調用對應的
MapperProxy
的
invoke
方法:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
如上代碼可以看到其先擷取了MapperMethod 執行個體,然後調用了MapperMethod 執行個體的execute方法。
cachedMapperMethod方法
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;
}
代碼解釋如下:
- ① 嘗試從
類型的ConcurrentHashMap
擷取目前methodCache
對應的method
MapperMethod
- ② 如果①沒有擷取到,則建立
執行個體MapperMethod
- ③ 将
放入{method=mapperMethod}
中methodCache
- ④ 傳回MapperMethod 執行個體
這裡先看一下一個Method對象是個什麼?
new MapperMethod(mapperInterface, method, sqlSession.getConfiguration())
new MapperMethod(mapperInterface, method, sqlSession.getConfiguration())
構造方法如下:
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
這裡我們可以看到,其建立了SqlCommand和MethodSignature執行個體。
MapperMethod的UML圖如下所示
再看一下MapperMethod執行個體對象
new SqlCommand(config, mapperInterface, method)
new SqlCommand(config, mapperInterface, method)
SqlCommand是MapperMethod靜态嵌套類,主要屬性是name和SqlCommandType。
public static class SqlCommand {
private final String name;
private final SqlCommandType type;
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
String statementName = mapperInterface.getName() + "." + method.getName();
MappedStatement ms = null;
if (configuration.hasStatement(statementName)) {
ms = configuration.getMappedStatement(statementName);
} else if (!mapperInterface.equals(method.getDeclaringClass())) { // issue #35
String parentStatementName = method.getDeclaringClass().getName() + "." + method.getName();
if (configuration.hasStatement(parentStatementName)) {
ms = configuration.getMappedStatement(parentStatementName);
}
}
if (ms == null) {
if(method.getAnnotation(Flush.class) != null){
name = null;
type = SqlCommandType.FLUSH;
} else {
throw new BindingException("Invalid bound statement (not found): " + statementName);
}
} else {
name = ms.getId();
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
}
//...
}
構造方法如下解釋如下
- ①
解析擷取到mapperInterface.getName() + "." + method.getName()
,如statementName
;com.mybatis.dao.EmployeeMapper.getEmpById
- ② 判斷configuration執行個體的
成員對象中是否有①中的statementName;Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
- ③ 如果存在,則擷取statementName對應的MappedStatement;
- ④ 如果不存在且目前接口Class不是方法的所屬Class,則根據方法的所屬Class的name與方法名字解析新的statementName
- ⑤ 如果configuration的
成員中存在新的statementName,則傳回對應的MappedStatementMap<String, MappedStatement> mappedStatements
- ⑥ 如果最終得到的MappedStatement為null,則判斷方法上面是否有注解
;@Flush
- ⑦ 如果有注解
,則指派@Flush
;name=null,type = SqlCommandType.FLUSH
- ⑧ 如果沒有注解
,則抛出異常@Flush
- ⑨ 如果最終得到的MappedStatement不為null,則指派
。name = ms.getId(); type = ms.getSqlCommandType();
- ⑩ 如果type為UNKNOWN,則抛出異常
SqlCommandType 是一個枚舉類,主要有值
UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH
public enum SqlCommandType {
UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH;
}
hasStatement(statementName)與getMappedStatement(statementName)
configuration.hasStatement(statementName)) ;
configuration.getMappedStatement(statementName);
上面我們看到這樣兩句代碼,代碼表面本身很好了解,我們跟進去看:
hasStatement代碼流程片段如下:
public boolean hasStatement(String statementName) {
return hasStatement(statementName, true);
}
public boolean hasStatement(String statementName, boolean validateIncompleteStatements) {
if (validateIncompleteStatements) {
buildAllStatements();
}
return mappedStatements.containsKey(statementName);
}
getMappedStatement代碼流程片段如下:
public MappedStatement getMappedStatement(String id) {
return this.getMappedStatement(id, true);
}
public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements) {
if (validateIncompleteStatements) {
buildAllStatements();
}
return mappedStatements.get(id);
}
getMappedStatement
方法在以下地方有被調用
可以看到,其都調用了
buildAllStatements();
。那麼這個方法是什麼呢?如下代碼所示,其實就是mybatis提供的
快速失敗
機制
protected void buildAllStatements() {
parsePendingResultMaps();
if (!incompleteCacheRefs.isEmpty()) {
synchronized (incompleteCacheRefs) {
incompleteCacheRefs.removeIf(x -> x.resolveCacheRef() != null);
}
}
if (!incompleteStatements.isEmpty()) {
synchronized (incompleteStatements) {
incompleteStatements.removeIf(x -> {
x.parseStatementNode();
return true;
});
}
}
if (!incompleteMethods.isEmpty()) {
synchronized (incompleteMethods) {
incompleteMethods.removeIf(x -> {
x.resolve();
return true;
});
}
}
}
在該方法上面有如下注釋:
Parses all the unprocessed statement nodes in the cache. It is recommended to call this method once all the mappers are added as it provides fail-fast statement validation.
解析緩存中所有未處理的statement節點。建議在添加所有映射程式後調用此方法,因為它提供
fail fast
語句驗證。
resolveCacheRef、parseStatementNode、resolve都會抛出異常