ibatis拦截器,实现sql打印

package com.alibaba.kaola.ad.searchad.dao.interceptors;

import java.sql.Connection;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.regex.Matcher;

import com.alibaba.kaola.ad.searchad.common.annotation.EnvCheck;
import com.alibaba.kaola.ad.searchad.common.utils.DateUtil;
import com.alibaba.kaola.ad.searchad.common.utils.EnvManager;
import com.alibaba.kaola.ad.searchad.common.utils.LoggerUtil;
import com.alibaba.kaola.ad.searchad.common.utils.ReflectHelper;
import com.alibaba.kaola.ad.searchad.common.utils.SpringContextUtil;
import com.alibaba.kaola.ad.searchad.common.utils.SqlUtils;
import com.alibaba.kaola.ad.searchad.dao.base.IbatisSwitch;

import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.util.TablesNamesFinder;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.springframework.core.annotation.AnnotationUtils;

/**
 * @author zhenyuan.he
 */
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class IbatisPrepareInterceptor implements Interceptor {

    static final String REGEX = "\\?";

    static final String FIELD_DELEGATE = "delegate";

    static final String FIELD_MAPPED_STATEMENT = "mappedStatement";

    @Override
    public Object intercept(Invocation invocation) {
        BoundSql boundSql = null;
        Configuration configuration = null;
        SqlCommandType sqlCommandType = null;
        long startTime = System.currentTimeMillis();
        try {
            final StatementHandler statementHandler = (StatementHandler)invocation.getTarget();
            boundSql = statementHandler.getBoundSql();
            final StatementHandler handler = (StatementHandler)ReflectHelper.getFieldValue(statementHandler,
                FIELD_DELEGATE);
            final MappedStatement mappedStatement = (MappedStatement)ReflectHelper.getFieldValue(handler,
                FIELD_MAPPED_STATEMENT);
            // 获取节点的配置
            configuration = mappedStatement.getConfiguration();
            sqlCommandType = mappedStatement.getSqlCommandType();

            //1、如果不是查询语句,放过不拦截
            if (!SqlCommandType.SELECT.equals(sqlCommandType)) {
                return invocation.proceed();
            }

            //4、如果没有设置需要做空间隔离,放过不拦截
            final String className = mappedStatement.getId().substring(0, mappedStatement.getId().lastIndexOf("."));
            final Class<?> targetMapper = Class.forName(className);
            final EnvCheck envCheck = AnnotationUtils.findAnnotation(targetMapper, EnvCheck.class);
            if (envCheck == null) {
                return invocation.proceed();
            }
            final String env = SpringContextUtil.getBean(EnvManager.class).getCurrentEnv();
            // 利用反射设置当前BoundSql对应的sql属性为我们建立好的分页Sql语句
            ReflectHelper.setFieldValue(boundSql, "sql", replaceSql(boundSql.getSql(), env));
            return invocation.proceed();
        } catch (Throwable e) {
            LoggerUtil.error(e, "IbatisPrepareInterceptor error");
            throw new RuntimeException(e);
        } finally {
            try {
                if (IbatisSwitch.SQL_PRINT_OPEN && sqlCommandType != null && boundSql != null) {
                    final String sql = showSql(configuration, boundSql);
                    LoggerUtil.warn(LoggerUtil.sqlLogger, "sql=", sql, ",tableName=",
                        String.join(",", getTableNames(boundSql.getSql())), ",sqlType=", sqlCommandType.name(),
                        ",cost=", System.currentTimeMillis() - startTime, "ms");
                }
            } catch (Throwable e) {
                LoggerUtil.error(e, "sql error");
            }
        }
    }

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

    @Override
    public void setProperties(Properties properties) {
    }

    /**
     * 新的sql
     *
     * @param sql 原sql
     * @param env 环境变量
     * @return 新sql
     */
    private static String replaceSql(String sql, String env) {
        final String envCondition = "env='" + env + "'";
        return SqlUtils.addWhereCondition(sql, envCondition);
    }

    /**
     * 进行?的替换
     */
    private String showSql(Configuration configuration, BoundSql boundSql) {
        // 获取参数
        Object parameterObject = boundSql.getParameterObject();
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        // sql语句中多个空格都用一个空格代替
        String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
        if (CollectionUtils.isEmpty(parameterMappings) || parameterObject == null) {
            return sql;
        }
        // 获取类型处理器注册器,类型处理器的功能是进行java类型和数据库类型的转换
        TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
        // 如果根据parameterObject.getClass()可以找到对应的类型,则替换
        if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            sql = sql.replaceFirst(REGEX,
                Matcher.quoteReplacement(getParameterValue(parameterObject)));
        } else {
            // MetaObject主要是封装了originalObject对象,提供了get和set的方法用于获取和设置originalObject的属性值,
            // 主要支持对JavaBean、Collection、Map三种类型对象的操作
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            for (ParameterMapping parameterMapping : parameterMappings) {
                String propertyName = parameterMapping.getProperty();
                Object obj;
                if (metaObject.hasGetter(propertyName)) {
                    obj = metaObject.getValue(propertyName);
                } else if (boundSql.hasAdditionalParameter(propertyName)) {
                    // 该分支是动态sql
                    obj = boundSql.getAdditionalParameter(propertyName);
                } else {
                    obj = null;
                }
                final String value = Matcher.quoteReplacement(getParameterValue(obj));
                sql = sql.replaceFirst(REGEX, value);
            }
        }
        return sql;
    }

    private static String getParameterValue(Object obj) {
        if (obj == null) {
            return StringUtils.EMPTY;
        }
        String value;
        if (obj instanceof String) {
            value = "'" + obj.toString() + "'";
        } else if (obj instanceof Date) {
            value = "'" + DateUtil.formatDate((Date)obj, DateUtil.DATETIME19_PATTERN) + "'";
        } else {
            value = obj.toString();
        }
        return value;
    }

    /**
     * 获取表名
     *
     * @param sql sql
     * @return 获取sql所有的表名
     */

    private Set<String> getTableNames(String sql) {
        Statement statement;
        try {
            statement = CCJSqlParserUtil.parse(sql);
        } catch (JSQLParserException e) {
            throw new RuntimeException("解析sql语句错误!sql:" + sql, e);
        }
        TablesNamesFinder tablesNamesFinder = new TablesNamesFinder();
        List<String> tableList = tablesNamesFinder.getTableList(statement);
        Set<String> tableNames = new HashSet<>();
        for (String tableName : tableList) {
            //获取去掉“`”的表名
            if (tableName.startsWith("`") && tableName.endsWith("`")) {
                tableNames.add(tableName.substring(1, tableName.length() - 1));
            } else {
                tableNames.add(tableName);
            }
        }
        return tableNames;
    }

}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
假设有以下需求:在使用MyBatis操作数据库时,需要将MySQL的特定语法转换为达梦数据库的语法,例如将MySQL的`LIMIT`语句转换为达梦的`FETCH FIRST`语句。 可以通过自定义MyBatis拦截器实现上述需求。 首先,需要实现一个拦截器类,继承自`org.apache.ibatis.plugin.Interceptor`接口,并实现`intercept()`方法。 ```java public class MysqlToDmInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { // 获取原始的SQL语句 String sql = (String) invocation.getArgs()[0]; // 判断是否为MySQL的语法 if (sql.contains("LIMIT")) { // 将MySQL的LIMIT语句转换为达梦的FETCH FIRST语句 sql = sql.replace("LIMIT", "FETCH FIRST"); } // 将处理后的SQL语句传递给下一个拦截器或执行器 invocation.getArgs()[0] = sql; return invocation.proceed(); } @Override public Object plugin(Object target) { // 对需要拦截的对象进行包装,返回一个代理对象 return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { // 可以在这里设置拦截器的属性 } } ``` 在上述代码中,通过判断SQL语句中是否包含`LIMIT`,来确定是否需要进行转换。如果需要转换,则将`LIMIT`替换为`FETCH FIRST`,并将处理后的SQL语句传递给下一个拦截器或执行器。 接下来,需要在MyBatis的配置文件中配置该拦截器: ```xml <configuration> <plugins> <plugin interceptor="com.example.MysqlToDmInterceptor"> <!-- 可以在这里设置拦截器的属性 --> </plugin> </plugins> </configuration> ``` 配置文件中的`<plugins>`标签用于配置所有的拦截器,每个拦截器需要使用`<plugin>`标签进行包装,并指定`interceptor`属性为拦截器类的全限定名。 最后,就可以使用MyBatis进行数据库操作了,拦截器会在执行SQL语句时自动进行转换: ```java SqlSession session = sqlSessionFactory.openSession(); try { UserMapper mapper = session.getMapper(UserMapper.class); List<User> users = mapper.selectByExample(new UserExample()); } finally { session.close(); } ``` 在这个例子中,如果`selectByExample()`方法生成的SQL语句中包含`LIMIT`,则拦截器会将其转换为`FETCH FIRST`,从而实现了MySQL和达梦数据库的动态转换。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值