第 51 章:数据源与事务
学习目标
- 配置多数据源
- 掌握
@Transactional用法 - 避开事务失效的坑
一、HikariCP(默认连接池)
💡 连接池干嘛的? 每次新建连接要走 TCP + 鉴权,开销很大。连接池预先开一批连接放着,业务要查 SQL 时借一个、用完还回去,避免每次都重建。
Spring Boot 2.x 默认用 HikariCP,业界最快,spring-boot-starter-jdbc / data-jpa 都自带,不用额外引依赖。
yaml
spring:
datasource:
url: jdbc:mysql://localhost:3306/myapp
username: root
password: xxx
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
maximum-pool-size: 20 # 最大连接数(默认 10)
minimum-idle: 5 # 最小空闲
connection-timeout: 30000 # 拿连接超时 30s
idle-timeout: 600000 # 空闲 10min 回收
max-lifetime: 1800000 # 连接最长 30min(必须小于 DB 的 wait_timeout)
pool-name: MyHikariPool连接数怎么算?
maximum-pool-size = (核心数 * 2) + 有效硬盘数数据库 1000 并发,100 个连接够用,再多会拖垮 DB。
二、多数据源
💡 多数据源 = 一个应用连多个数据库。常见两种场景:业务拆分(用户库 + 订单库)、读写分离(主库写 + 从库读)。配置思路:两个 DataSource bean +
@MapperScan把 mapper 包绑到对应数据库,代码看着一样,底层走不同库。
场景:用户库 + 订单库分开。
java
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.user")
public DataSource userDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("spring.datasource.order")
public DataSource orderDataSource() {
return DataSourceBuilder.create().build();
}
}yaml
spring:
datasource:
user:
url: jdbc:mysql://localhost:3306/user_db
username: root
password: xxx
order:
url: jdbc:mysql://localhost:3306/order_db
username: root
password: xxxMapper 扫描不同包:
java
@Configuration
@MapperScan(basePackages = "com.example.mapper.user", sqlSessionTemplateRef = "userSqlSessionTemplate")
public class UserDataSourceConfig { }
@Configuration
@MapperScan(basePackages = "com.example.mapper.order", sqlSessionTemplateRef = "orderSqlSessionTemplate")
public class OrderDataSourceConfig { }⚠️ 坑 1:多数据源下事务默认不跨数据源。
@Transactional只对一个数据库生效,你这边orderMapper.insert()提交成功、那边userMapper.insert()失败回滚,两边独立,会出现"订单成功 + 用户失败"的不一致。要跨库事务考虑:
- 业务规避(优先):下单成功发消息,异步扣库存 / 积分,各管各的本地事务
- 分布式事务(复杂场景):Seata,见后续章节
三、@Transactional 基础
java
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderMapper orderMapper;
private final StockMapper stockMapper;
@Transactional // 整个方法在一个事务里
public void createOrder(Order order) {
orderMapper.insert(order);
stockMapper.decrement(order.getSkuId(), order.getCount()); // 一起成功 / 一起失败
}
}7 种传播行为(最常用的 3 种):
| 传播行为 | 含义 |
|---|---|
REQUIRED(默认) | 有事务就加入,没有就新建 |
REQUIRES_NEW | 无论有没有,都开新事务(挂起当前) |
NESTED | 嵌套事务(子事务回滚不影响父) |
java
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void logOperation() { // 无论如何,日志独立提交
logMapper.insert(log);
}⚠️ 坑 2:
@Transactional抛RuntimeException才回滚。受检异常不回滚!
java
@Transactional
public void create() throws IOException { // 抛 IOException 不回滚!
orderMapper.insert(order);
throw new IOException(); // ❌ 不会回滚
}
// ✅ 显式指定
@Transactional(rollbackFor = Exception.class)
public void create() throws Exception { }四、事务失效的 6 个坑
坑 1:自调用
java
@Service
public class OrderService {
@Transactional
public void createOrder() { }
public void createOrderWithLog() {
this.createOrder(); // ❌ 走 this,不走代理,事务失效
}
}
// ✅ 注入自己
@Service
public class OrderService {
@Autowired private OrderService self; // 自己注入自己
public void createOrderWithLog() {
self.createOrder(); // 走代理,生效
}
}坑 2:异常被吞
java
@Transactional
public void create() {
try {
orderMapper.insert(order);
} catch (Exception e) {
log.error("失败", e); // ❌ 异常被吞,事务不会回滚
}
}坑 3:方法不是 public
java
@Transactional
private void create() { } // ❌ private 不生效坑 4:数据库引擎不支持
MySQL MyISAM 没有事务,必须用 InnoDB。
坑 5:没被 Spring 管理
java
// ❌ new 出来的对象
new UserService().create(); // 事务不生效坑 6:多线程调用
java
@Transactional
public void create() {
new Thread(() -> orderMapper.insert(order)).start(); // ❌ 子线程不受当前事务控制
}五、事务隔离级别
MySQL 默认 REPEATABLE READ,Spring 默认跟随数据库。
java
@Transactional(isolation = Isolation.READ_COMMITTED)
public void read() { }| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| READ_UNCOMMITTED | ✅ | ✅ | ✅ |
| READ_COMMITTED | ❌ | ✅ | ✅ |
| REPEATABLE_READ | ❌ | ❌ | ✅ |
| SERIALIZABLE | ❌ | ❌ | ❌ |
生产推荐
READ_COMMITTED(行锁 + 间隙锁少,幻读用业务补偿)。
六、本章小结
| 要点 | 关键 |
|---|---|
| HikariCP | 最大连接数 = (核心数 × 2) + 硬盘数 |
| 事务 | @Transactional |
| 失效坑 | 自调用/异常被吞/private/非 Spring 管理 |
| 跨库事务 | 业务规避 / Seata |
动手练习
- 写
@Transactional转账(扣 A 加 B),故意抛异常验证回滚 - 故意自调用,看事务是否生效
下一章:第 52 章:数据库设计与索引 →