关于spring-hibernate中,多数据源配置的一种实现方法(通过AbstractRoutingDataSource)
最近项目中需要用到动态切换数据源且希望做到最少的操作配置,于是研究了一番(网上copy),自己总结了一些容易出问题的地方:
- AbstractRoutingDataSource 实现切换数据源的原理
- 数据源配置
- 代码示例
AbstractRoutingDataSource 实现切换数据源的原理
查看这个类可以发现。它继承了DataSource,那么找到他的getConnection方法
public Connection getConnection() throws SQLException {
return this.determineTargetDataSource().getConnection();
}
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = this.determineCurrentLookupKey();
DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);
if(dataSource == null && (this.lenientFallback || lookupKey == null)) {
dataSource = this.resolvedDefaultDataSource;
}
if(dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
} else {
return dataSource;
}
}
可以看出关键代码determineTargetDataSource中,这里处理了数据源切换,那么只需要写一个子类,将determineCurrentLookupKey这个方法重写下,即可做到数据源切换。
数据源配置
用到的部分代码如下,其余的用项目中的即可:
-数据库配置文件
jdbc.driverClassName = com.mysql.jdbc.Driver
hibernate.connection.url = jdbc:mysql://xxxx?useUnicode=true&characterEncoding=UTF-8
hibernate.connection.username = xxxx
hibernate.connection.password = xxxx
hibernate.dialect = org.hibernate.dialect.MySQL5Dialect
hibernate.default_schema = xxxx
openData.jdbc.driverClassName = com.mysql.jdbc.Driver
openData.hibernate.connection.url = jdbc:mysql://xxxx?useUnicode=true&characterEncoding=UTF-8
openData.hibernate.connection.username = xxxx
openData.hibernate.connection.password = xxxx
openData.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect
openData.hibernate.default_schema = xxxx
applicationContext.xml
<!-- 配置数据源 -->
<bean id="dataDefaultSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="${hibernate.connection.url}" />
<property name="username" value="${hibernate.connection.username}" />
<property name="password" value="${hibernate.connection.password}" />
<property name="maxActive" value="256" />
<property name="initialSize" value="16" />
<property name="maxWait" value="60000" />
<property name="minIdle" value="16" />
<property name="timeBetweenEvictionRunsMillis" value="3000" />
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="validationQuery" value="SELECT 1 FROM DUAL" />
<property name="testWhileIdle" value="true" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<!-- mysql 不支持 poolPreparedStatements <property name="poolPreparedStatements"
value="true" /> <property name="maxPoolPreparedStatementPerConnectionSize"
value="20" /> -->
<!-- 配置过滤器:wall——防注入攻击(WallFilter默认的防注入配置,也可以自己另外配置),stat-监控统计功能 -->
<!-- <property name="filters" value="wall,stat" /> -->
</bean>
<!-- 配置数据源用于存放访问统计数据 -->
<bean id="dataSiteMainSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="${siteMain.hibernate.connection.url}" />
<property name="username" value="${siteMain.hibernate.connection.username}" />
<property name="password" value="${siteMain.hibernate.connection.password}" />
<property name="maxActive" value="256" />
<property name="initialSize" value="16" />
<property name="maxWait" value="60000" />
<property name="minIdle" value="16" />
<property name="timeBetweenEvictionRunsMillis" value="3000" />
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="validationQuery" value="SELECT 1 FROM DUAL" />
<property name="testWhileIdle" value="true" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
</bean>
<bean id="dataSource" class="cn.test.developer.ChooseDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry value-ref="dataDefaultSource" key="default"></entry>
<entry value-ref="dataSiteMainSource" key="siteMain"></entry>
</map>
</property>
<property name="defaultTargetDataSource" ref="dataDefaultSource">
</property>
</bean>
<!-- 配置hibernate SessionFactory -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="namingStrategy" ref="customNamingStrategy" />
<property name="dataSource" ref="dataSource" />
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">${hibernate.dialect}</prop>
<!--<prop key="hibernate.default_schema">${hibernate.default_schema}</prop>-->
<prop key="hibernate.show_sql">${hibernate.connection.show_sql}</prop>
<prop key="hibernate.max_fetch_depth">${hibernate.max_fetch_depth}</prop>
<prop key="hibernate.jdbc.batch_size">${hibernate.jdbc.batch_size}</prop>
<prop key="hibernate.generate_statistics">${hibernate.generate_statistics}</prop>
<prop key="hibernate.hbm2ddl.auto">none</prop>
<prop key="hiberante.format_sql">${hibernate.connection.format_sql}</prop>
<!-- 二级缓存EhCache配置 -->
<prop key="hibernate.cache.region.factory_class">${hibernate.cache.region.factory_class}</prop>
<!-- 设置二级缓存插件EHCache的Provider类-->
<prop key="hibernate.cache.provider_class">${hibernate.cache.provider_class}</prop>
<prop key="hibernate.cache.use_query_cache">${hibernate.cache.use_query_cache}</prop>
<prop key="hibernate.cache.use_second_level_cache">${hibernate.cache.use_second_level_cache}</prop>
<prop key="hibernate.cache.provider_configuration_file_resource_path">${ehcache.file_resource_path}</prop>
<!-- 强制Hibernate以更人性化的格式将数据存入二级缓存 -->
<prop key="hibernate.cache.use_structured_entries">${hibernate.cache.use_structured_entries}</prop>
<!-- Hibernate将收集有助于性能调节的统计数据 -->
<prop key="hibernate.generate_statistics">${hibernate.generate_statistics}</prop>
<!-- 项目启动时初始化CurrentSessionContext,用于获取session(sessionFactory.getCurrentSession()) -->
<prop key="hibernate.current_session_context_class">${hibernate.current_session_context_class}</prop>
</props>
</property>
<property name="packagesToScan" value="cn.test.**.entity" />
</bean>
<!-- 事务管理器 -->
<bean id="txManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="get*" read-only="true" propagation="REQUIRED" />
<tx:method name="*" propagation="REQUIRED" />
</tx:attributes>
</tx:advice>
<tx:annotation-driven transaction-manager="txManager"/>
<aop:config expose-proxy="true">
<aop:advisor advice-ref="txAdvice" pointcut="execution(* cn.test..service..*(..))" order="2"/>
</aop:config>
<bean id="uniqueTableApp" class="cn.test.key.impl.HibernateUniqueTableApp">
</bean>
<bean class="cn.test.key.SequenceFactory">
<property name="uniqueTableApp" ref="uniqueTableApp"></property>
<!--<property name="transactionTemplate" ref="transactionTemplate"></property>-->
<property name="cacheKeyNum" value="1"></property>
</bean>
<bean id="dataSourceAspect" class="cn.test.aop.DataSourceAspect" />
<aop:config>
<aop:aspect id="c" ref="dataSourceAspect" order="1">
<aop:pointcut id="tx" expression="execution(* cn.test..service..*(..))"/>
<aop:before pointcut-ref="tx" method="before"/>
</aop:aspect>
</aop:config>
此处代码中需要主义的地方有:dataSource配置的key要和后面修改数据库是的名称保持一致,
-切换数据源和事务管理的先后顺序:这里通过order 来设置优先级,保证切换数据源的切面在事务管理之前
-sessionFactory配置注意的地方的地方:将默认的数据库配置取消,不然会无法切换
代码示例:(与配置文件相匹配)
1:动态选择数据源 方法类
package cn.test.developer;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class ChooseDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
String dataSource = HandleDataSource.getDataSource();
HandleDataSource.clearDataSource();//每次设置完清除,保证获取默认数据库
return dataSource;
}
}
2:数据源切换类
package cn.test.developer;
/**
* @version 2018-08-07 19:48
*/
public class HandleDataSource {
// 数据源名称线程池,ThreadLocal是线程安全的,并且不能在多线程之间共享
public static final ThreadLocal<String> holder = new ThreadLocal<String>();
public static void setDataSource(String dataSource){
holder.set(dataSource);
}
public static String getDataSource(){
return ((String)holder.get());
}
public static void clearDataSource() {
holder.remove();
}
}
3:切面类
package cn.test.aop;
import cn.test.developer.DataSource;
import cn.test.developer.DataSourceConst;
import cn.test.developer.HandleDataSource;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Before;
/**
* 反射获取获取数据库名并设置数据库类
* @version 2018-08-07 19:59
*/
public class DataSourceAspect {
public void pointCut(){};
/**
* 在事务开始之前插入到service上,通过反射获取注解上的数据库
* @param point
*/
@Before(value="pointCut()")
public void before(JoinPoint point){
//拦截的实体类
Object target = point.getTarget();
//拦截的方法名称
//String methodName = point.getSignature().getName();
Class<?>[] classes = target.getClass().getInterfaces();
//拦截的放参数类型
// Class<?>[] parameterTypes = ((MethodSignature) point.getSignature())
// .getMethod().getParameterTypes();
//默认设置为默认库
HandleDataSource.setDataSource(DataSourceConst.DEFAULT);
try {
if (classes.length > 0) {
// Method m = classes[0].getMethod(methodName, parameterTypes);
if (classes[0] != null && classes[0].isAnnotationPresent(DataSource.class)) {
DataSource data = classes[0].getAnnotation(DataSource.class);
HandleDataSource.setDataSource(data.value());
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
4:数据源名称配置类
package cn.test.developer;
/**
* 数据源配置名称--与配置文件中的key同名
* @version 2018-08-08 11:21
*/
public class DataSourceConst {
public static final String DEFAULT = "default";
public static final String SITE_MAIN = "siteMain";
}
5:自定义注解类
package cn.test.developer;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 用注解来反射读取注解中输入的数据源来确定要操作的数据源
* @version 2018-08-07 19:53
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface DataSource {
String value();
}
如何使用
我这里使用的方式是注解加在service类上,可根据需求切换到方法上也可以。
package cn.test.system.sitechart.service;
@DataSource(DataSourceConst.SITE_MAIN)
public interface ISiteChartMainService extends IBaseService<SiteChartMainEO> {
void getSiteChart(SiteChartMainEO mainEO);
}
以上就是使用AbstractRoutingDataSource 实现动态数据源切换的方式,如果有什么没有注意到的地方,请各路大神评论指正。