Springboot customizes Mybatis interceptor to realize dynamic query condition SQL automatic assembly splicing (toy)

foreword

ps: I recently participated in the defense of 3100. The battle was very fierce. I just finished the battle. Let me update my half-written blog.

This article is aimed at the daily writing of queries, making a simple package for those dynamic condition sql, and automatically generating it (throwing a brick to attract jade, make a small toy, don’t spray it if you don’t like it).

text

Let's take a look at the queries we usually write, and basically write some dynamic SQL:
 

Write an if in a field, does anyone find it annoying.

Many of the queries in each table have this kind of requirement. According to what query, according to what query, the condition is triggered if it is not empty.

Write every day and write every day, copy and change, copy and change, does anyone find it annoying.


Some observers may see this and say, just use a plug-in to automatically generate it.
Some judges will say, just use mybatis-plus.

Makes sense, but I just want the whole little toy. Leave me alone.

open whole

The idea of ​​encapsulating small toys implemented in this article:

① Formulated rules (such as marking custom annotations @JcSqlQuery or function naming with JcDynamics).

② If the triggered query conforms to the rules, it will automatically assemble the sql query condition according to the passed parameter object, if it is not empty.

③ Use the mybatis @Select annotation to write the default table query sql, and enter the custom mybatis interceptor by the way .

④After assembling the sql, execute it and finish.

Write the mapper function first:
 

/**
 * @Author JCccc
 * @Description
 * @Date 2023/12/14 16:56
 */
@Mapper
public interface DistrictMapper {

    @Select("select code,name,parent_code,full_name  FROM s_district_info")
    List<District> queryListJcDynamics(District district);

    @Select("select code,name,parent_code,full_name  FROM s_district_info")
    District queryOneJcDynamics(District district);

}

Then ParamClassInfo.java is used to collect classes that need to participate in dynamic sql assembly:

 

import lombok.Data;

/**
 * @Author JCccc
 * @Description
 * @Date 2021/12/14 16:56
 */
@Data
public class ParamClassInfo {

    private  String classType;
    private  Object keyValue;
    private  String  keyName;

}

Then there is a custom mybatis interceptor (some small functions are written here to realize self-assembly, and there are diagrams below):


MybatisInterceptor.java

import com.example.dotest.entity.ParamClassInfo;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.*;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static java.util.regex.Pattern.*;


/**
 * @Author JCccc
 * @Description
 * @Date 2021/12/14 16:56
 */
