在衆多的ORM框架中,Mybatis如今愈來愈多的被互聯網公司所使用;主要緣由仍是由於Mybatis使用簡單,操做靈活;本系列準備經過提問的方式來從源碼層來更加深刻的瞭解Mybatis。git
咱們最經常使用的使用Mybatis的方式是獲取一個Mapper接口對象,而後經過接口的方法名映射到配置文件中的statement;大體的代碼格式以下所示:github
public class BlogMain {
public static void main(String[] args) throws IOException {
String resource = "mybatis-config-sourceCode.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sqlSessionFactory.openSession();
try {
BlogMapper mapper = session.getMapper(BlogMapper.class);
// 常規方法
System.out.println(mapper.selectBlog(101));
// Object的方法
System.out.println(mapper.hashCode());
// public default方法
System.out.println(mapper.defaultValue());
// 父接口中的方法
System.out.println(mapper.selectParent(101));
} finally {
session.close();
}
}
複製代碼
以上除了使用常規的接口方法selectBlog,還使用了類型徹底不一樣的方法分別是:Object內部方法,接口的默認方法,以及父類中的方法,固然Mybatis都能很好的處理,那咱們每次調用的接口的方法時,Mybatis是如何幫咱們執行sql的;接下來將進行分析,同時一併看一下是如何處理這些特殊方法的。sql
經過使用動態代理,生成一個代理類,而後經過Mapper裏面的方法名稱和配置文件中的statement名稱作映射,而後根據statement類型分別執行sql。緩存
首先分析getMapper操做,而後再分析執行Mapper中的相關方法是如何調用相關sql的;bash
如上代碼中使用openSession建立的一個DefaultSqlSession類,此類中包含了執行了sql的增刪改查等操做,另外還包含了getMapper方法:session
private final Configuration configuration;
@Override
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
複製代碼
此處的Configuration是關鍵,也是Mybatis的一個核心類,能夠先簡單理解爲就是咱們的配置文件mybatis-config.xml的一個映射類;繼續往下走:mybatis
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
複製代碼
這裏引出了MapperRegistry,全部的Mapper都在此類中註冊,經過key-value的形式存放,key對應xx.xx.xxMapper,而value存放的是Mapper的代理類,具體如類MapperRegistry代碼所示:app
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
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);
}
}
複製代碼
能夠看到每次getMapper的時候其實都是去knownMappers獲取一個MapperProxyFactory類,至因而什麼時候往knownMappers中添加數據的,是在解析mybatis-config.xml配置文件的時候,解析到mappers標籤的時候,以下所示:框架
<mappers>
<mapper resource="mapper/BlogMapper.xml" />
</mappers>
複製代碼
繼續解析裏面的BlogMapper.xml,會把BlogMapper.xml中的namespace做爲key,以下所示:ide
<mapper namespace="com.mybatis.mapper.BlogMapper">
<select id="selectBlog" parameterType="long" resultType="blog">
select * from blog where id = #{id}
</select>
</mapper>
複製代碼
namespace是必填的,此值做爲MapperRegistry中的knownMappers的key,而value就是此Mapper類的一個代理工廠類MapperProxyFactory,每次調用getMapper的時候都會newInstance一個實例,代碼以下:
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
複製代碼
能夠發現經過jdk自帶的代理類Proxy.newProxyInstance(...)建立了一個代理類,設置MapperProxy做爲InvocationHandler,在實例化MapperProxy時同時傳入了一個methodCache對象,此對象是一個Map,存放的就是每一個Mapper裏面的方法,這裏定義爲MapperMethod;至此咱們瞭解了getMapper的大體流程,下面繼續看執行方法;
由上分析可知,經過getMapper返回的是Mapper的一個動態代理類,而且指定了MapperProxy做爲InvocationHandler,因此咱們每次調用方法時其實調用了MapperProxy中的invoke方法:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
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;
}
複製代碼
以上有兩個判斷分別是:是不是Object類中的方法,以及是不是默認方法;這兩種狀況也是我在上面實例中展現的緣由,是不須要映射xxMapper.xml中的statement的,能夠直接執行返回結果;接下來就是非這兩種狀況的處理,這裏使用的緩存作優化,也就是說我若是連續調用同一個Mapper下面同一個方法屢次,不會建立多個MapperMethod;爲何須要緩存,主要是由於每次實例化MapperMethod須要初始化不少東西,以下所示:
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
複製代碼
主要是SqlCommand和MethodSignature這兩個實例,這兩個類大體意思是:SqlCommand保存了方法是何種操做類型包括增刪改查,未知,刷新以及對應的xxMapper.xml中的statement的ID;MethodSignature保存了方法的簽名包括返回類型等;此處咱們大體瞭解一下就行,後面的文章會繼續進行詳細介紹;有了上面初始化好的這些參數就能夠執行調用MapperMethod的execute方法了:
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;
}
複製代碼
根據SqlCommand的命令類型分別執行不一樣的sql;執行時還須要對參數進行處理,執行完以後還須要對結果集進行處理,固然還有緩存結果集的處理;此處咱們大體瞭解一下就行,後面每一個點會單獨進行提問介紹;好了執行完以後就能夠返回結果了;
本文大體瞭解到經過getMapper獲取了一個xxMapper接口的動態代理類,而且每次get操做都會獲取一個新的對象,Mybatis並無對此類進行緩存,而是對xxMapper接口中的每一個方法(MapperMethod)進行緩存,這裏的緩存方法是被每一個動態代理類對象所共享的,沒有對代理類進行緩存主要是由於每一個類能夠有本身的sqlSession;另一點是Mybatis處理了是不是Object類中的方法,以及是不是默認方法兩種特殊的方法;最後根據方法名稱映射的statement執行相關的sql。