3.7 事务与多数据源
DB.ds 管理数据源注册和线程内切换,DB.tx 管理本地事务。两者职责独立:切换数据源不会自动开启事务。
添加数据源
DataSourceProperty prop = new DataSourceProperty();
prop.setName("slave");
prop.setUrl("jdbc:mysql://slave-host:3306/db_slave");
prop.setUsername("root");
prop.setPassword("slave123");
prop.setDriverClassName("com.mysql.cj.jdbc.Driver");
DB.ds.setDataSource(prop);
切换数据源
// 切换到从库
List<User> users = DB.ds.use("slave", () ->
DB.pojo.selectWrapper(User.class)
.eq(User::getStatus, 1)
.queryBeanList()
);
// Runnable 重载
DB.ds.use("slave", () -> DB.pojo.insert(user));
设置默认数据源
DB.ds.setDefaultDataSource(dataSource);
删除数据源
DB.ds.removeDataSource("slave");
删除前应确认没有正在使用该数据源的请求。可以用 DB.ds.getAllDataSourceNames() 查看当前注册名称,用 DB.ds.testConnection(property) 在不注册的情况下测试连接参数。
编程式事务
默认数据源事务:
DB.tx.run(() -> {
DB.pojo.insert(order);
DB.pojo.insert(orderItem);
});
带返回值:
Long orderId = DB.tx.run(() -> {
DB.pojo.insert(order);
return order.getId();
});
指定数据源:
DB.tx.run("slave", () -> {
DB.jdbc.execute("UPDATE task SET status = ? WHERE id = ?", 1, taskId);
});
DB.tx.run(name, ...) 会在命名数据源作用域内建立本地事务;它不是跨数据源事务。
切换与事务组合
DB.ds.use("report", () ->
DB.tx.run(() -> {
DB.pojo.insert(report);
DB.pojo.insert(reportDetail);
})
);
更直接的写法是:
DB.tx.run("report", () -> {
DB.pojo.insert(report);
DB.pojo.insert(reportDetail);
});
同一线程中 DB.ds.use 会在回调结束后恢复先前的数据源上下文。不要把数据库操作交给新线程后仍假设原上下文有效。
事务边界
DB.tx只承诺单数据源本地事务;- 同一数据源已有事务时,适配器采用 REQUIRED 式加入语义;
- 当前公共事务抽象不提供
REQUIRES_NEW或NESTED; - 切换到另一数据源意味着另一条连接,无法与当前事务原子提交;
- 回调抛出异常时事务回滚,异常会按框架约定继续向外传播;
- 批量操作需要全有或全无时,必须显式置于事务中;
- 数据源上下文和连接绑定是线程本地的,不自动跨线程传播。
框架声明式事务分别见 Spring Boot 完整集成 和 Solon 完整集成。
读写分离建议
@Service
public class UserService {
// 写操作 → 主库
public void save(User user) {
DB.tx.run("master", () -> DB.pojo.insert(user));
}
// 读操作 → 从库
public User getById(Long id) {
return DB.ds.use("slave", () ->
DB.pojo.selectWrapper(User.class).eq(User::getId, id).queryBean()
);
}
}
上例只是显式路由示意,不是自动读写分离。真实项目还需要处理从库延迟、故障转移、连接池监控以及读写一致性。