Spring3 整合Hibernate3.5 動態切換SessionFactory

實現代碼html

一、定義全局切換SessionFactory的工具java

 

package com.hoo.framework.spring.support;
 
/**
 * <b>function:</b> 多數據源
 * @author hoojo
 * @createDate 2013-9-27 上午11:36:57
 * @file CustomerContextHolder.java
 * @package com.hoo.framework.spring.support
 * @project SHMB
 * @blog http://blog.csdn.net/IBM_hoojo
 * @email hoojo_@126.com
 * @version 1.0
 */
public abstract class CustomerContextHolder {
 
    public final static String SESSION_FACTORY_MYSQL = "mysql";
    public final static String SESSION_FACTORY_ORACLE = "oracle";
    
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();  
    
    public static void setCustomerType(String customerType) {  
        contextHolder.set(customerType);  
    }  
      
    public static String getCustomerType() {  
        return contextHolder.get();  
    }  
      
    public static void clearCustomerType() {  
        contextHolder.remove();  
    }  
}

 

一樣上面的靜態變量和前一文章中介紹的一致,它須要和下面配置文件中的SessionFactory的key對應。mysql

二、實現本身的SessionFactoryspring

定義好接口sql

 

package com.hoo.framework.spring.support.core;
 
import org.hibernate.SessionFactory;
 
/**
 * <b>function:</b> 動態SessionFactory接口
 * @author hoojo
 * @createDate 2013-10-12 下午03:29:52
 * @file DynamicSessionFactory.java
 * @package com.hoo.framework.spring.support.core
 * @project SHMB
 * @blog http://blog.csdn.net/IBM_hoojo
 * @email hoojo_@126.com
 * @version 1.0
 */
public interface DynamicSessionFactory extends SessionFactory {
    
    public SessionFactory getHibernateSessionFactory();
}

 

 

 

實現接口數據庫

 

package com.hoo.framework.spring.support.core;
 
import java.io.Serializable;
import java.sql.Connection;
import java.util.Map;
import java.util.Set;
import javax.naming.NamingException;
import javax.naming.Reference;
import org.hibernate.Cache;
import org.hibernate.HibernateException;
import org.hibernate.Interceptor;
import org.hibernate.SessionFactory;
import org.hibernate.StatelessSession;
import org.hibernate.TypeHelper;
import org.hibernate.classic.Session;
import org.hibernate.engine.FilterDefinition;
import org.hibernate.metadata.ClassMetadata;
import org.hibernate.metadata.CollectionMetadata;
import org.hibernate.stat.Statistics;
import com.hoo.framework.spring.support.CustomerContextHolder;
 
/**
 * <b>function:</b> 動態數據源實現
 * @author hoojo
 * @createDate 2013-10-12 下午03:31:31
 * @file DynamicSessionFactoryImpl.java
 * @package com.hoo.framework.spring.support.core
 * @project SHMB
 * @blog http://blog.csdn.net/IBM_hoojo
 * @email hoojo_@126.com
 * @version 1.0
 */
@SuppressWarnings({ "unchecked", "deprecation" })
public class DynamicSessionFactoryImpl implements DynamicSessionFactory {
 
    private static final long serialVersionUID = 5384069312247414885L;
    
    private Map<Object, SessionFactory> targetSessionFactorys;  
    private SessionFactory defaultTargetSessionFactory; 
    
    /**
     * @see com.hoo.framework.spring.support.core.DynamicSessionFactory#getHibernateSessionFactory()
     * <b>function:</b> 重寫這個方法,這裏最關鍵
     * @author hoojo
     * @createDate 2013-10-18 上午10:45:25
     */
    @Override
    public SessionFactory getHibernateSessionFactory() {
        SessionFactory targetSessionFactory = targetSessionFactorys.get(CustomerContextHolder.getCustomerType());  
        if (targetSessionFactory != null) {  
            return targetSessionFactory;  
        } else if (defaultTargetSessionFactory != null) {  
            return defaultTargetSessionFactory;  
        }  
        return null;
    }
 
 
    @Override
    public void close() throws HibernateException {
        this.getHibernateSessionFactory().close();
    }
 
