浅谈浅谈MyBatis通用通用Mapper实现原理实现原理
主要介绍了浅谈MyBatis通用Mapper实现原理,本文会先介绍通用 Mapper 的简单原理,然后使用最简单的代码
来实现这个过程。感兴趣的小伙伴们可以参考一下
本文会先介绍通用 Mapper 的简单原理,然后使用最简单的代码来实现这个过程。
基本原理基本原理
通用 Mapper 提供了一些通用的方法,这些通用方法是以接口的形式提供的,例如。
public interface SelectMapper<T> {
/**
* 根据实体中的属性值进行查询,查询条件使用等号
*/
@SelectProvider(type = BaseSelectProvider.class, method = "dynamicSQL")
List<T> select(T record);
}
接口和方法都使用了泛型,使用该通用方法的接口需要指定泛型的类型。通过 Java 反射可以很容易得到接口泛型的类型信
息,代码如下。
Type[] types = mapperClass.getGenericInterfaces();
Class<?> entityClass = null;
for (Type type : types) {
if (type instanceof ParameterizedType) {
ParameterizedType t = (ParameterizedType) type;
//判断父接口是否为 SelectMapper.class
if (t.getRawType() == SelectMapper.class) {
//得到泛型类型
entityClass = (Class<?>) t.getActualTypeArguments()[0];
break;
}
}
}
实体类中添加的 JPA 注解只是一种映射实体和数据库表关系的手段,通过一些默认规则或者自定义注解也很容易设置这种关
系,获取实体和表的对应关系后,就可以根据通用接口方法定义的功能来生成和 XML 中一样的 SQL 代码。动态生成 XML 样
式代码的方式有很多,最简单的方式就是纯 Java 代码拼字符串,通用 Mapper 为了尽可能的少的依赖选择了这种方式。如果
使用模板(如 FreeMarker,Velocity 和 beetl 等模板引擎)实现,自由度会更高,也能方便开发人员调整。
在 MyBatis 中,每一个方法(注解或 XML 方式)经过处理后,最终会构造成 MappedStatement 实例,这个对象包含了方法
id(namespace+id)、结果映射、缓存配置、SqlSource 等信息,和 SQL 关系最紧密的是其中的 SqlSource,MyBatis 最终
执行的 SQL 时就是通过这个接口的 getBoundSql 方法获取的。
在 MyBatis 中,使用@SelectProvider 这种方式定义的方法,最终会构造成 ProviderSqlSource,ProviderSqlSource 是一种处
于中间的 SqlSource,它本身不能作为最终执行时使用的 SqlSource,但是他会根据指定方法返回的 SQL 去构造一个可用于
最后执行的 StaticSqlSource,StaticSqlSource的特点就是静态 SQL,支持在 SQL 中使用#{param} 方式的参数,但是不支持
<if>,<where> 等标签。
为了能根据实体类动态生成支持动态 SQL 的方法,通用 Mapper 从这里入手,利用ProviderSqlSource 可以生成正常的
MappedStatement,可以直接利用 MyBatis 各种配置和命名空间的特点(这是通用 Mapper 选择这种方式的主要原因)。在
生成 MappedStatement 后,“过河拆桥” 般的利用完就把 ProviderSqlSource 替换掉了,正常情况下,ProviderSqlSource 根
本就没有执行的机会。在通用 Mapper 定义的实现方法中,提供了 MappedStatement 作为参数,有了这个参数,我们就可以
根据 ms 的 id(规范情况下是 接口名.方法名)得到接口,通过接口的泛型可以获取实体类(entityClass),根据实体和表的关系
我们可以拼出 XML 方式的动态 SQL,一个简单的方法如下。
/**
* 查询全部结果
*
* @param ms
* @return
*/
public String selectAll(MappedStatement ms) {
final Class<?> entityClass = getEntityClass(ms);
//修改返回值类型为实体类型
setResultType(ms, entityClass);
StringBuilder sql = new StringBuilder();
sql.append(SqlHelper.selectAllColumns(entityClass));
sql.append(SqlHelper.fromTable(entityClass, tableName(entityClass)));
sql.append(SqlHelper.orderByDefault(entityClass));
return sql.toString();
}