SpringBoot 结合 Mybatis 实现对多源数据库操作
系统环境:
- Java Jdk 版本:1.8
- Mysql 版本:8.0.19
- Mybatis 版本:3.5.6
- SpringBoot 版本:2.4.2
- Postgresql 版本:12.5
参考地址:
项目示例地址:
如果本文对你有帮助,请帮忙 github 点颗星哦~
一、为什么需要操作多源数据库
在日常开发中,我们会经常遇见各式各样的需求。比如,一个应用中操作多个数据库,而且数据库可能不是一种类型的数据库,需要从多种数据库中读取数据,这种需求屡见不鲜。

二、如何实现多源数据库
目前使用 SpringBoot 实现多数据库实现方案比较多,这里只列出两种常用方案:
- 方案一:创建多个数据源配置类,在类中配置数据源使用不同的 Datasource,并且指定应用该数据源配置的生效的 Mapper 包路径。这样我们使用中调用不同包中的 Mapper 就会切换到不同的数据库中,执行对应的 SQL 操作。
- 方案二:使用 baomidou 开源的组件 dynamic-datasource 实现多源数据库切换。该组件内部针对多个数据库的配置与操作进行了封装,加上与 SpringBoot 结合实现自动化配置,我们使用中只要在 Service 类上或者 Service 内部方法上添加 @DS 注解,就能轻松实现不同类型的数据库切换。
两种方式都能使 SpringBoot 支持多数据库,不过方案一需要进行很多数据源的配置,相对复杂,不过可以灵活配置。而方案二的组件对数据源相关配置进行了封装,直接可以通过注解方式来切换不同的类型的数据库,非常简单方便。两种方案都有各自的优缺点,使用哪种还需要大家自信斟酌。
三、准备实验的测试环境
在下面我们会写两个示例,分别使用两种方案来实现 SpringBoot + Mybatis 对多源数据库的支持,不过我们需要提前准备演示的数据库及其用例。
准备两种类型的数据库,如下:
- 数据库一:mysql
- 数据库二:postgresql
Mysql 和 Postgresql 中创建数据库 db1 与 db2,命令如下:
- Mysql:
CREATE DATABASE db1; - Postgresql:
CREATE DATABASE db2;
数据库创建完成后需要导入用于实验的表 SQL 文件,如下:
- 数据库一:mysql 实验的表 Sql 文件
- 数据库二:postgresql 实验的表 Sql 文件
两种类型数据库用于实验的表结构,如下:
- 数据库一:t_user_info 表:
| id | name | gender | age |
|---|---|---|---|
| 1 | 小豆丁 | 0 | 22 |
- 数据库二:t_account 表:
| id | username | password | create_time | update_time |
|---|---|---|---|---|
| 1 | mydlq | 123456 | 2021-01-01 00:00:00 | 2021-01-01 00:00:00 |
四、方案一配置数据源使用步骤
使用创建数据库配置类方式来实现多数据源支持,可以进行如下步骤:
1、配置文件添加数据库参数
需要在 SpringBoot 配置文件 application.yml 中,添加如下多个数据库连接配置。
datasource: ## 数据库一 db1: driverClassName: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://127.0.0.1:3306/db1?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&useSSL=false username: root password: 123456 ## 数据库二 db2: driverClassName: org.postgresql.Driver jdbc-url: jdbc:postgresql://127.0.0.1:5432/db2?currentSchema=public username: postgre password: 1234562、创建 Mapper 包
提前在项目目录中创建用于存放 Mapper 的包,如下所示中 db1 包中存放数据库一的相关操作接口,db2 包中存放操作数据库二的相关操作接口:
- 数据库一包路径:/src/main/java/mydlq/dao/db1
- 数据库二包路径:/src/main/java/mydlq/dao/db2
提前在项目的 /resources 目录下创建 Mapper 对应的 xml 的目录:
- 数据库一 Mapper 对应的 xml 文件目录:/src/main/resources/mappers/db1
- 数据库二 Mapper 对应的 xml 文件目录:/src/main/resources/mappers/db2
3、创建数据源配置类
这里我们需要创建两个数据源配置类,每个类都对应一个数据源的配置,内容如下:
数据库一配置类 Db1DataSourceConfig
@Configuration@MapperScan(basePackages = Db1DataSourceConfig.PACKAGE, sqlSessionFactoryRef = Db1DataSourceConfig.PACKAGE)public class Db1DataSourceConfig {
/** 指定 Sql Session Factory 的 Bean 名称 */ static final String SQL_SESSION_FACTORY = "db1SqlSessionFactory"; /** 指定 Mapper 类的包路径 */ static final String PACKAGE = "mydlq.dao.db1"; /** 指定数据库 Mapper 对应的 xml 文件路径 */ static final String MAPPER = "classpath:mappers/db1/*.xml";
/** * 配置数据源,这里设置为 hikari 数据库连接池 * @return DataSource */ @Primary @Bean(name = "db1DataSource") @ConfigurationProperties("datasource.db1") public DataSource dataSource() { return DataSourceBuilder.create().type(HikariDataSource.class).build(); }
/** * 数据源事务管理器 * @return 数据源事务管理器 */ @Primary @Bean(name = "db1TransactionManager") public DataSourceTransactionManager dataSourceTransactionManager(@Qualifier("db1DataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); }
@Primary @Bean(name = SQL_SESSION_FACTORY) public SqlSessionFactory sqlSessionFactory(@Qualifier("db1DataSource") DataSource masterDataSource) throws Exception { final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); sessionFactory.setDataSource(masterDataSource); sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(Db1DataSourceConfig.MAPPER)); return sessionFactory.getObject(); }
}数据库二配置类 Db2DataSourceConfig
@Configuration@MapperScan(basePackages = Db2DataSourceConfig.PACKAGE, sqlSessionFactoryRef = Db2DataSourceConfig.PACKAGE)public class Db2DataSourceConfig {
/** 指定 Sql Session Factory 的 Bean 名称 */ static final String SQL_SESSION_FACTORY = "db2SqlSessionFactory"; /** 指定 Mapper 类的包路径 */ static final String PACKAGE = "mydlq.dao.db2"; /** 指定数据库 Mapper 对应的 xml 文件路径 */ static final String MAPPER = "classpath:mappers/db2/*.xml";
/** * 配置数据源,这里设置为 hikari 数据库连接池 * @return DataSource */ @Primary @Bean(name = "db2DataSource") @ConfigurationProperties("datasource.db2") public DataSource dataSource() { return DataSourceBuilder.create().type(HikariDataSource.class).build(); }
/** * 数据源事务管理器 * @return 数据源事务管理器 */ @Primary @Bean(name = "db2TransactionManager") public DataSourceTransactionManager dataSourceTransactionManager(@Qualifier("db2DataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); }
@Primary @Bean(name = SQL_SESSION_FACTORY) public SqlSessionFactory sqlSessionFactory(@Qualifier("db2DataSource") DataSource masterDataSource) throws Exception { final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); sessionFactory.setDataSource(masterDataSource); sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(Db2DataSourceConfig.MAPPER)); return sessionFactory.getObject(); }
}我们在使用时,调用:
- mydlq.dao.db1 包的 mapper 接口中的方法就会使用 Db1DataSourceConfig 的配置,切换数据库一读取数据。
- mydlq.dao.db2 包的 mapper 接口中的方法就会使用 Db2DataSourceConfig 的配置,切换数据库二读取数据。
五、方案一示例:配置数据源实现
使用创建数据库配置类方式来实现多数据源支持,这里直接使用上面准备的两种类型的数据库中两个表中的数据,实现一个项目中调用两种数据库进行查询。创建一个测试接口,将查询到的数据拼接在一起进行返回。示例代码如下:
1、Maven 引入相关依赖
Maven 的 pom.xml 文件引入 SpringBoot、Myabtis、Lombok、Mysql 和 Postgresql 相关依赖,内容如下:
<?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.4.2</version> </parent>
<groupId>mydlq.club</groupId> <artifactId>springboot-mybatis-many-database-config</artifactId> <version>0.0.1</version> <name>springboot-mybatis-many-database-config</name> <description>database demo</description>
<properties> <java.version>1.8</java.version> </properties>
<dependencies> <!-- SpringBoot Web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <!-- Mysql --> <dependency> <artifactId>mysql-connector-java</artifactId> <groupId>mysql</groupId> </dependency> <!-- Postgresql --> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <scope>runtime</scope> </dependency> <!-- Mybatis SpringBoot --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.4</version> </dependency> </dependencies>
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
</project>上面没有配置数据库连接池,由于 SpringBoot 2.x 以后默认引入 Hikari 连接池,性能很好,所以我们使用其默认连接池即可。
2、配置文件中添加相关参数
在 SpringBoot 配置文件 application.yml 中,配置数据库相关配置。由于我们会用到两个数据库,所以这里我们配置两套数据库配置:
- db1:用于连接
mysql数据库的配置。 - db2:用于连接
postgresql数据库的配置。
application.yml 文件内容如下:
datasource: db1: driverClassName: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://127.0.0.1:3306/db1?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&useSSL=false username: root password: 123456 db2: driverClassName: org.postgresql.Driver jdbc-url: jdbc:postgresql://127.0.0.1:5432/db2?currentSchema=public username: postgre password: 1234563、创建数据库配置类
数据库一配置类: Db1DataSourceConfig
import javax.sql.DataSource;import com.zaxxer.hikari.HikariDataSource;import org.apache.ibatis.session.SqlSessionFactory;import org.mybatis.spring.SqlSessionFactoryBean;import org.mybatis.spring.annotation.MapperScan;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.boot.jdbc.DataSourceBuilder;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Primary;import org.springframework.core.io.support.PathMatchingResourcePatternResolver;import org.springframework.jdbc.datasource.DataSourceTransactionManager;
@Configuration@MapperScan(basePackages = Db1DataSourceConfig.PACKAGE, sqlSessionFactoryRef = Db1DataSourceConfig.PACKAGE)public class Db1DataSourceConfig {
/** 指定 Sql Session Factory 的 Bean 名称 */ static final String SQL_SESSION_FACTORY = "db1SqlSessionFactory"; /** 指定 Mapper 类的包路径 */ static final String PACKAGE = "mydlq.club.example.dao.db1"; /** 指定数据库 Mapper 对应的 xml 文件路径 */ static final String MAPPER = "classpath:mappers/db1/*.xml";
/** * 配置数据源,这里设置为 hikari 数据库连接池 * @return DataSource */ @Primary @Bean(name = "db1DataSource") @ConfigurationProperties("datasource.db1") public DataSource dataSource() { return DataSourceBuilder.create().type(HikariDataSource.class).build(); }
/** * 数据源事务管理器 * @return 数据源事务管理器 */ @Primary @Bean(name = "db1TransactionManager") public DataSourceTransactionManager dataSourceTransactionManager(@Qualifier("db1DataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); }
@Primary @Bean(name = SQL_SESSION_FACTORY) public SqlSessionFactory sqlSessionFactory(@Qualifier("db1DataSource") DataSource masterDataSource) throws Exception { final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); sessionFactory.setDataSource(masterDataSource); sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(Db1DataSourceConfig.MAPPER)); return sessionFactory.getObject(); }
}数据库二配置类: Db2DataSourceConfig
import javax.sql.DataSource;import com.zaxxer.hikari.HikariDataSource;import org.apache.ibatis.session.SqlSessionFactory;import org.mybatis.spring.SqlSessionFactoryBean;import org.mybatis.spring.annotation.MapperScan;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.boot.jdbc.DataSourceBuilder;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.core.io.support.PathMatchingResourcePatternResolver;import org.springframework.jdbc.datasource.DataSourceTransactionManager;
@Configuration@MapperScan(basePackages = Db2DataSourceConfig.PACKAGE, sqlSessionFactoryRef = "db2SqlSessionFactory")public class Db2DataSourceConfig {
/** 指定 Sql Session Factory 的 Bean 名称 */ static final String SQL_SESSION_FACTORY = "db2SqlSessionFactory"; /** 指定 Mapper 类的包路径 */ static final String PACKAGE = "mydlq.club.example.dao.db2"; /** 指定数据库 Mapper 对应的 xml 文件路径 */ static final String MAPPER = "classpath:mappers/db2/*.xml";
/** * 配置数据源,设置为 hikari * @return DataSource */ @Bean(name = "db2DataSource") @ConfigurationProperties("datasource.db2") public DataSource masterDataSource() { return DataSourceBuilder.create().type(HikariDataSource.class).build(); }
/** * 数据源事务管理器 * @return 数据源事务管理器 */ @Bean(name = "db2TransactionManager") public DataSourceTransactionManager masterTransactionManager() { return new DataSourceTransactionManager(masterDataSource()); }
@Bean(name = SQL_SESSION_FACTORY) public SqlSessionFactory masterSqlSessionFactory(@Qualifier("db2DataSource") DataSource masterDataSource) throws Exception { final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); sessionFactory.setDataSource(masterDataSource); sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(Db2DataSourceConfig.MAPPER)); return sessionFactory.getObject(); }
}注解说明:
- @ConfigurationProperties:从配置文件中读取指定前缀的配置参数。
- @MapperScan:指定数据库配置对哪些包的 Mapper 生效,指定数据库 SQL 会话工厂。
- @Primary:声明这是一个主数据源(默认数据源),多数据源配置时必不可少。
- @Qualifier:显式选择传入的 Bean,根据配置的名称指定使用哪个 Bean。
4、创建实体类
数据库一的实体类: UserInfo
import lombok.Data;
@Datapublic class UserInfo { /** 主键 */ private Integer id; /** 姓名 */ private String name; /** 性别(0:男,1:女)*/ private String gender; /** 岁数 */ private Byte age;}数据库二的实体类: Account
import java.time.LocalDateTime;import lombok.Data;
@Datapublic class Account { /** 主键 */ private Integer id; /** 用户名 */ private String username; /** 密码 */ private String password; /** 创建时间 */ private LocalDateTime createTime; /** 更新时间 */ private LocalDateTime updateTime;}5、创建操作数据库的 Mapper 类和 Xml 文件
数据库一
创建 Mapper 类 UserInfoMapper
import mydlq.club.example.model.db1.UserInfo;import org.apache.ibatis.annotations.Mapper;
@Mapperpublic interface UserInfoMapper {
/** * 根据主键查找数据 * * @param id 主键ID * @return 数据 */ UserInfo selectByPrimaryKey(Integer id);
}位置 /resources/mappers/db1 创建 Mapper 文件 UserInfoMapper.xml
<?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="mydlq.club.example.dao.db1.UserInfoMapper">
<resultMap id="BaseResultMap" type="mydlq.club.example.model.db1.UserInfo"> <id column="id" jdbcType="INTEGER" property="id" /> <result column="name" jdbcType="VARCHAR" property="name" /> <result column="gender" jdbcType="VARCHAR" property="gender" /> <result column="age" jdbcType="TINYINT" property="age" /> </resultMap>
<select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap"> SELECT id, `name`, gender, age FROM t_user_info WHERE id = #{id,jdbcType=INTEGER} </select>
</mapper>数据库二
创建 Mapper 类 AccountMapper
import mydlq.club.example.model.db2.Account;import org.apache.ibatis.annotations.Mapper;
@Mapperpublic interface AccountMapper { /** * 根据主键查找数据 * * @param id 主键ID * @return 数据 */ Account selectByPrimaryKey(Integer id);
}位置 /resources/mappers/db2 创建 Mapper 文件 AccountMapper.xml
<?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="mydlq.club.example.dao.db2.AccountMapper">
<resultMap id="BaseResultMap" type="mydlq.club.example.model.db2.Account"> <id column="id" jdbcType="BIGINT" property="id" /> <result column="username" jdbcType="VARCHAR" property="username" /> <result column="password" jdbcType="VARCHAR" property="password" /> <result column="create_time" jdbcType="TIMESTAMP" property="createTime" /> <result column="update_time" jdbcType="TIMESTAMP" property="updateTime" /> </resultMap>
<select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap"> SELECT id, username, "password", create_time, update_time FROM t_account WHERE id = #{id,jdbcType=INTEGER} </select>
</mapper>6、创建测试的 Service 类
创建测试的 Service 类,类中创建 findUserInfo() 和 findAccount() 两个方法,分别通过 AccountMapper 和 UserInfoMapper 访问 Mysql 和 Postgresql 两个数据库获取数据。
import mydlq.club.example.dao.db1.UserInfoMapper;import mydlq.club.example.dao.db2.AccountMapper;import mydlq.club.example.model.db1.UserInfo;import mydlq.club.example.model.db2.Account;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;
@Servicepublic class TestService {
@Autowired private AccountMapper accountMapper; @Autowired private UserInfoMapper userInfoMapper;
/** * 数据库一,查询用户信息 * @param id 主键ID * @return 用户信息 */ public UserInfo findUserInfo(Integer id){ return userInfoMapper.selectByPrimaryKey(id); }
/** * 数据库二,查询账户数据 * @param id 主键ID * @return 账户数据 */ public Account findAccount(Integer id){ return accountMapper.selectByPrimaryKey(id); }
}7、创建测试的 Controller 类
创建测试用的 Controller 类,里面调用上面创建的 Service 类中查询两个数据库数据的方法 findUserInfo() 和 findAccount() 来获取两个库中的数据,并提供测试访问的接口 /test,方便后续进行测试。
import mydlq.club.example.model.AccountUserInfo;import mydlq.club.example.model.db1.UserInfo;import mydlq.club.example.model.db2.Account;import mydlq.club.example.service.TestService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;
@RestControllerpublic class TestController {
@Autowired private TestService testService;
/** * 同时查询两个库的数据 * @return 查询结果 */ @GetMapping("/test") public Object hello1() { // 查询账户数据 Account account = testService.findAccount(1); // 查询用户信息 UserInfo userInfo = testService.findUserInfo(1); // 创建响应对象 AccountUserInfo accountUserInfo = new AccountUserInfo(); accountUserInfo.setAccount(account); accountUserInfo.setUserInfo(userInfo); return accountUserInfo; }
}8、创建 SpringBoot 启动类
创建 SpringBoot 启动类:
import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplicationpublic class Application {
public static void main(String[] args) { SpringApplication.run(Application.class, args); }
}9、访问接口进行测试
输入地址:http://localhost:8080/test 测试是否能够从”mysql”和”postgresql”两个数据库中获取数据,结果如下:
{ "userInfo": { "id": 1, "name": "小豆丁", "gender": "0", "age": 22 }, "account": { "id": 1, "username": "mydlq", "password": "123456", "createTime": "2021-01-01T00:00:00", "updateTime": "2021-01-01T00:00:00" }}根据上面测试结果可知,按上面配置确实能够实现从不同类型的数据库中读取数据,不过这种方法需要对创建多个数据源配置类才能适配多个数据库,实际使用起来还是比较繁琐的。下面将会演示如何使用 dynamic-datasource 提供的注解方式来实现多源数据库。
六、方案二使用数据库切换组件步骤
使用 dynamic-datasource 来支持多源数据库要简单的多,这里简单介绍下它的使用,如果想详细了解可以访问其官方文档,里面有详细的介绍如何使用该插件。
dynamic-datasource 官方文档地址:https://dynamic-datasource.com/
1、Maven 引入 dynamic-datasource 依赖
Maven 中引入 dynamic-datasource-spring-boot-starter 依赖:
<dependency> <groupId>com.baomidou</groupId> <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <version>3.3.1</version></dependency>2、配置文件添加数据库参数
需要在 SpringBoot 配置文件 application.yml 中添加多数据源的数据库连接配置。
spring: datasource: dynamic: primary: db1 #设置默认的数据源或者数据源组,默认值即为 master strict: false #设置严格模式,默认false不启动. 启动后在未匹配到指定数据源时候会抛出异常,不启动则使用默认数据源。 datasource: db1: url: jdbc:mysql://127.0.0.1:3306/db1?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&useSSL=false username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver db2: url: jdbc:postgresql://127.0.0.1:5432/db2?currentSchema=public username: postgre password: 123456 driver-class-name: org.postgresql.Driver
## 配置 mapper 对应的 xml 文件目录mybatis: mapper-locations: classpath:mappers/db1/*.xml,classpath*:/mappers/db2/*.xml3、Service 类或者方法添加 @DS 注解
在 Service 类或者类中方法上添加 @DS 注解来标识使用哪个数据库。
@DS("db1") //默认使用数据库1@Servicepublic class TestService {
/** * 切换到数据库一读取数据 */ @DS("db1") public UserInfo findUserInfo(Integer id){ ... }
/** * 切换到数据库二读取数据 */ @DS("db2") public Account findAccount(Integer id){ ... }
}注解 @DS 可以配置在类或者方法上,同时存在就近原则,方法上注解优先于类上注解。上面类和方法上都加了 @DS 注解,如果类中方法上添加 @DS 注解,那则在调用数据库 Dao 方法时,使用以类上面的 @DS 注解中指定的数据库。如果方法上面添加了该注解,则使用注解中指定的数据库。当然,如果类和方法上都没有加注解的话,那么将会使用配置文件中 spring.datasource.dynamic.primary 指定的默认数据库。
注意:该组件实现方式是通过 Spring 的 AOP 进行数据源处理的,所以方法调用同一个类中的其它方法是不生效的(会使用配置的默认数据库),必须通过引入操作数据库的 Bean 来调用切换数据库的方法才能正常使用。
七、方案二示例:使用数据库切换组件实现
示例项目地址:SpringBoot + Mybatis 通过使用 dynamic-datasource 实现多源数据库示例
使用组件 dynamic-datasource 来实现多数据源支持,这里直接读取上面准备的两种类型的数据库中两个表中的数据,实现一个项目中调用两种数据库进行查询。创建一个测试接口,将查询到的数据拼接在一起进行返回。示例代码如下:
1、Maven 引入相关依赖
Maven 的 pom.xml 文件引入 SpringBoot、Myabtis、Lombok、dynamic-datasource、Mysql 和 Postgresql 相关依赖,内容如下:
<?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.4.1</version> </parent>
<groupId>mydlq.club</groupId> <artifactId>springboot-mybatis-many-database-dynamic</artifactId> <version>0.0.1</version> <name>springboot-mybatis-many-database-dynamic</name> <description>database demo</description>
<properties> <java.version>1.8</java.version> </properties>
<dependencies> <!-- SpringBoot Web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <!-- Mysql --> <dependency> <artifactId>mysql-connector-java</artifactId> <groupId>mysql</groupId> </dependency> <!-- Postgresql --> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <scope>runtime</scope> </dependency> <!-- Mybatis SpringBoot --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.4</version> </dependency> <!-- Dynamic Datasource --> <dependency> <groupId>com.baomidou</groupId> <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <version>3.3.1</version> </dependency> </dependencies>
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
</project>上面没有配置数据库连接池,由于 SpringBoot 2.x 以后默认引入 Hikari 连接池,性能很好,所以我们使用其默认连接池即可。
2、配置文件中添加相关参数
在 SpringBoot 配置文件 application.yml 中,配置数据库相关配置。由于我们会用到两个数据库,所以这里我们配置两套数据库配置:
- db1:用于连接
mysql数据库的配置。 - db2:用于连接
postgresql数据库的配置。
application.yml 文件内容如下:
spring: datasource: dynamic: primary: db1 #设置默认的数据源或者数据源组,默认值即为master strict: false #设置严格模式,默认false不启动. 启动后在未匹配到指定数据源时候会抛出异常,不启动则使用默认数据源。 datasource: db1: url: jdbc:mysql://192.168.2.31:30336/db1?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&useSSL=false username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver db2: url: jdbc:postgresql://192.168.2.31:30432/db2?currentSchema=public username: postgre password: 123456 driver-class-name: org.postgresql.Driver
## 指定 Mapper 对应的 xml 文件目录地址mybatis: mapper-locations: classpath:mappers/db1/*.xml,classpath*:/mappers/db2/*.xml3、创建实体类
数据库1的实体类: UserInfo
import lombok.Data;
@Datapublic class UserInfo { /** 主键 */ private Integer id; /** 姓名 */ private String name; /** 性别(0:男,1:女)*/ private String gender; /** 岁数 */ private Byte age;}数据库2的实体类: Account
import java.time.LocalDateTime;import lombok.Data;
@Datapublic class Account { /** 主键 */ private Integer id; /** 用户名 */ private String username; /** 密码 */ private String password; /** 创建时间 */ private LocalDateTime createTime; /** 更新时间 */ private LocalDateTime updateTime;}4、创建操作数据库的 Mapper 类和 Xml 文件
数据库1
创建 Mapper 类 UserInfoMapper
import mydlq.club.example.model.db1.UserInfo;import org.apache.ibatis.annotations.Mapper;
@Mapperpublic interface UserInfoMapper {
/** * 根据主键查找数据 * * @param id 主键ID * @return 数据 */ UserInfo selectByPrimaryKey(Integer id);
}位置 /resources/mappers/db1 创建 Mapper 文件 UserInfoMapper.xml
<?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="mydlq.club.example.dao.db1.UserInfoMapper">
<resultMap id="BaseResultMap" type="mydlq.club.example.model.db1.UserInfo"> <id column="id" jdbcType="INTEGER" property="id" /> <result column="name" jdbcType="VARCHAR" property="name" /> <result column="gender" jdbcType="VARCHAR" property="gender" /> <result column="age" jdbcType="TINYINT" property="age" /> </resultMap>
<select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap"> SELECT id, `name`, gender, age FROM t_user_info WHERE id = #{id,jdbcType=INTEGER} </select>
</mapper>数据库2
创建 Mapper 类 AccountMapper
import mydlq.club.example.model.db2.Account;import org.apache.ibatis.annotations.Mapper;
@Mapperpublic interface AccountMapper { /** * 根据主键查找数据 * * @param id 主键ID * @return 数据 */ Account selectByPrimaryKey(Integer id);
}位置 /resources/mappers/db2 创建 Mapper 文件 AccountMapper.xml
<?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="mydlq.club.example.dao.db2.AccountMapper">
<resultMap id="BaseResultMap" type="mydlq.club.example.model.db2.Account"> <id column="id" jdbcType="BIGINT" property="id" /> <result column="username" jdbcType="VARCHAR" property="username" /> <result column="password" jdbcType="VARCHAR" property="password" /> <result column="create_time" jdbcType="TIMESTAMP" property="createTime" /> <result column="update_time" jdbcType="TIMESTAMP" property="updateTime" /> </resultMap>
<select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap"> SELECT id, username, "password", create_time, update_time FROM t_account WHERE id = #{id,jdbcType=INTEGER} </select>
</mapper>5、创建测试的 Service 类
创建测试的 Service 类,类中创建 findUserInfo() 和 findAccount() 两个方法,并且:
- 在
findUserInfo()方法上加上@DS("db1")注解,表示调用方法时切换数据库一获取数据。 - 在
findAccount()方法上加上@DS("db2")注解,表示调用方法时切换数据库二获取数据。
这样我们使用 findUserInfo() 和 findAccount() 方法时就会从不同的数据库中获取数据。
import com.baomidou.dynamic.datasource.annotation.DS;import mydlq.club.example.dao.db1.UserInfoMapper;import mydlq.club.example.dao.db2.AccountMapper;import mydlq.club.example.model.db1.UserInfo;import mydlq.club.example.model.db2.Account;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;
@Servicepublic class TestService {
@Autowired private AccountMapper accountMapper; @Autowired private UserInfoMapper userInfoMapper;
/** * 通过 @DS 注解指定使用 DB1 数据库,查询用户信息 * @param id 主键ID * @return 用户信息 */ @DS("db1") public UserInfo findUserInfo(Integer id){ return userInfoMapper.selectByPrimaryKey(id); }
/** * 通过 @DS 注解指定使用 DB2 数据库,查询账户数据 * @param id 主键ID * @return 账户数据 */ @DS("db2") public Account findAccount(Integer id){ return accountMapper.selectByPrimaryKey(id); }
}7、创建测试的 Controller 类
创建测试用的 Controller 类,里面调用上面创建的 Service 类中查询两个数据库数据的方法 findUserInfo() 和 findAccount() 来获取两个库中的数据,并提供测试访问的接口 /test,方便后续进行测试。
import mydlq.club.example.model.AccountUserInfo;import mydlq.club.example.model.db1.UserInfo;import mydlq.club.example.model.db2.Account;import mydlq.club.example.service.TestService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;
@RestControllerpublic class TestController {
@Autowired private TestService testService;
/** * 同时查询两个库的数据 * @return 查询结果 */ @GetMapping("/test") public Object hello1() { // 查询账户数据 Account account = testService.findAccount(1); // 查询用户信息 UserInfo userInfo = testService.findUserInfo(1); // 创建响应对象 AccountUserInfo accountUserInfo = new AccountUserInfo(); accountUserInfo.setAccount(account); accountUserInfo.setUserInfo(userInfo); return accountUserInfo; }
}8、创建 SpringBoot 启动类
创建 SpringBoot 启动类:
import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplicationpublic class Application {
public static void main(String[] args) { SpringApplication.run(Application.class, args); }
}9、访问接口进行测试
输入地址:http://localhost:8080/test 测试是否能够从”mysql”和”postgresql”两个数据库中获取数据,结果如下:
{ "userInfo": { "id": 1, "name": "小豆丁", "gender": "0", "age": 22 }, "account": { "id": 1, "username": "mydlq", "password": "123456", "createTime": "2021-01-01T00:00:00", "updateTime": "2021-01-01T00:00:00" }}根据上面测试结果可知,使用 dynamic-datasource 实现多源数据库是非常方便的,能够正常从两个不同类型的数据库中读取数据,并且使用插件的注解方式要比使用配置数据源方式简单的多。
八、使用多源数据库切换注意事项
使用时虽然能对多个数据库操作,不过设计到事务时,例如使用 Spring 的 @Transactional 注解添加到事务的方法上,如果方法中只操作一个数据库是可以正常执行事务的。如果操作多个数据库,事务是无法正常执行的。如果想对多个数据库进行事务,需要使用分布式事务来解决该问题。
