spring 有个类叫AbstractRoutingDataSource,只需要继承并重写方法就行了。
首先要先定义几个数据源
<bean id="master" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${master.jdbc.driverClassName}"/>
<property name="url" value="${master.jdbc.url}"/>
<property name="username" value="${master.jdbc.username}"/>
<property name="password" value="${master.jdbc.password}"/>
<property name="filters" value="stat,wall"/>
</bean>
<bean id="assistant" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${log.jdbc.driverClassName}"/>
<property name="url" value="${log.jdbc.url}"/>
<property name="username" value="${log.jdbc.username}"/>
<property name="password" value="${log.jdbc.password}"/>
</bean>
定义一个注解叫RoutingDataSource
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RoutingDataSource {
DataSources value() default DataSources.MASTER;
}
RoutingDataSource里只有一个属性 value是个枚举类
public enum DataSources {
MASTER("master"),
LOG("assistant");
private String key;
DataSources(String key) {
this.key = key;
}
public String key() {
return key;
}
}
然后再定义一个与线程绑定的动态数据源
public class DataSourceKeyHolder {
private static final ThreadLocal<LinkedList<String>> holder = new ThreadLocal<LinkedList<String>>() {
@Override
protected LinkedList<String> initialValue() {
return new LinkedList<>();
}
};
public static void set(String key) {
holder.get().push(key);
}
public static void clear() {
holder.get().pop();
}
public static String getCurrentKey() {
if (holder.get().size() == 0)
return null;
return holder.get().getFirst();
}
public static boolean isNestedCall() {
return holder.get().size() > 1;
}
}
定义了一些基本方法,使用的是栈,这样就能满足嵌套调用,取的话只取第一个。再定义一个DynamicDataSource 继承于AbstractRoutingDataSource,并重写其中的determineCurrentLookupKey方法
public class DynamicDataSource extends AbstractRoutingDataSource {
protected Object determineCurrentLookupKey() {
return DataSourceKeyHolder.getCurrentKey();
}
@Override
public Connection getConnection() throws SQLException {
Connection connection = super.getConnection();
if (DataSourceKeyHolder.getCurrentKey() != null) {
log.info("Datasource route to {}, key={}", connection, DataSourceKeyHolder.getCurrentKey());
}
return connection;
}
}
最后 ,则是具体的设置数据源的方法,在这使用AOP切入
@Component
@Aspect
public class RoutingDataSourceAdvisor {
@Pointcut("execution(@RoutingDataSource * com.ljh..*Service+.*(..))")
private void routingDataSource() {
}
@Around("routingDataSource()")
public Object routing(ProceedingJoinPoint joinPoint) throws Exception {
Class<?> clazz = joinPoint.getTarget().getClass();
String className = clazz.getName();
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
String methodName = method.getName();
Object[] arguments = joinPoint.getArgs();
String key;
RoutingDataSource routingDataSource = method.getAnnotation(RoutingDataSource.class);
key = routingDataSource.value().getKey();
Object result = null;
DataSourceKeyHolder.set(key);
try {
checkPROPAGATION(clazz, method);
result = joinPoint.proceed(arguments);
} catch (Throwable e) {
log.error("Error occurred during datasource(key=" + key + ") routing, ", e);
} finally {
DataSourceKeyHolder.clear();
}
return result;
}
private void checkPROPAGATION(Class<?> clazz, Method method) {
if (DataSourceKeyHolder.isNestedCall()) {
Transactional transactional = method.getAnnotation(Transactional.class);
if (transactional == null) {
transactional = clazz.getAnnotation(Transactional.class);
}
if (transactional != null) {
if (transactional.propagation() != Propagation.REQUIRES_NEW) {
throw new RuntimeException("必须定义为propagation = REQUIRES_NEW");
}
}
}
}
}
在这里,要注意判断事务的传播级别是否是PROPAGATION_REQUIRED ,如果不是Propagation.REQUIRES_NEW 的话,那数据源就是你的第一个数据源,它会和线程绑定,接下来取的话就不是调用我们定义的数据源了,而是从spring的线程变量里直接取了,所以这里要给个判断。