刚开始使用Mybaits的同学有没有这样的疑惑,为什么我们没有编写Mapper的实现类,却能调用Mapper的方法呢?本篇文章我带大家一起来解决这个疑问

上一篇文章我们获取到了DefaultSqlSession,接着我们来看第一篇文章测试用例后面的代码
EmployeeMapper employeeMapper = sqlSession.getMapper(Employee.class); List
<Employee> allEmployees = employeeMapper.getAll();
为 Mapper 接口创建代理对象

我们先从 DefaultSqlSession 的 getMapper 方法开始看起,如下:
1 public <T> T getMapper(Class<T> type) { 2 return
configuration.<T>getMapper(type,this); 3 } 4 5 // Configuration 6 public
<T> T getMapper(Class<T> type, SqlSession sqlSession) { 7 return
mapperRegistry.getMapper(type, sqlSession); 8 } 9 10 // MapperRegistry 11
public <T> T getMapper(Class<T> type, SqlSession sqlSession) { 12 // 从
knownMappers 中获取与 type 对应的 MapperProxyFactory 13 final MapperProxyFactory<T>
mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); 14 if
(mapperProxyFactory ==null) { 15 throw new BindingException("Type " + type + "
is not known to the MapperRegistry."); 16 } 17 try { 18 // 创建代理对象 19 return
mapperProxyFactory.newInstance(sqlSession);20 } catch (Exception e) { 21 throw
new BindingException("Error getting mapper instance. Cause: " + e, e); 22 } 23
}
这里最重要就是两行代码,第13行和第19行,我们接下来就分析这两行代码

获取MapperProxyFactory

根据名称看,可以理解为Mapper代理的创建工厂,是不是Mapper的代理对象由它创建呢?我们先来回顾一下knownMappers
集合中的元素是何时存入的。这要在我前面的文章中找答案,MyBatis 在解析配置文件的 <mappers> 节点的过程中,会调用 MapperRegistry
的 addMapper 方法将 Class 到 MapperProxyFactory 对象的映射关系存入到
knownMappers。有兴趣的同学可以看看我之前的文章,我们来回顾一下源码:
private void bindMapperForNamespace() { // 获取映射文件的命名空间 String namespace =
builderAssistant.getCurrentNamespace();if (namespace != null) { Class<?>
boundType =null; try { // 根据命名空间解析 mapper 类型 boundType =
Resources.classForName(namespace); } catch (ClassNotFoundException e) { } if
(boundType !=null) { // 检测当前 mapper 类是否被绑定过 if (!
configuration.hasMapper(boundType)) { configuration.addLoadedResource(
"namespace:" + namespace); // 绑定 mapper 类 configuration.addMapper(boundType);
} } } }// Configuration public <T> void addMapper(Class<T> type) { // 通过
MapperRegistry 绑定 mapper 类 mapperRegistry.addMapper(type); } // MapperRegistry
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 { /* * 将
type 和 MapperProxyFactory 进行绑定,MapperProxyFactory 可为 mapper 接口生成代理类*/
knownMappers.put(type,new MapperProxyFactory<T>(type)); MapperAnnotationBuilder
parser= new MapperAnnotationBuilder(config, type); // 解析注解中的信息 parser.parse();
loadCompleted= true; } finally { if (!loadCompleted) {
knownMappers.remove(type); } } } }
在解析Mapper.xml的最后阶段,获取到Mapper.xml的namespace,然后利用反射,获取到namespace的Class,并创建一个
MapperProxyFactory的实例,namespace的Class作为参数,最后将namespace的Class为key,
MapperProxyFactory的实例为value存入knownMappers。

注意,我们这里是通过映射文件的命名空间的Class当做knownMappers的Key。然后我们看看
getMapper方法的13行,是通过参数Employee.class也就是Mapper接口的Class来获取
MapperProxyFactory,所以我们明白了为什么要求xml配置中的namespace要和和对应的Mapper接口的全限定名了

生成代理对象

我们看第19行代码 return mapperProxyFactory.newInstance(sqlSession);,
很明显是调用了MapperProxyFactory的一个工厂方法,我们跟进去看看
public class MapperProxyFactory<T> { //存放Mapper接口Class private final Class<T>
mapperInterface;private final Map<Method, MapperMethod> methodCache = new
ConcurrentHashMap();public MapperProxyFactory(Class<T> mapperInterface) { this
.mapperInterface = mapperInterface; } public Class<T> getMapperInterface() {
return this.mapperInterface; } public Map<Method, MapperMethod>
getMethodCache() {return this.methodCache; } protected T
newInstance(MapperProxy<T> mapperProxy) { //生成mapperInterface的代理类 return
Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this
.mapperInterface}, mapperProxy); }public T newInstance(SqlSession sqlSession) {
/* * 创建 MapperProxy 对象,MapperProxy 实现了 InvocationHandler 接口,代理逻辑封装在此类中 *
将sqlSession传入MapperProxy对象中,第二个参数是Mapper的接口,并不是其实现类*/ MapperProxy<T>
mapperProxy =new MapperProxy(sqlSession, this.mapperInterface, this
.methodCache);return this.newInstance(mapperProxy); } }
上面的代码首先创建了一个 MapperProxy 对象,该对象实现了 InvocationHandler
接口。然后将对象作为参数传给重载方法,并在重载方法中调用 JDK 动态代理接口为 Mapper接口 生成代理对象。

