Mybatis 注入全局参数
来源:互联网 发布:有哪些抢购软件 编辑:程序博客网 时间:2024/05/20 03:05
在项目中使用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 注入全局参数
- mybatis中 #$ 注入参数的不同
- 三、初学SpringMVC+Mybatis之Spring参数注入
- mybatis中collection子查询注入参数为null
- 【MyBatis-06】MyBatis全局配置
- 参数注入
- mybatis中的全局配置文件
- mybatis--全局配置文件详解
- MyBatis-2 全局配置文件
- Mybatis的全局配置文件
- mybatis-全局配置文件
- mybatis全局配置文件参考
- MyBatis 全局配置文件详解
- mybatis全局配置文件详解
- MyBatis 全局配置文件解析
- asp.net core 全局注入
- tomcat 全局参数过滤
- CloudStack全局配置参数
- iOS socket udp 广播
- UESTC 1218 Pick The Sticks
- Java编程思想小笔记
- FUSE用户态文件系统中自己实现的highlevel接口函数从注册到调用完全追踪
- Xcode 7:Storyboard Reference、Strong IBOutlet以及Scene Dock
- Mybatis 注入全局参数
- Just For Fun:智力题【2】
- 破解文件缓存不更新问题
- Android网络数据传输之网络协议
- MFC笔记之---win32 SDK
- 当出现lazy加载异常 看是否创建了事务
- 内存管理黄金法则
- java随机生成数字字母验证码
- Leetcode128: Combination Sum