MyBatis-plus 自定义通用方法及其实现原理
文章目录背景1. 自定义通用方法的实现1.1 新增 Mapper 方法与 SQL 语句脚本映射枚举1.2 新增 Mapper 方法的定义类1.3 新增 SQL 注入器1.4 新增配置类将 SQL 注入器添加到容器1.5 新增基类 Mapper2. 实现原理2.1 自定义 SQL 注入器的注入2.2 自定义 SQL 注入器的使用2.3 Mapper 操作数据库的实现背景项目中使用了读写分离的数据库访
文章目录
背景
项目中使用了读写分离的数据库访问框架,这个框架基于 Proxy 模式
,对使用方屏蔽了主从数据库,查询时默认路由到从库。但是因为存在主从延迟,当主库数据变更较大时延迟会达到秒级,造成了一些线上问题。中间件同事建议在对主从延迟敏感的场景中绑定主库查询,绑定方式是在 SQL 语句的开头添加特定的注释字符串,访问框架会根据这个字符串执行主库路由。这个场景涉及到 SQL 语句的拦截修改,立即想到了如下两个方案,评估后最终选择了第二个方案
- 基于 MyBatis 的
@Intercepts
拦截器实现,这种方式如果要做到方法级别的拦截修改,需要一些方法标注的额外开发量- 扩展 MyBatis-plus 的通用方法,增加专门的走主库的查询方法
1. 自定义通用方法的实现
MyBatis-plus
提供了许多通用的数据库操作方法,其定义包含在枚举com.baomidou.mybatisplus.core.enums.SqlMethod
中,仿照其实现,自定义一个通用的数据库操作方法的步骤如下
1.1 新增 Mapper 方法与 SQL 语句脚本映射枚举
新增枚举SqlMethodEx
,其内部属性定义与 SqlMethod
完全一致
SqlMethodEx
实际维护了上层Mapper 方法
与底层SQL 语句脚本
的映射,本例中MASTER_SELECT_LIST
定义了一个Mapper#selectListFromMaster()
方法及其对应的 SQL 语句脚本的基本结构,可以看到本例中笔者在 SQL 语句开始处添加了一个前缀字符串
public enum SqlMethodEx {
MASTER_SELECT_LIST("selectListFromMaster", "从主库查询满足条件多条数据",
"<script>%s " + SqlConstant.DA_MASTER_PARAM + "SELECT %s FROM %s %s %s\n</script>"),
;
private final String method;
private final String desc;
private final String sql;
SqlMethodEx(String method, String desc, String sql) {
this.method = method;
this.desc = desc;
this.sql = sql;
}
public String getMethod() {
return method;
}
public String getDesc() {
return desc;
}
public String getSql() {
return sql;
}
}
1.2 新增通用方法的定义类
新增 SelectListFromMasterMethod
类继承于 AbstractMethod
,负责将我们自定义的通用 Mapper方法 及其对应的 SQL 语句脚本组装为 MappedStatement
对象并添加到容器中,对 MappedStatement
对象不了解的读者可参考 MyBatis Mapper 操作数据库源码流程总结
public class SelectListFromMasterMethod extends AbstractMethod {
@Override
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
SqlMethodEx sqlMethod = SqlMethodEx.MASTER_SELECT_LIST;
String sqlSelectColumns = sqlSelectColumns(tableInfo, true);
String sqlWhere = sqlWhereEntityWrapper(true, tableInfo);
String sql = String.format(sqlMethod.getSql(), sqlFirst(), sqlSelectColumns, tableInfo.getTableName(), sqlWhere, sqlComment());
SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
return this.addSelectMappedStatementForTable(mapperClass, getMethod(sqlMethod), sqlSource, tableInfo);
}
private String getMethod(SqlMethodEx sqlMethod) {
return sqlMethod.getMethod();
}
}
1.3 新增 SQL 注入器
新增 MasterSqlInjector
类继承于 DefaultSqlInjector
,负责将所有通用的 Mapper方法 的定义类收集起来,后续将使用这些方法定义类为每一个具体的 Mapper 添加通用方法
public class MasterSqlInjector extends DefaultSqlInjector {
@Override
public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
List<AbstractMethod> methods = super.getMethodList(mapperClass);
methods.add(new SelectListFromMasterMethod());
return methods;
}
}
1.4 新增配置类将 SQL 注入器添加到容器
MyBatis-plus
的组件注入和普通的 Spring 配置注入完全一致,本例中笔者自定义的 SQL 注入器 MasterSqlInjector
注入到容器后,即相当于提供了 Mapper#selectListFromMaster()
方法的底层 SQL 语句基础,接下来则需要定义上层的 Mapper 方法供使用方调用
@Configuration
public class MybatisPlusConfig {
@Bean
public MasterSqlInjector masterSqlInjector() {
return new MasterSqlInjector();
}
}
1.5 新增基类 Mapper
新增 MasterMapper
继承于 BaseMapper
,并在其中定义一个 selectListFromMaster方法
使用时具体的业务 Mapper 只需要继承
MasterMapper
即可像使用其它MyBatis-plus 提供的全局方法一样使用其定义的MasterMapper#selectListFromMaster()
方法
public interface MasterMapper<T> extends BaseMapper<T> {
/**
* 根据 entity 条件,从主库查询任意条记录
*
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
List<T> selectListFromMaster(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
}
2. 实现原理
2.1 自定义 SQL 注入器的注入
在 MyBatis-plus 源码解析 中,笔者提到了自动配置类配置SqlSessionFactory
的 MybatisPlusAutoConfiguration#sqlSessionFactory()
方法 ,可以看到本文中自定义的 SQL 注入器通过 @Bean
托管给 Spring 后,也是在这里存入到 MyBatis-plus
的 GlobalConfig
全局缓存中的
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
// TODO 使用 MybatisSqlSessionFactoryBean 而不是 SqlSessionFactoryBean
MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
factory.setDataSource(dataSource);
......
// TODO 此处必为非 NULL
GlobalConfig globalConfig = this.properties.getGlobalConfig();
......
// TODO 注入sql注入器
this.getBeanThen(ISqlInjector.class, globalConfig::setSqlInjector);
......
factory.setGlobalConfig(globalConfig);
return factory.getObject();
}
private <T> void getBeanThen(Class<T> clazz, Consumer<T> consumer) {
if (this.applicationContext.getBeanNamesForType(clazz, false, false).length > 0) {
consumer.accept(this.applicationContext.getBean(clazz));
}
}
2.2 自定义 SQL 注入器的使用
MyBatis-plus 源码解析 分析了 MyBatis-plus
配置工作的主要流程,SQL 注入器的使用也在其中,这一部分本文不再重复。简单来说,就是根据 MyBatis-plus
提供的操作数据库的通用方法给每一个 Mapper 准备对应的 MappedStatement
对象的过程
2.3 Mapper 操作数据库的实现
这部分和本文的相关性比较大,但是内容细节非常多,为了分析的完整,读者可参考 MyBatis Mapper 操作数据库源码流程总结 做大致了解,关于 SQL 语句的细节处理可参考 MyBatis-plus 转化处理 SQL 语句的源码分析
更多推荐
所有评论(0)