Mybatis 注入全局参数

在项目中使用mybatis作为dao层,大部分时间都需要使用到mybatis提供的动态sql功能,一般情况下所有的表都是在同一个数据库下的,进行数据操作时都是使用jdbc中默认的schema。但是如果系统升级了,将一部分表抽到新的schema上,作为程序员可就苦逼了,在对应表的xml文件中都需要手动指定schema,如果schema再换呢?。。。。XXXXXXX

所以更好的办法就是全局配置,在官方文档http://mybatis.github.io/mybatis-3/configuration.html#plugins中始终没找到如何配置全局的参数,倒是发现了plugins这个好东西。

简单来说就是拦截器,这就是一个重要的切入点。
首先先根据文档配置好拦截器,因为我是同spring整合的,所以在spring的xml中配置:

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    ...
        <property name="plugins">
            <array>
                <bean class="cn.cml.text.SchemaInterceptor"/>
            </array>
        </property>
    ...
    </bean>

这样拦截器就配置ok了,只需要实现方法:

public Object intercept(Invocation invocation)

和配置切入点,我们需要在做update和query的时候指定schema,在类上添加注解:

@Intercepts({ @Signature(method = "update", args = { MappedStatement.class, Object.class }, type = Executor.class),
        @Signature(method = "query", args = { MappedStatement.class, Object.class, RowBounds.class,
                ResultHandler.class }, type = Executor.class) })

方法名称是固定的,具体可以看Executor这个借口定义。

既然找到了入口,就可以着手实现动态注入参数了。
首先得找到切入点MappedStatement,主要的方法getBoundSql,在这个方法中的实现如下:

public BoundSql getBoundSql(Object parameterObject) {
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings == null || parameterMappings.size() <= 0) {
      boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
    }

    // check for nested result maps in parameter mappings (issue #30)
    for (ParameterMapping pm : boundSql.getParameterMappings()) {
      String rmId = pm.getResultMapId();
      if (rmId != null) {
        ResultMap rm = configuration.getResultMap(rmId);
        if (rm != null) {
          hasNestedResultMaps |= rm.hasNestedResultMaps();
        }
      }
    }

    return boundSql;
  }

boundSql其实是从sqlSource上获取的,继续跟进查看实现类:
这里写图片描述
看到了吧DynamicSqlSource是不是感觉很亲切,看名字就知道是处理动态sql的。要证明很简单,只需要debug一下就知道了!

继续跟进发现它调用了SqlNode.apply()方法,该类也有很多实现。要找到具体是哪个类进行参数注入的,只需要在xml中随便加入动态参数,如:select * from ${db},运行后就可以看到各种错误了。这样就容易定位到了TextSqlNode这个类,具体实现可以自己去看看。

回过头来SqlNode.apply()这个方法有个参数DynamicContext,而在TextSqlNode的类中发现获取注入的参数对象其实是调用 context.getBindings().put(“value”, parameter); 哈哈 binggo, context.getBindings()是map对象,这就明了了,参数就是从map中获取的。那么需要注入全局参数,只需要在map中注入即可。

那么问题来了,该如何实现呢?主要还是使用反射和动态代理,仔细看看拦截器的实现


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

Plugin.wrap也是使用动态代理的。

具体实现步骤:
1、现根据MappedStatement获取到SqlSource对象
2、如果是DynamicSqlSource则获取成员变量rootSqlNode就拿到了SqlNode对象
3、使用动态代理,将SqlNode更换成我们代理类
4、在代理类上调用apply方法时,动态注入全局参数

废话不多说,下面上源码:

import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlSource;
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.scripting.xmltags.DynamicContext;
import org.apache.ibatis.scripting.xmltags.DynamicSqlSource;
import org.apache.ibatis.scripting.xmltags.SqlNode;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

@Intercepts({ @Signature(method = "update", args = { MappedStatement.class, Object.class }, type = Executor.class),
        @Signature(method = "query", args = { MappedStatement.class, Object.class, RowBounds.class,
                ResultHandler.class }, type = Executor.class) })
public class SchemaInterceptor implements Interceptor {

    protected static Log LOG = LogFactory.getLog(SchemaInterceptor.class);
    private static final String SCHEMA = "schema";

    private String schema;

    private Set<Integer> sourceStorage = new HashSet<Integer>();

    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        Object[] args = invocation.getArgs();

        MappedStatement mappedStatement = (MappedStatement) args[0];

        SqlSource sqlSource = mappedStatement.getSqlSource();

        // 只拦截动态sql
        if (sqlSource instanceof DynamicSqlSource) {

            // 获取到sqlNode对象
            Field field = DynamicSqlSource.class.getDeclaredField("rootSqlNode");
            field.setAccessible(true);
            SqlNode sqlnode = (SqlNode) field.get(sqlSource);

            if (!sourceStorage.contains(sqlSource.hashCode())) {

                // 获取动态代理对象
                SqlNode proxyNode = proxyNode(sqlnode);

                field.set(sqlSource, proxyNode);

                sourceStorage.add(sqlSource.hashCode());
            }

        }

        int sqlSourceCount = sourceStorage.size();

        if (sqlSourceCount >= 1000) {
            LOG.error("========>sqlsource数量预警==================》");
        } else {
            LOG.info("=============>sqlSourceSize:" + sqlSourceCount);
        }

        return invocation.proceed();

    }

    private SqlNode proxyNode(SqlNode sqlnode) {
        SqlNode proxyNode = (SqlNode) Proxy.newProxyInstance(sqlnode.getClass().getClassLoader(),
                new Class[] { SqlNode.class }, new SqlNodeInvocationHandler(sqlnode));
        return proxyNode;
    }

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

    @Override
    public void setProperties(Properties properties) {
        LOG.debug("setProperties====>" + properties);
    }

    private class SqlNodeInvocationHandler implements InvocationHandler {

        private SqlNode target;

        public SqlNodeInvocationHandler(SqlNode target) {
            super();
            this.target = target;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            DynamicContext context = (DynamicContext) args[0];
            context.getBindings().put(SCHEMA, schema);
            return method.invoke(target, args);
        }

    }

    public void setSchema(String schema) {
        if (StringUtils.isNotBlank(schema)) {
            if (!schema.endsWith(".")) {
                schema += ".";
            }
        }
        this.schema = schema;
    }

}

具体注入参数代码:

    @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            DynamicContext context = (DynamicContext) args[0];
            context.getBindings().put("dbSchema", "myschema.");
            return method.invoke(target, args);
        }

还没有封装好,需要的自己再封装下就可以了!
当然,功能是实现了,感觉还是不太优雅,有没有官方对外接口呢?

转载于:https://www.cnblogs.com/cmlblog/p/7663121.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值