Mysql-Plus 自定义注入器

在MyBatis-Plus中,自定义注入器功能提供了一种方式,可以在MyBatis的核心操作流程中无缝集成个人的业务逻辑。这包括在数据库的查询、更新或删除操作的前后注入特定的代码。通过这种机制,MyBatis-Plus的功能得以灵活扩展,从而更好地满足各种复杂多变的业务需求。
在将MyBatis-Plus与Doris数据库对接的过程中,实现动态的行转列查询逻辑,以适应更加丰富和高效的数据处理方式。

自定义注入器的应用场景

自定义注入器可以应用于多种场景,比如:

  1. 数据审计:在执行数据库操作前后记录日志,用于追踪数据的变更历史。
  2. 数据脱敏:在查询敏感信息前对数据进行脱敏处理,保护用户隐私。
  3. 性能监控:监控数据库操作的性能,帮助优化系统性能。
  4. 自定义查询方法:当标准的CRUD操作无法满足复杂的查询需求时,可以通过SQL注入器添加自定义的查询方法。
  5. 复杂数据处理:在需要进行复杂的数据处理,如多表联结、子查询、聚合函数等时,SQL注入器可以帮助生成相应的SQL语句。
  6. 性能优化:通过自定义SQL语句,可以针对特定的查询场景进行性能优化。
  7. 数据权限控制:在需要根据用户权限动态生成SQL语句时,SQL注入器可以用来实现数据权限的控制。
  8. 遗留系统迁移:在将遗留系统迁移到MyBatis-Plus时,可能需要保留原有的SQL语句结构,SQL注入器可以帮助实现这一过渡。

自定义全局方法攻略

1. 定义SQL

首先,你需要定义自定义方法的SQL语句。这通常在继承了AbstractMethod的类中完成,例如DorisGenericQuery

1
2
3
4
5
6
7
8
public class DorisGenericQuery extends AbstractMethod {
@Override
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
String sql = getSQLString(tableInfo);
SqlSource sqlSource = languageDriver.createSqlSource(this.configuration, sql, modelClass);
return this.addSelectMappedStatementForOther(mapperClass,"BigDataSelect", sqlSource, Map.class);
}
}

这里注入的是 返回一个List<Map<String,Object>>的方法。

SQL注意用<script> .... </script>包含需要执行的语句

官方分页selectMapPage 如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<script>
<if test="ew != null and ew.sqlFirst != null">
${ew.sqlFirst}
</if> SELECT <choose>
<when test="ew != null and ew.sqlSelect != null">
${ew.sqlSelect}
</when>
<otherwise>p_id_card,tag_code,tag_id,out_date,tag_value,p_id,tag_name,tag_time</otherwise>
</choose> FROM tbl_p_tags
<if test="ew != null">
<where>
<if test="ew.entity != null">
<if test="ew.entity.pIdCard != null">p_id_card=#{ew.entity.pIdCard}</if>
<if test="ew.entity['tagCode'] != null"> AND tag_code=#{ew.entity.tagCode}</if>
<if test="ew.entity['tagId'] != null"> AND tag_id=#{ew.entity.tagId}</if>
<if test="ew.entity['outDate'] != null"> AND out_date=#{ew.entity.outDate}</if>
<if test="ew.entity['tagValue'] != null"> AND tag_value=#{ew.entity.tagValue}</if>
<if test="ew.entity['pId'] != null"> AND p_id=#{ew.entity.pId}</if>
<if test="ew.entity['tagName'] != null"> AND tag_name=#{ew.entity.tagName}</if>
<if test="ew.entity['tagTime'] != null"> AND tag_time=#{ew.entity.tagTime}</if>
</if>
<if test="ew.sqlSegment != null and ew.sqlSegment != '' and ew.nonEmptyOfWhere">
<if test="ew.nonEmptyOfEntity and ew.nonEmptyOfNormal"> AND</if> ${ew.sqlSegment}
</if>
</where>
<if test="ew.sqlSegment != null and ew.sqlSegment != '' and ew.emptyOfWhere">
${ew.sqlSegment}
</if>
</if> <if test="ew != null and ew.sqlComment != null">
${ew.sqlComment}
</if>
</script>

