dynamic-datasource-spring-boot-starter提供了多数据源切换,通过它可以更加方便的集成多数据源。

创建工程

新建springboot工程,引入相关依赖,maven配置如下:

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>top.wteng</groupId>
    <artifactId>DynamicDSBootIntegration</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>DynamicDSBootIntegration</name>
    <description>Dynamic Datasource Integration</description>
    <properties>
        <java.version>11</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

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

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.26</version>
        </dependency>

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
        </dependency>

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

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

数据源配置

dynamic-datasource-spring-boot-starter使得多数据源集成十分方便,只需在resource/application.yaml中配置数据源,这里配置两个mysql数据源,分别对应ds0ds1两个数据库,ds0中有表t_user,ds1中有表t_order,如下:

CREATE TABLE `ds0`.`t_user`  (
  `id` int(0) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

CREATE TABLE `ds1`.`t_order`  (
  `id` int(0) NOT NULL AUTO_INCREMENT,
  `number` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `user_id` int(0) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

数据源配置如下:

spring:
  datasource:
    dynamic:
        primary: ds0 # 主数据源
        strict: false # 是否严格匹配数据源,若为true,未设置数据源的库操作会报错,否则使用主数据源
        datasource:
            ds0: # 数据源1
                url: jdbc:mysql://localhost:3306/ds0?useSSL=false
                username: root
                password: root
            ds1: # 数据源2
                url: jdbc:mysql://localhost:3306/ds1?useSSL=false
                username: root
                password: root
  jpa: # jpa配置
    generate-ddl: true
    show-sql: false
    hibernate:
      ddl-auto: none

dynamic-datasource-spring-boot-starter同时还可以配置主从库,以组划分,同一组内数据源进行负载均衡,所有以下划线 _ 分割的数据源 首部即为组的名称,如ds2_1ds2_2属于同一组ds2,具体可查看说明文档

dao层实现

dao层实现与单数据源下jpa实现完全一致,只需通过@DS注解标记每个实现类或方法应使用的数据源即可,以t_user为例,定义实体如下:

package top.wteng.dds.entity;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.GenerationType;

@Entity(name = "t_user")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    @Column()
    private String name;

    @Column()
    private String password;


    public User() {
    }

    public User(Integer id, String name, String password) {
        this.id = id;
        this.name = name;
        this.password = password;
    }

    public Integer getId() {
        return this.id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return this.password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public User id(Integer id) {
        setId(id);
        return this;
    }

    public User name(String name) {
        setName(name);
        return this;
    }

    public User password(String password) {
        setPassword(password);
        return this;
    }

    @Override
    public String toString() {
        return "{" +
            " id='" + getId() + "'" +
            ", name='" + getName() + "'" +
            ", password='" + getPassword() + "'" +
            "}";
    }

}

repository定义如下:

package top.wteng.dds.repository;

import org.springframework.stereotype.Repository;
import org.springframework.data.jpa.repository.JpaRepository;

import top.wteng.dds.entity.User;

@Repository
public interface UserRep extends JpaRepository<User, Integer> {
}

为减少重复工作,定义基本增删改查的基类接口与实现,定义公共基类接口如下:

package top.wteng.dds.service;

import java.util.List;

public interface BaseService<T, ID>  {
    T findById(ID id);
    List<T> findAll();

    T save(T t);
    List<T> saveAll(Iterable<T> entities);

    void deleteById(ID id);
    void delete(T t);
    void deleteAll();
    void deleteAll(Iterable<T> entities);
}

基类接口实现如下:

package top.wteng.dds.service.impl;

import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;

import top.wteng.dds.service.BaseService;

public class BaseServiceImpl<T, ID> implements BaseService<T, ID> {
    protected final JpaRepository<T, ID> repository;

    BaseServiceImpl(JpaRepository<T, ID> repository) {
        this.repository = repository;
    }

    @Override
    public T findById(ID id) {
        return this.repository.findById(id).orElse(null);
    }

    @Override
    public List<T> findAll() {
        return this.repository.findAll();
    }

    @Override
    public T save(T t) {
        return this.repository.save(t);
    }

    @Override
    public List<T> saveAll(Iterable<T> entities) {
        return this.repository.saveAll(entities);
    }

    @Override
    public void deleteById(ID id) {
        this.repository.deleteById(id);
    }

    @Override
    public void delete(T t) {
        this.repository.delete(t);
    }

    @Override
    public void deleteAll() {
        this.repository.deleteAll();
    }

    @Override
    public void deleteAll(Iterable<T> entities) {
        this.repository.deleteAll(entities);
    }
}

然后定义User对应的服务类接口:

package top.wteng.dds.service;

import top.wteng.dds.entity.User;

public interface UserService extends BaseService<User, Integer> {
}

User类接口实现,就是在这里通过@DS注解标记使用的数据源,也可以标记具体方法,方法上的标记优先于类标记,当配置了主从多库时,数据源名称可以写具体名称或只写组名称,若写组名称,则在组内进行负载均衡:

package top.wteng.dds.service.impl;

import com.baomidou.dynamic.datasource.annotation.DS;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import top.wteng.dds.entity.User;
import top.wteng.dds.repository.UserRep;
import top.wteng.dds.service.UserService;

@Service
@DS("ds0") // 此类的操作使用ds0数据源
public class UserServiceImpl extends BaseServiceImpl<User, Integer> implements UserService {
    UserServiceImpl(@Autowired UserRep userRep) {
        super(userRep);
    }
}

t_orderdao层实现与t_user一致,只是在服务实现类OrderServiceImpl上将数据源标记为ds1即可。

测试

编写测试如下:

package top.wteng.dds;

import java.util.List;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import top.wteng.dds.entity.Order;
import top.wteng.dds.entity.User;
import top.wteng.dds.service.OrderService;
import top.wteng.dds.service.UserService;

@SpringBootTest
public class DynamicModuleTest {
    @Autowired private UserService userService;
    @Autowired private OrderService orderService;

    @Test
    public void doSomething() {
        User user = new User();
        user.setName("user_" + String.valueOf(System.currentTimeMillis()));
        user.setPassword("password");
        this.userService.save(user);
        System.out.println("user saved ...");

        Order order = new Order();
        order.setNumber("O" + String.valueOf(System.currentTimeMillis()));
        order.setUserId(user.getId());
        this.orderService.save(order);
        System.out.println("order saved ..");

        List<User> users = this.userService.findAll();
        System.out.println("========= all users =======");
        users.forEach(u -> System.out.println(u.toString()));

        List<Order> orders = this.orderService.findAll();
        System.out.println("========= all orders =======");
        orders.forEach(o -> System.out.println(o.toString())); 
    }
}

运行测试,即可看到输入,查看数据库,数据被存到了不同的库中:

Snipaste_2021-09-10_14-32-24.png