Spring4+hibernate4+多数据源动态datasource+异构数据库

        有台服务器上安装了Sqlserver和Oracle,其中Sqlserver上面有数据库A、B、C,Oracle上面有数据库D。实现方式如下:

        1、新建一个类DBContextHolder,通过两个static的ThreadLocal<String>属性存放当前要使用的dataSource的别名和sessionFactory的别名。

package com.util.db;

public class DBContextHolder{
  public static final String DATASOURCE_A = "dataSourceA";
  public static final String DATASOURCE_B = "dataSourceB";
  public static final String DATASOURCE_C = "dataSourceC";
  public static final String DATASOURCE_D = "dataSourceD";
  public final static String SESSION_FACTORY_SQLSERVER = "sqlserver";
  public final static String SESSION_FACTORY_ORACLE = "oracle";

  private static final ThreadLocal<String> dataSourceContextHolder = new ThreadLocal<String>();
  private static final ThreadLocal<String> sessionFactoryContextHolder = new ThreadLocal<String>();

  public static void setDataSourceType(String type) {
    dataSourceContextHolder.set(type);
  }
  public static String getDataSourceType() {
    return dataSourceContextHolder.get();
  }
  public static void clearDataSourceType() {
    dataSourceContextHolder.remove();
  }

  public static void setSessionFactoryType(String type) {
    sessionFactoryContextHolder.set(type);
  }
  public static String getSessionFactoryType() {
    return sessionFactoryContextHolder.get();
  }
  public static void clearSessionFactoryType() {
    sessionFactoryContextHolder.remove();
  }
}

        2、新建一个类,继承抽象类AbstractRoutingDataSource。这个抽象类是Spring为动态切换数据源准备的。要使用这个抽象类,一个是要重写determineCurrentLookupKey方法,告诉它当前要用哪个数据源;一是在Spring的配置文件中将几个数据源注入到targetDataSources属性中。本文中,该属性的形式为:HashMap<String,DataSource>。

package com.util.db;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class DynamicDataSource extends AbstractRoutingDataSource {

  @Override
  protected Object determineCurrentLookupKey() {
    return DBContextHolder.getDataSourceType();
  }
}

        3、参照步骤2,写一个动态切换数据源的工具类。关键有几点:

            a、定义属性Map<Object, SessionFactory> targetSessionFactorys,通过Spring注入几个SessionFactory;

            b、实现方法public SessionFactory getHibernateSessionFactory(),根据DBContextHolder中存放的当前sessionFactory别名来从targetSessionFactorys属性中取出对应的SessionFactory;

            c、实现SessionFactory,重写其所有方法,将原本直接调用session的地方,改为getHibernateSessionFactory()。

package com.util.db;

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.Session;
import org.hibernate.SessionBuilder;
import org.hibernate.SessionFactory;
import org.hibernate.StatelessSession;
import org.hibernate.StatelessSessionBuilder;
import org.hibernate.TypeHelper;
import org.hibernate.engine.spi.FilterDefinition;
import org.hibernate.metadata.ClassMetadata;
import org.hibernate.metadata.CollectionMetadata;
import org.hibernate.stat.Statistics;

/**
 * Created by Administrator on 16-3-29.
 */
@SuppressWarnings({ "unchecked", "deprecation" })
public class DynamicSessionFactory implements SessionFactory {
  private static final long serialVersionUID = 156487979415646L;
  private Map<Object, SessionFactory> targetSessionFactorys;
  private SessionFactory defaultTargetSessionFactory;

  public void setTargetSessionFactorys(Map<Object, SessionFactory> targetSessionFactorys) {
    this.targetSessionFactorys = targetSessionFactorys;
  }

  public void setDefaultTargetSessionFactory(SessionFactory defaultTargetSessionFactory) {
    this.defaultTargetSessionFactory = defaultTargetSessionFactory;
  }

  @Override
  public SessionFactoryOptions getSessionFactoryOptions() {
    return getHibernateSessionFactory().getSessionFactoryOptions();
  }

  @Override
  public SessionBuilder withOptions() {
    return getHibernateSessionFactory().withOptions();
  }

  @Override
  public Session openSession() throws HibernateException {
    return getHibernateSessionFactory().openSession();
  }

  @Override
  public Session getCurrentSession() throws HibernateException {
    return getHibernateSessionFactory().getCurrentSession();
  }

  @Override
  public StatelessSessionBuilder withStatelessOptions() {
    return getHibernateSessionFactory().withStatelessOptions();
  }

