spring hibernate实现动态替换表名(分表)的方法
1.概述
其实最简单的办法就是使用原生sql,如 session.createsqlquery("sql"),或者使用jdbctemplate。但是项目中已经使用了hql的方式查询,修改起来又累,风险又大!所以,必须找到一种比较好的解决方案,实在不行再改写吧!经过3天的时间的研究,终于找到一种不错的方法,下面讲述之。
2.步骤
2.1 新建hibernate interceptor类
/** * created by hdwang on 2017/8/7. * * hibernate拦截器:表名替换 */ public class autotablenameinterceptor extends emptyinterceptor { private string srcname = stringutils.empty; //源表名 private string destname = stringutils.empty; // 目标表名 public autotablenameinterceptor() {} public autotablenameinterceptor(string srcname,string destname){ this.srcname = srcname; this.destname = destname; } @override public string onpreparestatement(string sql) { if(srcname.equals(stringutils.empty) || destname.equals(stringutils.empty)){ return sql; } sql = sql.replaceall(srcname, destname); return sql; } }
这个interceptor会拦截所有数据库操作,在发送sql语句之前,替换掉其中的表名。
2.2 配置到sessionfactory去
先看一下sessionfactory是个啥东西。
<bean id="sessionfactory" class="org.springframework.orm.hibernate4.localsessionfactorybean" > <property name="datasource" ref="defaultdatasource"></property> <property name="packagestoscan"> <list> <value>com.my.pay.task.entity</value> <value>com.my.pay.paycms.entity</value> <value>com.my.pay.data.entity.payincome</value> </list> </property> <property name="mappinglocations"> <list> <value>classpath*:/hibernate/hibernate-sql.xml</value> </list> </property> <property name="hibernateproperties"> <props> <prop key="hibernate.dialect">org.hibernate.dialect.mysql5dialect</prop> <prop key="hibernate.show_sql">false</prop> <prop key="hibernate.format_sql">false</prop> <prop key="hibernate.hbm2ddl.auto">none</prop> <!-- 开启查询缓存 --> <prop key="hibernate.cache.use_query_cache">false</prop> <!-- 配置二级缓存 --> <prop key="hibernate.cache.use_second_level_cache">true</prop> <!-- 强制hibernate以更人性化的格式将数据存入二级缓存 --> <prop key="hibernate.cache.use_structured_entries">true</prop> <!-- hibernate将收集有助于性能调节的统计数据 --> <prop key="hibernate.generate_statistics">false</prop> <!-- 指定缓存配置文件位置 --> <prop key="hibernate.cache.provider_configuration_file_resource_path">/spring/ehcache.xml</prop> <prop key="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.ehcacheregionfactory</prop> <prop key="hibernate.current_session_context_class">jta</prop> <prop key="hibernate.transaction.factory_class">org.hibernate.engine.transaction.internal.jta.cmttransactionfactory</prop> <prop key="hibernate.transaction.manager_lookup_class">com.atomikos.icatch.jta.hibernate3.transactionmanagerlookup</prop> </props> </property> </bean>
public class localsessionfactorybean extends hibernateexceptiontranslator implements factorybean<sessionfactory>, resourceloaderaware, initializingbean, disposablebean { private datasource datasource; private resource[] configlocations; private string[] mappingresources; private resource[] mappinglocations; private resource[] cacheablemappinglocations; private resource[] mappingjarlocations; private resource[] mappingdirectorylocations; private interceptor entityinterceptor; private namingstrategy namingstrategy; private object jtatransactionmanager; private object multitenantconnectionprovider; private object currenttenantidentifierresolver; private regionfactory cacheregionfactory; private properties hibernateproperties; private class<?>[] annotatedclasses; private string[] annotatedpackages; private string[] packagestoscan; private resourcepatternresolver resourcepatternresolver = new pathmatchingresourcepatternresolver(); private configuration configuration; private sessionfactory sessionfactory;
那其实呢,sessionfactory是localsessionfactorybean对象的一个属性,这点可以在localsessionfactorybean类中可以看到,至于bean的注入为何是class的属性而非class本身,那是因为它实现了 factorybean<sessionfactory> 接口。sessionfacotry是由localsessionfactorybean对象配置后生成的。生成后将sessionfactory对象注入到了spring容器,且仅此一个而已,默认单例嘛。
我们对数据库的操作都是用session对象,它是由sessionfactory对象生成的。下面是sessionfactory对象的两个方法:
/** * open a {@link session}. * <p/> * jdbc {@link connection connection(s} will be obtained from the * configured {@link org.hibernate.service.jdbc.connections.spi.connectionprovider} as needed * to perform requested work. * * @return the created session. * * @throws hibernateexception indicates a problem opening the session; pretty rare here. */ public session opensession() throws hibernateexception; /** * obtains the current session. the definition of what exactly "current" * means controlled by the {@link org.hibernate.context.spi.currentsessioncontext} impl configured * for use. * <p/> * note that for backwards compatibility, if a {@link org.hibernate.context.spi.currentsessioncontext} * is not configured but jta is configured this will default to the {@link org.hibernate.context.internal.jtasessioncontext} * impl. * * @return the current session. * * @throws hibernateexception indicates an issue locating a suitable current session. */ public session getcurrentsession() throws hibernateexception;
那我们的项目使用getcurrentsession()获取session对象的。
hibernate interceptor怎么配置呢?
localsessionfactorybean对象的entityinterceptor属性可以配置,你可以在xml中配置它,加到sessionfactory这个bean的xml配置中去。
<property name="entityinterceptor"> <bean class="com.my.pay.common.autotablenameinterceptor"/> </property>
那,它只能配置一个。因为sessionfactory是单例,他也只能是单例,引用sessionfactory的dao对像也是单例,service,controller通通都是单例。那么有个问题就是,动态替换表名,如何动态?动态多例这条路已经封死了。那只剩下,动态修改interceptor对象的值。听起来像是不错的建议。我尝试后只能以失败告终,无法解决线程安全问题!待会儿描述原因。
所以配置到xml中无法实现我的需求。那么就只能在代码中设置了,还好sessionfactory对象提供了我们修改它的入口。
@resource(name = "sessionfactory") private sessionfactory sessionfactory; protected session getsession(){ if(autotablenameinterceptorthreadlocal.get() == null){ return this.sessionfactory.getcurrentsession(); }else{ sessionbuilder builder = this.sessionfactory.withoptions().interceptor(autotablenameinterceptorthreadlocal.get()); session session = builder.opensession(); return session; } }
/** * 线程域变量,高效实现线程安全(一个请求对应一个thread) */ private threadlocal<autotablenameinterceptor> autotablenameinterceptorthreadlocal = new threadlocal<>(); public list<wfpaylog> find(long merchantid, long poolid,string sdk, long appid,string province, integer price, string serverorder, string imsi,integer iscallback,string state, date start, date end, paging paging) { 。。。。 //定制表名拦截器,设置到线程域 autotablenameinterceptorthreadlocal.set(new autotablenameinterceptor("wf_pay_log","wf_pay_log_"+ dateutil.formatdate(start,dateutil.yearmonth_pattern))); list<wfpaylog> wfpaylogs; if (paging == null) { wfpaylogs = (list<wfpaylog>) find(hql.tostring(), params); //find方法里面有 this.getsession().createquery("hql") 等方法 } else { wfpaylogs = (list<wfpaylog>) findpaging(hql.tostring(), "select count(*) " + hql.tostring(), params, paging); } return wfpaylogs; }
红色标识的代码就是核心代码,核心说明。意思是,在dao层对象中,注入sessionfactory对象创建session就可以操作数据库了,我们改变了session的获取方式。当需要改变表名的时候,我们定义线程域变量,在需要interceptor的时候将interceptor对象保存到线程域中去,然后你操作的时候再拿到这个配置有拦截器的session去操作数据库,这个时候interceptor就生效了。
不用线程域变量保存,直接定义对象成员变量肯定是不行的,因为会有并发问题(多个请求(线程)同时调用dao方法,dao方法执行的时候又调用getsession()方法,可能当你getsession的时候,别的请求,已经把interceptor给换掉了。),当然用synchronized也可以解决。线程域的使用,比synchronized同步锁高效得多。线程域的使用,保证了interceptor对象和请求(线程)是绑在一起的,dao方法的执行,只要执行语句在同一个线程内,线程所共享的对象信息肯定一致的,所以不存在并发问题。
上面曾说过,单例interceptor不行,原因是:无法解决线程安全问题。 autotablenameinterceptor是一个单例,你在dao层可以修改他的值,比如新增set操作,没问题。可是你set的同时,别的请求也在set,就会导致destname,srcname的值一直在变动,除非你的请求是串行的(排队的,一个一个来的)。而且可能n个dao实例都会调用interceptor, 你怎么实现线程同步?除非你在dao操作的时候锁住整个interceptor对象,这个多影响性能! 使用线程域,没法实现,经过测试,发现hibernate底层会有多个线程调用interceptor方法,而不是我们的请求线程!所以,从dao到interceptor已经不是一个线程。interceptor的onpreparestatement回调方法又是如此的单调,功能有限,哎。再说了,使用单例,是sessionfactory的全局配置,影响效率,通过代码添加是临时性的。
以上这篇spring hibernate实现动态替换表名(分表)的方法就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持。
推荐阅读
-
spring hibernate实现动态替换表名(分表)的方法
-
Yii操作数据库实现动态获取表名的方法
-
flask/django 动态查询表结构相同表名不同数据的Model实现方法
-
flask/django 动态查询表结构相同表名不同数据的Model实现方法
-
Yii操作数据库实现动态获取表名的方法_PHP
-
Yii操作数据库实现动态获取表名的方法_PHP
-
Yii操作数据库实现动态获取表名的方法,yii数据库_PHP教程
-
Java中spring hibernate如何实现动态替换表名
-
Yii操作数据库实现动态获取表名的方法,yii数据库
-
Yii操作数据库实现动态获取表名的方法,yii数据库_PHP教程