    @Override
    public boolean containsFetchProfileDefinition(String s) {
        return this.getHibernateSessionFactory().containsFetchProfileDefinition(s);
    }
 
    @Override
    public void evict(Class clazz) throws HibernateException {
        this.getHibernateSessionFactory().evict(clazz);
    }
 
    @Override
    public void evict(Class clazz, Serializable serializable) throws HibernateException {
        this.getHibernateSessionFactory().evict(clazz, serializable);
    }
 
    @Override
    public void evictCollection(String s) throws HibernateException {
        this.getHibernateSessionFactory().evictCollection(s);
    }
 
    @Override
    public void evictCollection(String s, Serializable serializable) throws HibernateException {
        this.getHibernateSessionFactory().evictCollection(s, serializable);
    }
 
    @Override
    public void evictEntity(String entity) throws HibernateException {
        this.getHibernateSessionFactory().evictEntity(entity);
    }
 
    @Override
    public void evictEntity(String entity, Serializable serializable) throws HibernateException {
        this.getHibernateSessionFactory().evictEntity(entity, serializable);
    }
 
    @Override
    public void evictQueries() throws HibernateException {
        this.getHibernateSessionFactory().evictQueries();        
    }
 
    @Override
    public void evictQueries(String queries) throws HibernateException {
        this.getHibernateSessionFactory().evictQueries(queries);        
    }
 
    @Override
    public Map<String, ClassMetadata> getAllClassMetadata() {
        return this.getHibernateSessionFactory().getAllClassMetadata();
    }
 
    @Override
    public Map getAllCollectionMetadata() {
        return this.getHibernateSessionFactory().getAllClassMetadata();
    }
 
    @Override
    public Cache getCache() {
        return this.getHibernateSessionFactory().getCache();
    }
 
    @Override
    public ClassMetadata getClassMetadata(Class clazz) {
        return this.getHibernateSessionFactory().getClassMetadata(clazz);
    }
 
    @Override
    public ClassMetadata getClassMetadata(String classMetadata) {
        return this.getHibernateSessionFactory().getClassMetadata(classMetadata);
    }
 
    @Override
    public CollectionMetadata getCollectionMetadata(String collectionMetadata) {
        return this.getHibernateSessionFactory().getCollectionMetadata(collectionMetadata);
    }
 
    @Override
    public Session getCurrentSession() throws HibernateException {
        return this.getHibernateSessionFactory().getCurrentSession();
    }
 
    @Override
    public Set getDefinedFilterNames() {
        return this.getHibernateSessionFactory().getDefinedFilterNames();
    }
 
    @Override
    public FilterDefinition getFilterDefinition(String definition) throws HibernateException {
        return this.getHibernateSessionFactory().getFilterDefinition(definition);
    }
 
    @Override
    public Statistics getStatistics() {
        return this.getHibernateSessionFactory().getStatistics();
    }
 
    @Override
    public TypeHelper getTypeHelper() {
        return this.getHibernateSessionFactory().getTypeHelper();
    }
 
    @Override
    public boolean isClosed() {
        return this.getHibernateSessionFactory().isClosed();
    }
 
    @Override
    public Session openSession() throws HibernateException {
        return this.getHibernateSessionFactory().openSession();
    }
 
    @Override
    public Session openSession(Interceptor interceptor) throws HibernateException {
        return this.getHibernateSessionFactory().openSession(interceptor);
    }
 
    @Override
    public Session openSession(Connection connection) {
        return this.getHibernateSessionFactory().openSession(connection);
    }
 
    @Override
    public Session openSession(Connection connection, Interceptor interceptor) {
        return this.getHibernateSessionFactory().openSession(connection, interceptor);
    }
 
