spring hibernate實(shí)現(xiàn)動(dòng)態(tài)替換表名(分表)的方法
1.概述
其實(shí)最簡單的辦法就是使用原生sql,如 session.createSQLQuery("sql"),或者使用jdbcTemplate。但是項(xiàng)目中已經(jīng)使用了hql的方式查詢,修改起來又累,風(fēng)險(xiǎn)又大!所以,必須找到一種比較好的解決方案,實(shí)在不行再改寫吧!經(jīng)過3天的時(shí)間的研究,終于找到一種不錯(cuò)的方法,下面講述之。
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; // 目標(biāo)表名 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; } }
這個(gè)interceptor會(huì)攔截所有數(shù)據(jù)庫操作,在發(fā)送sql語句之前,替換掉其中的表名。
2.2 配置到sessionFactory去
先看一下sessionFactory是個(gè)啥東西。
<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> <!-- 配置二級(jí)緩存 --> <prop key="hibernate.cache.use_second_level_cache">true</prop> <!-- 強(qiáng)制Hibernate以更人性化的格式將數(shù)據(jù)存入二級(jí)緩存 --> <prop key="hibernate.cache.use_structured_entries">true</prop> <!-- Hibernate將收集有助于性能調(diào)節(jié)的統(tǒng)計(jì)數(shù)據(jù) --> <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;
那其實(shí)呢,sessionFactory是LocalSessionFactoryBean對(duì)象的一個(gè)屬性,這點(diǎn)可以在LocalSessionFactoryBean類中可以看到,至于bean的注入為何是class的屬性而非class本身,那是因?yàn)樗鼘?shí)現(xiàn)了 FactoryBean<SessionFactory> 接口。sessionFacotry是由LocalSessionFactoryBean對(duì)象配置后生成的。生成后將sessionFactory對(duì)象注入到了spring容器,且僅此一個(gè)而已,默認(rèn)單例嘛。
我們對(duì)數(shù)據(jù)庫的操作都是用session對(duì)象,它是由sessionFactory對(duì)象生成的。下面是sessionFactory對(duì)象的兩個(gè)方法:
/** * 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;
那我們的項(xiàng)目使用getCurrentSession()獲取session對(duì)象的。
hibernate interceptor怎么配置呢?
LocalSessionFactoryBean對(duì)象的entityInterceptor屬性可以配置,你可以在xml中配置它,加到sessionFactory這個(gè)bean的xml配置中去。
<property name="entityInterceptor"> <bean class="com.my.pay.common.AutoTableNameInterceptor"/> </property>
那,它只能配置一個(gè)。因?yàn)閟essionFactory是單例,他也只能是單例,引用sessionFactory的Dao對(duì)像也是單例,service,controller通通都是單例。那么有個(gè)問題就是,動(dòng)態(tài)替換表名,如何動(dòng)態(tài)?動(dòng)態(tài)多例這條路已經(jīng)封死了。那只剩下,動(dòng)態(tài)修改interceptor對(duì)象的值。聽起來像是不錯(cuò)的建議。我嘗試后只能以失敗告終,無法解決線程安全問題!待會(huì)兒描述原因。
所以配置到xml中無法實(shí)現(xiàn)我的需求。那么就只能在代碼中設(shè)置了,還好sessionFactory對(duì)象提供了我們修改它的入口。
@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; } }
/** * 線程域變量,高效實(shí)現(xiàn)線程安全(一個(gè)請(qǐng)求對(duì)應(yīng)一個(gè)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) { 。。。。 //定制表名攔截器,設(shè)置到線程域 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; }
紅色標(biāo)識(shí)的代碼就是核心代碼,核心說明。意思是,在DAO層對(duì)象中,注入sessionFactory對(duì)象創(chuàng)建session就可以操作數(shù)據(jù)庫了,我們改變了session的獲取方式。當(dāng)需要改變表名的時(shí)候,我們定義線程域變量,在需要interceptor的時(shí)候?qū)nterceptor對(duì)象保存到線程域中去,然后你操作的時(shí)候再拿到這個(gè)配置有攔截器的session去操作數(shù)據(jù)庫,這個(gè)時(shí)候interceptor就生效了。
不用線程域變量保存,直接定義對(duì)象成員變量肯定是不行的,因?yàn)闀?huì)有并發(fā)問題(多個(gè)請(qǐng)求(線程)同時(shí)調(diào)用dao方法,dao方法執(zhí)行的時(shí)候又調(diào)用getSession()方法,可能當(dāng)你getSession的時(shí)候,別的請(qǐng)求,已經(jīng)把interceptor給換掉了。),當(dāng)然用synchronized也可以解決。線程域的使用,比synchronized同步鎖高效得多。線程域的使用,保證了interceptor對(duì)象和請(qǐng)求(線程)是綁在一起的,dao方法的執(zhí)行,只要執(zhí)行語句在同一個(gè)線程內(nèi),線程所共享的對(duì)象信息肯定一致的,所以不存在并發(fā)問題。
上面曾說過,單例interceptor不行,原因是:無法解決線程安全問題。 AutoTableNameInterceptor是一個(gè)單例,你在dao層可以修改他的值,比如新增set操作,沒問題??墒悄鉺et的同時(shí),別的請(qǐng)求也在set,就會(huì)導(dǎo)致destName,srcName的值一直在變動(dòng),除非你的請(qǐng)求是串行的(排隊(duì)的,一個(gè)一個(gè)來的)。而且可能n個(gè)dao實(shí)例都會(huì)調(diào)用interceptor, 你怎么實(shí)現(xiàn)線程同步?除非你在dao操作的時(shí)候鎖住整個(gè)interceptor對(duì)象,這個(gè)多影響性能! 使用線程域,沒法實(shí)現(xiàn),經(jīng)過測試,發(fā)現(xiàn)hibernate底層會(huì)有多個(gè)線程調(diào)用interceptor方法,而不是我們的請(qǐng)求線程!所以,從dao到interceptor已經(jīng)不是一個(gè)線程。interceptor的onPrepareStatement回調(diào)方法又是如此的單調(diào),功能有限,哎。再說了,使用單例,是sessionFactory的全局配置,影響效率,通過代碼添加是臨時(shí)性的。
以上這篇spring hibernate實(shí)現(xiàn)動(dòng)態(tài)替換表名(分表)的方法就是小編分享給大家的全部內(nèi)容了,希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Springboot中用 Netty 開啟UDP服務(wù)方式
這篇文章主要介紹了Springboot中用 Netty 開啟UDP服務(wù)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11Mybatis-plus 批量插入太慢的問題解決(提升插入性能)
公司使用的Mybatis-Plus操作SQL,用過Mybatis-Plus的小伙伴一定知道他有很多API提供給我們使用,但是批量插入大數(shù)據(jù)太慢應(yīng)該怎么解決,本文就詳細(xì)的介紹一下,感興趣的可以了解一下2021-11-11淺談Spring框架中@Autowired和@Resource的區(qū)別
最近review別人代碼的時(shí)候,看到了一些@Autowired不一樣的用法,覺得有些意思,下面這篇文章主要給大家介紹了關(guān)于Spring框架中@Autowired和@Resource區(qū)別的相關(guān)資料,需要的朋友可以參考下2022-10-10Java C++題解leetcode1620網(wǎng)絡(luò)信號(hào)最好的坐標(biāo)
這篇文章主要為大家介紹了Java C++題解leetcode1620網(wǎng)絡(luò)信號(hào)最好的坐標(biāo)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01