拦截mybatis执行的sql,并将sql插入到数据库中

一、自定义mybatis的拦截器,获取mybatis执行的sql语句

@Intercepts({
        @Signature(type = Executor.class, method = "update", args = { // type: 被拦截对象;method:要拦截对象的那个方法;args:需要哪些参数,method的入参
                MappedStatement.class, Object.class})})
@Component
public class MybatisInterceptor implements Interceptor {

    private LogSqlService logSqlService = new LogSqlServiceImpl();

    /**
     * 拦截器方法
     * @Author: zgj
     * @Date: 2021/3/1 11:42
     * @param invocation: @Intercepts下的@Signature配置的对象
     * @return: java.lang.Object
     **/
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        try {
            MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];  // 获取xml中的一个select/update/insert/delete节点,主要描述的是一条SQL语句
            Object parameter = null;
            // 获取参数,if语句成立,表示sql语句有参数,参数格式是map形式
            if (invocation.getArgs().length > 1) {
                parameter = invocation.getArgs()[1];
                System.out.println("parameter = " + parameter);
            }
            String sqlId = mappedStatement.getId(); // 获取到节点的id,即sql语句的id
            System.out.println("sqlId = " + sqlId);
            BoundSql boundSql = mappedStatement.getBoundSql(parameter);  // BoundSql就是封装myBatis最终产生的sql类
            ArrayList<String> arrayList = new ArrayList<>();
            arrayList.add("INSERT INTO sys_log_operation");
            arrayList.add("INSERT INTO log_sql");
            // 排除掉执行日志的插入
            if (boundSql.getSql() != null && arrayList.stream().anyMatch(e -> boundSql.getSql().trim().startsWith(e))){
                return invocation.proceed();
            }
            Configuration configuration = mappedStatement.getConfiguration();  // 获取节点的配置
            String showSql = showSql(configuration, boundSql);
            LogSql logSql = new LogSql(showSql);
            int i = logSqlService.insertLogSql(logSql);
            String sql = getSql(configuration, boundSql, sqlId); // 获取到最终的sql语句
            System.out.println("sql = " + sql);
            //log.debug(sql);
        } catch (Exception e) {
            // log.error(e.getMessage(), e);
        }
        return invocation.proceed();  // 执行完上面的任务后,不改变原有的sql执行过程
    }

    
/*<br>    *如果参数是String,则添加单引号, 如果是日期,则转换为时间格式器并加单引号; 对参数是null和不是null的情况作了处理<br>  */

    private static String getParameterValue(Object obj) {
        String value = null;
        if (obj instanceof String) {
            value = "'" + obj.toString() + "'";
        } else if (obj instanceof Date) {
            DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);
            value = "'" + formatter.format(new Date()) + "'";
        } else {
            if (obj != null) {
                value = obj.toString();
            } else {
                value = "";
            }

        }
        return value;
    }

    // 进行?的替换
    public static String showSql(Configuration configuration, BoundSql boundSql) {
        Object parameterObject = boundSql.getParameterObject();  // 获取参数
        List<ParameterMapping> parameterMappings = boundSql  //获取sql中问号的基本信息
                .getParameterMappings();
        String sql = boundSql.getSql().replaceAll("[\\s]+", " ");  // sql语句中多个空格都用一个空格代替
        if (CollectionUtils.isNotEmpty(parameterMappings) && parameterObject != null) {
            TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry(); // 获取类型处理器注册器,类型处理器的功能是进行java类型和数据库类型的转换<br>       // 如果根据parameterObject.getClass()可以找到对应的类型,则替换
            if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(parameterObject)));

            } else {
                MetaObject metaObject = configuration.newMetaObject(parameterObject);// MetaObject主要是封装了originalObject对象,提供了get和set的方法用于获取和设置originalObject的属性值,主要支持对JavaBean、Collection、Map三种类型对象的操作
                for (ParameterMapping parameterMapping : parameterMappings) {
                    String propertyName = parameterMapping.getProperty();
                    if (metaObject.hasGetter(propertyName)) {
                        Object obj = metaObject.getValue(propertyName);
                        sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(obj)));
                    } else if (boundSql.hasAdditionalParameter(propertyName)) {
                        Object obj = boundSql.getAdditionalParameter(propertyName);  // 该分支是动态sql
                        sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(obj)));
                    } else {
                        sql = sql.replaceFirst("\\?", "缺失");
                    }//打印出缺失,提醒该参数缺失并防止错位
                }
            }
        }
        return sql;
    }

    /**
     *
     * @Author: zgj
     * @Date: 2021/3/1 11:28
     * @param target: 要代理的目标对象
     * @return: java.lang.Object
     **/
    @Override
    public Object plugin(Object target) {
        // interceptor 拦截器对象
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {

    }
}
自定义mybatis拦截器需要实现Interceptor接口,将该类通过@Component交给spring容器管理就可使用。 

