通过继承spring AbstractRoutingDataSource父类来进行动态的切换数据源,结合注解和spring aop来实现。
数据源配置
applicationContext.xml中加入aop代理配置
最后在service层实现动态设置
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* 自定义的多数据源路由器.
*
*/
public class CustomerRoutingDataSource extends AbstractRoutingDataSource {
private final transient Logger log = LoggerFactory.getLogger(CustomerRoutingDataSource.class);
@Override
protected Object determineCurrentLookupKey() {
final String dbType = CustomerDBContextHolder.getDataSourceType();
if (log.isInfoEnabled()) {
log.info("获取了KEY={}的数据源", dbType);
}
return dbType;
}
}
/**
* 自定义的上下文储存器
*/
public class CustomerDBContextHolder {
//线程安全的ThreadLocal
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public static void setDataSourceType(String dbType) {
Assert.notNull(dbType, "DBType cannot be null");
contextHolder.set(dbType);
}
public static String getDataSourceType() {
String str = (String) contextHolder.get();
if (StringUtils.isBlank(str))
str = DataSource.master;
return str;
}
public static void clearDataSourceType() {
contextHolder.remove();
}
}
import java.lang.annotation.*;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
/**
*注解
*/
public @interface DataSource {
String name() default DataSource.master;
public static String master = "master";
public static String slave = "slave";
}
import java.lang.reflect.Method;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
*切点
*/
@Aspect
@Component
public class DataSourceAspect {
//定义切点
@Pointcut("@annotation(org.pos.manager.data.utils.spring.DataSource)")
public void daoAspect() {
}
@Before("daoAspect()")
public void doBefore(JoinPoint joinPoint){
try {
CustomerDBContextHolder.setDataSourceType(getDataSource(joinPoint));
} catch (Exception e) {
e.printStackTrace();
}
}
@SuppressWarnings("rawtypes")
public static String getDataSource(JoinPoint joinPoint) throws Exception {
String targetName = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
Object[] arguments = joinPoint.getArgs();
Class targetClass = Class.forName(targetName);
Method[] methods = targetClass.getMethods();
String name = "";
for (Method method : methods) {
if (method.getName().equals(methodName)) {
Class[] clazzs = method.getParameterTypes();
if (clazzs.length == arguments.length) {
name = method.getAnnotation(DataSource.class).name();
break;
}
}
}
return name;
}
}
数据源配置
<bean id="dataSourceWrite" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass" value="${master.jdbc.driverClassName}" />
<property name="jdbcUrl" value="${master.jdbc.url}" />
<property name="user" value="${master.jdbc.userDS.username}" />
<property name="password" value="${master.jdbc.userDS.password}" />
<!-- 连接池中保留的最小连接数 -->
<property name="minPoolSize" value="1" />
<!-- 连接池中保留的最大连接数 -->
<property name="maxPoolSize" value="200" />
<!-- 最大空闲时间,1800秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0-->
<property name="maxIdleTime" value="1800" />
<!-- 当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。Default: 3-->
<property name="acquireIncrement" value="5" />
<!-- 最大的PreparedStatement的数量 -->
<property name="maxStatements" value="1000" />
<!-- 每60秒检查所有连接池中的空闲连接。Default: 0 -->
<property name="idleConnectionTestPeriod" value="60" />
<!-- 定义在从数据库获取新连接失败后重复尝试的次数。Default: 30-->
<property name="acquireRetryAttempts" value="30" />
<property name="breakAfterAcquireFailure" value="true" />
<property name="testConnectionOnCheckout" value="false" />
</bean>
<bean id="dataSourceRead" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass" value="${slave.jdbc.driverClassName}" />
<property name="jdbcUrl" value="${slave.jdbc.url}" />
<property name="user" value="${slave.jdbc.userDS.username}" />
<property name="password" value="${slave.jdbc.userDS.password}" />
<!-- 连接池中保留的最小连接数 -->
<property name="minPoolSize" value="1" />
<!-- 连接池中保留的最大连接数 -->
<property name="maxPoolSize" value="200" />
<!-- 最大空闲时间,1800秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0-->
<property name="maxIdleTime" value="1800" />
<!-- 当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。Default: 3-->
<property name="acquireIncrement" value="5" />
<!-- 最大的PreparedStatement的数量 -->
<property name="maxStatements" value="1000" />
<!-- 每60秒检查所有连接池中的空闲连接。Default: 0 -->
<property name="idleConnectionTestPeriod" value="60" />
<!-- 定义在从数据库获取新连接失败后重复尝试的次数。Default: 30-->
<property name="acquireRetryAttempts" value="30" />
<property name="breakAfterAcquireFailure" value="true" />
<property name="testConnectionOnCheckout" value="false" />
</bean>
<bean id="dataSource" class="org.pos.manager.data.utils.spring.CustomerRoutingDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="master" value-ref="dataSourceWrite" />
<entry key="slave" value-ref="dataSourceRead" />
</map>
</property>
<property name="defaultTargetDataSource" ref="dataSourceWrite"/>
</bean>
applicationContext.xml中加入aop代理配置
<aop:aspectj-autoproxy proxy-target-class="true" />
最后在service层实现动态设置
@Component(value = "accountBankService")
public class AccountBankServiceImpl implements AccountBankService {
@Autowired
@Qualifier(value = "accountBankDao")
private AccountBankDao accountBankDao;
@DataSource(name = DataSource.slave)
@Override
public List<AccountBank> getAccountBankList() throws Exception {
return accountBankDao.getAccountBankList();
}
}