Mybatis源码学习(四)自定义Mapper方法执行流程
前言
虽然SqlSession提供了CRUD的各种方法,但是操作起来还是比较繁琐,mybatis提供了更好的方法,就是我们此章要详解介绍的
Mapper
上述的各个 insert、update、delete 和 select 方法都很强大,但也有些繁琐,它们并不符合类型安全,对你的 IDE 和单元测试也不是那么友好。因此,使用映射器类来执行映射语句是更常见的做法。
1. 简单的栗子,接口类的调试
1.1 代码截图
测试类
Mapper.xml文件
Mapper接口
1.2代码调试跟踪
刚开始学习的同学可能会在调试被代理接口时不知如何操作,介绍一个使用的方法进行调试
step1 接口调用处断点
从截图中我们可以发现此时获取到的mapper是一个MapperProxy对象,有动态代理基础的应该知道,代理类都是通过执行invoke() 方法进行代理的
step2 找到相应的代理类的invoke()方法,进行断点
通过在代理类的invoke() 方法上断点,我们就可以执行后续的调试工作了
2. MapperProxy介绍
MapperProxy是实现类InvocationHandler接口的类,实现了invoke()方法
2.1 如何获取MapperProxy对象
DefaultSqlSession
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
Configuration
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
MapperRegistry
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
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);
}
}
MapperProxyFactory
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
我们可以看见最终是通过MapperRegistry的内部的一个Map<Class<?>, MapperProxyFactory<?>> knownMappers HashMap获取到Mapper的代理工厂,通过代理工厂的newInstance获取到的MapperProxy.那么knownMappers中何时存放的键值对呢! 通过查看调用链可以发现,在我们通过SqlSessionFactoryBuilder的build方法创建SqlSessionFactory时就已经把Mapper和Mapper的代理工厂放入到HashMap中了.
2.2 MapperProxy内部执行流程
invoke()
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//如果是Object,直接调用当前类方法
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
//非Object类,从缓存中找到Method进行调用
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
cachedInvoker()
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
try {
// A workaround for https://bugs.openjdk.java.net/browse/JDK-8161372
// It should be removed once the fix is backported to Java 8 or
// MyBatis drops Java 8 support. See gh-1929
//Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();
//methodCache 是一个ConcurrentHashMap<Method, MapperMethodInvoker>
//先从methodCache 中获取
MapperMethodInvoker invoker = methodCache.get(method);
if (invoker != null) {
//获取到直接返回
return invoker;
}
//
return methodCache.computeIfAbsent(method, m -> {
//接口中的default修饰的方法
if (m.isDefault()) {
try {
if (privateLookupInMethod == null) {
return new DefaultMethodInvoker(getMethodHandleJava8(method));
} else {
return new DefaultMethodInvoker(getMethodHandleJava9(method));
}
} catch (IllegalAccessException | InstantiationException | InvocationTargetException
| NoSuchMethodException e) {
throw new RuntimeException(e);
}
} else {
//非default方法返回一个PlainMethodInvoker
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
});
} catch (RuntimeException re) {
Throwable cause = re.getCause();
throw cause == null ? re : cause;
}
}
PlainMethodInvoker 内部维护了一个MapperMethod,在调用PlainMethodInvoker 的invoke()时会最终调用MapperMethod的execute方法
interface MapperMethodInvoker {
Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable;
}
private static class PlainMethodInvoker implements MapperMethodInvoker {
private final MapperMethod mapperMethod;
public PlainMethodInvoker(MapperMethod mapperMethod) {
super();
this.mapperMethod = mapperMethod;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
return mapperMethod.execute(sqlSession, args);
}
}
3. MapperMethod介绍
3.1 内部结构
一个公有构造方法和一个公共方法execute()
3.2 execute()详解
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
//判断何种类型的sql
switch (command.getType()) {
case INSERT: {
//把方法参数转换成sql语句中的参数
Object param = method.convertArgsToSqlCommandParam(args);
//调用SqlSession执行命令,并包装结果
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
//把方法参数转换成sql语句中的参数
Object param = method.convertArgsToSqlCommandParam(args);
//调用SqlSession执行命令,并包装结果
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
//把方法参数转换成sql语句中的参数
Object param = method.convertArgsToSqlCommandParam(args);
//调用SqlSession执行命令,并包装结果
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
//改方法没有返回值时,并且方法中有ResultHandler
if (method.returnsVoid() && method.hasResultHandler()) {
//使用ResultHandler的方式调用
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
//方法返回数组获集合时,使用数组方式调用
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
//返回Map时使用Map方式调用
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
//使用游标的方式调用
result = executeForCursor(sqlSession, args);
} else {
//把方法参数转换成sql语句中的参数
Object param = method.convertArgsToSqlCommandParam(args);
//调用SqlSession的selectOne
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
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;
}
convertArgsToSqlCommandParam() 转换参数列表为Sql命令的参数
public Object convertArgsToSqlCommandParam(Object[] args) {
return paramNameResolver.getNamedParams(args);
}
public Object getNamedParams(Object[] args) {
//获取Param注解标记的参数个数
final int paramCount = names.size();
if (args == null || paramCount == 0) {
//没有参数直接返回
return null;
} else if (!hasParamAnnotation && paramCount == 1) {
//没有Param注解,并且只有一个参数
Object value = args[names.firstKey()];
//包装成Map返回
return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null);
} else {
final Map<String, Object> param = new ParamMap<>();
int i = 0;
//遍历参数的Map,取出参数数组中对应名称的参数,名称从names中获得
//放入map中
for (Map.Entry<Integer, String> entry : names.entrySet()) {
param.put(entry.getValue(), args[entry.getKey()]);
// add generic param names (param1, param2, ...)
final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
// ensure not to overwrite parameter named with @Param
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
ParamNameResolver构造方法, 初始化时解析Mapper方法中Param注解对应的参数
public ParamNameResolver(Configuration config, Method method) {
this.useActualParamName = config.isUseActualParamName();
final Class<?>[] paramTypes = method.getParameterTypes();
final Annotation[][] paramAnnotations = method.getParameterAnnotations();
final SortedMap<Integer, String> map = new TreeMap<>();
int paramCount = paramAnnotations.length;
// get names from @Param annotations
//从Param注解标记的参数获取所有的参数名
for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
if (isSpecialParameter(paramTypes[paramIndex])) {
// skip special parameters
continue;
}
String name = null;
for (Annotation annotation : paramAnnotations[paramIndex]) {
if (annotation instanceof Param) {
hasParamAnnotation = true;
name = ((Param) annotation).value();
break;
}
}
if (name == null) {
// @Param was not specified.
if (useActualParamName) {
name = getActualParamName(method, paramIndex);
}
if (name == null) {
// use the parameter index as the name ("0", "1", ...)
// gcode issue #71
name = String.valueOf(map.size());
}
}
//把参数的位置和参数名称放入map
map.put(paramIndex, name);
}
names = Collections.unmodifiableSortedMap(map);
}
增删改时的返回行数处理
private Object rowCountResult(int rowCount) {
final Object result;
if (method.returnsVoid()) {
result = null;
} else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) {
//返回Integer时,直接返回
result = rowCount;
} else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) {
//long包装
result = (long) rowCount;
} else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) {
//boolean包装
result = rowCount > 0;
} else {
throw new BindingException("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType());
}
return result;
}
executeWithResultHandler()无返回值的查询并且参数中有ResultHandler
private void executeWithResultHandler(SqlSession sqlSession, Object[] args) {
//获取MappedStatement
MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(command.getName());
//mapper的方法的返回值必须是void,但是xml或者注解上必须提供返回类型
if (!StatementType.CALLABLE.equals(ms.getStatementType())
&& void.class.equals(ms.getResultMaps().get(0).getType())) {
throw new BindingException("method " + command.getName()
+ " needs either a @ResultMap annotation, a @ResultType annotation,"
+ " or a resultType attribute in XML so a ResultHandler can be used as a parameter.");
}
//把方法参数转换成sql语句中的参数
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
//如果有行数限制,解析分页参数
RowBounds rowBounds = method.extractRowBounds(args);
//调用sqlSession的select方法带上分页的
sqlSession.select(command.getName(), param, rowBounds, method.extractResultHandler(args));
} else {
//调用sqlSession的select方法
sqlSession.select(command.getName(), param, method.extractResultHandler(args));
}
}
executeForMany()
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
//把方法参数转换成sql语句中的参数
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
//有分页时
RowBounds rowBounds = method.extractRowBounds(args);
//调用sqlSession的分页list查询
result = sqlSession.selectList(command.getName(), param, rowBounds);
} else {
//调用无分页的list查询
result = sqlSession.selectList(command.getName(), param);
}
// issue #510 Collections & arrays support
//如果返回不是List
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
//返回数组时
if (method.getReturnType().isArray()) {
//转换成数组
return convertToArray(result);
} else {
//转换成方法上声明的Collection
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
return result;
}
executeForMap()
private <K, V> Map<K, V> executeForMap(SqlSession sqlSession, Object[] args) {
Map<K, V> result;
//把方法参数转换成sql语句中的参数
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
//有分页时
RowBounds rowBounds = method.extractRowBounds(args);
//调用sqlSession的分页map查询
result = sqlSession.selectMap(command.getName(), param, method.getMapKey(), rowBounds);
} else {
//调用sqlSession的map查询
result = sqlSession.selectMap(command.getName(), param, method.getMapKey());
}
return result;
}
executeForCursor()
private <T> Cursor<T> executeForCursor(SqlSession sqlSession, Object[] args) {
Cursor<T> result;
//把方法参数转换成sql语句中的参数
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
//有分页时
RowBounds rowBounds = method.extractRowBounds(args);
//调用sqlSession的分页cursor查询
result = sqlSession.selectCursor(command.getName(), param, rowBounds);
} else {
//调用sqlSession的cursor查询
result = sqlSession.selectCursor(command.getName(), param);
}
return result;
}
总结
本章主要是学习了自定义Mapper的执行流程.
首先从SqlSession中获取到Mapper的代理类MapperProxy,执行自定义Mapper的方法时会调用MapperPorxy的invoke()
MapperPorxy内部会通过MapperMethod和执行类型和返回值来调用SqlSession的对应方法进行处理
流程图如下
喜欢的小伙伴请动动小手关注和点赞吧,也可留言一起探讨怎样更好的学习源码!!!
文章链接
Mybatis源码学习(一)初探执行流程
Mybatis源码学习(二)配置文件解析到SqlSessionFactory构建
Mybatis源码学习(三)SqlSession详解
Mybatis源码学习(四)自定义Mapper方法执行流程
Mybatis源码学习(五)Executor和StatementHandler详解
Mybatis源码学习(六)结果集自动封装机制
Mybatis源码学习(七)mybatis缓存详解
Mybatis源码学习(八)Mybatis设计模式总结