
mybatis 多次查询是否需加 @transactional,取决于数据一致性、连接复用与隔离性需求;纯读场景可不加,但若要求同事务内连接复用、结果一致性或避免幻读/不可重复读,则应显式声明 readonly 事务。
mybatis 多次查询是否需加 @transactional,取决于数据一致性、连接复用与隔离性需求;纯读场景可不加,但若要求同事务内连接复用、结果一致性或避免幻读/不可重复读,则应显式声明 readonly 事务。
在 Spring + MyBatis 架构中,@Transactional 的作用远不止保障“写操作”的原子性——它本质是事务边界的声明机制,控制着数据库连接的生命周期、事务上下文的传播、以及隔离级别的语义约束。即使方法中仅包含多次查询(如 getUserOrders 先查用户、再查订单),是否添加 @Transactional(readOnly = true) 仍需结合业务语义综合判断。
✅ 为什么「不加」也可能正常工作?
默认情况下(无 @Transactional),Spring 的 DataSourceTransactionManager 不会开启事务,每次 MyBatis 执行 SQL 都会通过 DataSource 获取一个新连接(或从连接池借用),执行完即释放(自动提交模式)。这意味着:
- ✅ 无事务开销:避免 MVCC 版本维护、undo log 生成、锁竞争等;
- ✅ 高并发友好:各查询独立,互不影响;
- ❌ 但存在风险:两次查询可能跨越不同数据库快照——例如:用户刚被软删除(
is_deleted=1),而订单表尚未同步更新状态;或中间有其他事务提交了新订单,导致第二次查询结果“多出一条”,出现幻读。
✅ 为什么「加 @Transactional(readOnly = true)」更专业?
当显式添加该注解后,Spring 会:
- 复用同一个数据库连接(Connection)贯穿整个方法调用;
- 将两次查询纳入同一事务上下文,受统一隔离级别约束(如 MySQL 默认
REPEATABLE_READ); - 向数据库声明“只读意图”,部分数据库(如 PostgreSQL、Oracle)可据此启用优化(如跳过 redo 日志写入、使用只读快照);
- 触发 MyBatis 一级缓存复用(同一 SqlSession 内
findByUsername与findByUserId若参数命中缓存,可避免二次 DB 查询)。
@Service
@Transactional(readOnly = true) // 推荐:类级声明,简洁且统一
public class UserService {
@Autowired private UserMapper userMapper;
@Autowired private OrderMapper orderMapper;
public List<Order> getUserOrders(String username) {
User user = userMapper.findByUsername(username); // 查询1:获取用户
if (user == null) {
throw new UsernameNotFoundException("User not found: " + username);
}
return orderMapper.findByUserId(user.getId()); // 查询2:基于同一事务连接查订单
}
}⚠️ 注意:
readOnly = true并非强制数据库只读(它不阻止INSERT/UPDATE语句执行),而是向 Spring 事务管理器和底层 JDBC 驱动传递语义提示。若方法内误写更新逻辑,仍会报错(如 HikariCP 可配置connection.isReadOnly()检查)。
? 关键影响对比表
| 场景 | 无 @Transactional
|
@Transactional(readOnly = true) |
|---|---|---|
| 数据库连接 | 每次查询新建/借用独立连接 | 方法内复用同一连接 |
| 事务隔离 | 无事务上下文,各查询使用各自快照 | 同一事务快照,满足 REPEATABLE_READ 等语义 |
| MyBatis 一级缓存 | 跨查询不生效(不同 SqlSession) | 同一 SqlSession 内缓存可复用 |
| 并发一致性 | 可能出现不可重复读、幻读 | 保证事务内读一致性(取决于隔离级别) |
| 性能开销 | 极低(无事务管理) | 轻量(无锁/无 undo log,但有连接持有成本) |
? 常见误区与最佳实践
- 误区1:“只读方法一定不能加事务” → 错。金融对账、风控校验等强一致性读场景,必须加事务保证两次查询看到相同数据版本。
-
误区2:“加了
readOnly=true就绝对安全” → 错。若数据库引擎不支持事务(如 MyISAM)、或隔离级别被降级为READ_UNCOMMITTED,仍可能脏读。 -
最佳实践:
- 对核心业务读方法(尤其含多表关联、多次查询依赖同一状态),统一使用
@Service @Transactional(readOnly = true)类级注解; - 在
application.yml中显式配置默认隔离级别(如spring.jpa.properties.hibernate.connection.isolation=TRANSACTION_REPEATABLE_READ); - 配合 MyBatis 日志(
logging.level.org.apache.ibatis=DEBUG)验证是否真正复用连接与缓存; - 若发现一级缓存干扰业务(如修改 List 后二次查询返回脏数据),可通过
@Options(flushCache = FlushCachePolicy.TRUE)或手动sqlSession.clearCache()控制。
- 对核心业务读方法(尤其含多表关联、多次查询依赖同一状态),统一使用
总之,@Transactional 不是“写操作专属标签”,而是 Spring 数据一致性的基础设施。是否启用,应由业务对数据确定性的要求决定,而非简单按 CRUD 类型划分。

















