【mybatis plus源码解析】(二)详解SQL注入器底层原理,mybatis plus是如何实现自动注入CRUD操作
系列文章目录
【mybatis plus源码解析】mybatis plus执行原理
【mybatis plus源码解析】(二)详解自定义SQL注入器底层原理
文章目录
- 系列文章目录
- 前言
- 一、ISqlInjector SQL自动注入器接口的相关uml图
-
- ISqlInjector顶级接口,只做一件事
- 再来看看AbstractSqlInjector抽象类
- DefaultSqlInjector默认SQL 注入器
- 默认的注入器类治理介绍完了,里面其实做了一件事就是调用注入mapper默认实现的CRUD方法
- 二、注入方法相关的类
-
- 先看看UML图
- AbstractMethod抽象方法类
- 简单看个SelectById类,以这个方法为例子
- 了解完上面的类,现在回头看看初始化时注入方法,不了解的可以先看我的上篇文章。
- 总结
前言
上篇文章介绍了【mybatis plus源码解析】mybatis plus执行原理,这回接着上篇文章,建议看完上篇文章再来看这篇,这里主要介绍相关类
一、ISqlInjector SQL自动注入器接口的相关uml图
ISqlInjector顶级接口,只做一件事
再来看看AbstractSqlInjector抽象类
AbstractSqlInjector类实现了inspectInject注入方法
/ * SQL 自动注入器 * * @author hubin * @since 2018-04-07 */public abstract class AbstractSqlInjector implements ISqlInjector { protected final Log logger = LogFactory.getLog(this.getClass()); @Override public void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) { //通过反射获取实体类对象 Class<?> modelClass = ReflectionKit.getSuperClassGenericType(mapperClass, Mapper.class, 0); if (modelClass != null) { String className = mapperClass.toString(); //获取mapperRegistry缓存用于对比,防止覆盖 Set<String> mapperRegistryCache = GlobalConfigUtils.getMapperRegistryCache(builderAssistant.getConfiguration()); if (!mapperRegistryCache.contains(className)) { //通过实体类对象反射方式获取信息(比如实体类上的注解)封装成TableInfo存储表信息对象 TableInfo tableInfo = TableInfoHelper.initTableInfo(builderAssistant, modelClass); List<AbstractMethod> methodList = this.getMethodList(mapperClass, tableInfo);//获取要注入的方法集合 if (CollectionUtils.isNotEmpty(methodList)) { // 循环注入自定义方法 methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo)); } else { logger.debug(mapperClass.toString() + ", No effective injection method was found."); } mapperRegistryCache.add(className); } } } / * * 获取 注入的方法 *
* * @param mapperClass 当前mapper * @return 注入的方法集合 * @since 3.1.2 add mapperClass */ public abstract List<AbstractMethod> getMethodList(Class<?> mapperClass,TableInfo tableInfo);}
DefaultSqlInjector默认SQL 注入器
DefaultSqlInjector实现了AbstractSqlInjector类的getMethodList方法,规定哪些方法可以实现自动注入
/ * SQL 默认注入器 * * @author hubin * @since 2018-04-10 */public class DefaultSqlInjector extends AbstractSqlInjector { @Override public List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo) { Stream.Builder<AbstractMethod> builder = Stream.<AbstractMethod>builder() .add(new Insert()) .add(new Delete()) .add(new DeleteByMap()) .add(new Update()) .add(new SelectByMap()) .add(new SelectCount()) .add(new SelectMaps()) .add(new SelectMapsPage()) .add(new SelectObjs()) .add(new SelectList()) .add(new SelectPage()); if (tableInfo.havePK()) { builder.add(new DeleteById()) .add(new DeleteBatchByIds()) .add(new UpdateById()) .add(new SelectById()) .add(new SelectBatchByIds()); } else { logger.warn(String.format("%s ,Not found @TableId annotation, Cannot use Mybatis-Plus 'xxById' Method.", tableInfo.getEntityType())); } return builder.build().collect(toList()); }}
默认的注入器类治理介绍完了,里面其实做了一件事就是调用注入mapper默认实现的CRUD方法
二、注入方法相关的类
先看看UML图
AbstractMethod抽象方法类
/ * 抽象的注入方法类 * * @author hubin * @since 2018-04-06 */@SuppressWarnings("serial")public abstract class AbstractMethod implements Constants { protected static final Log logger = LogFactory.getLog(AbstractMethod.class); protected Configuration configuration; protected LanguageDriver languageDriver; protected MapperBuilderAssistant builderAssistant; / * 方法名称 * @since 3.5.0 */ protected final String methodName; / * @see AbstractMethod#AbstractMethod(java.lang.String) * @since 3.5.0 */ @Deprecated public AbstractMethod() { methodName = null; } / * @param methodName 方法名 * @since 3.5.0 */ protected AbstractMethod(String methodName) { Assert.notNull(methodName, "方法名不能为空"); this.methodName = methodName; } / * 注入自定义方法 */ public void inject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) { this.configuration = builderAssistant.getConfiguration(); this.builderAssistant = builderAssistant; this.languageDriver = configuration.getDefaultScriptingLanguageInstance(); /* 注入自定义方法 */ injectMappedStatement(mapperClass, modelClass, tableInfo); } / * 是否已经存在MappedStatement * * @param mappedStatement MappedStatement * @return true or false */ private boolean hasMappedStatement(String mappedStatement) { return configuration.hasStatement(mappedStatement, false); } / * SQL 更新 set 语句 * * @param table 表信息 * @return sql set 片段 */ protected String sqlLogicSet(TableInfo table) { return "SET " + table.getLogicDeleteSql(false, false); } / * SQL 更新 set 语句 * * @param logic 是否逻辑删除注入器 * @param ew 是否存在 UpdateWrapper 条件 * @param table 表信息 * @param alias 别名 * @param prefix 前缀 * @return sql */ protected String sqlSet(boolean logic, boolean ew, TableInfo table, boolean judgeAliasNull, final String alias,final String prefix) { String sqlScript = table.getAllSqlSet(logic, prefix); if (judgeAliasNull) { sqlScript = SqlScriptUtils.convertIf(sqlScript, String.format("%s != null", alias), true); } if (ew) { sqlScript += NEWLINE; sqlScript += convertIfEwParam(U_WRAPPER_SQL_SET, false); } sqlScript = SqlScriptUtils.convertSet(sqlScript); return sqlScript; } / * SQL 注释 * * @return sql */ protected String sqlComment() { return convertIfEwParam(Q_WRAPPER_SQL_COMMENT, true); } / * SQL 注释 * * @return sql */ protected String sqlFirst() { return convertIfEwParam(Q_WRAPPER_SQL_FIRST, true); } protected String convertIfEwParam(final String param, final boolean newLine) { return SqlScriptUtils.convertIf(SqlScriptUtils.unSafeParam(param), String.format("%s != null and %s != null", WRAPPER, param), newLine); } / * SQL 查询所有表字段 * * @param table 表信息 * @param queryWrapper 是否为使用 queryWrapper 查询 * @return sql 脚本 */ protected String sqlSelectColumns(TableInfo table, boolean queryWrapper) { /* 假设存在用户自定义的 resultMap 映射返回 */ String selectColumns = ASTERISK; if (table.getResultMap() == null || table.isAutoInitResultMap()) { /* 未设置 resultMap 或者 resultMap 是自动构建的,视为属于mp的规则范围内 */ selectColumns = table.getAllSqlSelect(); } if (!queryWrapper) { return selectColumns; } return convertChooseEwSelect(selectColumns); } / * SQL 查询记录行数 * * @return count sql 脚本 */ protected String sqlCount() { return convertChooseEwSelect(ASTERISK); } / * SQL 设置selectObj sql select * * @param table 表信息 */ protected String sqlSelectObjsColumns(TableInfo table) { return convertChooseEwSelect(table.getAllSqlSelect()); } protected String convertChooseEwSelect(final String otherwise) { return SqlScriptUtils.convertChoose(String.format("%s != null and %s != null", WRAPPER, Q_WRAPPER_SQL_SELECT), SqlScriptUtils.unSafeParam(Q_WRAPPER_SQL_SELECT), otherwise); } / * SQL map 查询条件 */ protected String sqlWhereByMap(TableInfo table) { if (table.isWithLogicDelete()) { // 逻辑删除 String sqlScript = SqlScriptUtils.convertChoose("v == null", " ${k} IS NULL ", " ${k} = #{v} "); sqlScript = SqlScriptUtils.convertForeach(sqlScript, COLUMN_MAP, "k", "v", "AND"); sqlScript = SqlScriptUtils.convertIf(sqlScript, String.format("%s != null and !%s.isEmpty", COLUMN_MAP, COLUMN_MAP), true); sqlScript += (NEWLINE + table.getLogicDeleteSql(true, true)); sqlScript = SqlScriptUtils.convertWhere(sqlScript); return sqlScript; } else { String sqlScript = SqlScriptUtils.convertChoose("v == null", " ${k} IS NULL ", " ${k} = #{v} "); sqlScript = SqlScriptUtils.convertForeach(sqlScript, COLUMN_MAP, "k", "v", "AND"); sqlScript = SqlScriptUtils.convertWhere(sqlScript); sqlScript = SqlScriptUtils.convertIf(sqlScript, String.format("%s != null and !%s", COLUMN_MAP, COLUMN_MAP_IS_EMPTY), true); return sqlScript; } } / * EntityWrapper方式获取select where * * @param newLine 是否提到下一行 * @param table 表信息 * @return String */ protected String sqlWhereEntityWrapper(boolean newLine, TableInfo table) { if (table.isWithLogicDelete()) { String sqlScript = table.getAllSqlWhere(true, true, WRAPPER_ENTITY_DOT); sqlScript = SqlScriptUtils.convertIf(sqlScript, String.format("%s != null", WRAPPER_ENTITY), true); sqlScript += (NEWLINE + table.getLogicDeleteSql(true, true) + NEWLINE); String normalSqlScript = SqlScriptUtils.convertIf(String.format("AND ${%s}", WRAPPER_SQLSEGMENT), String.format("%s != null and %s != '' and %s", WRAPPER_SQLSEGMENT, WRAPPER_SQLSEGMENT, WRAPPER_NONEMPTYOFNORMAL), true); normalSqlScript += NEWLINE; normalSqlScript += SqlScriptUtils.convertIf(String.format(" ${%s}", WRAPPER_SQLSEGMENT), String.format("%s != null and %s != '' and %s", WRAPPER_SQLSEGMENT, WRAPPER_SQLSEGMENT, WRAPPER_EMPTYOFNORMAL), true); sqlScript += normalSqlScript; sqlScript = SqlScriptUtils.convertChoose(String.format("%s != null", WRAPPER), sqlScript, table.getLogicDeleteSql(false, true)); sqlScript = SqlScriptUtils.convertWhere(sqlScript); return newLine ? NEWLINE + sqlScript : sqlScript; } else { String sqlScript = table.getAllSqlWhere(false, true, WRAPPER_ENTITY_DOT); sqlScript = SqlScriptUtils.convertIf(sqlScript, String.format("%s != null", WRAPPER_ENTITY), true); sqlScript += NEWLINE; sqlScript += SqlScriptUtils.convertIf(String.format(SqlScriptUtils.convertIf(" AND", String.format("%s and %s", WRAPPER_NONEMPTYOFENTITY, WRAPPER_NONEMPTYOFNORMAL), false) + " ${%s}", WRAPPER_SQLSEGMENT), String.format("%s != null and %s != '' and %s", WRAPPER_SQLSEGMENT, WRAPPER_SQLSEGMENT, WRAPPER_NONEMPTYOFWHERE), true); sqlScript = SqlScriptUtils.convertWhere(sqlScript) + NEWLINE; sqlScript += SqlScriptUtils.convertIf(String.format(" ${%s}", WRAPPER_SQLSEGMENT), String.format("%s != null and %s != '' and %s", WRAPPER_SQLSEGMENT, WRAPPER_SQLSEGMENT, WRAPPER_EMPTYOFWHERE), true); sqlScript = SqlScriptUtils.convertIf(sqlScript, String.format("%s != null", WRAPPER), true); return newLine ? NEWLINE + sqlScript : sqlScript; } } protected String sqlOrderBy(TableInfo tableInfo) { /* 不存在排序字段,直接返回空 */ List<TableFieldInfo> orderByFields = tableInfo.getOrderByFields(); if (CollectionUtils.isEmpty(orderByFields)) { return StringPool.EMPTY; } orderByFields.sort(Comparator.comparingInt(TableFieldInfo::getOrderBySort)); StringBuilder sql = new StringBuilder(); sql.append(NEWLINE).append(" ORDER BY "); sql.append(orderByFields.stream().map(tfi -> String.format("%s %s", tfi.getColumn(), tfi.getOrderByType())).collect(joining(","))); /* 当wrapper中传递了orderBy属性,@orderBy注解失效 */ return SqlScriptUtils.convertIf(sql.toString(), String.format("%s == null or %s", WRAPPER, WRAPPER_EXPRESSION_ORDER), true); } / * 过滤 TableFieldInfo 集合, join 成字符串 */ protected String filterTableFieldInfo(List<TableFieldInfo> fieldList, Predicate<TableFieldInfo> predicate,Function<TableFieldInfo, String> function, String joiningVal) { Stream<TableFieldInfo> infoStream = fieldList.stream(); if (predicate != null) { return infoStream.filter(predicate).map(function).collect(joining(joiningVal)); } return infoStream.map(function).collect(joining(joiningVal)); } / * 获取乐观锁相关 * * @param tableInfo 表信息 * @return String */ protected String optlockVersion(TableInfo tableInfo) { if (tableInfo.isWithVersion()) { return tableInfo.getVersionFieldInfo().getVersionOli(ENTITY, ENTITY_DOT); } return EMPTY; } / * 查询 */ protected MappedStatement addSelectMappedStatementForTable(Class<?> mapperClass, String id, SqlSource sqlSource,TableInfo table) { String resultMap = table.getResultMap(); if (null != resultMap) { /* 返回 resultMap 映射结果集 */ return addMappedStatement(mapperClass, id, sqlSource, SqlCommandType.SELECT, null, resultMap, null, NoKeyGenerator.INSTANCE, null, null); } else { /* 普通查询 */ return addSelectMappedStatementForOther(mapperClass, id, sqlSource, table.getEntityType()); } } / * 查询 * @since 3.5.0 */ protected MappedStatement addSelectMappedStatementForTable(Class<?> mapperClass, SqlSource sqlSource, TableInfo table) { return addSelectMappedStatementForTable(mapperClass, this.methodName, sqlSource, table); } / * 查询 */ protected MappedStatement addSelectMappedStatementForOther(Class<?> mapperClass, String id, SqlSource sqlSource,Class<?> resultType) { return addMappedStatement(mapperClass, id, sqlSource, SqlCommandType.SELECT, null, null, resultType, NoKeyGenerator.INSTANCE, null, null); } / * 查询 * * @since 3.5.0 */ protected MappedStatement addSelectMappedStatementForOther(Class<?> mapperClass, SqlSource sqlSource, Class<?> resultType) { return addSelectMappedStatementForOther(mapperClass, this.methodName, sqlSource, resultType); } / * 插入 */ protected MappedStatement addInsertMappedStatement(Class<?> mapperClass, Class<?> parameterType, String id, SqlSource sqlSource, KeyGenerator keyGenerator, String keyProperty, String keyColumn) { return addMappedStatement(mapperClass, id, sqlSource, SqlCommandType.INSERT, parameterType, null, Integer.class, keyGenerator, keyProperty, keyColumn); } / * 插入 * @since 3.5.0 */ protected MappedStatement addInsertMappedStatement(Class<?> mapperClass, Class<?> parameterType, SqlSource sqlSource, KeyGenerator keyGenerator, String keyProperty, String keyColumn) { return addInsertMappedStatement(mapperClass, parameterType, this.methodName, sqlSource, keyGenerator, keyProperty, keyColumn); } / * 删除 */ protected MappedStatement addDeleteMappedStatement(Class<?> mapperClass, String id, SqlSource sqlSource) { return addMappedStatement(mapperClass, id, sqlSource, SqlCommandType.DELETE, null, null, Integer.class, NoKeyGenerator.INSTANCE, null, null); } / * @since 3.5.0 */ protected MappedStatement addDeleteMappedStatement(Class<?> mapperClass, SqlSource sqlSource) { return addDeleteMappedStatement(mapperClass, this.methodName, sqlSource); } / * 更新 */ protected MappedStatement addUpdateMappedStatement(Class<?> mapperClass, Class<?> parameterType, String id, SqlSource sqlSource) { return addMappedStatement(mapperClass, id, sqlSource, SqlCommandType.UPDATE, parameterType, null, Integer.class, NoKeyGenerator.INSTANCE, null, null); } / * 更新 * * @since 3.5.0 */ protected MappedStatement addUpdateMappedStatement(Class<?> mapperClass, Class<?> parameterType, SqlSource sqlSource) { return addUpdateMappedStatement(mapperClass, parameterType, this.methodName, sqlSource); } / * 添加 MappedStatement 到 Mybatis 容器 */ protected MappedStatement addMappedStatement(Class<?> mapperClass, String id, SqlSource sqlSource,SqlCommandType sqlCommandType, Class<?> parameterType,String resultMap, Class<?> resultType, KeyGenerator keyGenerator,String keyProperty, String keyColumn) { String statementName = mapperClass.getName() + DOT + id; if (hasMappedStatement(statementName)) { logger.warn(LEFT_SQ_BRACKET + statementName + "] Has been loaded by XML or SqlProvider or Mybatis's Annotation, so ignoring this injection for [" + getClass() + RIGHT_SQ_BRACKET); return null; } /* 缓存逻辑处理 */ boolean isSelect = sqlCommandType == SqlCommandType.SELECT; return builderAssistant.addMappedStatement(id, sqlSource, StatementType.PREPARED, sqlCommandType, null, null, null, parameterType, resultMap, resultType, null, !isSelect, isSelect, false, keyGenerator, keyProperty, keyColumn, configuration.getDatabaseId(), languageDriver, null); } / * @since 3.5.0 */ protected MappedStatement addMappedStatement(Class<?> mapperClass, SqlSource sqlSource,SqlCommandType sqlCommandType, Class<?> parameterType,String resultMap, Class<?> resultType, KeyGenerator keyGenerator,String keyProperty, String keyColumn) { return addMappedStatement(mapperClass, this.methodName, sqlSource, sqlCommandType, parameterType, resultMap, resultType, keyGenerator, keyProperty, keyColumn); } / * 注入自定义 MappedStatement * * @param mapperClass mapper 接口 * @param modelClass mapper 泛型 * @param tableInfo 数据库表反射信息 * @return MappedStatement */ public abstract MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo); / * 获取自定义方法名,未设置采用默认方法名 * https://gitee.com/baomidou/mybatis-plus/pulls/88 * * @return method * @author 义陆无忧 * @see AbstractMethod#AbstractMethod(java.lang.String) * @deprecated 3.5.0 */ @Deprecated public String getMethod(SqlMethod sqlMethod) { return StringUtils.isBlank(methodName) ? sqlMethod.getMethod() : this.methodName; }}
简单看个SelectById类,以这个方法为例子
/ * 根据ID 查询一条数据 * * @author hubin * @since 2018-04-06 */public class SelectById extends AbstractMethod { public SelectById() { //给methodName属性赋值 super(SqlMethod.SELECT_BY_ID.getMethod()); } / * @param name 方法名 * @since 3.5.0 */ public SelectById(String name) { //给methodName属性赋值 super(name); } @Override public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) { //获取SqlMethod类,这是一个枚举类,下面会有介绍 SqlMethod sqlMethod = SqlMethod.SELECT_BY_ID; //生成最终的SqlSource对象 SqlSource sqlSource = new RawSqlSource(configuration, String.format(sqlMethod.getSql(), sqlSelectColumns(tableInfo, false), tableInfo.getTableName(), tableInfo.getKeyColumn(), tableInfo.getKeyProperty(), tableInfo.getLogicDeleteSql(true, true)), Object.class); //将最终封装好的MappedStatement对象加入配置类对象中 return this.addSelectMappedStatementForTable(mapperClass, getMethod(sqlMethod), sqlSource, tableInfo); }}
断点看看
可以看到sqlMethod枚举类封装了相关sql语句,短点往下
最终生成了封装好sql语句和参数的SqlSource对象,这里生成的是StaticSqlSource静态对象
了解完上面的类,现在回头看看初始化时注入方法,不了解的可以先看我的上篇文章。
来看看parserInjector这个方法
GlobalConfigUtils.getSqlInjector(configuration)这句代码其实就是返回配置文件中设置的SqlInjector注入器,说明我们可以自定义注入器,默认注入器是DefaultSqlInjector。官方文档也正好证明这一点
最终这个方法调用了AbstractSqlInjector的inspectInject方法将CRUD相关的sql对象注入到配置中去
总结
到这里基本就介绍完了。到这里其实就能想到可以通过自定义BaseMapper,并且编写响应method对象,自定义实现ISqlInjector注入器。这样我们就能够对mybatis-plus进行扩展来实现更多的CRUD方法。后面文章我会出一片详细的