  @Override
  public StatelessSession openStatelessSession() {
    return getHibernateSessionFactory().openStatelessSession();
  }

  @Override
  public StatelessSession openStatelessSession(Connection connection) {
    return getHibernateSessionFactory().openStatelessSession(connection);
  }

  @Override
  public ClassMetadata getClassMetadata(
      @SuppressWarnings("rawtypes") Class entityClass) {
    return getHibernateSessionFactory().getClassMetadata(entityClass);
  }

  @Override
  public ClassMetadata getClassMetadata(String entityName) {
    return getHibernateSessionFactory().getClassMetadata(entityName);
  }

  @Override
  public CollectionMetadata getCollectionMetadata(String roleName) {
    return getHibernateSessionFactory().getCollectionMetadata(roleName);
  }

  @Override
  public Map<String, ClassMetadata> getAllClassMetadata() {
    return getHibernateSessionFactory().getAllClassMetadata();
  }

  @SuppressWarnings("rawtypes")
  @Override
  public Map getAllCollectionMetadata() {
    return getHibernateSessionFactory().getAllCollectionMetadata();
  }

  @Override
  public Statistics getStatistics() {
    return getHibernateSessionFactory().getStatistics();
  }

  @Override
  public void close() throws HibernateException {
    getHibernateSessionFactory().close();
  }

  @Override
  public boolean isClosed() {
    return getHibernateSessionFactory().isClosed();
  }

  @Override
  public Cache getCache() {
    return getHibernateSessionFactory().getCache();
  }

  @SuppressWarnings({ "deprecation", "rawtypes" })
  @Override
  public void evict(Class persistentClass) throws HibernateException {
    getHibernateSessionFactory().evict(persistentClass);
  }

  @SuppressWarnings({ "deprecation", "rawtypes" })
  @Override
  public void evict(Class persistentClass, Serializable id)
      throws HibernateException {
    getHibernateSessionFactory().evict(persistentClass, id);
  }

  @SuppressWarnings("deprecation")
  @Override
  public void evictEntity(String entityName) throws HibernateException {
    getHibernateSessionFactory().evictEntity(entityName);
  }

  @SuppressWarnings("deprecation")
  @Override
  public void evictEntity(String entityName, Serializable id)
      throws HibernateException {
    getHibernateSessionFactory().evictEntity(entityName, id);
  }

  @SuppressWarnings("deprecation")
  @Override
  public void evictCollection(String roleName) throws HibernateException {
    getHibernateSessionFactory().evictCollection(roleName);
  }

  @SuppressWarnings("deprecation")
  @Override
  public void evictCollection(String roleName, Serializable id)
      throws HibernateException {
    getHibernateSessionFactory().evictCollection(roleName, id);
  }

  @SuppressWarnings("deprecation")
  @Override
  public void evictQueries(String cacheRegion) throws HibernateException {
    getHibernateSessionFactory().evictQueries(cacheRegion);
  }

  @SuppressWarnings("deprecation")
  @Override
  public void evictQueries() throws HibernateException {
    getHibernateSessionFactory().evictQueries();
  }

  @SuppressWarnings("rawtypes")
  @Override
  public Set getDefinedFilterNames() {
    return getHibernateSessionFactory().getDefinedFilterNames();
  }

  @Override
  public FilterDefinition getFilterDefinition(String filterName)
      throws HibernateException {
    return getHibernateSessionFactory().getFilterDefinition(filterName);
  }

  @Override
  public boolean containsFetchProfileDefinition(String name) {
    return getHibernateSessionFactory()
        .containsFetchProfileDefinition(name);
  }

  @Override
  public TypeHelper getTypeHelper() {

    return getHibernateSessionFactory().getTypeHelper();
  }

  @Override
  public Reference getReference() throws NamingException {
    return getHibernateSessionFactory().getReference();
  }

  public SessionFactory getHibernateSessionFactory() {
    SessionFactory targetSessionFactory = targetSessionFactorys
        .get(DBContextHolder.getSessionFactoryType());
    if (targetSessionFactory != null) {
      return targetSessionFactory;
    } else if (defaultTargetSessionFactory != null) {
      return defaultTargetSessionFactory;
    }
    return null;
  }
}

        4、新建HibernateTransactionManager的子类DynamicTransactionManager,重写getSessionFactory方法,不然取出来的不是SessionFactory,而是DynamicSessionFactory。

package com.util.db;

