首頁  >  文章  >  Java  >  Java中spring hibernate如何實作動態取代表名

Java中spring hibernate如何實作動態取代表名

黄舟
黄舟原創
2017-08-09 10:05:142335瀏覽

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本身,那是因為它實作了 FactoryBeana7bd7c0154d420bde695d24770c97980 介面。 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同步鎖定高效得多。線程域的使用,保證了interceptor物件和請求(線程)是綁在一起的,dao方法的執行,只要執行語句在同一個線程內,線程所共享的物件資訊肯定一致的,所以不存在並發問題。

 

上面曾說過,單例interceptor不行,原因是:無法解決執行緒安全性問題。 AutoTableNameInterceptor是一個單例,你在dao層可以修改他的值,例如新增set操作,沒問題。但你set的同時,別的請求也在set,就會導致destName,srcName的值一直在變動,除非你的請求是串列的(排隊的,一個一個來的)。而且可能n個dao實例都會呼叫interceptor, 你怎麼實作執行緒同步?除非你在dao操作的時候鎖住整個interceptor對象,這個多影響效能! 使用線程域,沒法實現,經過測試,發現hibernate底層會有多個線程呼叫interceptor方法,而不是我們的請求線程!所以,從dao到interceptor已經不是一個執行緒。 interceptor的onPrepareStatement回呼方法又是如此的單調,功能有限,哎。再說了,使用單例,是sessionFactory的全域配置,影響效率,透過程式碼添加是臨時性的。

 

以上是Java中spring hibernate如何實作動態取代表名的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn