千家信息网

利用SpringBoot实现多数据源的方式有哪些

发表于:2024-09-22 作者:千家信息网编辑
千家信息网最后更新 2024年09月22日,本篇内容介绍了"利用SpringBoot实现多数据源的方式有哪些"的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够
千家信息网最后更新 2024年09月22日利用SpringBoot实现多数据源的方式有哪些

本篇内容介绍了"利用SpringBoot实现多数据源的方式有哪些"的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

基于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的相关约定

  • dynamic-datasource只做 切换数据源 这件核心的事情,并不限制你的具体操作,切换了数据源可以做任何CRUD。

  • 配置文件所有以下划线 _ 分割的数据源 首部 即为组的名称,相同组名称的数据源会放在一个组下。

  • 切换数据源可以是组名,也可以是具体数据源名称。组名则切换时采用负载均衡算法切换。

  • 默认的数据源名称为 master ,你可以通过 spring.datasource.dynamic.primary 修改。

  • 方法上的注解优先于类上注解。

  • 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=8080server.servlet.context-path=/ddlogging.level.root=INFOlogging.level.me.mason.demo.dynamicdatasource.mapper=DEBUG# mybatis-plusmybatis-plus.type-aliases-package=me.mason.demo.dynamicdatasource.entity# 默认位置,可不配置#mybatis-plus.mapper-locations=classpath*:/mapper/*.xmlmybatis.mapper-locations=classpath*:/mapper/*.xml# 使用数据库自增IDmybatis-plus.global-config.db-config.id-type=auto# masterspring.datasource.master.driver-class-name=com.mysql.cj.jdbc.Driverspring.datasource.master.url=jdbc:mysql://10.0.1.243:3306/scheduling?useSSL=false&serverTimezone=GMT%2B8&characterEncoding=UTF-8spring.datasource.master.username=rootspring.datasource.master.password=123456# slavespring.datasource.slave.driver-class-name=com.mysql.cj.jdbc.Driverspring.datasource.slave.url=jdbc:mysql://10.0.1.243:3306/scheduling1?useSSL=false&serverTimezone=GMT%2B8&characterEncoding=UTF-8spring.datasource.slave.username=rootspring.datasource.slave.password=123456

自定义注解

// 标记注解可使用在方法与类上@Target({ElementType.METHOD,ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)public @interface DS {        // 默认值为MASTER    String value() default DataSourceConstants.DS_KEY_MASTER;}

编写DataSourceConstants

/** * 数据源常量 **/public class DataSourceConstants {    /**     * master数据源     */    public static final String DS_KEY_MASTER = "master";    /**     * slave数据源     */    public static final String DS_KEY_SLAVE = "slave";}

动态数据源名称上下文处理

/** * 动态数据源名称上下文处理 **/public class DynamicDataSourceContextHolder {    /**     * 动态数据源名称上下文     */    private static final ThreadLocal DATASOURCE_CONTEXT_KEY_HOLDER = new ThreadLocal<>();    /**     * 设置数据源     * @param key     */    public static void setContextKey(String key){        DATASOURCE_CONTEXT_KEY_HOLDER.set(key);    }    /**     * 获取数据源名称     * @return     */    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;    /** id */    private Long id;    /** 姓名 */    private String name;    /** 手机号 */    private String phone;    /** 职称职别 */    private String title;    /** 邮箱 */    private String email;    /** 性别 */    private String gender;    /** 出生时间 */    private Date dateOfBirth;    /** 1:已删除,0:未删除 */    private Integer deleted;    /** 创建时间 */    private Date sysCreateTime;    /** 创建人 */    private String sysCreateUser;    /** 更新时间 */    private Date sysUpdateTime;    /** 更新人 */    private String sysUpdateUser;    /** 版本号 */    private Long recordVersion;    public TestUser() {    }}

TestUserMapper

@Repositorypublic interface TestUserMapper extends BaseMapper {    /**     * 自定义查询     * @param wrapper 条件构造器     * @return     */    List selectAll(@Param(Constants.WRAPPER) Wrapper wrapper);}

TestUserService

@Service//@DS(DataSourceConstants.DS_KEY_SLAVE)public class TestUserService {    @Autowired    private TestUserMapper testUserMapper;    /**     * 查询master库User     * @return     *///    @DS(DataSourceConstants.DS_KEY_MASTER)    public List getMasterUser(){        QueryWrapper queryWrapper = new QueryWrapper<>();        return testUserMapper.selectAll(queryWrapper.isNotNull("name"));    }    /**     * 查询slave库User     * @return     *///    @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;    /**     * 查询master库User     * @return     *///    @DS(DataSourceConstants.DS_KEY_MASTER)    public List getMasterUser(){        QueryWrapper queryWrapper = new QueryWrapper<>();        return testUserMapper.selectAll(queryWrapper.isNotNull("name"));    }    /**     * 查询slave库User     * @return     *///    @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;    /**     * 查询master库User     * @return     *///    @DS(DataSourceConstants.DS_KEY_MASTER)    public List getMasterUser(){        QueryWrapper queryWrapper = new QueryWrapper<>();        return testUserMapper.selectAll(queryWrapper.isNotNull("name"));    }    /**     * 查询slave库User     * @return     *///    @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;    /**     * 查询master库User     * @return     */    @DS(DataSourceConstants.DS_KEY_SLAVE)    public List getMasterUser(){        QueryWrapper queryWrapper = new QueryWrapper<>();        return testUserMapper.selectAll(queryWrapper.isNotNull("name"));    }    /**     * 查询slave库User     * @return     */    @DS(DataSourceConstants.DS_KEY_MASTER)    public List getSlaveUser(){        return testUserMapper.selectList(null);    }}

效果

"利用SpringBoot实现多数据源的方式有哪些"的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注网站,小编将为大家输出更多高质量的实用文章!

0