spring 多数据源的使用

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);
    }
    
原文地址:https://www.cnblogs.com/dwtfukgv/p/14848407.html