Mybatis自定义Sql模板语法问题
作者:horgn黄小锤、
一、说明
mybatis 原有mapper.xml的语法标签写法过于繁琐,如下面的<if>标签:
<select id="selectByParams" parameterType="map" resultType="user"> select * from user <where> <if test="id != null ">id=#{id}</if> <if test="name != null and name.length()>0" >and name=#{name}</if> <if test="age != null and age.length()>0">and age = #{age}</if> </where> </select> <select id="selectByParams" parameterType="map" resultType="user">
select * from user
<where>
<if test="id != null ">id=#{id}</if>
<if test="name != null and name.length()>0" >and name=#{name}</if>
<if test="age != null and age.length()>0">and age = #{age}</if>
</where>
</select> 这些写法功能单一还要写很多不必要的代码,于是可用自定义语法代替这类标签,如:
<select id="selectByParams" parameterType="map" resultType="user">
select * from user
where status = 1
{{and id = [id]}}
{{and code = [code,notEmpty]}}
{{and name like [name,like,notBlank]}}
{{and id in ([ids,list])}}
</select> 使用自定义标签代替 Mybatis 原有的查询条件相关语法,如:if、forEach 标签,解决原有语法写法太繁琐的问题,且增加了一些参数常用的操作和功能。
二、自定义语法和标签说明
1、自定义语法,由于mybatis已经使用了 ${} 和 #{} 标签,所以自定义语法使用 { { }} 和 [ ] 作为标签
2、格式: { {sql [name,key]}} ,如:{ {and code = [code,notEmpty]}} ,本语句的意思:条件 code = ? 在参数code不为空且不为空字符串的条件下生效,比如 code=2 时,生成条件 code = 2。参数名称必须放在第一个位置,关键字可为空。
3、参数说明:以上格式中的 sql 代表一般的sql语句,[ ] 中的 name 代表参数名称,key 代表关键字。
4、关键字类型: notNull 、 notEmpty 、 notBlank 、 trim 、 upper 、 lower 、 list 、 like 、 llike 、 rlike 、 const 、 hidden 、 default
5、其中 list 、 like 、 llike 、 rlike 、 const 、hidden 为参数类型关键字,notNull 、 notEmpty 、 notBlank 为参数条件类型关键字, trim 、 upper 、 lower 为参数特殊处理关键字。
6、语法标签中所有参数值用英文逗号,分隔,关键字严格区分大小写,除了参数类型关键字,其它关键字可组合使用。
7、参数类型关键字必须放在第二个位置,且一个参数只能是其中一种参数类型,同一个参数不能同时是两个或多个参数类型。
8、条件类型和特殊处理类型关键字可组合使用,且位置不固定,可随意搭配。当参数没有参数类型关键字时,条件类型和特殊处理类型关键字可放在第二个位置。
9、参数名称最多可以为三级名称,即:code 、user.code、user.dept.code,名称层级取决于入参类型。当mapper接口的参数为单个对象参数,如:Map、User等,参数名可直接使用 id、code、name等。当参数为两个及以上,需要使用 @Param 定义参数名称,如:func(@Param("user") User user, @Param("map") Map map),则参数名称需要为:user.id, user.name, map.code 等。
三、关键字说明
default
本关键字用于为参数设置默认值,当参数不满足指定条件时,使用默认值生成sql语句。如:{ {and type = [type,default,1]}},当参数 type 为 null(空) 时,使用默认值生成语句:and type = 1。当参数 type 的值为 2时,生成语句:and type = 2。
注意:默认值关键字 default 后面必须跟一个长度不为0的默认值,否则该关键字不生效。而且default 关键字和默认值必须放在标签最后面。
notNull
本关键字用于声明该参数不能为空,即该参数必填,否则会报参数为空异常。如:{ {and code = [code,notNull]}},当参数code为空(null)时,报异常:参数[code]不能为空。但空字符串""不会触发本条件,如需过滤空字符串,则需要与下面两个关键字组合使用。
注意:notNull 和 default 在逻辑上是有冲突的,所以当设置了默认值default,则本关键字会失效。
notEmpty
本关键字用于声明该参数不能为空且长度不能为0,即:str != null && str.length() != 0。且本关键字主要用于字符串参数。如:{ {code = [code,notEmpty]}},当参数code为null或字符串""时,条件不生效。
关键字组合:{ {and code = [code,notNull,notEmpty]}},即参数code必填且不能为空字符串。
notBlank
本关键字与 notEmpty 的区别就是多了一个 trim 操作,即:str != null && str.trim().length() != 0。且本关键字主要用于字符串参数。
notBlank 的功能包含了 notEmpty,所以这两个关键字只需要存在一个即可。
trim
本关键字也是作用于字符串参数,用于自动 trim 字符串前后空格。如:{ {and code = [code,trim]}},当参数code=" a "时,生成语句:and code = 'a'。
关键字组合:{ {and code = [code,notNull,notBlank,trim]}}
upper
本关键字的作用即自动将参数转换为大写,如参数 code = 'abc' 自动转换成 code = 'ABC'。
关键字组合:{ {and upper(code) = [code,notNull,notBlank,trim,upper]}}
lower
本关键字即把参数自动转换成小写,与 upper 的作用相同。
like、llike、rlike
参数类型关键字:即模糊查询,自动给参数加上 % 符号。分别对应:'%value%'、'%value'、'value%'。如:{ {and name like [name,like,trim,notEmpty]}}。当参数 name = ' 张三 '时,生成语句:and name like '%张三%'。
注意:所有参数类型关键字需要放在标签 [ ] 中的第二个位置,第一个位置是参数名称。
list
参数类型关键字:本参数作用于 in 查询条件,参数必须是 Controller<?> 类的子类,如 List、ArrayList等。语法:{ {and id in ([ids,list])}}。当参数 ids = [1,2,3] 时,生成语句:and id in (1,2,3)。
注意:list 类型的参数,当参数 ids == null || ids.size() == 0 时,条件都不会生效。
关键字组合:
1、default 当 list 与 default 组合使用时,设置默认值需要用 | 代替 , 号,如需要默认值 1,2,3,需要填写成 1|2|3。即:{{and id in (ids,list,default,1|2|3)}}
2、notEmpty、notBlank、trim、upper、lower 当 list 与 以上五个关键字组合使用时,以上五个关键字都是作用于 list 中的元素而不是list本身。如:{{and id in ([ids,trim,notBlank,upper])}},当参数ids=['a',' b ',' ', 'Ef'] 时,生成语句:and id in ('A','B','EF')。其中 trim 关键字去掉了第二个元素的前后空格,notBlank 过滤掉了第三个空字符串元素,upper 则将所有元素转换成大写。
3、notNull 本关键字与list组合即参数ids必填。
const
参数类型关键字:本关键字与mybatis中的 ${} 类似,即将参数当成sql语句放在sql中,本参数有Sql注入的风险。如:order by {{[orderby,const,notBlank,default,t.id]}}。当参数 orderby 为空或空字符串时,使用默认值生成语句:order by t.id,当参数 orderby = 't.code desc' 时,生成语句:order by t.code desc
注意:当默认值中有逗号 , 时,需要替换成 |, 即:t.id,t.code,t.name 需要写成:t.id|t.code|t.name,如:order by {{[orderby,default,t.id|t.code|t.name]}}
hidden
参数类型关键字:本关键字用于隐藏参数本身,即当参数生效时,生成的语句中不包含参数本身。如:{{order by id [orderby,hidden]}}, 当参数 orderby 不为空时(可以是任意值),生成语句:order by id。
四、实现逻辑
1、实现 mybatis 的 InnerInterceptor 拦截器,并在本拦截器中处理自定义语法的逻辑。
public class MyBatisSqlInnerInterceptor implements InnerInterceptor {
@Override
public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
String sql = boundSql.getSql(); // 获取原始sql
//TODO 处理自己的自定义语法逻辑和参数
String newSql = .....
// 逻辑处理完成,将新sql放回去
PluginUtils.MPBoundSql mpBoundSql = PluginUtils.mpBoundSql(boundSql);
mpBoundSql.sql(newSql);
InnerInterceptor.super.beforeQuery(executor, ms, parameter, rowBounds, resultHandler, boundSql);
}
}2、将自定义拦截器添加到 mybaits 中。(注意:自定义拦截器必须放在分页插件之前)
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new MyBatisSqlInnerInterceptor()); // 自定义Sql语法拦截器
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); // 分页插件
return interceptor;
}
}五、实现代码
1、新增关键字类型枚举类
/**
* 自定义Sql语法关键字类型枚举
*/
public enum SqlWrapperType {
/** 普通参数,默认值 */
Param {
@Override
String getKey() {
return "param";
}
@Override
Integer getType() {
return 0;
}
},
/** 必填参数 */
NotNull {
@Override
String getKey() {
return "notNull";
}
@Override
Integer getType() {
return 0;
}
},
/** 参数不能为空或长度为0 */
NotEmpty {
@Override
String getKey() {
return "notEmpty";
}
@Override
Integer getType() {
return 0;
}
},
/** 字符串不能为空或 trim() 后长度不能为0 */
NotBlank {
@Override
String getKey() {
return "notBlank";
}
@Override
Integer getType() {
return 0;
}
},
/** 如果参数是字符串,则自动trum前后的空格 */
Trim {
@Override
String getKey() {
return "trim";
}
@Override
Integer getType() {
return 0;
}
},
/** 参数值自动转换成大写 */
Upper {
@Override
String getKey() {
return "upper";
}
@Override
Integer getType() {
return 0;
}
},
/** 参数值自动转换成小写 */
Lower {
@Override
String getKey() {
return "lower";
}
@Override
Integer getType() {
return 0;
}
},
/** 模糊查询 %value% */
Like {
@Override
String getKey() {
return "like";
}
@Override
Integer getType() {
return 1;
}
},
/** 模糊查询 %value */
LLike {
@Override
String getKey() {
return "llike";
}
@Override
Integer getType() {
return 2;
}
},
/** 模糊查询 value% */
RLike {
@Override
String getKey() {
return "rlike";
}
@Override
Integer getType() {
return 3;
}
},
/** in 查询 */
List {
@Override
String getKey() {
return "list";
}
@Override
Integer getType() {
return 4;
}
},
/** sql语句参数,需要注意Sql注入 */
Const {
@Override
String getKey() {
return "const";
}
@Override
Integer getType() {
return 5;
}
},
/** 设置参数默认值 */
Default {
@Override
String getKey() {
return "default";
}
@Override
Integer getType() {
return 6;
}
},
/** 隐藏参数值 */
Hidden {
@Override
String getKey() {
return "hidden";
}
@Override
Integer getType() {
return 7;
}
};
public static SqlWrapperType get(String key){
for (SqlWrapperType type : SqlWrapperType.values()) {
if(type.getKey().equals(key)){
return type;
}
}
throw new IllegalArgumentException("自定义Sql语法关键字[" + key + "]不存在");
}
abstract String getKey();
abstract Integer getType();
public boolean equal(SqlWrapperType type){
return this.getType().equals(type.getType());
}
}
2、新增参数对象实体类
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* 自定义Sql语法参数封装器参数对象
*/
@Data
@AllArgsConstructor
public class SqlParamEntity {
private String key;
private String sign;
private SqlWrapperType type;
private boolean hasDefault;
private String defaultValue;
private boolean notNull;
private boolean notEmpty;
private boolean notBlank;
private boolean trim;
private Integer change;
}3、新增参数处理类
import com.zorgn.common.ExtList;
import com.zorgn.common.ExtUtil;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.zorgn.core.mybatis.SqlWrapper.*;
/**
* 自定义Sql语法参数封装器
*/
public class SqlParamWrapper {
private static final String LikeSign = "%";
private static final String SplitSign = ",";
private static final Pattern pattern = Pattern.compile("(?<=\\[)(.+?)(?=])");
private String paramBody;
private final int paramEntitiesSize;
private final List<SqlParamEntity> paramEntities = new ArrayList<>();
/**
* 自定义Sql语法参数封装器
*/
public SqlParamWrapper(String paramSql){
this.paramBody = paramSql;
initParamSql();
paramEntitiesSize = paramEntities.size();
}
/**
* 初始化自定义Sql语法参数
*/
private void initParamSql(){
Matcher matcher = pattern.matcher(paramBody);
int count = 1;
while (matcher.find()){
String key = matcher.group(), parSign = SignPar + count++ + SignPar, defaultVal = null;
this.paramBody = this.paramBody.replace(ParBgtStr + key + ParEndStr, parSign);
SqlWrapperType type = SqlWrapperType.Param;
boolean hasDefault = false,notNull = false,notEmpty = false,notBlank = false,trim = false;
Integer change = 0;
if(key.contains(SplitSign)){
List<String> keyList = ExtList.splitStr(key, ",", ExtUtil::isBlank);
key = keyList.get(0).toLowerCase();
if(keyList.size() >= 2){
type = SqlWrapperType.get(keyList.get(1));
}
if(keyList.contains(SqlWrapperType.Default.getKey())){
int idx = keyList.indexOf(SqlWrapperType.Default.getKey()) + 1;
if(keyList.size() > idx){
String defstr = keyList.get(idx);
if(!isBlank(defstr)) {
hasDefault = true;
if(SqlWrapperType.List.equal(type)){
defaultVal = defstr;
}else{
defaultVal = defstr.replace("|",",");
}
}
}
}
if(keyList.contains(SqlWrapperType.NotNull.getKey())){
notNull = true;
}
if(keyList.contains(SqlWrapperType.NotEmpty.getKey())){
notEmpty = true;
}
if(keyList.contains(SqlWrapperType.NotBlank.getKey())){
notBlank = true;
}
if(keyList.contains(SqlWrapperType.Trim.getKey())){
trim = true;
}
if(keyList.contains(SqlWrapperType.Upper.getKey())){
change = 1;
}
if(keyList.contains(SqlWrapperType.Lower.getKey())){
change = 2;
}
}
paramEntities.add(new SqlParamEntity(key, parSign, type, hasDefault, defaultVal, notNull, notEmpty, notBlank, trim, change));
}
}
/**
* 根据参数初始化自定义sql查询条件参数
*/
public String getParamSql(Map<?,?> map) {
String newParSql = this.paramBody;
List<String> notValueKeys = new ArrayList<>();
for (SqlParamEntity entity : paramEntities) {
Object val = map.get(entity.getKey());
if(null == val){
if(entity.isHasDefault()){
val = initDefaultValue(entity);
}else if(entity.isNotNull()){
if(paramEntitiesSize == 1){
throw new NullPointerException("参数[" + entity.getKey() + "]不能为空");
}else{
notValueKeys.add(entity.getKey());
continue;
}
}else {
if(paramEntitiesSize == 1){
return "";
}else{
notValueKeys.add(entity.getKey());
continue;
}
}
}else if ((isEmpty(val.toString()) && entity.isNotEmpty()) || (isBlank(val.toString()) && entity.isNotBlank())){
if (entity.isHasDefault()) {
val = initDefaultValue(entity);
} else if (entity.isNotNull()) {
throw new NullPointerException("参数[" + entity.getKey() + "]不能为空");
} else if (paramEntitiesSize > 1){
if(notValueKeys.size() < paramEntitiesSize - 1){
notValueKeys.add(entity.getKey());
continue;
} else if (paramEntitiesSize != notValueKeys.size() + 1){
notValueKeys.add(entity.getKey());
continue;
} else if(paramEntitiesSize == notValueKeys.size() + 1){
return "";
}
} else {
return "";
}
}
if(val instanceof String && entity.isTrim()){
val = val.toString().trim();
}
if(entity.getChange() > 0 && val instanceof String){
val = entity.getChange() == 1 ? val.toString().toUpperCase() : val.toString().toLowerCase();
}
if(SqlWrapperType.Hidden.equal(entity.getType())){
newParSql = newParSql.replace(entity.getSign(), "");
} else if(SqlWrapperType.List.equal(entity.getType())){
if(val instanceof Collection){
Collection<?> collection = (Collection<?>) val;
if(collection.size() > 0){
StringJoiner sj = new StringJoiner(SplitSign);
for (Object o : collection) {
if(o instanceof String){
String str = (String) o;
if(entity.isNotBlank() && isBlank(str)){
continue;
}
if(entity.isNotEmpty() && isEmpty(str)){
continue;
}
if(entity.isTrim()){
str = str.trim();
}
if(entity.getChange() > 0){
str = entity.getChange() == 1 ? str.toUpperCase() : str.toLowerCase();
}
sj.add("'" + str + "'");
}else {
sj.add(initValue(o) + "");
}
}
newParSql = newParSql.replace(entity.getSign(), sj.toString());
}else{
notValueKeys.add(entity.getKey());
}
continue;
} else {
throw new IllegalArgumentException("参数[" + entity.getKey() + "]必须为list集合");
}
} else if(SqlWrapperType.Like.equal(entity.getType())){
val = LikeSign + val + LikeSign;
} else if(SqlWrapperType.LLike.equal(entity.getType())){
val = LikeSign + val;
} else if(SqlWrapperType.RLike.equal(entity.getType())){
val = val + LikeSign;
}
newParSql = newParSql.replace(entity.getSign(), (SqlWrapperType.Const.equal(entity.getType()) ? val : initValue(val)).toString());
}
if(notValueKeys.size() > 0 && notValueKeys.size() != paramEntitiesSize){
//多参数时,所有参数必填
throw new NullPointerException("参数" + notValueKeys + "不能为空");
}else if(notValueKeys.size() == paramEntitiesSize){
//多参数且全部为空,则条件不生产
newParSql = "";
}
return newParSql;
}
private Object initDefaultValue(SqlParamEntity entity){
if(SqlWrapperType.List.equal(entity.getType())){
return Arrays.asList(entity.getDefaultValue().split("\\|"));
}else{
return entity.getDefaultValue();
}
}
private Object initValue(Object value){
if (null == value) return null;
if(value instanceof String || value instanceof Character){
return "'" + value + "'";
}else {
return value;
}
}
/** 判断字符串是否为空或 trim() 后长度为0 */
private static boolean isBlank(String str){
return null == str || str.trim().length() == 0;
}
/** 判断字符串是否为空或长度为0 */
private static boolean isEmpty(String str){
return null == str || str.length() == 0;
}
}4、新增sql处理类
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 自定义Sql语法封装器
*/
public class SqlWrapper {
static final String SqlBgtStr = "{{";
static final String SqlEndStr = "}}";
static final String ParBgtStr = "[";
static final String ParEndStr = "]";
static final String SignSql = "$";
static final String SignPar = "?";
private static final Pattern pattern = Pattern.compile("(?<=\\{\\{)(.+?)(?=}})");
private String sqlBody;
private final Map<String, SqlParamWrapper> paramWrappers = new HashMap<>();
/**
* 自定义Sql语法封装器
*/
public SqlWrapper(String sql){
this.sqlBody = sql;
initSqlWrapper();
}
/**
* 初始化自定义Sql语法
*/
private void initSqlWrapper(){
Matcher matcher = pattern.matcher(this.sqlBody);
int count = 1;
while (matcher.find()){
String group = matcher.group();
String parSign = SignSql + count++ + SignSql;
paramWrappers.put(parSign, new SqlParamWrapper(group));
this.sqlBody = this.sqlBody.replace(SqlBgtStr + group + SqlEndStr, " " + parSign + " ");
}
}
/**
* 根据参数初始化自定义sql查询条件
*/
public String getSql(Map<?,?> map) {
String newSqlBody = this.sqlBody.replace(" ", " ");
if(null == map || map.size() == 0){
for (Map.Entry<String, SqlParamWrapper> next : paramWrappers.entrySet()) {
newSqlBody = newSqlBody.replace(next.getKey(), "");
}
return newSqlBody;
}
for (Map.Entry<String, SqlParamWrapper> next : paramWrappers.entrySet()) {
newSqlBody = newSqlBody.replace(next.getKey(), next.getValue().getParamSql(map));
}
return newSqlBody;
}
/**
* 判断sql是否包含自定义语法
*/
public static boolean matcherSql(String sql){
return pattern.matcher(sql).find();
}
}5、添加自定义语法拦截器
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.AbstractWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.zorgn.common.ExtMap;
import com.zorgn.core.mybatis.MyBaitsSqlInnerException;
import com.zorgn.core.mybatis.SqlWrapper;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigDecimal;
import java.sql.SQLException;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* 自定义Sql语法拦截器
*/
public class MyBatisSqlInnerInterceptor implements InnerInterceptor {
private static final Map<String, SqlWrapper> SQL_WRAPPER_MAP = new HashMap<>();
private static final Logger logger = LoggerFactory.getLogger("SQL");
private static final String regex = "[\\s]+"; // 替换空格、换行、tab缩进等;
private static final String LogPreparing = "{} - ==> Preparing: {}";
private static final String LogParameters = "{} - ==> Parameters: {}";
private final boolean openThreeLevelParams; // 是否开启三级参数名,默认是二级:user.id,三级:user.creator.id
/**
* 构造器:自定义Sql语法拦截器,默认为二级参数名称:user.id
*/
public MyBatisSqlInnerInterceptor(){
this(false);
}
/**
* 构造器:自定义Sql语法拦截器,并设置是否开启三级参数名称,三级参数名称:user.creator.id
*/
public MyBatisSqlInnerInterceptor(Boolean openThreeLevelParams){
this.openThreeLevelParams = openThreeLevelParams;
}
/**
* Query 前置事件,处理自定义Sql逻辑,打印 Sql 语句和参数
*/
@Override
public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
String sql = boundSql.getSql().replaceAll(regex, " ");
String sqlId = ms.getId();
if(SQL_WRAPPER_MAP.containsKey(sqlId) || SqlWrapper.matcherSql(sql)) {
SqlWrapper sqlWrapper = SQL_WRAPPER_MAP.get(sqlId);
if (null == sqlWrapper) {
sqlWrapper = new SqlWrapper(sql);
SQL_WRAPPER_MAP.put(sqlId, sqlWrapper);
logger.info("初始化Sql模板, SqlId: {}, SQL: {}", sqlId, sql);
}
String newSql = null;
try {
PluginUtils.MPBoundSql mpBoundSql = PluginUtils.mpBoundSql(boundSql);
Map<?,?> newParamMap = initParameter(parameter, sqlId);
newSql = sqlWrapper.getSql(newParamMap);
mpBoundSql.sql(newSql);
logger.info(LogPreparing, sqlId, newSql);
logger.info(LogParameters, sqlId, getSqlParameter(parameter));
} catch (Exception e) {
throw new MyBaitsSqlInnerException("Sql模板异常:" + e.getMessage(), sqlId, (null == newSql ? sql : newSql), e);
}
}else{
logger.info(LogPreparing, sqlId, sql);
logger.info(LogParameters, sqlId, getSqlParameter(parameter));
}
InnerInterceptor.super.beforeQuery(executor, ms, parameter, rowBounds, resultHandler, boundSql);
}
/**
* Insert、Update、Delete 前置任务,打印 Sql 语句及参数
*/
@Override
public void beforeUpdate(Executor executor, MappedStatement ms, Object parameter) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
String sqlId = ms.getId();
logger.info(LogPreparing, sqlId, boundSql.getSql().replaceAll(regex, " "));
logger.info(LogParameters, sqlId, getSqlParameter(parameter));
InnerInterceptor.super.beforeUpdate(executor, ms, parameter);
}
/**
* 初始化多参数
*/
private Map<?,?> initParameter(Object parameter, String sqlId){
if(null == parameter){
return null;
}
// 多参数处理,去除分页参数
if(parameter instanceof MapperMethod.ParamMap){
MapperMethod.ParamMap<?> paramMap = (MapperMethod.ParamMap<?>) parameter;
return initMapperMethodParamMap(paramMap);
}
if(parameter instanceof Map){
return (Map<?,?>)parameter;
}
if(isBaseClassTypes(parameter)){
Map<String,Object> newParamMap = new HashMap<>();
newParamMap.put(getSqlIdMethodParamName(sqlId, parameter).toLowerCase(), parameter);
return newParamMap;
}
return objectToMap(parameter);
}
/**
* 多参数处理
*/
private Map<String,Object> initMapperMethodParamMap( MapperMethod.ParamMap<?> paramMap){
Map<String,Object> newParamMap = new HashMap<>();
for (String key : paramMap.keySet()) {
if(key.startsWith("param")){
continue;
}
Object object = paramMap.get(key);
if(null == object || object instanceof AbstractWrapper){
// 自定义sql查询语句不需要 QueryWrapper 对象
continue;
}
if(object instanceof Page){
Page<?> page = (Page<?>) object;
newParamMap.put("page.current", page.getCurrent());
newParamMap.put("page.size", page.getSize());
continue;
}
if(isBaseClassTypes(object)){
newParamMap.put(key, object);
continue;
}
Map<?,?> map = (object instanceof Map) ? (Map<?,?>) object : objectToMap(object);
for (Map.Entry<?, ?> entry : map.entrySet()) {
Object value = entry.getValue();
if(null == value){
continue;
}
String sonKey = (key + "." + entry.getKey()).toLowerCase();
if(!openThreeLevelParams || isBaseClassTypes(value)){
newParamMap.put(sonKey, value);
}else{
initThreeLevelParams(newParamMap, sonKey, value);
}
}
}
return newParamMap;
}
/**
* 处理三级参数名称
*/
private void initThreeLevelParams(Map<String,Object> newParamMap, String sonKey, Object value){
if(value instanceof Map){
Map<?,?> sonMap = (Map<?,?>) value;
for (Map.Entry<?, ?> son : sonMap.entrySet()) {
Object sonValue = son.getValue();
if(null == sonValue){
continue;
}
newParamMap.put(sonKey + "." + son.getKey(), sonValue);
}
return;
}
for (Map.Entry<?, ?> son : objectToMap(value).entrySet()) {
Object sonValue = son.getValue();
if(null == sonValue){
continue;
}
newParamMap.put(sonKey + "." + son.getKey(), sonValue);
}
}
/**
* 根据 sqlId 获取 mapper 方法参数名称
*/
private String getSqlIdMethodParamName(String sqlId, Object parameter){
try {
int index = sqlId.lastIndexOf(".");
String mapperId = sqlId.substring(0, index);
String methodId = sqlId.substring(++ index);
return Class.forName(mapperId).getMethod(methodId, parameter.getClass()).getParameters()[0].getName();
} catch (Exception ex){
throw new RuntimeException("获取Sql模板参数名称失败", ex);
}
}
/**
* 判断参数类型是否是常用基本类型
*/
private boolean isBaseClassTypes(Object parameter){
return parameter instanceof String
|| parameter instanceof Integer
|| parameter instanceof Collection
|| parameter instanceof Long
|| parameter instanceof Date
|| parameter instanceof Double
|| parameter instanceof Boolean
|| parameter instanceof Float
|| parameter instanceof Short
|| parameter instanceof Character
|| parameter instanceof BigDecimal;
}
/**
* 对象属性转map集合
*/
private Map<?,?> objectToMap(Object parameter){
return ((JSONObject)JSONObject.toJSON(parameter)).toJavaObject(Map.class);
}
/**
* 获取sql参数,用于日志打印
*/
private Object getSqlParameter(Object parameter){
if(null == parameter){
return null;
}
if(parameter instanceof Map){
Map<Object,Object> map = new HashMap<>();
Map<?,?> pm = (Map<?,?>) parameter;
pm.forEach((k, v) -> {
if(null == v || k.toString().startsWith("param")){
return;
}
if(v instanceof IPage) {
IPage<?> page = (IPage<?>) v;
map.put(k, ExtMap.parse("current", page.getCurrent(), "size", page.getSize()));
return;
}
if(v instanceof AbstractWrapper){
map.put(k, getAbstractWrapperInfo(v));
return;
}
map.put(k,v);
});
return map;
}
if(parameter instanceof IPage) {
IPage<?> page = (IPage<?>) parameter;
return ExtMap.parse("page", ExtMap.parse("current", page.getCurrent(), "size", page.getSize()));
}
if(parameter instanceof AbstractWrapper){
return getAbstractWrapperInfo(parameter);
}
return parameter;
}
private ExtMap<Object, Object> getAbstractWrapperInfo(Object parameter){
AbstractWrapper<?,?,?> wrapper = (AbstractWrapper<?,?,?>) parameter;
ExtMap<Object, Object> vars = new ExtMap<>();
vars.put("entity", JSONObject.toJSONString(wrapper.getEntity()));
vars.put("sql_segment", wrapper.getCustomSqlSegment());
vars.put("sql_vars", wrapper.getParamNameValuePairs());
return vars;
}
}6、将自定义拦截器添加到mysql中,即可使用本自定义sql语法(详情请看 四 - 2 )
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。
