Springboot 自定义 Mybatis拦截器,实现 动态查询条件SQL自动组装拼接(玩具)

这篇具有很好参考价值的文章主要介绍了Springboot 自定义 Mybatis拦截器,实现 动态查询条件SQL自动组装拼接(玩具)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

ps:最近在参与3100保卫战,战况很激烈,刚刚打完仗,来更新一下之前写了一半的博客。

该篇针对日常写查询的时候,那些动态条件sql 做个简单的封装,自动生成(抛砖引玉,搞个小玩具,不喜勿喷)。

正文

来看看我们平时写那些查询,基本上都要写的一些动态sql:
 

Springboot 自定义 Mybatis拦截器,实现 动态查询条件SQL自动组装拼接(玩具),跟我一起玩转 SpringBoot,Mybatis,spring boot,mybatis,java,自定义拦截器,动态sql

一个字段写一个if ,有没有人觉得烦的。

每张表的查询,很多都有这种需求,根据什么查询,根据什么查询,不为空就触发条件。

天天写天天写,copy 改,copy改, 有没有人觉得烦的。


Springboot 自定义 Mybatis拦截器,实现 动态查询条件SQL自动组装拼接(玩具),跟我一起玩转 SpringBoot,Mybatis,spring boot,mybatis,java,自定义拦截器,动态sql

可能有看官看到这就会说, 用插件自动生成就好了。
也有看官会说,用mybatis-plus就好了。

确实有道理,但是我就是想整个小玩具。你管我。

开整

本篇实现的封装小玩具思路:

①制定的规则(比如标记自定义注解 @JcSqlQuery 或是 函数命名带上JcDynamics)。

② 触发的查询符合规则的, 都自动去根据传参对象,不为空就自动组装 sql查询条件。

③ 利用mybatis @Select 注解,把默认表查询sql写好,顺便进到自定义的mybatis拦截器里面。

④组装完sql,就执行,完事。

先写mapper函数 :
 

