mybatis(spring boot集成mybatis,拦截器实现动态sql)

程序员都是些折了翼的天使

来看看我们这个项目中是如何对mybatis动态生成sql进行改进的吧

spring boot在配置了MapperScan之后会自动扫描相关的包,并对有@Mapper标记的类进行注册

@Configuration
@EnableAutoConfiguration
@ServletComponentScan
@ComponentScan({"xxxx.scm","xxxxx.base"})
@MapperScan({"xxxxx.dao","xxxxx.mapper"})
public class Application {
    
    /**
     * 主函数入口
     *
     * @param args 参数
     */
    public static void main(String[] args) {
        Security.addProvider(new BouncyCastleProvider());
        SpringApplication.run(Application.class, args);
    }

}

在dao中可以预定义基础类,比如拦截器、基础Mapper、基础Provider等。

//拦截器
@Intercepts({
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
})
@Component
public class BaseInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementUtils.genCommonStatement((MappedStatement)(invocation.getArgs()[0])); // 生成通用查询语句
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object o) {
        return Plugin.wrap(o, this);
    }

    @Override
    public void setProperties(Properties properties) {
    }
}

这个是mybatis拦截器的标准写法,实现Interceptor接口的三个方法。可以拦截的接口一共有四种,用@Signature注解进行标记。因为Executor接口的query方法有两种不同的实现方式,所以需要拦截两种。具体拦截的内容实现放在StatementUtils中,避免因代码过长而产生维护上面的困难。

public static synchronized void genCommonStatement(MappedStatement mappedStatement) {
        Configuration configuration = mappedStatement.getConfiguration();//取出当前sql语句的Configuration
        String msId = mappedStatement.getId();//取出当前msid
        int lastIndex = msId.lastIndexOf(".");
        String methodName = msId.substring(lastIndex + 1);//获取当前sql的方法名
        String interfaceName = msId.substring(0, lastIndex);//获取接口名(Mapper)
        Class<?> mapperClass = null;
        try {
            mapperClass = Class.forName(interfaceName);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        Class entityClass = findEntityFromMapper(mapperClass);//获取实体
        List<SqlNode> sqlNodes = null;
        // 替换 ResultMap
        setResultMap(entityClass, mappedStatement);
        // 替换通用查询语句的 sqlSource
        switch (methodName) {
            case "add":
                sqlNodes = getInsert(entityClass, mappedStatement);
                break;
            case "update":
                sqlNodes = getUpdate(entityClass, mappedStatement);
                break;
            case "find":
                sqlNodes = getSelect(entityClass, mappedStatement, false);
            case "findOne":
                sqlNodes = getSelect(entityClass, mappedStatement, true);
                break;
            case "delete":
                sqlNodes = getDelete(entityClass, mappedStatement);
                break;
            case "count":
                sqlNodes = getCount(entityClass, mappedStatement);
                break;
            default:
                break;
        }
        // TODO 注入通用参数
        // TODO 注入用户自定义参数
        if (sqlNodes == null) {
            return;
        }
        DynamicSqlSource dynamicSqlSource = new DynamicSqlSource(configuration, new MixedSqlNode(sqlNodes));
        MetaObject metaObject = SystemMetaObject.forObject(mappedStatement);
        metaObject.setValue("sqlSource", dynamicSqlSource);
    }

这段代码通过使用反射技术,从传入参数中拿到sqlnode,然后根据条件对sqlnode进行重构,然后重新放入到被拦截的执行语句中。从而实现了动态生成sql。值得一提的是,如果sql事先不存在,将会产生空指针异常的错误。由于生产环境是并发环境,所以该方法是一个同步方法,有点消耗性能。不过,由于同一时间被拦截语句可能有多条,这么做也是为了避免插入混乱的情况。如果后期数据存取方面性能有所影响,这里需要修改。为了避免空指针异常的问题,需要给生成默认的sql,拦截器只能修改不能无中生有,这是需要注意的一点。

用拦截器动态生成sql的缺点:不是所有的情况都很简单,有些情况比较复杂,无法和普通情况下的一起进行拦截

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

海燕技术栈

你的鼓励我的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值