MyBatis 返回动态结果类型插件
说明
虽然写了这么一个插件,但是个人建议尽可能不去这么用,如果这个插件真正能方便你,使用起来也没任何问题。
关于插件的一些个人修改建议,在插件的注释中有说明。
插件用途:可以在 MyBatis 参数中带上要返回的类型Class
,插件就会改变返回值类型为你指定的类型。
用法
说的可能不清楚,看个简单的用法。
MyBatis 中定义如下方法:
Object selectById(@Param("id")Long id, @Param("resultType")Class resultType);
//或
Object selectById(@Param("id")Long id, @Param("resultType")String resultType);
支持直接的Class
或者String
类型的全限定类名,必须指定参数的key
为resultType
,通过拦截器参数可以修改这个值,参数的顺序无所谓。
用法:
City city = (City) mapper.selectById(1L, City.class);
//或
City city = (City) mapper.selectById(1L, "tk.mybatis.model.City");
更变态直观的例子就是SQL也是动态传入${sql}这样方式的,不同SQL配不同的结果类型更能说明问题。你可以结合这篇 MyBatis 执行动态 SQL 博客使用。
如果看到这里觉得有用,你就可以继续往下看实现原理。
源码地址
虽然在git上,但我并不会长期维护这个插件。
实现原理
前面写过一篇 MyBatis Excutor 拦截器的巧妙用法,这个插件算是一个“修改MappedStatement
时解决并发的问题”可以参考复制的例子。
从上往下开始说实现。
拦截器签名:
@Intercepts(@Signature(
type = Executor.class,
method = "query",
args = {
MappedStatement.class,
Object.class,
RowBounds.class,
ResultHandler.class
}
)
)
public class ResultTypeInterceptor implements Interceptor
拦截的Executor
的查询方法,拦截Executor
的好处是自由度更高,但是难度也比其他的高,如果能够避免并发问题,实现起来会更容易。
拦截器处理代码
@Override
public Object intercept(Invocation invocation) throws Throwable {
final Object[] args = invocation.getArgs();
MappedStatement ms = (MappedStatement) args[0];
Object parameterObject = args[1];
//获取参数中设置的返回值类型
Class resultType = getResultType(parameterObject);
if(resultType == null){
return invocation.proceed();
}
//复制ms,重设类型
args[0] = newMappedStatement(ms, resultType);
return
首先通过invocation.getArgs()
可以得到当前执行方法的参数,这个参数列表和签名中定义的args
中的类型是一致的。
其中第一个是(MappedStatement) args[0]
,第二个是参数对象parameterObject
。
首先要做的就是从参数对象中获得通过参数传递进来的resultType
,使用 getResultType
方法获取。对参数不了解的人可能不清楚这里的参数可能是什么样,如果你想了解,可以看 深入了解MyBatis参数, 或者只是参考这个方法了解如何在这里处理参数。
得到返回值类型后,就该根据新的类型去创建一个resultMap
,然后用这个新的类型根据现有的MappedStatement
去创建一个新的MappedStatement
,之所以创建一个新的而不是直接修改MappedStatement
就是为了避免并发问题。
使用 newMappedStatement
创建新的MappedStatement
对象,这个方法很重要,如果你要对MappedStatement
做其他的修改,参考这个方法就可以。
通过下面代码就可以替换原有的ms :
args[0] = newMappedStatement(ms, resultType);
因为后续方式是使用参数(args
)中的这个 ms,而不是 MyBatis Configuration
中全局的ms
,所以这里就避免了修改全局ms
引起并发的问题。
虽然少数人能看到这里,但是我仍然建议,只学这段代码中的用法,尽可能不要去使用这个插件实现返回动态结果类型。直接使用会很不直观。但是呢,如果你是在开发一些通用框架方法,可以尝试在内部这么使用。