您好,登录后才能下订单哦!
# SpringBoot中怎么利用MyBatis-Plus配置多数据源
## 前言
在现代企业级应用开发中,多数据源的需求变得越来越普遍。无论是读写分离、分库分表,还是需要同时访问多个不同类型的数据库,多数据源配置都成为了开发者必须掌握的技能。MyBatis-Plus作为MyBatis的增强工具,在多数据源场景下提供了简洁高效的解决方案。本文将详细介绍在SpringBoot项目中如何利用MyBatis-Plus配置和使用多数据源。
## 一、多数据源应用场景
### 1.1 常见使用场景
- **读写分离**:主库负责写操作,从库负责读操作
- **分库分表**:数据水平拆分到不同数据库实例
- **多租户系统**:每个租户使用独立的数据源
- **异构数据库**:同时访问MySQL、Oracle等不同数据库
### 1.2 技术选型对比
| 方案 | 优点 | 缺点 |
|--------------------|-----------------------------|-----------------------------|
| 原生JDBC | 灵活性强 | 代码冗余,维护成本高 |
| Spring AbstractRoutingDataSource | 与Spring集成好 | 需要自行实现路由逻辑 |
| MyBatis-Plus多数据源 | 配置简单,功能完善 | 需要引入额外依赖 |
## 二、环境准备
### 2.1 项目依赖
```xml
<!-- Spring Boot Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MyBatis-Plus Starter -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<!-- 多数据源核心依赖 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.6.1</version>
</dependency>
<!-- 数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- 其他工具 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
准备两个数据库实例,本文示例使用:
- 主库:ds1
(端口3306)
- 从库:ds2
(端口3307)
application.yml
配置示例:
spring:
datasource:
dynamic:
primary: master # 设置默认数据源
strict: false # 是否严格匹配数据源
datasource:
master:
url: jdbc:mysql://localhost:3306/ds1?useSSL=false&serverTimezone=UTC
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
slave:
url: jdbc:mysql://localhost:3307/ds2?useSSL=false&serverTimezone=UTC
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
@Configuration
@MapperScan(basePackages = "com.example.mapper")
public class DataSourceConfig {
/**
* 无需@Bean注解,dynamic-datasource会自动配置
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
// 默认使用主数据源
@Override
public User getMasterUser(Long id) {
return userMapper.selectById(id);
}
// 指定从数据源
@DS("slave")
@Override
public User getSlaveUser(Long id) {
return userMapper.selectById(id);
}
}
public class DataSourceContextHolder {
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
public static void setDataSource(String dsName) {
CONTEXT_HOLDER.set(dsName);
}
public static String getDataSource() {
return CONTEXT_HOLDER.get();
}
public static void clearDataSource() {
CONTEXT_HOLDER.remove();
}
}
// 使用示例
public List<User> getAllUsers() {
try {
DataSourceContextHolder.setDataSource("slave");
return userMapper.selectList(null);
} finally {
DataSourceContextHolder.clearDataSource();
}
}
@Service
public class OrderService {
@DS("master")
@Transactional
public void createOrder(Order order) {
// 主库操作
orderMapper.insert(order);
// 切换数据源需要新开事务
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.execute(status -> {
try {
DataSourceContextHolder.setDataSource("slave");
// 从库操作
logMapper.insertLog(order);
return true;
} finally {
DataSourceContextHolder.clearDataSource();
}
});
}
}
@Autowired
private DynamicRoutingDataSource dynamicRoutingDataSource;
public void addNewDataSource(String dsName, DataSourceProperty property) {
DataSource dataSource = dynamicDataSourceCreator.createDataSource(property);
dynamicRoutingDataSource.addDataSource(dsName, dataSource);
}
public class TenantDataSourceSelector {
public static String determineCurrentLookupKey() {
// 从当前线程获取租户信息
String tenantId = TenantContext.getCurrentTenant();
return "tenant_" + tenantId;
}
}
// 配置类添加
@Bean
public AbstractRoutingDataSource multiTenantDataSource() {
DynamicRoutingDataSource ds = new DynamicRoutingDataSource();
ds.setDataSourceSelector(TenantDataSourceSelector::determineCurrentLookupKey);
return ds;
}
spring:
datasource:
dynamic:
datasource:
master:
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
@RestController
@RequestMapping("/datasource")
public class DataSourceMonitorController {
@Autowired
private DynamicRoutingDataSource dynamicRoutingDataSource;
@GetMapping("/stats")
public Map<String, Object> getDataSourceStats() {
Map<String, DataSource> dataSources = dynamicRoutingDataSource.getCurrentDataSources();
Map<String, Object> stats = new HashMap<>();
dataSources.forEach((name, ds) -> {
if (ds instanceof HikariDataSource) {
HikariDataSource hikari = (HikariDataSource) ds;
stats.put(name, hikari.getHikariPoolMXBean());
}
});
return stats;
}
}
现象:跨数据源事务无法回滚
解决方案:
1. 使用JTA分布式事务
2. 采用最终一致性方案
3. 拆分业务逻辑,避免跨库事务
排查步骤: 1. 检查@DS注解是否被AOP代理 2. 确认数据源名称拼写正确 3. 检查是否配置了strict模式
预防措施: 1. 确保每次操作后清理ThreadLocal 2. 使用try-finally代码块 3. 配置合理的连接超时时间
@Data
@TableName("t_user")
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private Integer age;
private String email;
}
public interface UserMapper extends BaseMapper<User> {
@DS("slave")
@Select("SELECT * FROM t_user WHERE age > #{age}")
List<User> selectUsersByAge(@Param("age") Integer age);
}
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/master/{id}")
public User getFromMaster(@PathVariable Long id) {
return userService.getMasterUser(id);
}
@GetMapping("/slave/{id}")
public User getFromSlave(@PathVariable Long id) {
return userService.getSlaveUser(id);
}
}
本文详细介绍了在SpringBoot项目中整合MyBatis-Plus实现多数据源配置的全过程,包括:
MyBatis-Plus的多数据源方案相比原生实现具有明显优势:
在实际项目中,建议根据具体业务场景选择合适的多数据源策略,并注意事务管理和连接泄漏问题。对于更复杂的分布式事务场景,可以考虑集成Seata等分布式事务框架。
完整示例代码已上传至GitHub: springboot-mybatisplus-multi-ds-demo “`
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。