这里要注意一点,MapperProxy这个InvocationHandler
创建的时候,传入的参数并不是Mapper接口的实现类,我们以前是怎么创建JDK动态代理的?先创建一个接口,然后再创建一个接口的实现类,最后创建一个InvocationHandler并将实现类传入其中作为目标类,创建接口的代理类,然后调用代理类方法时会回调InvocationHandler的invoke方法,最后在invoke方法中调用目标类的方法,但是我们这里调用Mapper接口代理类的方法时,需要调用其实现类的方法吗?不需要,我们需要调用对应的配置文件的SQL,所以这里并不需要传入Mapper的实现类到MapperProxy中,那Mapper接口的代理对象是如何调用对应配置文件的SQL呢?下面我们来看看。

Mapper代理类如何执行SQL?


上面一节中我们已经获取到了EmployeeMapper的代理类,并且其InvocationHandler为MapperProxy,那我们接着看Mapper接口方法的调用
List<Employee> allEmployees = employeeMapper.getAll();

知道JDK动态代理的同学都知道,调用代理类的方法,最后都会回调到InvocationHandler的Invoke方法,那我们来看看这个InvocationHandler(MapperProxy)
public class MapperProxy<T> implements InvocationHandler, Serializable {
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 { // 如果方法是定义在
Object 类中的,则直接调用 if (Object.class.equals(method.getDeclaringClass())) { try {
return method.invoke(this, args); } catch (Throwable var5) { throw
ExceptionUtil.unwrapThrowable(var5); } }else { // 从缓存中获取 MapperMethod
对象,若缓存未命中,则创建 MapperMethod 对象 MapperMethod mapperMethod = this
.cachedMapperMethod(method);// 调用 execute 方法执行 SQL return mapperMethod.execute(
this.sqlSession, args); } } private MapperMethod cachedMapperMethod(Method
method) { MapperMethod mapperMethod= (MapperMethod)this.methodCache.get(method);
if (mapperMethod == null) { //
创建一个MapperMethod,参数为mapperInterface和method还有Configuration mapperMethod = new
MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());
this.methodCache.put(method, mapperMethod); } return mapperMethod; } }
如上,回调函数invoke逻辑会首先检测被拦截的方法是不是定义在 Object 中的,比如 equals、hashCode
方法等。对于这类方法,直接执行即可。紧接着从缓存中获取或者创建 MapperMethod 对象,然后通过该对象中的 execute 方法执行
SQL。我们先来看看如何创建MapperMethod

创建 MapperMethod 对象
public class MapperMethod { //
包含SQL相关信息,比喻MappedStatement的id属性,(mapper.EmployeeMapper.getAll) private final
SqlCommand command;//包含了关于执行的Mapper方法的参数类型和返回类型。 private final MethodSignature
method;public MapperMethod(Class<?> mapperInterface, Method method,
Configuration config) {// 创建 SqlCommand 对象,该对象包含一些和 SQL 相关的信息 this.command = new
SqlCommand(config, mapperInterface, method);// 创建 MethodSignature
对象,从类名中可知,该对象包含了被拦截方法的一些信息 this.method = new MethodSignature(config,
mapperInterface, method); } }
MapperMethod包含SqlCommand 和MethodSignature 对象,我们来看看其创建过程