import org.springframework.orm.hibernate4.HibernateTransactionManager;
import javax.sql.DataSource;
import org.hibernate.SessionFactory;
import org.springframework.orm.hibernate4.SessionFactoryUtils;

public class DynamicTransactionManager extends HibernateTransactionManager {

  private static final long serialVersionUID = 2591641126163504954L;

  @Override
  public DataSource getDataSource() {
    return SessionFactoryUtils.getDataSource(getSessionFactory());
  }

  @Override
  public SessionFactory getSessionFactory() {
    DynamicSessionFactory dynamicSessionFactory = (DynamicSessionFactory) super.getSessionFactory();
    SessionFactory hibernateSessionFactory = dynamicSessionFactory.getHibernateSessionFactory();
    return hibernateSessionFactory;
  }

}

         5、配置Spring

            a、分别配置4个DataSource

<bean id="dataSourceA" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
  <!-- 设置JDBC驱动名称 -->
  <property name="driverClass" value="${jdbc.driver}" />
  <!-- 设置JDBC连接URL -->
  <property name="jdbcUrl" value="${jdbc.url1}" />
  <!-- 设置数据库用户名 -->
  <property name="user" value="${jdbc.username}" />
  <!-- 设置数据库密码 -->
  <property name="password" value="${jdbc.password}" />
  <!-- 设置连接池初始值 -->
  <property name="initialPoolSize" value="5" />
</bean>

            b、配置动态DataSource

<bean id="dynamicDataSource" class="com.util.db.DynamicDataSource">
  <property name="targetDataSources">
    <map key-type="java.lang.String">
      <entry value-ref="dataSourceA" key="dataSourceA" />
      <entry value-ref="dataSourceB" key="dataSourceB" />
      <entry value-ref="dataSourceC" key="dataSourceC" />
      <entry value-ref="dataSourceD" key="dataSourceD" />
    </map>
  </property>
  <property name="defaultTargetDataSource" ref="dataSourceSmart">
  </property>
</bean>

            c、分别配置两个SessionFactory,其中DataSource引用dynamicDataSource

<bean id="sqlServerSessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
  <!-- 数据源 -->
  <property name="dataSource" ref="dynamicDataSource" />
  <!-- hibernate的相关属性配置 -->
  <property name="hibernateProperties">
    <props>
      <!--针对oracle数据库的方言,特定的关系数据库生成优化的SQL-->
      <prop key="hibernate.dialect">${jdbc.dialect}</prop>
      <!-- 是否在控制台打印sql语句    -->
      <prop key="hibernate.show_sql">${jdbc.showSql}</prop>
      <!-- 输出格式化后的sql,更方便查看    -->
      <prop key="hibernate.format_sql">${jdbc.formatSql}</prop>
      <!-- 该参数用来允许使用outer join来载入此集合的数据。 -->
      <prop key="hibernate.use_outer_join">${jdbc.useOuterJoin}</prop>
      <!-- 允许查询缓存,个别查询仍然需要被设置为可缓存的.-->
      <prop key="hibernate.cache.use_query_cache">${jdbc.useQueryCache}</prop>
      <!--每次从数据库中取出的记录条数,oracle的话建议设为100,至少50 -->
      <prop key="hibernate.default_batch_fetch_size">${jdbc.defaultBatchFetchSize}</prop>
      <!--连接池的最大活动个数    -->
      <prop key="hibernate.dbcp.maxActive">100</prop>
      <!-- 当连接池中的连接已经被耗尽的时候,DBCP将怎样处理(0=失败,1=等待,2=增长)-->
      <prop key="hibernate.dbcp.whenExhaustedAction">1</prop>
      <!-- 最大等待时间 -->
      <prop key="hibernate.dbcp.maxWait">1200</prop>
      <!-- 没有人用连接的时候,最大闲置的连接个数     -->
      <prop key="hibernate.dbcp.maxIdle">20</prop>
      <!-- 以下是对prepared statement的处理,同上。  -->
      <prop key="hibernate.dbcp.ps.maxActive">100</prop>
      <prop key="hibernate.dbcp.ps.whenExhaustedAction">1</prop>
      <prop key="hibernate.dbcp.ps.maxWait">1200</prop>
      <prop key="hibernate.dbcp.ps.maxIdle">10</prop>
    </props>
  </property>
  <!-- 自动扫描实体对象 指定的包结构中存放实体类 -->
  <property name="packagesToScan">
    <list>
      <value>com.A.dict</value>
      <value>com.A.entity</value>
    </list>
  </property>