/**
 * @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);

}

Springboot 自定义 Mybatis拦截器,实现 动态查询条件SQL自动组装拼接(玩具),跟我一起玩转 SpringBoot,Mybatis,spring boot,mybatis,java,自定义拦截器,动态sql

然后是ParamClassInfo.java 这个用于收集需要参与动态sql组装的类:

 

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;

}

然后是一个自定义的mybatis拦截器(这里面写了一些小函数实现自主组装,下面有图解) :


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配置文件中属性
    }


代码简析:

驼峰转换下划线,用于转出数据库表的字段 :

Springboot 自定义 Mybatis拦截器,实现 动态查询条件SQL自动组装拼接(玩具),跟我一起玩转 SpringBoot,Mybatis,spring boot,mybatis,java,自定义拦截器,动态sql

通过反射把 sql入参的对象 不为空的属性名和对应的值,拿出来:

Springboot 自定义 Mybatis拦截器,实现 动态查询条件SQL自动组装拼接(玩具),跟我一起玩转 SpringBoot,Mybatis,spring boot,mybatis,java,自定义拦截器,动态sql

组件动态查询的sql 语句 :

Springboot 自定义 Mybatis拦截器,实现 动态查询条件SQL自动组装拼接(玩具),跟我一起玩转 SpringBoot,Mybatis,spring boot,mybatis,java,自定义拦截器,动态sql

写个简单测试用例:

    @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());
    }

 看下效果,可以看到都自动识别把不为空的字段属性和值拼接成查询条件了:

Springboot 自定义 Mybatis拦截器,实现 动态查询条件SQL自动组装拼接(玩具),跟我一起玩转 SpringBoot,Mybatis,spring boot,mybatis,java,自定义拦截器,动态sql

 Springboot 自定义 Mybatis拦截器,实现 动态查询条件SQL自动组装拼接(玩具),跟我一起玩转 SpringBoot,Mybatis,spring boot,mybatis,java,自定义拦截器,动态sql

好了,该篇就到这。 抛砖引玉,领悟分步封装思路最重要,都去搞些小玩具娱乐娱乐吧。文章来源地址https://www.toymoban.com/news/detail-663322.html

到了这里,关于Springboot 自定义 Mybatis拦截器,实现 动态查询条件SQL自动组装拼接(玩具)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Springboot中自定义拦截器

    Spring Boot 中使用拦截器 参考:https://blog.csdn.net/taojin12/article/details/88342576?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522170823498416800197050192%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257Drequest_id=170823498416800197050192biz_id=0utm_medium=distribute.pc_search_result.none-task-blog-2 all top_positive~defa

    2024年02月19日
    浏览(37)
  • mybatis拦截器实现数据权限

    前端的菜单和按钮权限都可以通过配置来实现,但很多时候,后台查询数据库数据的权限需要通过手动添加SQL来实现。 比如员工打卡记录表,有id,name,dpt_id,company_id等字段,后两个表示部门ID和分公司ID。 查看员工打卡记录SQL为: select id,name,dpt_id,company_id from t_record 当一个总部

    2024年02月08日
    浏览(42)
  • SpringBoot自定义拦截器interceptor使用详解

    Spring Boot拦截器Intercepter详解 Intercepter是由Spring提供的Intercepter拦截器,主要应用在日志记录、权限校验等安全管理方便。 使用过程 1.创建自定义拦截器,实现HandlerInterceptor接口,并按照要求重写指定方法 HandlerInterceptor接口源码: 根据源码可看出HandlerInterceptor接口提供了三个

    2024年02月13日
    浏览(26)
  • 自定义注解与拦截器实现不规范sql拦截(拦截器实现篇)

    最近考虑myBatis中sql语句使用规范的问题,如果漏下条件或者写一些不规范语句会对程序性能造成很大影响。最好的方法就是利用代码进行限制,通过拦截器进行sql格式的判断在自测环节就能找到问题。写了个简单情景下的demo,并通过idea插件来将myBatis的mapper方法都打上拦截器

    2024年01月22日
    浏览(35)
  • MyBatis拦截器优雅实现数据脱敏

    现代网络环境中,敏感数据的处理是至关重要的。敏感数据包括个人身份信息、银行账号、手机号码等,泄露这些数据可能导致用户隐私泄露、财产损失等严重后果。因此,对敏感数据进行脱敏处理是一种必要的安全措施。 比如页面上常见的敏感数据都是加*遮挡处理过的,

    2024年02月06日
    浏览(33)
  • MyBatis Plus 拦截器实现数据权限控制

    上篇文章介绍的MyBatis Plus 插件实际上就是用拦截器实现的,MyBatis Plus拦截器对MyBatis的拦截器进行了包装处理,操作起来更加方便 2.1、InnerInterceptor MyBatis Plus提供的InnerInterceptor接口提供了如下方法,主要包括:在查询之前执行,在更新之前执行,在SQL准备之前执行 2.2、编写简

    2024年01月17日
    浏览(33)
  • SpringBoot加入拦截器——登录拦截器的实现

            拦截器 Interceptor 在 Spring MVC 中的地位等同于 Servlet 规范中的过滤器 Filter,拦截的是处理器的执行,由于是全局行为,因此常用于做一些通用的功能,如请求日志打印、权限控制等。         核心原理:AOP思想 preHandle:  预先处理,在目标的controller方法执行之前,进行

    2024年02月15日
    浏览(31)
  • 自定义拦截器实现

    在 Spring MVC 框架中, 拦截器作为一种机制, 用于对请求进行拦截. 拦截器可以在请求进入处理器之前、处理器返回处理之后、视图渲染之前等各个环节进行拦截. 拦截器通常用于实现一下功能 : 鉴权和身份认证 日志记录和统计 请求参数和校验和过滤 缓存和性能优化 路径重定向

    2024年02月09日
    浏览(37)
  • 一张思维导图带你打通SpringBoot自定义拦截器的思路

    🧑‍💻作者名称:DaenCode 🎤作者简介:啥技术都喜欢捣鼓捣鼓,喜欢分享技术、经验、生活。 😎人生感悟:尝尽人生百味,方知世间冷暖。 📖所属专栏:SpringBoot实战 在开发中,都离不开拦截器的使用。比如说在开发登录功能时,采用JWT登录时通过对token进行验证实现登

    2024年02月14日
    浏览(38)
  • springboot实现拦截器

    内容:继承 HandlerInterceptorAdapter 并实现 WebMvcConfigurer , 拦截器中的方法将preHandle-Controller-postHandle-affterCompletion的顺序执行。 注意:只有preHandle方法返回true时后面的方法才会执行。当拦截器链存在多个拦截器时,postHandle在所有拦截器内的所有拦截器返回成功时才会调用,而

    2024年02月02日
    浏览(33)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包