幽沉谢世事,俯默窥唐虞。这篇文章主要讲述MyBatis 中 Mapper 接口的使用原理相关的知识,希望能为你提供帮助。
MyBatis 中 Mapper 接口的使用原理MyBatis 3 推荐使用 Mapper 接口的方式来执行 xml 配置中的 SQL,用起来很方便,也很灵活。在方便之余,想了解一下这是如何实现的,之前也大致知道是通过 JDK 的动态代理做到的,但这次想知道细节。
东西越多就越复杂,所以就以一个简单的仅依赖 MyBatis 3.4.0 的 CRUD 来逐步了解 Mapper 接口的调用。
通常是通过 xml 配置文件来创建SqlSessionFactory
对象,然后再获取SqlSession
对象,接着获取自定义的 Mapper 接口的代理对象,最后调用接口方法,示例如下:
/**
*
* @author xi
* @date 2018/10/01 14:12
*/
public class Demo {
public static void main(String[] args) throws IOException {
String resource = "
mybatis-config.xml"
;
InputStream is = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory =
new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = sqlSessionFactory.openSession();
RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
Role role = roleMapper.getRole(1L);
System.out.println(role);
}
}
如何解析配置文件,创建工厂,获取会话皆不是本次关注的重点,直接看下面这行即可:
RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
获取自定义 Mapper 代理对象的方法位于:
org.apache.ibatis.session.SqlSession#getMapper
,还是个范型方法/**
* Retrieves a mapper.
* @param <
T>
the mapper type
* @param type Mapper interface class
* @return a mapper bound to this SqlSession
*/
<
T>
T getMapper(Class<
T>
type);
实现该方法的子类有:
DefaultSqlSession
和SqlSessionManager
,这里关注默认实现即可:org.apache.ibatis.session.defaults.DefaultSqlSession#getMapper
@Override
public <
T>
T getMapper(Class<
T>
type) {
return configuration.<
T>
getMapper(type, this);
}
这里面出现了 Configuration 对象,简单来说就是包含了 xml 配置解析内容的对象,同样它也不是现在关注的重点,继续往下跟进:
org.apache.ibatis.session.Configuration#getMapper
public <
T>
T getMapper(Class<
T>
type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
这里出现了
MapperRegistry
对象,它是解析 Mapper.xml 中的内容(mapper
标签中的namespace
就包含了 Mapper 接口的全限定名称)得来的,含有一个 HashMap 类型的成员变量org.apache.ibatis.binding.MapperRegistry#knownMappers
,key 是 Mapper 接口的Class
对象,value 是org.apache.ibatis.binding.MapperProxyFactory
,从名称就可以看出是用来创建 Mapper 接口的代理对象的工厂,后面会用到。具体这个
knownMappers
是怎么填充的,详见org.apache.ibatis.binding.MapperRegistry#addMapper
方法,暂时不管,先往下走:org.apache.ibatis.binding.MapperRegistry#getMapper
@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);
}
}
根据 Mapper 接口的类型,从
knownMappers
中拿到对应的工厂,然后创建代理对象,继续跟进:org.apache.ibatis.binding.MapperProxyFactory#newInstance(org.apache.ibatis.session.SqlSession)
public T newInstance(SqlSession sqlSession) {
final MapperProxy<
T>
mapperProxy = new MapperProxy<
T>
(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
这里又出现了一个
MapperProxy
对象,理解起来应该是一个代理对象,打开一看它实现了java.lang.reflect.InvocationHandler
接口,这是挂羊头卖狗肉哇。先不看狗肉了,继续跟进:
org.apache.ibatis.binding.MapperProxyFactory#newInstance(org.apache.ibatis.binding.MapperProxy<
T>
)
@SuppressWarnings("
unchecked"
)
protected T newInstance(MapperProxy<
T>
mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
在这,看到了熟悉的
java.lang.reflect.Proxy
,这里的mapperInterface
是创建工厂时传入的 Mapper 接口。真正的 Mapper 接口的代理对象此时才产生,是真羊头。这不是既熟悉又陌生的 JDK 动态代理么,说它熟悉,因为前面的狗肉
mapperProxy
是一个InvocationHandler
对象,它拦截了所有对代理对象接口方法的调用。说它陌生是因为之前使用 JDK 动态代理时会有接口的具体实现子类,这里没看到。那就先看看org.apache.ibatis.binding.MapperProxy
:/**
* @author Clinton Begin
* @author Eduardo Macarron
*/
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;
}@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 判断 method 是不是 Object 类的方法,如 hashCode()、toString()
if (Object.class.equals(method.getDeclaringClass())) {
try {// 如果是,则调用当前 MapperProxy 对象的这些方法
// 跟 Mapper 接口的代理对象没关系
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
// 到这了,说明调用的是接口中的方法,具体的执行就不是本次关注的重点了
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}// 对 MapperMethod 做了缓存,这个 methodCache 是个 ConcurrentHashMap,在 MapperProxyFactory 中创建的
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;
}}
具体说明都在代码注释里面,没啥好说的了。
总结
- 通过 JDK 动态代理模式,创建 Mapper 接口的代理对象,拦截对接口方法的调用;
- Mapper 接口中不能使用重载,具体原因参见
org.apache.ibatis.binding.MapperMethod.SqlCommand#SqlCommand
,MyBatis 是通过mapperInterface.getName() + " ." + method.getName()
去获取 xml 中解析出来的 SQL 的,具体可能还要看一下org.apache.ibatis.session.Configuration#mappedStatements
。
推荐阅读
- Android(导入所需的系统jar包到Android studio)
- Cannot access org.springframework.context.ConfigurableApplicationContext
- Django 创建app 应用,数据库配置
- 最新机器人面试题精品推荐
- 推荐!Ruby on Rails面试题精品合集
- call, apply, bind
- What happened when new an object in JVM ?
- Fiddler安卓手机APP抓包
- 安卓其他SpringBoot官网快速集成方法