SpringBoot实现多数据源的两种方式

SpringBoot实现多数据源的两种方式,第1张

SpringBoot实现多数据源的两种方式 前言

公司项目有连接多个不同数据库的需求,特研究了一下,根据网上的资料,造了一个基于AOP方式的数据源切换轮子,但继续探索,突然发现有开源的多数据源管理启动器。不过,本篇两种方式都会介绍。

基于dynamic-datasource实现多数据源 dynamic-datasource介绍

dynamic-datasource-spring-boot-starter 是一个基于springboot的快速集成多数据源的启动器。

支持 Jdk 1.7+, SpringBoot 1.4.x 1.5.x 2.x.x

dynamic-datasource特性
  • 支持 数据源分组 ,适用于多种场景 纯粹多库 读写分离 一主多从 混合模式。
  • 支持数据库敏感配置信息 加密 ENC()。
  • 支持每个数据库独立初始化表结构schema和数据库database。
  • 支持无数据源启动,支持懒加载数据源(需要的时候再创建连接)。
  • 支持 自定义注解 ,需继承DS(3.2.0+)。
  • 提供并简化对Druid,HikariCp,BeeCp,Dbcp2的快速集成。
  • 提供对Mybatis-Plus,Quartz,ShardingJdbc,P6sy,Jndi等组件的集成方案。
  • 提供 自定义数据源来源 方案(如全从数据库加载)。
  • 提供项目启动后 动态增加移除数据源 方案。
  • 提供Mybatis环境下的 纯读写分离 方案。
  • 提供使用 spel动态参数 解析数据源方案。内置spel,session,header,支持自定义。
  • 支持 多层数据源嵌套切换 。(ServiceA >>> ServiceB >>> ServiceC)。
  • 提供 基于seata的分布式事务方案。
  • 提供 本地多数据源事务方案。 附:不能和原生spring事务混用。

我们目前只探讨使用dynamic-datasource进行数据源切换,其他请自行搜索

dynamic-datasource的相关约定
  1. dynamic-datasource只做 切换数据源 这件核心的事情,并不限制你的具体 *** 作,切换了数据源可以做任何CRUD。
  2. 配置文件所有以下划线 _ 分割的数据源 首部 即为组的名称,相同组名称的数据源会放在一个组下。
  3. 切换数据源可以是组名,也可以是具体数据源名称。组名则切换时采用负载均衡算法切换。
  4. 默认的数据源名称为 master ,你可以通过 spring.datasource.dynamic.primary 修改。
  5. 方法上的注解优先于类上注解。
  6. DS支持继承抽象类上的DS,暂不支持继承接口上的DS。
引入dynamic-datasource依赖

  com.baomidou
  dynamic-datasource-spring-boot-starter
  ${version}

配置数据源
spring:
  datasource:
    dynamic:
      primary: mysql #设置默认的数据源或者数据源组,默认值即为master
      strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
      datasource:
        mysql:
          url: jdbc:mysql://xx.xx.xx.xx:3306/dynamic
          username: root
          password: 123456
          driver-class-name: com.mysql.jdbc.Driver # 3.2.0开始支持SPI可省略此配置
        pgsql:
          url: ENC(xxxxx) # 内置加密
          username: ENC(xxxxx)
          password: ENC(xxxxx)
          driver-class-name: org.postgresql.Driver
使用 @DS 切换数据源

@DS 可以注解在方法上或类上,同时存在就近原则 方法上注解 优先于 类上注解。

注解结果不使用@DS注解默认数据源,即primary: mysql@DS(“dsName”)dsName可以为组名也可以为具体某个库的名称 @DS使用实例
@Service
@DS("mysql")
public class UserServiceImpl implements UserService {

  @Autowired
  private JdbcTemplate jdbcTemplate;

  // 不使用@DS注解则代表使用默认数据源
  // 如果类上存在,则使用类上标注的数据源
  public List selectAll() {
    return  jdbcTemplate.queryForList("select * from user");
  }
  
  @Override
  @DS("pgsql")
  // 方法上注解 优先于 类上注解,即使类上标注也优先采用方法上的标注
  public List selectByCondition() {
    return  jdbcTemplate.queryForList("select * from user where age >10");
  }
}
基于AOP手动实现多数据源