@Component
@Intercepts({
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class MybatisInterceptor implements Interceptor {


    private final static String JC_DYNAMICS = "JcDynamics";

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        //获取执行参数
        Object[] objects = invocation.getArgs();
        MappedStatement ms = (MappedStatement) objects[0];
        Object objectParam = objects[1];
        List<ParamClassInfo> paramClassInfos = convertParamList(objectParam);
        String queryConditionSqlScene = getQueryConditionSqlScene(paramClassInfos);
        //解析执行sql的map方法,开始自定义规则匹配逻辑
        String mapperMethodAllName = ms.getId();
        int lastIndex = mapperMethodAllName.lastIndexOf(".");
        String mapperClassStr = mapperMethodAllName.substring(0, lastIndex);
        String mapperClassMethodStr = mapperMethodAllName.substring((lastIndex + 1));
        Class<?> mapperClass = Class.forName(mapperClassStr);
        Method[] methods = mapperClass.getMethods();
        for (Method method : methods) {
            if (method.getName().equals(mapperClassMethodStr) && mapperClassMethodStr.contains(JC_DYNAMICS)) {
                BoundSql boundSql = ms.getSqlSource().getBoundSql(objects[1]);
                String originalSql = boundSql.getSql().toLowerCase(Locale.CHINA).replace("[\\t\\n\\r]", " ");
                //进行自动的 条件拼接
                String newSql = originalSql + queryConditionSqlScene;
                BoundSql newBoundSql = new BoundSql(ms.getConfiguration(), newSql,
                        boundSql.getParameterMappings(), boundSql.getParameterObject());
                MappedStatement newMs = newMappedStatement(ms, new MyBoundSqlSqlSource(newBoundSql));
                for (ParameterMapping mapping : boundSql.getParameterMappings()) {
                    String prop = mapping.getProperty();
                    if (boundSql.hasAdditionalParameter(prop)) {
                        newBoundSql.setAdditionalParameter(prop, boundSql.getAdditionalParameter(prop));
                    }
                }
                Object[] queryArgs = invocation.getArgs();
                queryArgs[0] = newMs;
                System.out.println("打印新SQL语句" + newSql);
            }
        }
        //继续执行逻辑
        return invocation.proceed();
    }

    private String getQueryConditionSqlScene(List<ParamClassInfo> paramClassInfos) {
        StringBuilder conditionParamBuilder = new StringBuilder();
        if (CollectionUtils.isEmpty(paramClassInfos)) {
            return "";
        }
        conditionParamBuilder.append("  WHERE ");
        int size = paramClassInfos.size();
        for (int index = 0; index < size; index++) {
            ParamClassInfo paramClassInfo = paramClassInfos.get(index);
            String keyName = paramClassInfo.getKeyName();
            //默认驼峰拆成下划线 ,比如 userName -》 user_name ,   name -> name
            //如果是需要取别名,其实可以加上自定义注解这些,但是本篇例子是轻封装,思路给到,你们i自己玩
            String underlineKeyName = camelToUnderline(keyName);
            conditionParamBuilder.append(underlineKeyName);
            Object keyValue = paramClassInfo.getKeyValue();
            String classType = paramClassInfo.getClassType();
            //其他类型怎么处理 ,可以按照类型区分 ,比如检测到一组开始时间,Date 拼接 between and等
//            if (classType.equals("String")){
//                conditionParamBuilder .append("=").append("\'").append(keyValue).append("\'");
//            }

            conditionParamBuilder.append("=").append("\'").append(keyValue).append("\'");
            if (index != size - 1) {
                conditionParamBuilder.append(" AND ");
            }
        }
        return conditionParamBuilder.toString();
    }

    private static List<ParamClassInfo> convertParamList(Object obj) {
        List<ParamClassInfo> paramClassList = new ArrayList<>();
        for (PropertyDescriptor pd : BeanUtils.getPropertyDescriptors(obj.getClass())) {
            if (!"class".equals(pd.getName())) {
                if (ReflectionUtils.invokeMethod(pd.getReadMethod(), obj) != null) {
                    ParamClassInfo paramClassInfo = new ParamClassInfo();
                    paramClassInfo.setKeyName(pd.getName());
                    paramClassInfo.setKeyValue(ReflectionUtils.invokeMethod(pd.getReadMethod(), obj));
                    paramClassInfo.setClassType(pd.getPropertyType().getSimpleName());
                    paramClassList.add(paramClassInfo);
                }
            }
        }
        return paramClassList;
    }


    public static String camelToUnderline(String line){
        if(line==null||"".equals(line)){
            return "";
        }
        line=String.valueOf(line.charAt(0)).toUpperCase().concat(line.substring(1));
        StringBuffer sb=new StringBuffer();
        Pattern pattern= compile("[A-Z]([a-z\\d]+)?");
        Matcher matcher=pattern.matcher(line);
        while(matcher.find()){
            String word=matcher.group();
            sb.append(word.toUpperCase());
            sb.append(matcher.end()==line.length()?"":"_");
        }
        return sb.toString();
    }


    @Override
    public Object plugin(Object o) {
        //获取代理权
        if (o instanceof Executor) {
            //如果是Executor(执行增删改查操作),则拦截下来
            return Plugin.wrap(o, this);
        } else {
            return o;
        }
    }

    /**
     * 定义一个内部辅助类,作用是包装 SQL
     */
    class MyBoundSqlSqlSource implements SqlSource {
        private BoundSql boundSql;

        public MyBoundSqlSqlSource(BoundSql boundSql) {
            this.boundSql = boundSql;
        }

        @Override
        public BoundSql getBoundSql(Object parameterObject) {
            return boundSql;
        }

    }

    private MappedStatement newMappedStatement(MappedStatement ms, SqlSource newSqlSource) {
        MappedStatement.Builder builder = new
                MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType());
        builder.resource(ms.getResource());
        builder.fetchSize(ms.getFetchSize());
        builder.statementType(ms.getStatementType());
        builder.keyGenerator(ms.getKeyGenerator());
        if (ms.getKeyProperties() != null && ms.getKeyProperties().length > 0) {
            builder.keyProperty(ms.getKeyProperties()[0]);
        }
        builder.timeout(ms.getTimeout());
        builder.parameterMap(ms.getParameterMap());
        builder.resultMaps(ms.getResultMaps());
        builder.resultSetType(ms.getResultSetType());
        builder.cache(ms.getCache());
        builder.flushCacheRequired(ms.isFlushCacheRequired());
        builder.useCache(ms.isUseCache());
        return builder.build();
    }


    @Override
    public void setProperties(Properties properties) {
        //读取mybatis配置文件中属性
    }


Code analysis:

Camel case conversion underscore, used to transfer out the field of the database table:

Through reflection, take out the attribute name and corresponding value of the non-empty object of the sql input parameter:

The sql statement of component dynamic query:

Write a simple test case:

    @Autowired
    DistrictMapper districtMapper;

    @Test
    public void test() {
        District query = new District();
        query.setCode("110000");
        query.setName("北京市");
        District district = districtMapper.queryOneJcDynamics(query);
        System.out.println(district.toString());

        District listQuery = new District();
        listQuery.setParentCode("110100");
        List<District> districts = districtMapper.queryListJcDynamics(listQuery);
        System.out.println(districts.toString());
    }

 Looking at the effect, you can see that all fields that are not empty are automatically recognized and spliced ​​into query conditions:

 

Well, that's all for this article. Throwing bricks to attract jade, comprehending the idea of ​​step-by-step encapsulation is the most important thing, let's go and make some small toys for entertainment.

Guess you like

Origin blog.csdn.net/qq_35387940/article/details/132340195