git地址: https://github.com/leezhang0525/boot
branch:1.1.1dynamicDataSource
本文档持续更新,目标为多组件系统,常用组件集成完毕后,后续会添加分布式内容。

1、说明

这里使用主从数据源,主从备份没有做,具体实现自行百度,这里简单使用两个数据库实现主从数据源

2、yml文件添加数据源

  1. server:
  2. port: 8081
  3. spring:
  4. datasource:
  5. master:
  6. url: jdbc:mysql://localhost:3306/pay?characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&useLocalSessionState=true
  7. username: root
  8. password: 123456
  9. driver-class-name: com.mysql.jdbc.Driver
  10. type: com.alibaba.druid.pool.DruidDataSource
  11. slave:
  12. url: jdbc:mysql://localhost:3306/payslave?characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&useLocalSessionState=true
  13. username: root
  14. password: 123456
  15. driver-class-name: com.mysql.jdbc.Driver
  16. type: com.alibaba.druid.pool.DruidDataSource

3、自定义主从数据源枚举

  1. package com.zhangsan.boot.enums.core;
  2. /**
  3. * 数据源枚举
  4. */
  5. public enum DataSourceType {
  6. MASTER("master"),
  7. SLAVE("slave");
  8. private String value;
  9. DataSourceType(String value) {
  10. this.value = value;
  11. }
  12. public String getValue() {
  13. return value;
  14. }
  15. }

4、新建动态数据源

1、数据源key值处理器

  1. package com.zhangsan.boot.config;
  2. import com.zhangsan.boot.enums.core.DataSourceType;
  3. /**
  4. * 数据源处理器
  5. */
  6. public class DataSourceContextHolder {
  7. private DataSourceContextHolder(){}
  8. private static final ThreadLocal<String> holder = new ThreadLocal<>();
  9. public static void putDataSource(DataSourceType dataSourceType) {
  10. holder.set(dataSourceType.getValue());
  11. }
  12. public static String getDataSource() {
  13. return holder.get();
  14. }
  15. public static void clearDataSource() {
  16. holder.remove();
  17. }
  18. }

2、编写DynamicDataSource类

继承 AbstractRoutingDataSource类,重写determineCurrentLookupKey方法

  1. package com.zhangsan.boot.config;
  2. import com.zhangsan.boot.enums.core.DataSourceType;
  3. import org.apache.commons.lang3.StringUtils;
  4. import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
  5. /**
  6. * 自定义动态数据源路由
  7. */
  8. public class DynamicDataSource extends AbstractRoutingDataSource {
  9. /**
  10. *
  11. * @return Map<Object, DataSource> resolvedDataSources key值
  12. */
  13. @Override
  14. protected Object determineCurrentLookupKey() {
  15. if(StringUtils.isNotBlank(DataSourceContextHolder.getDataSource())){
  16. return DataSourceContextHolder.getDataSource();
  17. }
  18. // 默认主库
  19. return DataSourceType.MASTER.getValue();
  20. }
  21. }

1650609249.jpg
1650609289(1).png

3、注入动态数据源

重写DruidConfig.java类

  1. package com.zhangsan.boot.config;
  2. import com.alibaba.druid.pool.DruidDataSource;
  3. import com.zhangsan.boot.enums.core.DataSourceType;
  4. import org.apache.ibatis.session.SqlSessionFactory;
  5. import org.mybatis.spring.SqlSessionFactoryBean;
  6. import org.mybatis.spring.mapper.MapperScannerConfigurer;
  7. import org.springframework.beans.factory.annotation.Qualifier;
  8. import org.springframework.boot.context.properties.ConfigurationProperties;
  9. import org.springframework.context.annotation.Bean;
  10. import org.springframework.context.annotation.Configuration;
  11. import org.springframework.context.annotation.DependsOn;
  12. import org.springframework.context.annotation.Primary;
  13. import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
  14. import org.springframework.core.io.support.ResourcePatternResolver;
  15. import org.springframework.jdbc.datasource.DataSourceTransactionManager;
  16. import javax.sql.DataSource;
  17. import java.util.HashMap;
  18. import java.util.Map;
  19. @Configuration
  20. public class DruidConfig {
  21. public static final String MAPPER_BASE_PACKAGE = "com.zhangsan.boot.dao";
  22. public static final String ENTITY_BASE_PACKAGE = "com.zhangsan.boot.entity";
  23. /**
  24. * 主数据源
  25. * @return
  26. */
  27. @Bean
  28. @ConfigurationProperties(prefix = "spring.datasource.master")
  29. public DataSource masterDataSource(){
  30. return new DruidDataSource();
  31. }
  32. /**
  33. * 从数据源
  34. * @return
  35. */
  36. @Bean
  37. @ConfigurationProperties(prefix = "spring.datasource.slave")
  38. public DataSource slaveDataSource(){
  39. return new DruidDataSource();
  40. }
  41. /**
  42. * 动态数据源
  43. * @return
  44. */
  45. @Bean
  46. @DependsOn({ "masterDataSource", "slaveDataSource"})
  47. @Primary
  48. public DataSource dynamicDataSource() {
  49. DynamicDataSource dataSource = new DynamicDataSource();
  50. Map<Object, Object> targetDataSources = new HashMap<>();
  51. targetDataSources.put(DataSourceType.MASTER.getValue(), masterDataSource());
  52. targetDataSources.put(DataSourceType.SLAVE.getValue(), slaveDataSource());
  53. dataSource.setTargetDataSources(targetDataSources);
  54. // 这里不需要重复添加默认数据源,DynamicDataSource类中已经制定默认主数据源key
  55. return dataSource;
  56. }
  57. @Bean
  58. public SqlSessionFactory sqlSessionFactory (@Qualifier("dynamicDataSource") DataSource dataSource) throws Exception {
  59. SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
  60. sqlSessionFactoryBean.setDataSource(dataSource);
  61. sqlSessionFactoryBean.setTypeAliasesPackage(ENTITY_BASE_PACKAGE);
  62. ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
  63. sqlSessionFactoryBean.setMapperLocations(resolver.getResources("classpath*:mappers/*.xml"));
  64. return sqlSessionFactoryBean.getObject();
  65. }
  66. /**
  67. * mapperScannerConfigurer
  68. * @return
  69. */
  70. @Bean
  71. public static MapperScannerConfigurer mapperScannerConfigurer() {
  72. MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
  73. mapperScannerConfigurer.setBasePackage(MAPPER_BASE_PACKAGE);
  74. mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory");
  75. return mapperScannerConfigurer;
  76. }
  77. @Bean
  78. public DataSourceTransactionManager transactionManager(@Qualifier("dynamicDataSource") DataSource dataSource) {
  79. return new DataSourceTransactionManager(dataSource);
  80. }
  81. }