本次代码参考 https://github.com/mianshenglee/my-example/tree/master/multi-datasource/dynamic-datasource ,因源码不满足的需求,因此我在此基础做了修改。

项目工程结构

项目依赖


    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.2.2.RELEASE
         
    
    me.mason.demo
    dynamic-datasource
    0.0.1-SNAPSHOT
    dynamic-datasource
    Demo project for dynamic datasource

    
        1.8
    

    
        
        
            org.springframework.boot
            spring-boot-starter-web
        
        
            org.springframework.boot
            spring-boot-configuration-processor
        
        
            com.alibaba
            druid-spring-boot-starter
            1.1.9
        
        
            org.springframework.boot
            spring-boot-starter-jdbc
        
        
            org.springframework.boot
            spring-boot-starter-aop
        
        
        
            mysql
            mysql-connector-java
            runtime
        
        
        
            com.baomidou
            mybatis-plus-boot-starter
            3.3.0
        
        
            org.projectlombok
            lombok
            true
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
            
                
                    org.junit.vintage
                    junit-vintage-engine
                
            
        

    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    


配置文件
server.port=8080
server.servlet.context-path=/dd

logging.level.root=INFO
logging.level.me.mason.demo.dynamicdatasource.mapper=DEBUG

# mybatis-plus
mybatis-plus.type-aliases-package=me.mason.demo.dynamicdatasource.entity
# 默认位置,可不配置
#mybatis-plus.mapper-locations=classpath*:/mapper
public class DataSourceConstants {
    
    public static final String DS_KEY_MASTER = "master";
    
    public static final String DS_KEY_SLAVE = "slave";
}
动态数据源名称上下文处理
public class DynamicDataSourceContextHolder {

    
    private static final ThreadLocal DATASOURCE_CONTEXT_KEY_HOLDER = new ThreadLocal<>();

    
    public static void setContextKey(String key){
        DATASOURCE_CONTEXT_KEY_HOLDER.set(key);
    }

    
    public static String getContextKey(){
        String key = DATASOURCE_CONTEXT_KEY_HOLDER.get();
        return key == null?DataSourceConstants.DS_KEY_MASTER:key;
    }

    
    public static void removeContextKey(){
        DATASOURCE_CONTEXT_KEY_HOLDER.remove();
    }
}
获取当前动态数据源方法
public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.getContextKey();
    }
}
动态数据源配置
@EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class })
@Configuration
// 此处我们
//@PropertySource("classpath:config/jdbc.properties")
@MapperScan(basePackages = "me.mason.demo.dynamicdatasource.mapper")
public class DynamicDataSourceConfig {
    @Bean(DataSourceConstants.DS_KEY_MASTER)
    // 需要与配置文件中对应
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DruidDataSource masterDataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean(DataSourceConstants.DS_KEY_SLAVE)
    @ConfigurationProperties(prefix = "spring.datasource.slave")
    public DruidDataSource slaveDataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @Primary
    public DynamicDataSource dynamicDataSource() {
        Map dataSourceMap = new HashMap<>(2);
        dataSourceMap.put(DataSourceConstants.DS_KEY_MASTER, masterDataSource());
        dataSourceMap.put(DataSourceConstants.DS_KEY_SLAVE, slaveDataSource());
        //设置动态数据源
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        dynamicDataSource.setTargetDataSources(dataSourceMap);
        dynamicDataSource.setDefaultTargetDataSource(masterDataSource());
        return dynamicDataSource;
    }

}
AOP切面
@Aspect
@Component
//@Order(-10)
public class DynamicDataSourceAspect {
	// 以在类上使用了@Service作为切入点
    @Pointcut("@within(org.springframework.stereotype.Service)")
    public void dataSourcePointCut() {
    }

    @Around("dataSourcePointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Class aClass = Class.forName(signature.getDeclaringType().getName());
        // 方法优先,如果方法上存在注解,则优先使用方法上的注解
        if (signature.getMethod().isAnnotationPresent(DS.class)) { DynamicDataSourceContextHolder.setContextKey(signature.getMethod().getAnnotation(DS.class).value());
            // 其次类优先,如果类上存在注解,则使用类上的注解
        }else  if (aClass.isAnnotationPresent(DS.class)) { DynamicDataSourceContextHolder.setContextKey(aClass.getAnnotation(DS.class).value());
            // 如果都不存在,则使用默认
        }   else {
 DynamicDataSourceContextHolder.setContextKey(DataSourceConstants.DS_KEY_MASTER);
        }
        try {
            return joinPoint.proceed();
        } finally {
            DynamicDataSourceContextHolder.removeContextKey();
        }
    }
}
编写TestUser实体
@Data
@TableName("test_user")
public class TestUser implements Serializable {

