在项目中使用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);
}

还没有封装好,需要的自己再封装下就可以了!

当然,功能是实现了,感觉还是不太优雅,有没有官方对外接口呢?

Mybatis 注入全局参数的更多相关文章

  1. mybatis由浅入深day01_6SqlMapConfig.xml(6.2settings全局参数配置_6.3typeAliases(类型别名)_6.4typeHandlers(类型处理器)_6.5mappers(映射配置))

    6 SqlMapConfig.xml mybatis的全局配置文件SqlMapConfig.xml,配置内容和顺序如下: properties(属性) settings(全局配置参数) typeAli ...

  2. 深入浅出mybatis之useGeneratedKeys参数用法

    目录 在settings元素中设置useGeneratedKeys参数 在xml映射器中配置useGeneratedKeys参数 在接口映射器中设置useGeneratedKeys参数 在MyBati ...

  3. Mybatis(二) 全局配置文件详解

    这节来说说全局配置文件的东西,非常简单.看一遍就懂了. --WH 一.全部配置内容 SqlMapConfig.xml的配置内容和顺序如下,顺序不能乱.现在来对这些属性的意思一一进行讲解. 二.prop ...

  4. 02、MyBatis XML 全局配置文件

    MyBatis-全局配置文件 在MyBatis中全局配置文件有着重要的地位,里面有9类行为信息;如果我们要想将MyBatis运用的熟练,配置全局配置文件是必不可少的步骤,所以我们一定要啃下这一块硬骨头 ...

  5. 由浅入深---MyBatis的全局配置文件

    从我开始接触代码,我就很怕写配置文件,一般的配置文件我都是直接从上一个项目复制到这个项目来改改,可能有部分同学也有我这种痛吧: 我目前一般的做法,先去找找例子(从网上,从github,从官网)之后再改 ...

  6. MyBatis的返回参数类型和查询结果遍历

    MyBatis的返回参数类型分两种 1. 对应的分类为: 1.1.resultMap: 1.2.resultType: 2 .对应返回值类型: 2.1.resultMap:结果集 2.2.result ...

  7. CloudStack4.2 更新全局参数API

    测试更新全局参数API http://192.168.153.34:8080/client/api?command=updateConfiguration&response=json& ...

  8. 小程序首页onLoad为异步,调用app.js中的全局参数的解决方案。

    一,先说一下遇到的问题: 在首页,为了携带app.js中一些参数去做请求动作,但是由于异步原因,发现请求时候,参数信息还未获取到但请求已经发出去. 若等app.js的全局参数返回来,再携带着它去做请求 ...

  9. MyBatis 传List参数 nested exception is org.apache.ibatis.binding.BindingException: Parameter 'idList' not found.

    在MyBatis传入List参数时,MyBatis报错:nested exception is org.apache.ibatis.binding.BindingException: Paramete ...

随机推荐

  1. 基于MySQL Binlog的Elasticsearch数据同步实践

    一.为什么要做 随着马蜂窝的逐渐发展,我们的业务数据越来越多,单纯使用 MySQL 已经不能满足我们的数据查询需求,例如对于商品.订单等数据的多维度检索. 使用 Elasticsearch 存储业务数 ...

  2. Java 多线程 -- 指令重排(HappenBefore)

    指令重排是指:代码执行顺序和预期不一致. 代码运行一般步骤为: 1.从内存中获取指令解码 2.计算值 3.执行代码操作 4.把结果写回内存 而写回内存的操作比较耗时,CPU为了性能,可能不会等它完成, ...

  3. python工业互联网监控项目实战5—Collector到opcua服务

    本小节演示项目是如何从连接器到获取Tank4C9服务上的设备对象的值,并通过Connector服务的url返回给UI端请求的.另外,实际项目中考虑websocket中间可能因为网络通信等原因出现中断情 ...

  4. 关于flex弹性布局

    http://www.ruanyifeng.com/blog/2015/07/flex-grammar.html

  5. 实用的linux 命令(上)

    今天介绍几个我常用的Linux 命令,每个命令这里只介绍其常用参数. 对于每个Linux 命令都可以使用man + 命令名称,查看其完整使用方法. 0,man man 命令是一个非常有用的命令,当你不 ...

  6. 为什么要你们现在要学习python

    说学习python之前,我们先来聊聊其他的.我们都认为成功靠的是勤奋和努力,但是事实是只靠勤奋和努力是不一定会成功的,而且很大一部分都不会成功. 你有没有想过,同样是做企业,有些公司年收入百万,而腾讯 ...

  7. zoj_2511 Design T-Shirt 贪心

    Design T-Shirt Time Limit: 2 Seconds      Memory Limit: 32768 KB Soon after he decided to design a T ...

  8. XEP-0199 XMPP Ping

    原文来自:https://xmpp.org/extensions/xep-0199.html,只翻译了技术方面的内容. 摘要:这个规范定义了一个通过XML流发送应用级别pings的XMPP扩展协议.这 ...

  9. OSChina 清明节乱弹 ——准备好纸巾了看乱弹

    2019独角兽企业重金招聘Python工程师标准>>> Osc乱弹歌单(2017)请戳(这里) [今日歌曲] @亚麻仔 :分享 范忆堂 的歌曲<酡颜 (夏热版)> 同一个 ...

  10. Eclipse Collections:让Java Streams更上一层楼

    \ 关键要点 \\ Eclipse Collections是一个高性能的Java集合框架,为原生JDK集合增加了丰富的功能.\\t Streams是JDK的一个非常受欢迎的功能,但它缺少了一些特性,严 ...