</bean>

            d、配置动态SessionFactory

<bean id="sessionFactory" class="com.util.db.DynamicSessionFactory">
  <property name="defaultTargetSessionFactory" ref="oracleSessionFactory" />
  <property name="targetSessionFactorys">
    <map key-type="java.lang.String">
      <entry value-ref="oracleSessionFactory" key="oracle"/>
      <entry value-ref="sqlServerSessionFactory" key="sqlserver"/>
    </map>
  </property>
</bean>

            e、配置改写后的事务管理器

<bean id="transactionManager" class="com.util.db.DynamicTransactionManager">
  <property name="sessionFactory" ref="sessionFactory" />
</bean>

  6、截止到目前为止,已经可以通过DBContextHolder.setDataSourceType(DataSource别名) 和 DBContextHolder.setSessionFactoryType(SessionFactory别名)来手动切换数据源和SessionFactory了。接下来,将通过AOP拦截DAO层,在DAO调用方法之前,切换到相应的数据库。

            a、配置Spring

<bean id="dataSourceInterceptor" class="com.util.db.DBInterceptor" />

<!--加了下面这句以后,使用CGLIB实现AOP,可以不用接口类直接用实现类-->
<aop:aspectj-autoproxy proxy-target-class="true"/>
<!-- 定义切面,在 * com.gcucwc.service.*(..) 中执行有关的hibernate session的事务操作 -->
<aop:config>
  <aop:pointcut id="serviceOperation" expression="execution(* com.*.service.*.*(..))" />
  <aop:advisor advice-ref="txAdvice" pointcut-ref="serviceOperation" />
  
  <aop:pointcut id="dsA" expression="execution(* com.A.dao.*.*(..))" />
  <aop:pointcut id="dsB" expression="execution(* com.B.dao.*.*(..))" />
  <aop:pointcut id="dsC" expression="execution(* com.C.dao.*.*(..))" />
  <aop:pointcut id="dsD" expression="execution(* com.D.dao.*.*(..))" />
  
  <aop:aspect id="dataSourceAspect" ref="dataSourceInterceptor">
    <aop:before method="setOld" pointcut-ref="dsOld"/>
    <aop:before method="setNew" pointcut-ref="dsNew"/>
    <aop:before method="setSun" pointcut-ref="dsSun"/>
    <aop:before method="setSmart" pointcut-ref="dsSmart"/>
  </aop:aspect>
</aop:config>

<!-- 该 BeanPostProcessor 将自动对标注 @Autowired 的 Bean 进行注入 -->
<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/>

            b、新建拦截类

package com.util.db;

import org.aspectj.lang.JoinPoint;
import static com.util.db.DBContextHolder.*;


public class DBInterceptor {
  
  public void setOld(JoinPoint jp) {
    setDataSourceType(DATASOURCE_A);
    setSessionFactoryType(SESSION_FACTORY_SQLSERVER);
  }

  public void setNew(JoinPoint jp) {
    setDataSourceType(DATASOURCE_B);
    setSessionFactoryType(SESSION_FACTORY_SQLSERVER);
  }

  public void setSun(JoinPoint jp) {
    setDataSourceType(DATASOURCE_C);
    setSessionFactoryType(SESSION_FACTORY_SQLSERVER);
  }

  public void setSmart(JoinPoint jp) {
    setDataSourceType(DATASOURCE_D);
    setSessionFactoryType(SESSION_FACTORY_ORACLE);
  }
}

        7、最后是DAO层的基础类的一些注意事项

public class BaseDAOImpl<T, ID extends Serializable> implements BaseDAO<T, ID> {

  @Autowired
  private DynamicSessionFactory sessionFactory; //注入的是DynamicSessionFactory
  
  public DynamicSessionFactory getSessionFactory() {
    return sessionFactory;
  }
  
  //需要注意的获取session方式
  public Session getSession() {
    return sessionFactory.getHibernateSessionFactory().getCurrentSession();
  }
  
  //示范方法
  @Override
  public void save(T t) {
    this.getSession().save(t);
  }
}

        注意:如果Service层直接调用DAO层未重写的BaseDAOImpl的方法,并不会触发相应的AOP。例如UserDAO extends BaseDAOImpl,然后UserDAO未重写save方法,此时调用UserDAO.save(T)并不会自动切换数据源。

        本文内容建立在前辈们的成果基础上,非完全本人原创,如有错漏烦请指正。

你可能感兴趣的:(spring,Hibernate,异构数据库,动态DataSource,多个数据源)