① 创建 SqlCommand 对象
public static class SqlCommand { //
name为MappedStatement的id,也就是namespace.methodName(mapper.EmployeeMapper.getAll)
private final String name; //SQL的类型,如insert,delete,update private final
SqlCommandType type;public SqlCommand(Configuration configuration, Class<?>
mapperInterface, Method method) {//
拼接Mapper接口名和方法名,(mapper.EmployeeMapper.getAll) String statementName =
mapperInterface.getName() + "." + method.getName(); MappedStatement ms = null;
//检测configuration是否有key为mapper.EmployeeMapper.getAll的MappedStatement if
(configuration.hasStatement(statementName)) {//获取MappedStatement ms =
configuration.getMappedStatement(statementName); } else if (!
mapperInterface.equals(method.getDeclaringClass())) { String parentStatementName
= method.getDeclaringClass().getName() + "." + method.getName(); if
(configuration.hasStatement(parentStatementName)) { ms=
configuration.getMappedStatement(parentStatementName); } }// 检测当前方法是否有对应的
MappedStatement 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 { // 设置 name 和 type 变量
name = ms.getId(); type = ms.getSqlCommandType(); if (type ==
SqlCommandType.UNKNOWN) {throw new BindingException("Unknown execution method
for: " + name); } } } } public boolean hasStatement(String statementName,
boolean validateIncompleteStatements) { //
检测configuration是否有key为statementName的MappedStatement return this
.mappedStatements.containsKey(statementName); }
通过拼接接口名和方法名,在configuration获取对应的MappedStatement,并设置设置 name 和 type 变量,代码很简单

② 创建 MethodSignature 对象

MethodSignature 包含了被拦截方法的一些信息,如目标方法的返回类型,目标方法的参数列表信息等。下面,我们来看一下
MethodSignature 的构造方法。
public static class MethodSignature { private final boolean returnsMany;
private final boolean returnsMap; private final boolean returnsVoid; private
final boolean returnsCursor; private final Class<?> returnType; private final
String mapKey;private final Integer resultHandlerIndex; private final Integer
rowBoundsIndex;private final ParamNameResolver paramNameResolver; public
MethodSignature(Configuration configuration, Class<?> mapperInterface, Method
method) {// 通过反射解析方法返回类型 Type resolvedReturnType =
TypeParameterResolver.resolveReturnType(method, mapperInterface);if
(resolvedReturnTypeinstanceof Class<?>) { this.returnType = (Class<?>)
resolvedReturnType; }else if (resolvedReturnType instanceof ParameterizedType) {
this.returnType = (Class<?>) ((ParameterizedType)
resolvedReturnType).getRawType(); }else { this.returnType =
method.getReturnType(); }// 检测返回值类型是否是 void、集合或数组、Cursor、Map 等 this.returnsVoid
=void.class.equals(this.returnType); this.returnsMany =
configuration.getObjectFactory().isCollection(this.returnType) || this
.returnType.isArray();this.returnsCursor = Cursor.class.equals(this.returnType);
// 解析 @MapKey 注解,获取注解内容 this.mapKey = getMapKey(method); this.returnsMap = this
.mapKey !=null; /* * 获取 RowBounds 参数在参数列表中的位置,如果参数列表中 * 包含多个 RowBounds
参数,此方法会抛出异常*/ this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class
);// 获取 ResultHandler 参数在参数列表中的位置 this.resultHandlerIndex =
getUniqueParamIndex(method, ResultHandler.class); // 解析参数列表 this
.paramNameResolver =new ParamNameResolver(configuration, method); } }
执行 execute 方法

前面已经分析了 MapperMethod 的初始化过程,现在 MapperMethod 创建好了。那么,接下来要做的事情是调用 MapperMethod 的
execute 方法,执行 SQL。传递参数sqlSession和method的运行参数args
return mapperMethod.execute(this.sqlSession, args);
我们去MapperMethod 的execute方法中看看

MapperMethod
public Object execute(SqlSession sqlSession, Object[] args) { Object result; //
根据 SQL 类型执行相应的数据库操作 switch (command.getType()) { case INSERT: { //
对用户传入的参数进行转换,下同 Object param = method.convertArgsToSqlCommandParam(args); //
执行插入操作,rowCountResult 方法用于处理返回值 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()) { // 执行查询操作,并将结果封装在 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); } break; case FLUSH: //
执行刷新操作 result = sqlSession.flushStatements(); break; default: throw new
BindingException("Unknown execution method for: " + command.getName()); } return
result; }
如上,execute 方法主要由一个 switch 语句组成,用于根据 SQL
类型执行相应的数据库操作。我们先来看看是参数的处理方法convertArgsToSqlCommandParam是如何将方法参数数组转化成Map的
public Object convertArgsToSqlCommandParam(Object[] args) { return
paramNameResolver.getNamedParams(args); }public Object getNamedParams(Object[]
args) {final int paramCount = names.size(); if (args == null || paramCount == 0
) {return null; } else if (!hasParamAnnotation && paramCount == 1) { return
args[names.firstKey()]; }else { //创建一个Map,key为method的参数名,值为method的运行时参数值 final
Map<String, Object> param =new ParamMap<Object>(); int i = 0; for
(Map.Entry<Integer, String> entry : names.entrySet()) { // 添加 <参数名, 参数值> 键值对到
param 中 param.put(entry.getValue(), args[entry.getKey()]); final String
genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1); if (!
names.containsValue(genericParamName)) { param.put(genericParamName,
args[entry.getKey()]); } i++; } return param; } }
我们看到,将Object[] args转化成了一个Map<参数名, 参数值> ,接着我们就可以看查询过程分析了,如下
// 执行查询操作,并返回一个结果 result = sqlSession.selectOne(command.getName(), param);
我们看到是通过sqlSession来执行查询的,并且传入的参数为command.getName()和param,也就是
namespace.methodName(mapper.EmployeeMapper.getAll)和方法的运行参数。

查询操作我们下一篇文章单独来讲

 

友情链接
ioDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:ixiaoyang8@qq.com
QQ群:637538335
关注微信