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去实现

  • 20
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值