2. 注册自定义方法

接下来,你需要创建一个类来继承DefaultSqlInjector,并重写getMethodList方法来注册你的自定义方法。

1
2
3
4
5
6
7
8
9
10
@Component
public class GenericQuerySqlInjector extends DefaultSqlInjector {
@Override
public List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo) {
List<AbstractMethod> methodList = super.getMethodList(mapperClass,tableInfo);
methodList.add(new DorisGenericQuery());
methodList.add(new DorisGenericPageQuery());
return methodList;
}
}

3. 定义BaseMapper

1
2
3
4
public interface BigDataQueryBaseMapper<T> extends BaseMapper<T> {
List<Map<String, Object>> BigDataSelect(@Param("pt") T po);
<P extends IPage<Map<String, Object>>> P BigDataSelectPage(P page, @Param("pt") T po);
}

这里我用我基于实体来做分析处理,没有传入查询参数,如果需要传入加入 @Param(Constants.WRAPPER) Wrapper<T> queryWrapper

4. 配置SqlInjector

使用@Component注入

在DefaultSqlInjector类上添加@Component

aplication.properties 中配置

1
mybatis-plus.global-config.sql-injector=com.example.MyLogicSqlInjector

application.yml 中配置

1
2
3
mybatis-plus:
global-config:
sql-injector: com.example.MyLogicSqlInjector

在组装动态属性返回时报错

Cause: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: Failed to process, Error SQL: select p_id_card,

map[?] AS ?
from (select p_id_card,map_agg(tag_code,tag_value) AS map
from tbl_p_tags
where tag_id in ( ?
,
?
,
?
,
?

)
group by p_id_card) t
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) ~[spring-webmvc-5.3.24.jar:5.3.24]
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898) ~[spring-webmvc-5.3.24.jar:5.3.24]
at javax.servlet.ht

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
StringBuilder selectStr = new StringBuilder("<script> select ");
selectStr.append(tableInfo.getKeySqlSelect()).append(",");
StringBuilder sqlScript = new StringBuilder("map[#{item.code}] AS #{item.code}");
String selectRules = SqlScriptUtils.convertForeach(sqlScript.toString(), "rules", null, "item", ",");
selectStr.append(selectRules);
StringBuilder fromStr = new StringBuilder(" from (select " );
fromStr.append(tableInfo.getKeySqlSelect()).append(",");
fromStr.append("map_agg(tag_code,tag_value) AS map ").append(NEWLINE);
fromStr.append("from ").append(tableInfo.getTableName()).append(NEWLINE);;
fromStr.append("where ").append("tag_id in (");
String subRuleS = SqlScriptUtils.convertForeach("#{subItem.tagId}", "item.selectedTags", (String)null, "subItem", ",");
String rules = SqlScriptUtils.convertForeach(subRuleS, "rules", null, "item", ",");
fromStr.append(rules).append(")").append(NEWLINE)
.append("group by ")
.append(tableInfo.getKeySqlSelect()).append(") t")
.append("</script>");
String sql = selectStr.append(fromStr).toString();
System.out.println(selectStr.append(fromStr));
SqlSource sqlSource = languageDriver.createSqlSource(this.configuration, sql, modelClass);
return this.addSelectMappedStatementForOther(mapperClass,"BigDataSelectPage", sqlSource, Map.class);
}
1
StringBuilder sqlScript = new StringBuilder("map[#{item.code}] AS #{item.code}"); 

中使用别名会报错如上

改成${}后正常

1
StringBuilder sqlScript = new StringBuilder("map[#{item.code}] AS ${item.code}"); 

在MyBatis中,使用#{}语法是为了防止SQL注入并进行预编译处理参数。然而,在映射文件中直接将#{}用作动态Map的键或别名的表达方式是不正确的。这是因为MyBatis期望#{}内部直接引用一个参数的名称,而不是作为动态字符串处理。