# 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進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。