- 文档
- 教程
- 后端
【免费下载链接】springboot-guide
SpringBoot2.0+从入门到实战!
本文以 springboot-guide 仓库中的多数据源示例项目为核心,完整讲解在 Spring Boot 2.x 环境下通过 Java 配置类为 MyBatis 配置两个独立数据源的全过程。你将学会如何在一个应用内同时连接两个数据库、为不同数据源分别创建 SqlSessionFactory 与事务管理器,并借助@MapperScan、@Qualifier、@Primary把不同包下的 Mapper 精确路由到对应的数据源。文中所有代码均可在仓库 source-code/basis/springboot-mybatis/springboot-mybatis-mutipleDatasource 下找到并直接运行验证。
一、为什么需要多数据源
在上一篇 《Spring Boot 整合 MyBatis》 中,我们使用的是单数据源:Spring Boot 的自动配置只需要你在application.properties里给出数据库连接参数,就可以开箱即用。但实际业务中,单数据源往往很快遇到瓶颈:
- 业务量大后做数据库拆分:按业务域把用户库、订单库、日志库拆到不同 MySQL 实例;
- 引入其他数据库:读库与写库分离、接入第三方业务库等场景;
- 微服务化过程中的过渡方案:在服务拆分完成前,先让一个应用同时访问多个库。
此时 Spring Boot 的自动配置就"不够用"了——它默认只认spring.datasource.*这一组配置。我们需要手工声明多个数据源 Bean,并分别为每个数据源装配一套 MyBatis 的 SqlSessionFactory 和 SqlSessionTemplate,这正是本文要解决的核心问题。
二、开发前的准备
2.1 环境参数
| 项目 | 参数 |
|---|---|
| 开发工具 | IDEA |
| 基础工具 | Maven + JDK8 |
| 所用技术 | Spring Boot + MyBatis |
| 数据库 | MySQL |
| Spring Boot 版本 | 2.1.0.RELEASE |
需要特别提醒:Spring Boot 2.0 之后多数据源配置存在一些"小坑"(见下文 2.5 节),版本不一致可能导致启动报错,请务必留意。
仓库中对应模块的pom.xml声明了spring-boot-starter-parent版本为2.1.0.RELEASE、java.version为1.8,与本文环境一致,可参考 pom.xml。
2.2 创建工程与项目结构
创建一个基础的 Spring Boot 项目(创建步骤与单数据源文章一致,这里不再赘述)。多数据源的关键在于按数据库拆分包结构:
springboot-mybatis-mutipleDatasource ├── src/main/java/top/snailclimb │ ├── MainApplication.java # 启动类 │ ├── bean │ │ ├── User.java # 用户实体(对应 db1 的 user 表) │ │ └── Money.java # 工资详情实体(对应 db2 的 money 表) │ ├── config │ │ ├── DataSource1Config.java # 数据源 1 的配置类 │ │ └── DataSource2Config.java # 数据源 2 的配置类 │ ├── controller │ │ ├── UserController.java # 访问 db1 的接口 │ │ └── MoneyController.java # 访问 db2 的接口 │ ├── db1 │ │ ├── dao/UserDao.java # db1 的 Mapper 接口 │ │ └── service/UserService.java │ └── db2 │ ├── dao/MoneyDao.java # db2 的 Mapper 接口 │ └── service/MoneyService.java └── src/main/resources/application.properties两个数据库的 Dao 与 Service 分属top.snailclimb.db1、top.snailclimb.db2两个包,这是后续用@MapperScan精确路由的前提。
2.3 创建两个数据库和数据表
本文共创建两个数据库erp与erp2,分别在两个库中建user用户表与money工资详情表。user表只有 4 个字段(id、姓名、年龄、余额),其中"余额 money"字段是为了便于演示事务管理;money表同样只有 4 个字段(id、基本工资、奖金、惩罚金)。
用户表(位于 erp 库):
CREATE TABLE `user` ( `id` int(13) NOT NULL AUTO_INCREMENT COMMENT '主键', `name` varchar(33) DEFAULT NULL COMMENT '姓名', `age` int(3) DEFAULT NULL COMMENT '年龄', `money` double DEFAULT NULL COMMENT '账户余额', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8工资详情表(位于 erp2 库):
CREATE TABLE `money` ( `id` int(33) NOT NULL AUTO_INCREMENT COMMENT '主键', `basic` int(33) DEFAULT NULL COMMENT '基本工资', `reward` int(33) DEFAULT NULL COMMENT '奖金', `punishment` int(33) DEFAULT NULL COMMENT '惩罚金', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf82.4 配置 pom.xml 依赖
整合 Spring Boot 与 MyBatis 需要引入mybatis-spring-boot-starter,使用 MySQL 则引入mysql-connector-java(scope 为 runtime),另外加上 web 与 test 依赖:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>2.5 配置 application.properties:Spring Boot 2.x 的两个坑
多数据源配置与单数据源最大的差异在配置键名上。Spring Boot 1.0 时代,数据源参数写成spring.datasource.url和spring.datasource.driverClassName;升级到 2.0 之后必须改为spring.datasource.jdbc-url和spring.datasource.driver-class-name,否则连接数据库时会抛出如下异常:
### Error querying database. Cause: java.lang.IllegalArgumentException: jdbcUrl is required with driverClassName.另一个坑是:在 2.0.2+ 版本中,datasource 后面需要显式加上hikari前缀(Spring Boot 2.x 默认使用 HikariCP 连接池),否则同样可能报错。
下面是仓库 application.properties 中的真实配置(注意文件保存为 GBK 编码,若复制后出现乱码请改用 UTF-8 重存):
server.port=8335 # 配置第一个数据源 spring.datasource.hikari.db1.jdbc-url=jdbc:mysql://127.0.0.1:3306/erp?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=GMT%2B8 spring.datasource.hikari.db1.username=root spring.datasource.hikari.db1.password=root spring.datasource.hikari.db1.driver-class-name=com.mysql.cj.jdbc.Driver # 配置第二个数据源 spring.datasource.hikari.db2.jdbc-url=jdbc:mysql://127.0.0.1:3306/erp2?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=GMT%2B8 spring.datasource.hikari.db2.username=root spring.datasource.hikari.db2.password=root spring.datasource.hikari.db2.driver-class-name=com.mysql.cj.jdbc.Driver对配置项逐条说明:
| 配置项 | 说明 |
|---|---|
server.port | 应用端口,此处为 8335 |
spring.datasource.hikari.db1.jdbc-url | 数据源 1 的 JDBC 连接地址,指向erp库 |
spring.datasource.hikari.db1.username/.password | 数据源 1 的账号密码(仓库示例为 root,请按自身环境修改) |
spring.datasource.hikari.db1.driver-class-name | MySQL 8 的驱动类com.mysql.cj.jdbc.Driver |
spring.datasource.hikari.db2.* | 数据源 2 的对应配置,指向erp2库 |
关于 JDBC URL 中的参数:serverTimezone=GMT%2B8表示东八区时区,%2B是+的 URL 编码。使用mysql-connector-java6.0+(本文为 8.x)时必须指定时区,否则可能报时区错误;驱动类也不能再写旧版的com.mysql.jdbc.Driver(该写法已废弃,控制台会提示The new driver class is com.mysql.cj.jdbc.Driver)。另外useSSL=true与characterEncoding=utf8分别控制 SSL 连接与字符集,可按实际环境调整。
2.6 创建实体 Bean
User.java对应 db1 的user表(仓库完整实现见 User.java):
public class User { private int id; private String name; private int age; private double money; // 此处省略 getter、setter 以及 toString 方法 }Money.java对应 db2 的money表(见 Money.java):
public class Money { private int basic; private int reward; private int punishment; // 此处省略 getter、setter 以及 toString 方法 }三、数据源配置(核心)
多数据源配置是本文最关键的部分,通过两个 Java 配置类分别声明数据源、SqlSessionFactory、事务管理器与 SqlSessionTemplate。整体思路是:每个数据源独立拥有一套DataSource → SqlSessionFactory → SqlSessionTemplate,并用@MapperScan把不同包的 Mapper 绑定到不同的 SqlSessionTemplate 上。
3.1 DataSource1Config:数据源 1 的配置
仓库完整源码见 DataSource1Config.java:
@Configuration @MapperScan(basePackages = "top.snailclimb.db1.dao", sqlSessionTemplateRef = "db1SqlSessionTemplate") public class DataSource1Config { /** * 生成数据源. @Primary 注解声明为默认数据源 */ @Bean(name = "db1DataSource") @ConfigurationProperties(prefix = "spring.datasource.hikari.db1") @Primary public DataSource testDataSource() { return DataSourceBuilder.create().build(); } /** * 创建 SqlSessionFactory */ @Bean(name = "db1SqlSessionFactory") @Primary public SqlSessionFactory testSqlSessionFactory(@Qualifier("db1DataSource") DataSource dataSource) throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); // bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/mapper/db1/*.xml")); return bean.getObject(); } /** * 配置事务管理 */ @Bean(name = "db1TransactionManager") @Primary public DataSourceTransactionManager testTransactionManager(@Qualifier("db1DataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } @Bean(name = "db1SqlSessionTemplate") @Primary public SqlSessionTemplate testSqlSessionTemplate(@Qualifier("db1SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception { return new SqlSessionTemplate(sqlSessionFactory); } }3.2 DataSource2Config:数据源 2 的配置
仓库完整源码见 DataSource2Config.java:
@Configuration @MapperScan(basePackages = "top.snailclimb.db2.dao", sqlSessionTemplateRef = "db2SqlSessionTemplate") public class DataSource2Config { @Bean(name = "db2DataSource") @ConfigurationProperties(prefix = "spring.datasource.hikari.db2") public DataSource testDataSource() { return DataSourceBuilder.create().build(); } @Bean(name = "db2SqlSessionFactory") public SqlSessionFactory testSqlSessionFactory(@Qualifier("db2DataSource") DataSource dataSource) throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); // bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/mapper/db2/*.xml")); return bean.getObject(); } @Bean(name = "db2TransactionManager") public DataSourceTransactionManager testTransactionManager(@Qualifier("db2DataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } @Bean(name = "db2SqlSessionTemplate") public SqlSessionTemplate testSqlSessionTemplate(@Qualifier("db2SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception { return new SqlSessionTemplate(sqlSessionFactory); } }3.3 三个关键注解与核心概念
对照两个配置类,逐点解释其作用:
@MapperScan(basePackages = "...", sqlSessionTemplateRef = "..."):指定要扫描的 Mapper 接口包,以及这些接口使用的SqlSessionTemplateBean 名称。数据源 1 扫描top.snailclimb.db1.dao并绑定db1SqlSessionTemplate,数据源 2 扫描top.snailclimb.db2.dao并绑定db2SqlSessionTemplate,从而让不同包的 Mapper 各自路由到对应数据库。SqlSessionTemplate是 MyBatis-Spring 的核心:它负责管理 MyBatis 的 SqlSession、调用 MyBatis 的 SQL 方法、翻译异常。它是线程安全的,可以被多个 DAO 共享使用。@ConfigurationProperties(prefix = "..."):将application.properties中对应前缀(如spring.datasource.hikari.db1)的配置绑定到由DataSourceBuilder.create().build()构建的 DataSource 上。注意两个前缀必须与配置文件严格一致。@Primary:数据源 1 的每个 Bean 都加上了@Primary,把它声明为默认数据源/默认 Bean,否则在自动注入或类型匹配时,Spring 会因为存在两个同类型 Bean 而报错(NoUniqueBeanDefinitionException)。从源码结构看,这是保证容器能正常启动的必要条件。@Qualifier("db1DataSource"):在方法参数上精确指定注入哪个 DataSource Bean,避免两个 DataSource 类型的歧义。@Bean(name = "..."):为每个 Bean 起唯一的名字(db1DataSource/db2DataSource、db1SqlSessionFactory/db2SqlSessionFactory等),供@Qualifier与@MapperScan引用。bean.setMapperLocations(...):这两行被注释掉了,因为本项目采用全注解方式开发,SQL 直接写在 Mapper 接口注解里,不需要加载 XML;如果某个数据源需要 XML Mapper,取消注释并按实际路径(如classpath:mybatis/mapper/db2/*.xml)配置即可。
四、Dao 层与 Service 层
Dao 与 Service 按数据库拆分为两个包:db1(user 相关)与db2(money 相关)。
4.1 Dao 层
Dao 层只做简单的查询演示。关键在于每个 Mapper 接口上都用@Qualifier标注它属于哪个 SqlSessionTemplate,这与配置类中@MapperScan的sqlSessionTemplateRef相呼应。仓库实现见 UserDao.java 与 MoneyDao.java:
// db1 包下的 UserDao @Qualifier("db1SqlSessionTemplate") public interface UserDao { /** * 通过名字查询用户信息 */ @Select("SELECT * FROM user WHERE name = #{name}") User findUserByName(String name); }// db2 包下的 MoneyDao @Qualifier("db2SqlSessionTemplate") public interface MoneyDao { /** * 通过id 查看工资详情 */ @Select("SELECT * FROM money WHERE id = #{id}") Money findMoneyById(@Param("id") int id); }这里使用的是全注解方式(@Select等),SQL 语句较短时比 XML 更清晰简洁;如果 SQL 较长或需要复杂动态拼装,可以在配置类中放开setMapperLocations并改用 XML 方式,两种方式可混用。
4.2 Service 层
Service 层非常简单,不包含复杂业务逻辑,只是封装 Dao 调用:
// db1 包下的 UserService(仓库源码见 source-code/basis/springboot-mybatis/springboot-mybatis-mutipleDatasource/src/main/java/top/snailclimb/db1/service/UserService.java) @Service public class UserService { @Autowired private UserDao userDao; /** * 根据名字查找用户 */ public User selectUserByName(String name) { return userDao.findUserByName(name); } }// db2 包下的 MoneyService @Service public class MoneyService { @Autowired private MoneyDao moneyDao; /** * 根据名字查找用户 */ public Money selectMoneyById(int id) { return moneyDao.findMoneyById(id); } }由于UserDao与MoneyDao处于不同的包、且分别被@MapperScan绑定到不同 SqlSessionTemplate,这里的@Autowired注入不会冲突。
五、Controller 层
Controller 层同样简单,一个接口对应一个数据源:
// UserController:查询 db1 中的用户 @RestController @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @RequestMapping("/query") public User testQuery() { return userService.selectUserByName("Daisy"); } }// MoneyController:查询 db2 中的工资详情 @RestController @RequestMapping("/money") public class MoneyController { @Autowired private MoneyService moneyService; @RequestMapping("/query") public Money testQuery() { return moneyService.selectMoneyById(1); } }仓库完整实现见 UserController.java 与 MoneyController.java。注意测试数据要预先插入:user表里要有名为Daisy的记录,money表里要有 id 为 1 的记录,接口才能查到数据。
六、启动类
启动类与单数据源时几乎一致,不需要额外改动(源码见 MainApplication.java):
//此注解表示SpringBoot启动类 @SpringBootApplication public class MainApplication { public static void main(String[] args) { SpringApplication.run(MainApplication.class, args); } }两个数据源的@MapperScan已写在各自的配置类上,因此启动类无需再重复扫描。从源码看,该模块还带有标准的上下文加载测试 SpringbootMybatisApplicationTests.java,可用mvn test验证容器能否正常装配两个数据源。
七、运行验证与常见问题
7.1 运行验证
- 保证 MySQL 中存在
erp、erp2两个库及对应表,并准备测试数据; - 按自身环境修改
application.properties中的账号密码; - 启动
MainApplication(端口 8335); - 浏览器或 curl 访问:
http://localhost:8335/user/query→ 返回 db1user表中名为 Daisy 的用户 JSON;http://localhost:8335/money/query→ 返回 db2money表中 id 为 1 的工资详情 JSON。
两个接口分别命中两个数据库,即说明多数据源配置成功。
7.2 常见问题与注意事项
jdbcUrl is required with driverClassName:Spring Boot 2.x 中必须使用jdbc-url与driver-class-name键名(见 2.5 节)。- 2.0.2+ 需要
hikari前缀:spring.datasource.hikari.db1.*中的hikari不能省略。 @Primary缺失导致启动失败:存在两个同类型 DataSource/SqlSessionFactory 时,必须用@Primary指定默认 Bean,否则 Spring 无法确定注入目标。@MapperScan的包路径与sqlSessionTemplateRef要配对:包扫描错、或sqlSessionTemplateRef名字拼错,Mapper 会被绑定到错误的数据源。- 多数据源事务:本文为每个数据源都单独配置了
DataSourceTransactionManager,@Transactional默认使用@Primary标注的 db1 事务管理器;跨库事务需要引入分布式事务方案(如 JTA),这超出本文范围,但属于多数据源架构中必须提前考虑的边界。 - 配置文件编码:仓库示例的
application.properties保存为 GBK 编码,建议统一为 UTF-8,避免中文注释乱码。
八、小结
本文基于 springboot-guide 仓库的 多数据源示例模块,完整演示了 Spring Boot 2.0+ 下 MyBatis 双数据源的配置套路:配置文件使用带hikari前缀的jdbc-url键名 → 每个数据源一套DataSource/SqlSessionFactory/SqlSessionTemplate/TransactionManagerBean →@MapperScan按包绑定 SqlSessionTemplate → Dao 接口用@Qualifier标注归属 → 默认数据源加@Primary。这套模式可以横向扩展为三数据源、四数据源,也是后续读写分离、分库分表等复杂数据架构的基础,建议读者结合仓库源码动手实践一遍。
- 文档
- 教程
- 后端
【免费下载链接】springboot-guide
SpringBoot2.0+从入门到实战!
相关推荐
BitCPM-CANN模型家族全面对比:8B/3B/1B/0.5B版本如何选择最适合你的方案?
BitCPM CANN模型家族全面对比:8B/3B/1B/0.5B版本如何选择最适合你的方案? BitCPM CANN是首个基于华为昇腾NPU原生构建的端到端1
Spring Boot 整合原生 MyBatis 实战:基于 mybatis-spring-boot-starter 的 ORM 示例解析
Spring Boot 整合原生 MyBatis 实战:基于 mybatis spring boot starter 的 ORM 示例解析 本篇技术指南以 sp
示例工程后端Talking Head Anime图像要求全解析:如何准备完美的256x256 PNG角色素材
Talking Head Anime图像要求全解析:如何准备完美的256x256 PNG角色素材 Talking Head Anime是一款能够从单张图像生成动
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考