    private static final long serialVersionUID = 1L;

    
    private Long id;
    
    private String name;
    
    private String phone;
    
    private String title;
    
    private String email;
    
    private String gender;
    
    private Date dateOfBirth;
    
    private Integer deleted;
    
    private Date sysCreateTime;
    
    private String sysCreateUser;
    
    private Date sysUpdateTime;
    
    private String sysUpdateUser;
    
    private Long recordVersion;

    public TestUser() {
    }

}
TestUserMapper
@Repository
public interface TestUserMapper extends baseMapper {

    
    List selectAll(@Param(Constants.WRAPPER) Wrapper wrapper);
}
TestUserService
@Service
//@DS(DataSourceConstants.DS_KEY_SLAVE)
public class TestUserService {
    @Autowired
    private TestUserMapper testUserMapper;

    
//    @DS(DataSourceConstants.DS_KEY_MASTER)
    public List getMasterUser(){
        QueryWrapper queryWrapper = new QueryWrapper<>();
        return testUserMapper.selectAll(queryWrapper.isNotNull("name"));
    }

    
//    @DS(DataSourceConstants.DS_KEY_MASTER)
    public List getSlaveUser(){
        return testUserMapper.selectList(null);
    }
}
TestUserController
@RestController
@RequestMapping("/user")
public class TestUserController {
    @Autowired
    private TestUserService testUserService;
    
    @GetMapping("/listall")
    public Object listAll() {
        int initSize = 2;
        Map result = new HashMap<>(initSize);
        List masterUser = testUserService.getMasterUser();
        result.put("masterUser", masterUser);
        List slaveUser = testUserService.getSlaveUser();
        result.put("getSlaveUser", slaveUser);
        return ResponseResult.success(result);
    }

}
MapperXml



    

启动测试 不使用注解
@Service
//@DS(DataSourceConstants.DS_KEY_SLAVE)
public class TestUserService {
    @Autowired
    private TestUserMapper testUserMapper;


    
//    @DS(DataSourceConstants.DS_KEY_MASTER)
    public List getMasterUser(){
        QueryWrapper queryWrapper = new QueryWrapper<>();
        return testUserMapper.selectAll(queryWrapper.isNotNull("name"));
    }

    
//    @DS(DataSourceConstants.DS_KEY_MASTER)
    public List getSlaveUser(){
        return testUserMapper.selectList(null);
    }

}
效果

该代码优先级与使用框架效果一致,即不使用注解将默认使用MASTER数据库,方法上存在注解优先使用方法上标注的注解。

已知MASTER 6条数据, SLAVE4条数据

访问 http://127.0.0.1:8080/dd/user/listall 查看效果

类上使用注解
@Service
@DS(DataSourceConstants.DS_KEY_SLAVE)
public class TestUserService {
    @Autowired
    private TestUserMapper testUserMapper;


    
//    @DS(DataSourceConstants.DS_KEY_MASTER)
    public List getMasterUser(){
        QueryWrapper queryWrapper = new QueryWrapper<>();
        return testUserMapper.selectAll(queryWrapper.isNotNull("name"));
    }

    
//    @DS(DataSourceConstants.DS_KEY_MASTER)
    public List getSlaveUser(){
        return testUserMapper.selectList(null);
    }
}
效果

方法上使用注解
@Service
@DS(DataSourceConstants.DS_KEY_SLAVE)
public class TestUserService {
    @Autowired
    private TestUserMapper testUserMapper;
    
    @DS(DataSourceConstants.DS_KEY_SLAVE)
    public List getMasterUser(){
        QueryWrapper queryWrapper = new QueryWrapper<>();
        return testUserMapper.selectAll(queryWrapper.isNotNull("name"));
    }
    
    @DS(DataSourceConstants.DS_KEY_MASTER)
    public List getSlaveUser(){
        return testUserMapper.selectList(null);
    }
}
效果

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/4693345.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-11-07
下一篇 2022-11-07

发表评论

登录后才能评论

评论列表(0条)

保存