spring 多数据源的使用
-
在同一个项目中需要使用多个数据源,这就需要根据不同的场景进行切换数据源,spring给我们提供一种很方便的方式,那就是使用 AbstractRoutingDataSource 进行切换数据源。
-
首先来看 AbstractRoutingDataSource 这个类,下面是这个类源码。
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean { // 需要存储的所有数据源,需要在 bean 生成后进行注入,key 为获得数据源名称,value 为数据源 private Map<Object, Object> targetDataSources; private Object defaultTargetDataSource; // 初始设计的默认数据源 // 如果在查找数据源时,找不到相应的数据源时是否使用默认数据源 private boolean lenientFallback = true; // 通过 JNDI 方式的来获取一个数据源 private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup(); // 将 targetDataSources 通过转化得到的数据源的集合。(如果不使用 JNDI 方式,它和 targetDataSources 是一样的) private Map<Object, DataSource> resolvedDataSources; // 转化后的默认数据源,如果在数据源的 key 为 null 时会使用,默认数据源,当然还有其他情况,下面会介绍 private DataSource resolvedDefaultDataSource; public void setTargetDataSources(Map<Object, Object> targetDataSources) { this.targetDataSources = targetDataSources; } public void setDefaultTargetDataSource(Object defaultTargetDataSource) { this.defaultTargetDataSource = defaultTargetDataSource; } public void setLenientFallback(boolean lenientFallback) { this.lenientFallback = lenientFallback; } public void setDataSourceLookup(DataSourceLookup dataSourceLookup) { this.dataSourceLookup = (dataSourceLookup != null ? dataSourceLookup : new JndiDataSourceLookup()); } /** * 因为该类实现了 InitializingBean,所以会在 bean 生成之后(@Bean 注解执行之后,或者 xml 解析完之后)执行该方法 */ @Override public void afterPropertiesSet() { if (this.targetDataSources == null) { // 必须在 Bean 生成后注入数据源 throw new IllegalArgumentException("Property 'targetDataSources' is required"); } this.resolvedDataSources = new HashMap<Object, DataSource>(this.targetDataSources.size()); for (Map.Entry<Object, Object> entry : this.targetDataSources.entrySet()) { Object lookupKey = resolveSpecifiedLookupKey(entry.getKey()); // 对数据源名称进行重定义 DataSource dataSource = resolveSpecifiedDataSource(entry.getValue()); // 获取数据源 this.resolvedDataSources.put(lookupKey, dataSource); } if (this.defaultTargetDataSource != null) { // 设置默认数据源 this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource); } } /** * 变换 lookupKey,由子类实现, 默认是 targetDataSources 的原始 key */ protected Object resolveSpecifiedLookupKey(Object lookupKey) { return lookupKey; } /** * 获取数据,直接获取,或者是通过 JNDI 的方法进行获取 */ protected DataSource resolveSpecifiedDataSource(Object dataSource) throws IllegalArgumentException { if (dataSource instanceof DataSource) { // targetDataSources 中直接存储的就是数据源 return (DataSource) dataSource; } else if (dataSource instanceof String) { // 通过 JNDI 的方法获取数据源 return this.dataSourceLookup.getDataSource((String) dataSource); } else { throw new IllegalArgumentException( "Illegal data source value - only [javax.sql.DataSource] and String supported: " + dataSource); } } /** * 获取数据源的数据库连接 */ @Override public Connection getConnection() throws SQLException { return determineTargetDataSource().getConnection(); } /** * 通过用户名和密码的方式获取数据库连接 */ @Override public Connection getConnection(String username, String password) throws SQLException { return determineTargetDataSource().getConnection(username, password); } @Override @SuppressWarnings("unchecked") public <T> T unwrap(Class<T> iface) throws SQLException { if (iface.isInstance(this)) { return (T) this; } return determineTargetDataSource().unwrap(iface); } @Override public boolean isWrapperFor(Class<?> iface) throws SQLException { return (iface.isInstance(this) || determineTargetDataSource().isWrapperFor(iface)); } /** * 通过 lookupKey 来获取数据源 */ protected DataSource determineTargetDataSource() { Assert.notNull(this.resolvedDataSources, "DataSource router not initialized"); Object lookupKey = determineCurrentLookupKey(); // 获取需要数据源的 lookupKey // 从已经转化过的数据源查找相应的数据源 DataSource dataSource = this.resolvedDataSources.get(lookupKey); // 如果没有找到数据源,或者可以使用默认数据源,或者 lookupKey 为 null,则使用默认数据源 if (dataSource == null && (this.lenientFallback || lookupKey == null)) { dataSource = this.resolvedDefaultDataSource; } if (dataSource == null) { throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]"); } return dataSource; } /** * 由子类实现,返回需要获取的数据源的 lookupKey */ protected abstract Object determineCurrentLookupKey(); }
-
如果要实现多数据源的方式就必须要继承类,然后实现其抽象方法,下面给出一种我的实现方式:
-
多数据源的的实现:
public class MultipleDataSource extends AbstractRoutingDataSource { // 通过 ThreadLocal 的方式来为每个线程一存储个不同的数据源名称 private static final ThreadLocal<String> dataSourceKey = new ThreadLocal<>(); /** * 设置数据源,可以考虑使用注解 aop 的方式,在每个方法执行进行拦截,然后将数据源名称(lookupKey) 设置进行去 */ public static void setDataSourceKey(String dataSource) { dataSourceKey.set(dataSource); } /** * 实现父类的获取数据名称的方法 */ @Override protected Object determineCurrentLookupKey() { String dsName = dataSourceKey.get(); dataSourceKey.remove(); // 注意要删除,否则可能内存泄漏 return dsName; } }
-
多数据源的注入 spring 容器:
@Bean("master") public DataSource masterDataSource(){ return new C3p0SingleDataSource(); } @Bean("slaver") public DataSource slaverDataSource(){ return new BladeDataSource(); } @Bean public DataSource dataSource(@Qualifier("master") DataSource master, @Qualifier("slaver") DataSource slaver){ Map<Object, Object> targetDataSources = new HashMap<>(); targetDataSources.put("master", master); targetDataSources.put("slaver", slaver); MultipleDataSource multipleDataSource = new MultipleDataSource(); multipleDataSource.setTargetDataSources(targetDataSources); // 注入 multipleDataSource.setDefaultTargetDataSource(master); // 设置默认数据源 return multipleDataSource; }
-
注意上面的 master 和 slaver 数据源都是随便 new 的,具体使用时还需要自己设置数据库的相应属性。
-
再考虑写一个多数据源的注解和 aop,可以加在方法和类上面,可以进行多数据源的使用:
@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface DataSource { String value() default ""; }
@Aspect @Component public class DataSourceAspect { public DataSourceAspect() { } @Around("@within(dataSource) || @annotation(dataSource)") public Object around(ProceedingJoinPoint joinPoint, DataSource dataSource) throws Throwable { if (null == dataSource) { dataSource = (DataSource)extractClassLevelAnnotation(joinPoint, DataSource.class); } MultipleDataSource.setDataSourceKey(dataSource.value()); return joinPoint.proceed(); } public static Annotation extractClassLevelAnnotation(JoinPoint joinPoint, Class clazz) { for(Annotation annotation : joinPoint.getTarget().getClass().getAnnotations()) { if (annotation.annotationType() == clazz) { return annotation; } } throw new RuntimeException("类:" + joinPoint.getTarget().getClass().getSimpleName() + "没有找到" + clazz.getName() + "注解"); } }
-
DataSource 注解可以类上面,表示类中的所有方法都使用该数据源,也可以加在方法上面,如果都加了,则会使用方法上面的那个。
@DataSource("slave") public interface UserDao { @DataSource("master") int insert(UserPO userPO); }