zoukankan      html  css  js  c++  java
  • SpringBoot 多数据源配置

    主要利用AOP+ThreadLocal+自定义注释实现注释切换

    pom.xml

    <!-- springboot-aop包,AOP切面注解,Aspectd等相关注解 -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    AbstractRoutingDataSource
    只支持单库事务,也就是说切换数据源要在开启事务之前执行。 
    spring DataSourceTransactionManager进行事务管理,开启事务,会将数据源缓存到DataSourceTransactionObject对象中进行后续的commit rollback等事务操作。

    这里主要看AbstractRoutingDataSource.java源码:

    配置的多个数据源会放在AbstractRoutingDataSource的 targetDataSources和defaultTargetDataSource中,然后通过afterPropertiesSet()方法将数据源分别进行复制到resolvedDataSources和resolvedDefaultDataSource中(AbstractAutowireCapableBeanFactory-->InitializingBean.afterPropertiesSet() 在创建bean的时候反射调用)
    AbstractRoutingDataSource的getConnection()的方法的时候,先调用determineTargetDataSource()方法返回DataSource
    (JdbcTemplate.execute()->DataSourceUtils.getConnection->dataSource.getConnection()->AbstractRoutingDataSource实现了了DataSource)

     

    标黄部分:根据Key获取对应数据源。

    这里新建类继承AbstractRoutingDataSource

    import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    
    public class DynamicDataSource extends AbstractRoutingDataSource {
        @Override
        protected Object determineCurrentLookupKey() {
            return DataSourceContextHolder.getDB();
        }
    }

     这里新建类,ThreadLocal用于存放数据源

    public class DataSourceContextHolder {
    
        /**
         * 默认数据源
         */
        public static final String DEFAULT_DS = "chenDataSource";
    
        private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
    
        // 设置数据源名
        public static void setDB(String dbType) {
            System.out.println("切换到{" + dbType + "}数据源");
            contextHolder.set(dbType);
        }
    
        // 获取数据源名
        public static String getDB() {
            return (contextHolder.get());
        }
    
        // 清除数据源名
        public static void clearDB() {
            contextHolder.remove();
        }
    }

    说明:

    此处,会疑惑会不会线程不安全啊,那么就要看看ThreadLocal.java的源码实现了。
    ThreadLocal:

    用于保存某个线程共享变量:对于同一个static ThreadLocal,不同线程只能从中get,set,remove自己的变量,而不会影响其他线程的变量。

    1、ThreadLocal.get: 获取ThreadLocal中当前线程共享变量的值。

    2、ThreadLocal.set: 设置ThreadLocal中当前线程共享变量的值。

    3、ThreadLocal.remove: 移除ThreadLocal中当前线程共享变量的值。

    4、ThreadLocal.initialValue: ThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值。

    工作原理:

    1、Thread类中有一个成员变量属于ThreadLocalMap类(一个定义在ThreadLocal类中的内部类),它是一个Map,他的key是ThreadLocal实例对象。

    2、当为ThreadLocal类的对象set值时,首先获得当前线程的ThreadLocalMap类属性,然后以ThreadLocal类的对象为key,设定value。get值时则类似。

    3、ThreadLocal变量的活动范围为某线程,是该线程“专有的,独自霸占”的,对该变量的所有操作均由该线程完成!也就是说,ThreadLocal 不是用来解决共享对象的多线程访问的竞争问题的,因为ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。当线程终止后,这些值会作为垃圾回收。

    4、由ThreadLocal的工作原理决定了:每个线程独自拥有一个变量,并非是共享的。

    import com.paic.phssp.springtest.dataSource.DynamicDataSource;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.boot.jdbc.DataSourceBuilder;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Primary;
    import org.springframework.jdbc.datasource.DataSourceTransactionManager;
    import org.springframework.transaction.PlatformTransactionManager;
    
    import javax.sql.DataSource;
    import java.util.HashMap;
    import java.util.Map;
    
    @Configuration
    public class DataSourceConfig {
    
        private Logger logger = LoggerFactory.getLogger(this.getClass());
    
        @Bean(name = "chenDataSource")
        @Qualifier("chenDataSource")
        @ConfigurationProperties(prefix = "spring.datasource.chen")
        public DataSource chenDataSource() {
            return DataSourceBuilder.create().build();
        }
    
        @Bean(name = "quartzds")
        @Qualifier("quartzds")
        @ConfigurationProperties(prefix = "spring.datasource.quartzds")
        public DataSource quartzDSDataSource() {
            return DataSourceBuilder.create().build();
        }
    
        /**
         * 动态数据源: 通过AOP在不同数据源之间动态切换
         *
         * @return
         */
        @Primary
        @Bean(name = "dynamicDataSource")
        public DataSource dynamicDataSource() {
            DynamicDataSource dynamicDataSource = new DynamicDataSource();
            // 默认数据源
            dynamicDataSource.setDefaultTargetDataSource(chenDataSource());
            // 配置多数据源
            Map<Object, Object> dsMap = new HashMap();
            dsMap.put("chenDataSource", chenDataSource());
            dsMap.put("quartzds", quartzDSDataSource());
    
            dynamicDataSource.setTargetDataSources(dsMap);
            return dynamicDataSource;
        }
    
        /**
         * 配置@Transactional注解事物
         * @return
         */
        @Bean
        public PlatformTransactionManager transactionManager() {
            return new DataSourceTransactionManager(dynamicDataSource());
        }
    }

    自定义注释

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD})
    public @interface IDataSource {
        String value() default "chenDataSource";
    }

    AOP从注释中获取数据源key,然后通过AbstractRoutingDataSource.determineTargetDataSource()获取数据源

    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.annotation.After;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.reflect.MethodSignature;
    import org.springframework.stereotype.Component;
    
    import java.lang.reflect.Method;
    
    /**
     * 自定义注解 + AOP的方式实现数据源动态切换。
     * Created by pure on 2018-05-06.
     */
        
    @Component
    public class DynamicDataSourceAspect {
        
        @Before("@annotation(IDataSource)")
        public void beforeSwitchDS(JoinPoint point) {
            //获得当前访问的class
            Class<?> className = point.getTarget().getClass();
            //获得访问的方法名
            String methodName = point.getSignature().getName();
            //得到方法的参数的类型
            Class[] argClass = ((MethodSignature) point.getSignature()).getParameterTypes();
            String dataSource = DataSourceContextHolder.DEFAULT_DS;
            try {
                // 得到访问的方法对象
                Method method = className.getMethod(methodName, argClass);
                // 判断是否存在@IDataSource注解
                if (method.isAnnotationPresent(IDataSource.class)) {
                    IDataSource annotation = method.getAnnotation(IDataSource.class);
                    // 取出注解中的数据源名
                    dataSource = annotation.value();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            // 切换数据源
            DataSourceContextHolder.setDB(dataSource);
        }
    
        @After("@annotation(IDataSource)")
        public void afterSwitchDS(JoinPoint point) {
            //清楚数据源
            DataSourceContextHolder.clearDB();
        }
    }

    应用:

    import com.paic.phssp.springtest.dao.UserMapper;
    import com.paic.phssp.springtest.dataSource.IDataSource;
    import com.paic.phssp.springtest.dto.User;
    import com.paic.phssp.springtest.service.IUserService;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    import java.util.List;
    
    @Service
    public class IUserServiceImpl implements IUserService {
    
        private final Logger log = LoggerFactory.getLogger(getClass());
    
        @Autowired
        private UserMapper userMapper;
    
        @Override
        @IDataSource(value="chenDataSource")
        public List<User> findAll() {
            return userMapper.getAll();
        }
    }

    运行结果:

    总结:

    上面叨叨了那么多,其实重点:AOP+注解 结构。

  • 相关阅读:
    【bzoj 2159】Crash 的文明世界
    【bzoj 4833】[Lydsy1704月赛]最小公倍佩尔数
    【解题报告】网络流24题
    【bzoj 4449】[Neerc2015]Distance on Triangulation
    【ARC 063F】Snuke's Coloring 2
    【LOJ 6041】「雅礼集训 2017 Day7」事情的相似度
    【AGC 005F】Many Easy Problems
    【AGC 002F】Leftmost Ball
    替罪羊树
    状压dp
  • 原文地址:https://www.cnblogs.com/xiaozhuanfeng/p/10461638.html
Copyright © 2011-2022 走看看