spring hibernate实现动态替换表名(分表)的方法
1.概述
其实最简单的办法就是使用原生sql,如session.createSQLQuery("sql"),或者使用jdbcTemplate。但是项目中已经使用了hql的方式查询,修改起来又累,风险又大!所以,必须找到一种比较好的解决方案,实在不行再改写吧!经过3天的时间的研究,终于找到一种不错的方法,下面讲述之。
2.步骤
2.1新建hibernateinterceptor类
/** *Createdbyhdwangon2017/8/7. * *hibernate拦截器:表名替换 */ publicclassAutoTableNameInterceptorextendsEmptyInterceptor{ privateStringsrcName=StringUtils.EMPTY;//源表名 privateStringdestName=StringUtils.EMPTY;//目标表名 publicAutoTableNameInterceptor(){} publicAutoTableNameInterceptor(StringsrcName,StringdestName){ this.srcName=srcName; this.destName=destName; } @Override publicStringonPrepareStatement(Stringsql){ if(srcName.equals(StringUtils.EMPTY)||destName.equals(StringUtils.EMPTY)){ returnsql; } sql=sql.replaceAll(srcName,destName); returnsql; } }
这个interceptor会拦截所有数据库操作,在发送sql语句之前,替换掉其中的表名。
2.2配置到sessionFactory去
先看一下sessionFactory是个啥东西。
com.my.pay.task.entity com.my.pay.paycms.entity com.my.pay.data.entity.payincome
classpath*:/hibernate/hibernate-sql.xml org.hibernate.dialect.MySQL5Dialect false false none false true true false /spring/ehcache.xml org.hibernate.cache.ehcache.EhCacheRegionFactory jta org.hibernate.engine.transaction.internal.jta.CMTTransactionFactory com.atomikos.icatch.jta.hibernate3.TransactionManagerLookup
publicclassLocalSessionFactoryBeanextendsHibernateExceptionTranslator implementsFactoryBean,ResourceLoaderAware,InitializingBean,DisposableBean{ privateDataSourcedataSource; privateResource[]configLocations; privateString[]mappingResources; privateResource[]mappingLocations; privateResource[]cacheableMappingLocations; privateResource[]mappingJarLocations; privateResource[]mappingDirectoryLocations; privateInterceptorentityInterceptor; privateNamingStrategynamingStrategy; privateObjectjtaTransactionManager; privateObjectmultiTenantConnectionProvider; privateObjectcurrentTenantIdentifierResolver; privateRegionFactorycacheRegionFactory; privatePropertieshibernateProperties; privateClass>[]annotatedClasses; privateString[]annotatedPackages; privateString[]packagesToScan; privateResourcePatternResolverresourcePatternResolver=newPathMatchingResourcePatternResolver(); privateConfigurationconfiguration; privateSessionFactorysessionFactory;
那其实呢,sessionFactory是LocalSessionFactoryBean对象的一个属性,这点可以在LocalSessionFactoryBean类中可以看到,至于bean的注入为何是class的属性而非class本身,那是因为它实现了FactoryBean
我们对数据库的操作都是用session对象,它是由sessionFactory对象生成的。下面是sessionFactory对象的两个方法:
/** *Opena{@linkSession}. * *JDBC{@linkConnectionconnection(s}willbeobtainedfromthe *configured{@linkorg.hibernate.service.jdbc.connections.spi.ConnectionProvider}asneeded *toperformrequestedwork. * *@returnThecreatedsession. * *@throwsHibernateExceptionIndicatesaproblemopeningthesession;prettyrarehere. */ publicSessionopenSession()throwsHibernateException; /** *Obtainsthecurrentsession.Thedefinitionofwhatexactly"current" *meanscontrolledbythe{@linkorg.hibernate.context.spi.CurrentSessionContext}implconfigured *foruse. * *Notethatforbackwardscompatibility,ifa{@linkorg.hibernate.context.spi.CurrentSessionContext} *isnotconfiguredbutJTAisconfiguredthiswilldefaulttothe{@linkorg.hibernate.context.internal.JTASessionContext} *impl. * *@returnThecurrentsession. * *@throwsHibernateExceptionIndicatesanissuelocatingasuitablecurrentsession. */ publicSessiongetCurrentSession()throwsHibernateException;
那我们的项目使用getCurrentSession()获取session对象的。
hibernateinterceptor怎么配置呢?
LocalSessionFactoryBean对象的entityInterceptor属性可以配置,你可以在xml中配置它,加到sessionFactory这个bean的xml配置中去。
那,它只能配置一个。因为sessionFactory是单例,他也只能是单例,引用sessionFactory的Dao对像也是单例,service,controller通通都是单例。那么有个问题就是,动态替换表名,如何动态?动态多例这条路已经封死了。那只剩下,动态修改interceptor对象的值。听起来像是不错的建议。我尝试后只能以失败告终,无法解决线程安全问题!待会儿描述原因。
所以配置到xml中无法实现我的需求。那么就只能在代码中设置了,还好sessionFactory对象提供了我们修改它的入口。
@Resource(name="sessionFactory") privateSessionFactorysessionFactory; protectedSessiongetSession(){ if(autoTableNameInterceptorThreadLocal.get()==null){ returnthis.sessionFactory.getCurrentSession(); }else{ SessionBuilderbuilder=this.sessionFactory.withOptions().interceptor(autoTableNameInterceptorThreadLocal.get()); Sessionsession=builder.openSession(); returnsession; } }
/** *线程域变量,高效实现线程安全(一个请求对应一个thread) */ privateThreadLocalautoTableNameInterceptorThreadLocal=newThreadLocal<>(); publicList find(LongmerchantId,LongpoolId,Stringsdk,LongappId,Stringprovince, Integerprice, StringserverOrder,Stringimsi,Integeriscallback,Stringstate, Datestart,Dateend,Pagingpaging){ 。。。。 //定制表名拦截器,设置到线程域 autoTableNameInterceptorThreadLocal.set(newAutoTableNameInterceptor("wf_pay_log","wf_pay_log_"+DateUtil.formatDate(start,DateUtil.YEARMONTH_PATTERN))); List wfPayLogs; if(paging==null){ wfPayLogs=(List )find(hql.toString(),params);//find方法里面有this.getSession().createQuery("hql")等方法 }else{ wfPayLogs=(List )findPaging(hql.toString(),"selectcount(*)"+hql.toString(),params,paging); } returnwfPayLogs; }
红色标识的代码就是核心代码,核心说明。意思是,在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的全局配置,影响效率,通过代码添加是临时性的。
以上这篇springhibernate实现动态替换表名(分表)的方法就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持毛票票。