5、自定义注解实现动态数据源

1、添加自定义注解

  1. package com.zhangsan.boot.annotation;
  2. import com.zhangsan.boot.enums.core.DataSourceType;
  3. import java.lang.annotation.*;
  4. /**
  5. * 主从库注解
  6. */
  7. @Target({ElementType.METHOD})
  8. @Retention(RetentionPolicy.RUNTIME)
  9. @Inherited
  10. public @interface DataSource {
  11. DataSourceType value() default DataSourceType.MASTER;
  12. }

2、编写切面管理注解

给自定义注解加上切面,通过注解管理和制定数据源

  1. package com.zhangsan.boot.aspect;
  2. import com.zhangsan.boot.annotation.DataSource;
  3. import com.zhangsan.boot.config.DataSourceContextHolder;
  4. import lombok.extern.slf4j.Slf4j;
  5. import org.aspectj.lang.ProceedingJoinPoint;
  6. import org.aspectj.lang.annotation.Around;
  7. import org.aspectj.lang.annotation.Aspect;
  8. import org.springframework.core.Ordered;
  9. import org.springframework.core.annotation.Order;
  10. import org.springframework.stereotype.Component;
  11. @Aspect
  12. @Component
  13. @Order(Ordered.HIGHEST_PRECEDENCE + 1)
  14. @Slf4j
  15. public class DataSourceAspect {
  16. @Around("execution(public * com.zhangsan.boot.service..*.*(..)) && @annotation(com.zhangsan.boot.annotation.DataSource) && @annotation(dataSource)")
  17. public Object setDataSource(ProceedingJoinPoint joinPoint, DataSource dataSource) throws Throwable {
  18. try {
  19. log.info("执行了数据路由切面,当前为:{}",dataSource.value());
  20. DataSourceContextHolder.putDataSource(dataSource.value());
  21. return joinPoint.proceed();
  22. } finally {
  23. DataSourceContextHolder.clearDataSource();
  24. log.info("清理了数据路由切面,threadLocal中dataSource为:{}",DataSourceContextHolder.getDataSource());
  25. }
  26. }
  27. }

6、编写测试类测试

  1. /**
  2. * 默认主库
  3. * @param id
  4. * @return
  5. */
  6. @Override
  7. @DataSource(DataSourceType.MASTER)
  8. public TPadDevice masterGetById(Long id) {
  9. return tPadDeviceMapper.selectByPrimaryKey(id);
  10. }
  11. /**
  12. * 从库
  13. * @param id
  14. * @return
  15. */
  16. @Override
  17. @DataSource(DataSourceType.SLAVE)
  18. public TPadDevice salverGetById(Long id) {
  19. return tPadDeviceMapper.selectByPrimaryKey(id);
  20. }

TestController.java

  1. @GetMapping("/masterDataSource")
  2. public TPadDevice dynamicDataSourceTest(Long id){
  3. log.info("这是测试");
  4. TPadDevice padDevice = padDeviceService.masterGetById(id);
  5. return padDevice;
  6. }
  7. @GetMapping("/slaveDataSource")
  8. public TPadDevice slaveDataSource(Long id){
  9. log.info("这是测试");
  10. TPadDevice padDevice = padDeviceService.salverGetById(id);
  11. return padDevice;
  12. }

测试成功(不同库中,表名相同的表记录不同)
1650610594(1).png
1650610640(1).png