    @Override
    public StatelessSession openStatelessSession() {
        return this.getHibernateSessionFactory().openStatelessSession();
    }
 
    @Override
    public StatelessSession openStatelessSession(Connection connection) {
        return this.getHibernateSessionFactory().openStatelessSession(connection);
    }
 
    @Override
    public Reference getReference() throws NamingException {
        return this.getHibernateSessionFactory().getReference();
    }
 
    public void setTargetSessionFactorys(Map<Object, SessionFactory> targetSessionFactorys) {
        this.targetSessionFactorys = targetSessionFactorys;
    }
 
    public void setDefaultTargetSessionFactory(SessionFactory defaultTargetSessionFactory) {
        this.defaultTargetSessionFactory = defaultTargetSessionFactory;
    }
 
}

 

 

上面最重要的就是getHibernateSessionFactory重寫這個方法,其餘方法和原來實現的無異。重寫這個方法後利用CustomerContextHolder動態設置SessionFactory類型就能夠動態的切換SessionFactory。express

三、動態的事務管理器,由於咱們這裏是動態切換SessionFactory,因此事務這塊也須要動態切換SessionFactory來完成事務的操做。編程

 

package com.hoo.framework.spring.support.tx;
 
import javax.sql.DataSource;
import org.hibernate.SessionFactory;
import org.springframework.orm.hibernate3.HibernateTransactionManager;
import org.springframework.orm.hibernate3.SessionFactoryUtils;
import com.hoo.framework.spring.support.core.DynamicSessionFactory;
 
/**
 * <b>function:</b> 重寫HibernateTransactionManager事務管理器,實現本身的動態的事務管理器
 * @author hoojo
 * @createDate 2013-10-12 下午03:54:02
 * @file DynamicTransactionManager.java
 * @package com.hoo.framework.spring.support.tx
 * @project SHMB
 * @blog http://blog.csdn.net/IBM_hoojo
 * @email hoojo_@126.com
 * @version 1.0
 */
public class DynamicTransactionManager extends HibernateTransactionManager {
 
    private static final long serialVersionUID = -4655721479296819154L;
    
    /** 
     * @see org.springframework.orm.hibernate4.HibernateTransactionManager#getDataSource()
     * <b>function:</b> 重寫
     * @author hoojo
     * @createDate 2013-10-12 下午03:55:24
     */
    @Override
    public DataSource getDataSource() {
        return SessionFactoryUtils.getDataSource(getSessionFactory());
    }
 
    /** 
     * @see org.springframework.orm.hibernate4.HibernateTransactionManager#getSessionFactory()
     * <b>function:</b> 重寫
     * @author hoojo
     * @createDate 2013-10-12 下午03:55:24
     */
    @Override
    public SessionFactory getSessionFactory() {
        DynamicSessionFactory dynamicSessionFactory = (DynamicSessionFactory) super.getSessionFactory();  
        SessionFactory hibernateSessionFactory = dynamicSessionFactory.getHibernateSessionFactory();  
        return hibernateSessionFactory;  
    }
}

 

這裏主要重寫getDataSource()/getSessionFactory()這兩個方法,getSessionFactory方法是利用咱們上面定義的接口來動態獲取咱們在上下文(CustomerContextHolder)中定義切換的SessionFactory對象。而getDataSource則是得到動態SessionFactory的DataSource,這裏也不難理解。session

