有台服务器上安装了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)并不会自动切换数据源。
本文内容建立在前辈们的成果基础上,非完全本人原创,如有错漏烦请指正。