自定义Interceptor实现对Mybatis超时执行SQL的监控

从事代码一年多了,遇到了很多的bug,也解决了很多bug,特此趁机记录一下:
      责任链设计模式是很多框架经常采用设计模式,一定程度上拥抱了面向对象的开放和扩展,给用户暴露接口的形式能够无侵入性让用户能够做一些系统性的工作。责任链设计模式 让这些对象形成一条链,并沿着这条链传递请求,直到链上的某一个对象决定处理此请求。
    在Spring中Aop中定义过切面Aspect可以实现对方法执行的前后等进行拦截,在Netty中ChannelPipeline中持有众多自定义的ChannelHandler来完成编解码,序列化和反序列化,拆包和其他业务功能,在Tomcat也采用责任链模式,Pipline拥有很多valve,实现Engine,Host,Context,Wrapper的处理和装饰功能。Servlet也使用Filter和Listener形成责任链模式,Log4j也是同样的模型。Dubbo等是通过Invoker来实现责任链模式。类似的框架太多了,希望以后能够详细见识。

     今天要说的基于动态代理的方式的Mybatis中的Plugin插件,这个可能比较陌生,虽然性能也据说不高,但还是在此演示下。首先思考三个问题,插件在哪里侵入代码?插件如何实现对原有功能和对象的代理和操作?插件要实现什么功能?

首先:配置文件中配置如下

 <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"
          p:dataSource-ref="dataSource"
          p:configLocation="classpath:mybatis-config.xml">
        <property name="plugins">
            <array>
                <bean class="com.XX.XX.aopmethod.NotifyInterceptor">
                    <property name="properties">
                        <value>
                            notifyTime=30
                        </value>
                    </property>
                </bean>
            </array>
        </property>
    </bean>
其次定义拦截器,
/**
 * Created by wisdom on 2018/6/13.
 */
@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 NotifyInterceptor implements Interceptor {
    private static final Logger LOGGER = LogManager.getLogger(NotifyInterceptor.class);
    private Properties properties;

    @Override
    public Object intercept(Invocation arg0) throws Throwable {
        MappedStatement mappedStatement = (MappedStatement) arg0.getArgs()[0];
        Object parameter = null;
        if (arg0.getArgs().length < 1) {
            parameter = arg0.getArgs()[1];
        }
        long start = System.currentTimeMillis();
        Object returnValue = arg0.proceed();
        long end = System.currentTimeMillis();
        long time = (end - start);
        String sql = this.getActualSql(mappedStatement.getConfiguration(), mappedStatement.getBoundSql(parameter),
                mappedStatement.getId(), time);
        if (time > Long.parseLong(String.valueOf(properties.get("notifyTime")))) {
            LOGGER.warn(sql);
        }

        return returnValue;
    }

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

    public String getActualSql(Configuration configuration, BoundSql boundSql, String sqlId, long time) {
        String sql = showSql(configuration, boundSql);
        StringBuilder str = new StringBuilder("执行缓慢的SQL:");
        str.append(sqlId);
        str.append(":");
        str.append(sql);
        str.append(":");
        str.append(time);
        str.append("ms");
        return str.toString();
    }

    public String showSql(Configuration configuration, BoundSql boundSql) {
        Object parameterObject = boundSql.getParameterObject();
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
        if (parameterMappings.size() > 0 && parameterObject != null) {
            TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
            if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                sql = sql.replaceFirst("\\?", getParameterValue(parameterObject));

            } else {
                MetaObject metaObject = configuration.newMetaObject(parameterObject);
                for (ParameterMapping parameterMapping : parameterMappings) {
                    String propertyName = parameterMapping.getProperty();
                    if (metaObject.hasGetter(propertyName)) {
                        Object obj = metaObject.getValue(propertyName);
                        sql = sql.replaceFirst("\\?", getParameterValue(obj));
                    } else if (boundSql.hasAdditionalParameter(propertyName)) {
                        Object obj = boundSql.getAdditionalParameter(propertyName);
                        sql = sql.replaceFirst("\\?", getParameterValue(obj));
                    }
                }
            }
        }
        return sql;
    }

    private static String getParameterValue(Object obj) {
        String value;
        if (obj instanceof String) {
            value = "'" + obj.toString() + "'";
        } else if (obj instanceof Date) {
            value = "'" + FormatUtil.dateToString((Date) obj) + "'";
        } else {
            if (obj != null) {
                value = obj.toString();
            } else {
                value = "";
            }

        }
        return value;
    }

    @Override
    public void setProperties(Properties properties) {
        this.properties = properties;
    }

     该setProperties方法,这个方法在Configuration初始化当前的Interceptor时就会执行,这里存放超时时间。看MyInterceptor类上我们用@Intercepts标记了这是一个Interceptor,然后在@Intercepts中定义了两个@Signature,即两个拦截点。第一个@Signature我们定义了该Interceptor将拦截Executor接口中参数类型为MappedStatement、Object的update方法;第二个@Signature我们定义了该Interceptor将拦截Executor中参数类型为MappedStatement、Object,RowBounds和ResultHandler的query方法。方法内部通过计算方法执行前后的时间差,来衡量执行耗时,如果大于指定值,则可以报警和通知,不过这里为了简单,只是记录下来。运行效果如下:

com.XX.XX.getUserInfoByDsId:select top 1 * from UserInfo WITH(nolock) where DsId = 'test':5709ms(项目启动,处理缓慢)
com.XX.XX.getUserInfoByDsId:select top 1 * from UserInfo WITH(nolock) where DsId = 'test':10ms

   通过如上的代码,就可以实现监视每条SQL的执行效率,并且实现超时自动通知功能。

   同时也可以猜测各种框架的责任链模式使用都是大同小异,对用户来说,要做的开发不是很大,熟练掌握其API就可以完成功能。当然要对该模式的实现依赖的动态代理和持有的链表或者其他数据容器有所了解,就可以游刃有余啦。




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MyBatis提供了拦截器(Interceptor)的机制,可以在SQL执行前、后或者代替原有SQL执行。通过自定义拦截器,我们可以实现SQL的修改。 自定义拦截器需要实现Interceptor接口,其中最重要的方法是intercept()方法,该方法的参数Invocation代表一个拦截的方法调用。在该方法中,我们可以通过Invocation对象获取到原有的SQL以及其它相关信息,然后对其进行修改。 下面是一个简单的示例,演示如何在SQL执行前拦截并修改SQL: ```java public class MyInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { // 获取原始的SQL语句 MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0]; Object parameter = invocation.getArgs()[1]; BoundSql boundSql = mappedStatement.getBoundSql(parameter); String originalSql = boundSql.getSql(); // 修改SQL语句 String newSql = originalSql + " limit 10"; // 将修改后的SQL语句设置回去 BoundSql newBoundSql = new BoundSql(mappedStatement.getConfiguration(), newSql, boundSql.getParameterMappings(), boundSql.getParameterObject()); MappedStatement newMappedStatement = copyFromMappedStatement(mappedStatement, new BoundSqlSqlSource(newBoundSql)); invocation.getArgs()[0] = newMappedStatement; // 执行SQL return invocation.proceed(); } // 复制MappedStatement对象,将新的BoundSqlSqlSource设置进去 private MappedStatement copyFromMappedStatement(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()); builder.keyProperty(StringUtils.join(ms.getKeyProperties(), ",")); builder.timeout(ms.getTimeout()); builder.parameterMap(ms.getParameterMap()); builder.resultMaps(ms.getResultMaps()); builder.cache(ms.getCache()); builder.useCache(ms.isUseCache()); return builder.build(); } // 定义一个内部类,实现SqlSource接口 private class BoundSqlSqlSource implements SqlSource { BoundSql boundSql; public BoundSqlSqlSource(BoundSql boundSql) { this.boundSql = boundSql; } @Override public BoundSql getBoundSql(Object parameterObject) { return boundSql; } } } ``` 上面的示例是在原有SQL语句后面加了一个limit 10的限制,这只是一个简单的修改示例。实际应用中,我们可以根据需要对SQL语句进行各种操作,甚至可以完全替换原有SQL语句。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值