四、至此,重寫的接口和實現都完成,下面開始配置相關的代碼oracle

 

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:aop="http://www.springframework.org/schema/aop" 
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans-3.2.xsd 
    http://www.springframework.org/schema/aop 
    http://www.springframework.org/schema/aop/spring-aop-3.2.xsd 
    http://www.springframework.org/schema/tx  
    http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">
    
    <!-- 配置c3p0數據源 -->
    <bean id="dataSourceOracle" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
        <property name="driverClass" value="${datasource.driver}"/>
        <property name="jdbcUrl" value="${datasource.url}"/>
        <property name="user" value="${datasource.username}"/>
        <property name="password" value="${datasource.password}"/>
                
        <property name="acquireIncrement" value="${c3p0.acquireIncrement}"/>
        <property name="initialPoolSize" value="${c3p0.initialPoolSize}"/>
        <property name="minPoolSize" value="${c3p0.minPoolSize}"/>
        <property name="maxPoolSize" value="${c3p0.maxPoolSize}"/>
        <property name="maxIdleTime" value="${c3p0.maxIdleTime}"/>
        <property name="idleConnectionTestPeriod" value="${c3p0.idleConnectionTestPeriod}"/>
        <property name="maxStatements" value="${c3p0.maxStatements}"/>
        <property name="numHelperThreads" value="${c3p0.numHelperThreads}"/>
        <property name="preferredTestQuery" value="${c3p0.preferredTestQuery}"/>
        <property name="testConnectionOnCheckout" value="${c3p0.testConnectionOnCheckout}"/>
    </bean>
    
    <bean id="dataSourceMySQL" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
        <property name="driverClass" value="com.mysql.jdbc.Driver"/>
        <property name="jdbcUrl" value="jdbc:mysql://172.31.108.178:3306/world?useUnicode=true&amp;characterEncoding=UTF-8&amp;zeroDateTimeBehavior=convertToNull"/>
        <property name="user" value="root"/>
        <property name="password" value="jp2011"/>
                
        <property name="acquireIncrement" value="${c3p0.acquireIncrement}"/>
        <property name="initialPoolSize" value="${c3p0.initialPoolSize}"/>
        <property name="minPoolSize" value="${c3p0.minPoolSize}"/>
        <property name="maxPoolSize" value="${c3p0.maxPoolSize}"/>
        <property name="maxIdleTime" value="${c3p0.maxIdleTime}"/>
        <property name="idleConnectionTestPeriod" value="${c3p0.idleConnectionTestPeriod}"/>
        <property name="maxStatements" value="${c3p0.maxStatements}"/>
        <property name="numHelperThreads" value="${c3p0.numHelperThreads}"/>
        <property name="preferredTestQuery" value="${c3p0.preferredTestQuery}"/>
        <property name="testConnectionOnCheckout" value="${c3p0.testConnectionOnCheckout}"/>
    </bean>
    
    <!-- Annotation 配置sessionFactory,配置數據庫鏈接,注入hibernate數據庫配置 -->
    <bean id="mySQLSessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
        <property name="dataSource" ref="dataSourceMySQL"/>
        <property name="packagesToScan" value="com.hoo.**.mysqlentity"/>
        <property name="annotatedClasses">
            <array>
                <value>com.hoo.common.entity.IDGenerator</value>
            </array>
        </property>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
                <!-- 連接釋放策略 on_close | after_transaction | after_statement | auto  -->
                <prop key="hibernate.connection.release_mode">after_transaction</prop>
                <prop key="hibernate.show_sql">true</prop>
                <prop key="hibernate.format_sql">true</prop>
            </props>
        </property>
    </bean>
    
    <!-- Annotation 配置sessionFactory,配置數據庫鏈接,注入hibernate數據庫配置 -->
    <bean id="oracleSessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
        <property name="dataSource" ref="dataSourceOracle"/>
        <property name="packagesToScan" value="com.hoo.**.entity"/>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">org.hibernate.dialect.OracleDialect</prop>
                <prop key="hibernate.connection.release_mode">after_transaction</prop>
                <prop key="hibernate.show_sql">true</prop>
                <prop key="hibernate.format_sql">true</prop>
                <!--prop key="hibernate.hbm2ddl.auto">update</prop-->
            </props>
        </property>
    </bean>
    
    <!-- 動態SessionFactory -->
    <bean id="sessionFactory" class="com.hoo.framework.spring.support.core.DynamicSessionFactoryImpl">
        <property name="defaultTargetSessionFactory" ref="oracleSessionFactory"/>
        <property name="targetSessionFactorys">
            <map>     
                <entry value-ref="oracleSessionFactory" key="oracle"/>
                <entry value-ref="mySQLSessionFactory" key="mysql"/>
            </map> 
        </property>
    </bean>
 
    <!-- 自定義動態切換SessionFactory事務管理器,注入sessionFactory  -->
    <bean id="transactionManager" class="com.hoo.framework.spring.support.tx.DynamicTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>
    
    <!-- 配置事務的傳播特性 -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="add*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
            <tx:method name="edit*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
            <tx:method name="remove*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
            <tx:method name="execute*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
            <tx:method name="*" read-only="true" />
        </tx:attributes>
    </tx:advice>
    
    <!-- 配置那些類、方法歸入到事務的管理 -->
    <aop:config>
        <aop:pointcut expression="execution(* com.hoo.**.service.impl.*.*(..))" id="transactionManagerMethod"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="transactionManagerMethod" />
    </aop:config>
</beans>

 

 

配置也和咱們以前的配置差很少,就是事務管理器部分注入的SessionFactory是咱們本身定義的。

五、簡單測試

 

@Test
public void testAdd() {
    // 主要就是這行代碼 它纔是完成SessionFactory的切換
    CustomerContextHolder.setCustomerType(CustomerContextHolder.SESSION_FACTORY_MYSQL);
    
    DeviceInfo entity = new DeviceInfo();
    entity.setSbbh(System.currentTimeMillis() + "");
    entity.setIpdz("my ip address2");
    entity.setJd(1234);
    try {
        service.add(entity);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

 

 

通過測試發現能夠查詢數據,利用hibernate查詢分頁也能夠生成對應數據庫的分頁語句。一樣在服務層的方法中添加完操做後,特地拋出一個異常,數據也能正常回滾操做,因此DynamicTransactionManager也是起到了該有的做用!

這裏個人測試用例是手動切換CustomerContextHolder.setCustomerType的,但實際開發中咱們仍是得用Spring的Aop中的Interceptor進行切面編程,完成動態切換SessionFactory。上一篇文章Spring3.3 整合 Hibernate三、MyBatis3.2 配置多數據源/動態切換數據源 方法已經提到了(讀者能夠參考該篇博文中的第二節的三、7小節DataSourceMethodInterceptor MultipleDataSourceInterceptor),這裏就再也不贅述!

 

3、引起的問題

上述的實現若是在使用不當的狀況下,在實際開發中極可能存在一些問題!

問題1是這樣的,一般事務是在Service這層完成,這個應該是沒有異議。假若是這樣的話,問題便出現了。而一般咱們在MVC中的視圖層中僅僅會調用Service中一個方法來完成全部當前業務的處理。若是這個Service中同時操做dbA、dbB兩個數據庫,事務提交的時候是用哪一個數據庫的事務呢?因此咱們把不一樣數據庫的操做放在一個方法,就會出現事務的問題的,除非兩個數據庫的事務可以同時回滾!

大體情景是:Service中的add4Oracle操做Oracle數據庫,Service中的add4MySQL操做MySQL數據庫,最後把Service中的add4Oracle、add4MySQL方法放到一個operation方法中,MVC視圖層的業務控制調用Service中的operation。像這樣的狀況,若是add4Oracle或add4MySQL方法中的某一個方法出現異常,就須要把兩個數據庫事務都回滾。

解決辦法就是在Service或Dao中的一個方法中同時操做兩個數據庫,手動完成事務控制。出現異常就所有回滾,沒有異常就所有提交,只能這樣犧牲下。

 

問題2是利用攔截器不能同時切換一個方法操做兩個數據庫的可能,例如一個service中的query方法即須要用query MySQL,也須要query Oracle。那麼解決辦法就是在query方法中調用當前service的query4Oracle和query4Oracle,那樣繞過去仍是能利用攔截器進行動態切換的。

相關文章
相關標籤/搜索