二、将sql语句插入到数据表中

这里试过用mybatis,但是发现mapper层spring注入不进去,于是使用最原始的方法。

1. 创建实体类

@Data
@TableName(value = "log_sql")
public class LogSql implements Serializable {
    @TableId(value = "id", type = IdType.ASSIGN_UUID)
    private String id;

    @TableField(value = "sql")
    private String sql;

    public LogSql(String sql) {
        this.sql = sql;
    }
}

2. 将sql插入数据表类

@Component
public class JDBCconfig {
    public static Connection connection;

    @Value("${spring.datasource.url}")
    private String url;

    @Value("${spring.datasource.username}")
    private String username;

    @Value("${spring.datasource.password}")
    private String password;

    public int jdbcLianjie(LogSql logSql) {
        PreparedStatement ps = null;
        int execute = 0;
        try {
            connection = DriverManager.getConnection(url, username, password);
            connection.setAutoCommit(false);
            String sql = "insert into log_sql values(?,?)";
            //开启事务
            ps = connection.prepareStatement(sql);
            logSql.setId(UUID.randomUUID().toString());
            ps.setString(1, logSql.getId());
            ps.setString(2, logSql.getSql());
            execute = ps.executeUpdate();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }finally {
            if (ps != null) {
                try {
                    ps.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
        }
        return execute;
    }

    public void close(boolean flag){
        try {
            if (flag) {
                // 事务提交
                connection.commit();
            }else {
                // 事务回滚
                connection.rollback();
            }
            if (connection != null) {
                connection.close();
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }
}

连接参数通过@value获取配置文件中配置mybatis的数据源,代码中也包含了对事务的处理。

3. 配置通过getbean获取bean

尝试使用@Autowired获取bean对象,发现为null,不知道为什么,所以用spring的getbean方法获取bean对象
@Component
public class SpringContextHolder implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Autowired
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringContextHolder.applicationContext = applicationContext;
    }


    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }


    public static Object getBean(String name) {
        return getApplicationContext().getBean(name);
    }

    /**
     * 通过class获取Bean.
     *
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getBean(Class<T> clazz) {
        return getApplicationContext().getBean(clazz);
    }

    /**
     * 通过name,以及Clazz返回指定的Bean
     *
     * @param name
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getBean(String name, Class<T> clazz) {
        return getApplicationContext().getBean(name, clazz);
    }

}

4. 业务层

通过getbean获取执行sql的对象,调用插入方法。该方法在上面的自定义的mybatis拦截器中调用。
@Service
public class LogSqlServiceImpl implements LogSqlService {

    public int insertLogSql(LogSql sql) {
        JDBCconfig jdbCconfig = SpringContextHolder.getBean(JDBCconfig.class);
        int result = jdbCconfig.jdbcLianjie(sql);
        return result;
    }
}

三、被拦截的sql发生事务回滚时,该插入操作不回滚的问题

尝试直接加@Transactional注解,发现不起效果,于是使用Connection的事务操作,主要问题是在什么时候去执行事务提交和事务回滚操作。

1、事务提交

可以通过spring的aop动态代理拦截有@Transactional注解得controller层方法的结果,如果返回正常,则执行SpringContextHolder.getBean(JDBCconfig.class).close(true);将事务提交并关闭连接。

2、事务回滚

可以通过@ExceptionHandler捕获所有的异常,毕竟mybatis执行的sql在有异常的时候才会回滚,可以在该方法中执行SpringContextHolder.getBean(JDBCconfig.class).close(false);将事务回滚。

@RestControllerAdvice
public class GlobalException {
    @ExceptionHandler
    public R handlerException(Exception e){
        SpringContextHolder.getBean(JDBCconfig.class).close(false);
        return R.error().data("异常原因",e.getMessage());
    }
}
  • 1
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值