🎉🎉《Spring Boot实战案例合集》目前已更新177个案例,我们将持续不断的更新。文末有电子书目录。
💪💪永久更新承诺
我们郑重承诺,所有订阅合集的粉丝都将享受永久免费的后续更新服务。
💌💌如何获取
订阅我们的合集《点我订阅》,并通过私信联系我们,我们将第一时间将电子书发送给您。
环境:SpringBoot3.4.2
1. 简介
@Transactional 是 Spring 中用于声明式事务管理的核心注解,旨在简化数据库事务操作。在传统的编程式事务中,我们需手动编写事务的开启、提交或回滚代码,而通过 @Transactional 注解将事务逻辑与业务代码解耦。只需在方法或类上添加该注解,Spring 会基于 AOP(面向切面编程)自动拦截调用,在方法执行前开启事务,执行后根据异常情况提交或回滚。这种设计显著提升了代码的可读性和可维护性。
但如果滥用@Transactional,会对系统性能产生显著负面影响,主要体现在以下几个方面:
过度使用会导致事务范围过大,延长数据库连接占用时间,增加锁竞争和死锁风险
不必要的细粒度事务会引发频繁的提交和回滚操作,加重数据库负载
在非关键数据操作或只读场景中滥用事务,会无谓消耗系统资源,降低整体吞吐量。
本篇文章会介绍基于 JPA 和 JDBC 时,@Transactional 注解对查询性能的影响。
纯查询到底要不要事务?
2.1 准备环境
配置文件
spring:datasource:driverClassName: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/batchusername: rootpassword: 123123type: com.zaxxer.hikari.HikariDataSourcehikari:minimumIdle: 10maximumPoolSize: 10---spring:jpa:generateDdl: falsehibernate:ddlAuto: updateopenInView: trueshow-sql: false
创建实体对象
(name = "o_user")public class User {(strategy = GenerationType.IDENTITY)private Integer id ;private String name ;private Integer age ;private String phone ;private String sex ;// getters, setters}
准备接口
public ResponseEntity<?> query() {return ResponseEntity.ok(this.userService.queryUser()) ;}
准备数据(500w)
2.1 使用Repository查询测试
测试1,不使用@Transactional注解
private final UserRepository userRepository ;public User queryUser() {return this.userRepository.findById(4888888).orElse(null) ;}
使用JMeter测试结果如下:
吞吐量平均:9700
测试2,使用@Transactional
public User queryUser() {}
使用JMeter测试结果如下:
吞吐量平均:12700
这是否打破了你原有的认知呢?按照常规理论,使用 @Transactional 注解通常会使性能变差,然而当前呈现的数据却表明,使用该注解后性能反而有所提升。
测试3,使用只读事务
(readOnly = true)public User queryUser() {}
使用JMeter测试结果如下:
吞吐量平均:9300
该结果与不使用注解相差不大。
思考:为什么使用了@Transactional注解反而性能更高呢?欢迎大家留言讨论。
2.3 使用JDBC查询
测试1,不使用@Transactional注解
private final JdbcTemplate jdbcTemplate ;public User queryUser() {return this.jdbcTemplate.queryForObject("select id, name, age, phone, sex from o_user where id = 4888888", new RowMapper<User>() {public User mapRow(ResultSet rs, int rowNum) throws SQLException {User user = new User() ;user.setId(rs.getInt("id")) ;user.setAge(rs.getInt("age")) ;user.setName(rs.getString("name")) ;user.setPhone(rs.getString("phone")) ;user.setSex(rs.getString("sex")) ;return user ;}}) ;}
JMeter测试结果:
吞吐量平均:25000
JPA是简单了,代价就是性能太差了。
测试2,使用@Transactional注解
public User queryUser() {}
JMeter测试结果:
吞吐量平均:13100
这倒是符合我们的预期,使用了@Transactional注解性能明显下降。
测试3,使用只读事务
(readOnly = true)public User queryUser() {}
JMeter测试结果:
同样符合预期,与读写事务差不多。
2.4 使用EntityManager查询
测试1,不使用@Transactional注解
private final EntityManager em ;public User queryUser() {return this.em.find(User.class, 4888888) ;}
JMeter测试结果:
吞吐量平均:24000
测试2,使用@Transactional注解
public User queryUser() {return this.em.find(User.class, 4888888) ;}
JMeter测试结果:
吞吐量平均:13000
测试3,使用只读事务
(readOnly = true)public User queryUser() {}
JMeter测试结果:
吞吐量平均:9800
2.5 性能柱状图
2.6 查询使用事务总结
保证一致性:在一个事务中,所有查询看到的是同一时间点的数据快照(取决于隔离级别),避免了中途数据被其他事务修改导致的不一致
性能优化:Spring 提供 @Transactional(readOnly = true),明确标记为只读事务。这可以让底层数据库(如 Oracle、MySQL InnoDB)进行优化,例如启用只读快照、减少锁竞争等。
连接复用:在一个事务中的多个操作可以复用同一个数据库连接,减少连接创建/释放开销。
与写操作兼容:如果将来该查询方法被包含在一个更大的写事务中,有 @Transactional 可以无缝集成。
如下多个查询使用事务保证了同一时间点的数据:
private final UserRepository userRepository ;private final OrderRepository orderRepository ;public UserInfoDto getUserInfo(Long userId) {User user = userRepository.findById(userId);List<Order> orders = orderRepository.findByUserId(userId);return new UserInfoDto(user, orders);}
JSON处理不再头疼!Jackson Tree模型解锁JSON操作的开挂模式
8种测试方法,让 Spring Boot 接口稳定性提升 200%
高级开发!自定义注解100行代码实现 @Resource/@Autowired 注入功能
性能优化!3种方法优化@Transactional长事务问题,方法三性能提升4倍(不修改业务代码)
性能狂飙!Spring Boot 基于注解的 8 个缓存应用技巧
Spring AI + LangGraph4j 多智能体开发,太强大了!
真心强大!Spring AI + MCP 智能体工具动态更新
高级开发!一个注解动态控制Controller接口,支持实时更新


