【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)
你投入得越多,就能得到越多得价值