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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM