【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)

 

posted @ 2019-11-21 21:44  N!CE波  阅读(1615)  评论(0编辑  收藏  举报