为什么选择Mybatis Dynamic SQL?
MBG已经推荐所有的java新项目,都采用Mybatis Dynamic SQL,以java API的方式书写SQL,而不是XML。
This is the default runtime. It is the recommended runtime for all new Java projects
实践总结
1. 代码量少很多。一行流式代码即可完成,无需臃肿的example用法
2. 0 XML,再也不用和XML打交道
- 不存在XML的SQL注入风险。Fortify安全扫描中,再也不会被安全漏洞所困扰;
- 不存在重构风险。过去重构代码时,IDE会忽略掉XML中的对应信息,导致重构不完整,出现软件错误;新的方式全基于JAVA,IDE重构完整;
- 不存在自定义的mapper函数难以找到XML内定义SQL问题。原来需要手动去XML查找,无法借IDE之力。新的方式由于全基于JAVA,IDE可以直接找到自定义的SQL
- 不存在自定义SQL的语法错问题。原来XML的方式是手写SQL,且需要填jdbcType,列信息等繁琐配置,且无法避免错误语法问题。新的方式基于JAVA API,不存在手写SQL导致的潜在语法问题。
- 少了额外的XML,生成的文件更少,让注意力更聚焦;
3. 比基于XML生成的example系列通用函数更全
- 终于具备非常常用的批量插入了(insertMultiple)
- 也可以直接使用万能的select函数了(不再担心XML的selectByExample函数涉及安全风险,而不敢生成,导致简单的select函数都要自定义的搞笑事情)
4. 自定义的mapper可以继承通用mapper。原来XML的方式,自定义的mapper和生成的通用mapper是2个独立的接口,无法使用继承,否则会出现多个bean的错误。新的方式,自定义mapper可以继承生成的通用mapper,全局只需要一个自定义的mapper的bean即可。
代码对比
通用场景
旧的基于XML的Criteria模式
SparringWorkflowNodeEntityExample example = new SparringWorkflowNodeEntityExample();
SparringWorkflowNodeEntityExample.Criteria criteria = example.createCriteria();
criteria.andDeleteFlagEqualTo(0);
criteria.andWorkflowIdEqualTo(lessonId);
example.setOrderByClause("sequence_number ASC");
List<SparringWorkflowNode> workflowNodeEntities = sparringWorkflowNodeEntityMapper.selectByExample(example);
新的基于JAVA API的流式模式
List<SparringWorkflowNode> workflowNodeEntities = sparringWorkflowNodeMapper.select(c->c.where(deleteFlag, isEqualTo(0)).and(workflowId, isEqualTo(lessonId)).orderBy(sequenceNumber));
实践细节
MBG的配置文件
generator-configuration.xml这个文件,targetRuntime="MyBatis3DynamicSql" 这是最关键的配置,决定了这是基于JAVA API的Dynamic SQL的方式。
通用Mapper
略
自定义Mapper
对于无法使用通用mapper的场景,可以使用自定义的方式。
自定义一个接口,继承通用mapper(这样可以无需使用2个bean)
@Mapper
public interface SparringWorkflowMapperCustomized extends SparringWorkflowMapper {
@TimeIt
@SelectProvider(value = WorkflowSqlBuilder.class, method = "searchWorkflowVectorSimple")
List<SparringWorkflow> searchWorkflowSimple(String query, ESparringWorkflowType workflowType, String account);
@SelectProvider(value = WorkflowSqlBuilder.class, method = "getStudentAllLessons")
List<SparringWorkflow> getStudentAllLessons(ESparringWorkflowType workflowType, String account);
}
通过Mybatis的JAVA API 定义SQL (可以参考Mybatis 官方 mybatis – MyBatis 3 | SQL 语句构建器)
@Slf4j
public class WorkflowSqlBuilder {
final static String TABLE_NAME = "sparring_workflow";
final static String WEIGHT_VECTOR = "(setweight(to_tsvector('zh', workflow_name), 'A') || setweight(to_tsvector('zh', workflow_desc), 'C'))";
public static String searchWorkflowVectorSimple(final String query, final ESparringWorkflowType workflowType, final String account){
return new SQL(){{
SELECT(" * ");
FROM(String.format("%s, websearch_to_tsquery('zh', #{query}) search, ts_rank_cd(%s, search, 1) rank", TABLE_NAME, WEIGHT_VECTOR));
WHERE(WEIGHT_VECTOR + " @@ search");
WHERE("delete_flag = 0");
if(workflowType != null){
WHERE("workflow_type = #{workflowType.code}"); //mybatis会寻找code这个field的getter,实际执行的是workflowType.getCode()
}
/*
在sql中,jsonb后面只有一个?,但是由于mybatis也使用?作为未知参数,会出现错误。因此,这里采用??等价sql里的?
*/
if(Strings.isNotBlank(account)){
WHERE("users::jsonb??#{account}");
}
ORDER_BY("rank DESC");
}}.toString();
}
public static String getStudentAllLessons(final ESparringWorkflowType workflowType, final String account){
return new SQL(){{
SELECT("*");
FROM(TABLE_NAME);
WHERE("delete_flag = 0");
if(workflowType != null){
WHERE("workflow_type=#{workflowType.code}");
}
if(Strings.isNotBlank(account)){
WHERE("users::jsonb??#{account}");
}
ORDER_BY("updated_date DESC");
}}.toString();
}
}