zoukankan      html  css  js  c++  java
  • 【Spring Boot】Spring Boot之使用AOP实现数据库多数据源自动切换

    一、添加maven坐标

      <!-- aop -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-aop</artifactId>
            </dependency>
            <!-- jdbc -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-jdbc</artifactId>
            </dependency>
            <!-- mysql -->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <scope>runtime</scope>
            </dependency>
            <!-- mybatis -->
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>1.3.2</version>
            </dependency>

    二、加入Mybtis配置类(方便测试)

    /**
     * @author zhangboqing
     * @date 2018/8/3
     *
     * Mybatis配置
     */
    @Configuration
    @MapperScan(basePackages = {"com.zbq.springbootdemo.dao"}, sqlSessionFactoryRef = "sqlSessionFactory")
    //或者直接在Mapper类上面添加注解@Mapper,建议使用上面那种,不然每个mapper加个注解也挺麻烦的
    public class MyBatisConfig {
    
    }

    三、加入多数据源配置

    1)修改application.yml添加数据库配置属性

    spring:
      datasource:
        primary:
          hikari:
            connection-test-query: SELECT 1 FROM DUAL
            connection-timeout: 600000
            maximum-pool-size: 500
            max-lifetime: 1800000
            minimum-idle: 20
            validation-timeout: 3000
            idle-timeout: 60000
            connection-init-sql: SET NAMES utf8mb4
            jdbc-url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull
            username: root
            password: 123
            driver-class-name: com.mysql.jdbc.Driver
        secondary:
          hikari:
            connection-test-query: SELECT 1 FROM DUAL
            connection-timeout: 600000
            maximum-pool-size: 500
            max-lifetime: 1800000
            minimum-idle: 20
            validation-timeout: 3000
            idle-timeout: 60000
            connection-init-sql: SET NAMES utf8mb4
            jdbc-url: jdbc:mysql://localhost:3326/test?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull
            username: root
            password: 123
            driver-class-name: com.mysql.jdbc.Driver

    2)添加DataSourceConfig配置类(自定义DataSource数据源)

    /**
     * @author zhangboqing
     * @date 2019-11-17
     */
    @Configuration
    // 自定义数据源一定要排除SpringBoot自动配置数据源,不然会出现循环引用的问题,The dependencies of some of the beans in the application context form a cycle
    @EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
    public class DataSourceConfig {
    
    
        @Bean(name = "primary")
        @ConfigurationProperties(prefix = "spring.datasource.primary.hikari")
        public DataSource primaryDataSource() {
            return DataSourceBuilder.create().build();
        }
    
        @Bean(name = "secondary")
        @ConfigurationProperties(prefix = "spring.datasource.secondary.hikari")
        public DataSource secondaryDataSource() {
            return DataSourceBuilder.create().build();
        }
    
        /**
         * 动态数据源
         * 通过AOP+注解实现动态切换
         *
         * @return
         */
        @Primary
        @Bean(name = "dynamicDataSource")
        public DataSource dataSource() {
            DynamicDataSourceRouter dynamicDataSource = new DynamicDataSourceRouter();
            // 默认数据源
            dynamicDataSource.setDefaultTargetDataSource(primaryDataSource());
            // 配置多数据源
            Map<Object, Object> dataSourceMap = new HashMap(5);
            dataSourceMap.put("primary", primaryDataSource());
            dataSourceMap.put("secondary", secondaryDataSource());
            dynamicDataSource.setTargetDataSources(dataSourceMap);
            return dynamicDataSource;
        }
    
        /**
         * 配置@Transactional注解事物
         *
         * @return
         */
        @Bean
        public PlatformTransactionManager transactionManager() {
            return new DataSourceTransactionManager(dataSource());
        }
    
    }
    /**
     * @author zhangboqing
     * @date 2019-11-17
     */
    public class DynamicDataSourceRouter extends AbstractRoutingDataSource{
        @Override
        protected Object determineCurrentLookupKey() {
            return DataSourceNameContextHolder.getDataSourceName();
        }
    
        @Override
        public void setLogWriter(PrintWriter pw) throws SQLException {
            super.setLogWriter(pw);
        }
    }

    3)定义 @DataSourceName注解(用于指定sql对应的数据源)

    /**
     * @author zhangboqing
     * @date 2019-11-17
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD})
    @Documented
    public @interface DataSourceName {
    
        /**
         * 指定数据源名称
         * @return dataSourceName
         */
        String value() default "primary";
    }

    4)定义DataSourceNameContextHolder类(使用ThreadLocal存放当前线程持有的数据源名称)

    /**
     * @author zhangboqing
     * @date 2019-11-17
     */
    @Slf4j
    public class DataSourceNameContextHolder {
    
        private static final ThreadLocal<String> dataSourceNameContextHolder = new NamedThreadLocal<>("DataSourceContext");
    
        /** 默认数据源名称 */
        public static final String DEFAULT_DATASOURCE_NAME = "primary";
    
        public static void setDataSourceName(String dataSourceName) {
            log.info("切换到[{}]数据源", dataSourceName);
            dataSourceNameContextHolder.set(dataSourceName);
        }
    
        public static String getDataSourceName() {
    
            return dataSourceNameContextHolder.get() != null ? dataSourceNameContextHolder.get() : DEFAULT_DATASOURCE_NAME;
        }
    
        public static void resetDataSourceName() {
            dataSourceNameContextHolder.remove();
        }
    }

    5)定义DynamicDataSourceAspect切面类(通过AOP的方式拦截指定注解实现数据源切换)

    /**
     * @author zhangboqing
     * @date 2019-11-17
     */
    @Aspect
    @Component
    public class DynamicDataSourceAspect {
    
        @Before("@annotation(dataSourceName)")
        public void beforeSwitchDataSource(DataSourceName dataSourceName){
            // 切换数据源
            DataSourceNameContextHolder.setDataSourceName(dataSourceName.value());
        }
    
        @After("@annotation(com.zbq.springbootdemo.config.multidatasource.DataSourceName)")
        public void afterSwitchDataSource(){
            DataSourceNameContextHolder.resetDataSourceName();
        }
    }

    四、添加测试

    1)在Mybtis配置类指定的包下定义一个Dao类并使用注解指定数据源

    /**
     * @author zhangboqing
     * @date 2019-11-21
     */
    @Repository
    public interface UserDao {
    
        @DataSourceName("secondary")
        @Select("select * from user order by create_time desc limit 1 ")
        public User getNewstOne();
    
        // 默认是primary,所以可以不指定
    //    @DataSourceName("primary")
        @Select("select * from user order by create_time desc limit 1 ")
        public User getNewstOne2();
    }

    2)定义测试类执行

    /**
     * @author zhangboqing
     * @date 2019-11-21
     */
    @SpringBootTest
    @Slf4j
    class UserServiceImplTest {
    
        @Autowired
        private UserDao userDao;
    
        @Test
        void getNewestOne() {
            User newestOne = userDao.getNewstOne();
            User newestOne2 = userDao.getNewstOne2();
            log.info(newestOne.toString());
            log.info(newestOne2.toString());
        }
    }

    3)执行结果可知多数据源生效,同样的sql查询结果分别来自于两个库

    2019-11-21 21:40:51.124  INFO 8202 --- [           main] c.z.s.service.UserServiceImplTest        : User(uid=1, phone=2222222222, createTime=null, updateTime=null)
    2019-11-21 21:40:51.124  INFO 8202 --- [           main] c.z.s.service.UserServiceImplTest        : User(uid=1, phone=1111111111, createTime=null, updateTime=null)
  • 相关阅读:
    express实现前后端通信上传图片,存储数据库(mysql)傻瓜教程(二)
    express实现前后端通信上传图片,存储数据库(mysql)傻瓜教程(一)
    [转载] 在阿里做了五年技术主管,我有话想说
    阿里云RDS MySql还原到本地Linux/Centos
    如何领域驱动设计?-实践感悟&总结分享
    jira + confluence 安装和破解
    RabbitMQ安装和配置
    [转]技术路线的选择重要但不具有决定性
    .Net core2.0日志组件Log4net、Nlog简单性能测试
    在微服务中使用领域事件
  • 原文地址:https://www.cnblogs.com/756623607-zhang/p/11908621.html
Copyright © 2011-2022 走看看