Mybatis中mapper的實現原理
本文作者:鍾昕靈,叩丁狼高階講師。原創文章,轉載請註明出處。
相信只要是使用過MyBatis開發的同學,都有使用其中的Mapper介面來開發,因為確實是很方便,方便到我們只需要編寫介面而不需要寫實現類,就能夠完成對資料庫的CRUD操作,但是不知道大家有沒有去思考過,如果我們真的只有Mapper介面的話,程式又是如何去完成實際的業務的呢?來看看下面的程式碼
cn.wolfcode.mybatis.mapper.UserMapper介面
public interface UserMapper {
void save(User u);
}
UserMapper.xml對映檔案
<mapper namespace="cn.wolfcode.mybatis.mapper.UserMapper"> <insert id="save"> INSERT INTO user (id, username, password) VALUES (NULL, #{username}, #{password}) </insert> </mapper>
UserServiceImpl業務方法
public void save(User u) throws IOException { SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml")); SqlSession session = factory.openSession(); UserMapper mapper = session.getMapper(UserMapper.class); mapper.save(u); session.commit(); session.close(); }
從上面的程式碼中可以看出,我們只需要給MyBatis提供Mapper介面和與之匹配的對映檔案,就能夠讓MyBatis按照我們的需求執行到對應的SQL
這裡的實現原理就是我們前面所講過的 動態代理,接下來我們看一波原始碼
通過debug斷點除錯,我們可以依次看到下面的程式碼
DefaultSqlSession:
public <T> T getMapper(Class<T> type) { return this.configuration.getMapper(type, this); } Configuration: public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return this.mapperRegistry.getMapper(type, sqlSession); } MapperRegistry: public <T> T getMapper(Class<T> type, SqlSession sqlSession) { MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type); if(mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } else { try { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception var5) { throw new BindingException("Error getting mapper instance. Cause: " + var5, var5); } } }
該方法中最關鍵程式碼:mapperProxyFactory.newInstance(sqlSession); MapperProxyFactory是一個建立MapperProxy的工廠類,呼叫其中的newInstance方法可以獲取到一個代理物件,繼續往下看
MapperProxyFactory:
protected T newInstance(MapperProxy<T> mapperProxy) {
return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
return this.newInstance(mapperProxy);
}
在該類中可以看到,
Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
最終由JDK的動態代理,動態的為我們在記憶體中建立了一個代理物件
到此,我們已經看到了一部分真相,就是我們為mybatis提供Mapper介面,而mybatis使用JDK的動態代理為我們生成實現類
相信大家和我一樣,還想繼續瞭解一下,在這個代理類中具體為我們做了什麼,那好,我們繼續
如果大家瞭解JDK的動態代理的話,那麼就應該知道我們現在最關心的應該是InvocationHandler的實現,從上面的程式碼中可以看到,它叫做MapperProxy 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;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
MapperMethod mapperMethod = this.cachedMapperMethod(method);
return mapperMethod.execute(this.sqlSession, args);
}
}
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = (MapperMethod)this.methodCache.get(method);
if(mapperMethod == null) {
mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());
this.methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
}
在invoke方法中可以看到,如果我們呼叫的是Object中的方法,不做任何處理,直接呼叫,否則執行: mapperMethod.execute(this.sqlSession, args);
MapperMethod:
public Object execute(SqlSession sqlSession, Object[] args) {
Object param;
Object result;
if(SqlCommandType.INSERT == this.command.getType()) {
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
} else if(SqlCommandType.UPDATE == this.command.getType()) {
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
} else if(SqlCommandType.DELETE == this.command.getType()) {
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
} else {
if(SqlCommandType.SELECT != this.command.getType()) {
throw new BindingException("Unknown execution method for: " + this.command.getName());
}
if(this.method.returnsVoid() && this.method.hasResultHandler()) {
this.executeWithResultHandler(sqlSession, args);
result = null;
} else if(this.method.returnsMany()) {
result = this.executeForMany(sqlSession, args);
} else if(this.method.returnsMap()) {
result = this.executeForMap(sqlSession, args);
} else {
param = this.method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(this.command.getName(), param);
}
}
if(result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
throw new BindingException("Mapper method '" + this.command.getName()
+ " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
} else {
return result;
}
}
主要將SQL分為兩類執行,DML和SQL 如果是DML,呼叫SQLSession中對應的方法執行,並且使用rowCountResult方法根據方法的返回值和受影響的行數做處理
如果是查詢,則要根據方法的返回值的型別來執行不同的方法 如果Collection系的集合獲取陣列來接收,使用selectList方法執行查詢 如果使用Map集合,呼叫selectMap方法執行查詢 否則,呼叫selectOne執行查詢
相信,原始碼看到這裡,大家心裡應該很清楚MyBatis中Mapper介面的使用原理了
最後總結一下: MapperProxyFactory中,使用JDK的動態代理生成Mapper介面的代理代理類 由動態處理器MapperProxy中呼叫MapperMethod中的方法處理執行SQL 最後,在MapperMethod中根據執行的方法返回值決定呼叫SqlSession中的對應方法執行SQL