面试官:@Transactional(readOnly=true) 有什么用?还有谁不会?! ...

宝塔山  金牌会员 | 2024-1-1 14:44:55 | 来自手机 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 887|帖子 887|积分 2661

原文翻译自:https://medium.com
今天,我想谈谈 Spring 提供的@Transactional(readOnly = true)。
之所以聊这个是因为我公司项目的代码里有很多@Transactional(readOnly = true),用过的同学都说@Transactional(readOnly = true)提高了性能。先思考以下几点:

  • @Transactional(readOnly = true)是如何工作的,为什么使用它可以提高性能?
  • 当我们使用 JPA 时,是否应该总是将@Transactional(readOnly = true)添加到服务层的只读方法?有什么取舍吗?
在开始之前,我们使用 Hibernate 来实现 JPA。
推荐一个开源免费的 Spring Boot 实战项目:
https://github.com/javastacks/spring-boot-best-practice
1、@Transactional(readOnly = true)是如何工作的,为什么使用它可以提高性能?
首先,让我们看一下事务接口。
  1. /**
  2. * A boolean flag that can be set to {@code true} if the transaction is
  3. * effectively read-only, allowing for corresponding optimizations at runtime.
  4. * <p>Defaults to {@code false}.
  5. * <p>This just serves as a hint for the actual transaction subsystem;
  6. * it will <i>not necessarily</i> cause failure of write access attempts.
  7. * A transaction manager which cannot interpret the read-only hint will
  8. * <i>not</i> throw an exception when asked for a read-only transaction
  9. * but rather silently ignore the hint.
  10. * @see org.springframework.transaction.interceptor.TransactionAttribute#isReadOnly()
  11. * @see org.springframework.transaction.support.TransactionSynchronizationManager#isCurrentTransactionReadOnly()
  12. */
  13. boolean readOnly() default false;
复制代码
我们可以看到 readOnly = true 选项允许优化。事务管理器将使用只读选项作为提示。让我们看看用于事务管理器的JpaTransactionManager。
  1. @Override
  2. protected void doBegin(Object transaction, TransactionDefinition definition) {
  3. JpaTransactionObject txObject = (JpaTransactionObject) transaction;
  4.   // .
  5.   // Delegate to JpaDialect for actual transaction begin.
  6.   int timeoutToUse = determineTimeout(definition);
  7.   Object transactionData = getJpaDialect().beginTransaction(em,
  8.     new JpaTransactionDefinition(definition, timeoutToUse, txObject.isNewEntityManagerHolder()));
  9.   //...
  10. }
复制代码
在JpaTransactionManager中,doBegin方法委托JpaDialect来开始实际的事务,并在JpaDialect中调用beginTransaction。让我们来看看HibernateJpaDialect类。
  1. @Override
  2. public Object beginTransaction(EntityManager entityManager, TransactionDefinition definition)
  3.   throws PersistenceException, SQLException, TransactionException {
  4.    // ...
  5.    // Adapt flush mode and store previous isolation level, if any.
  6.    FlushMode previousFlushMode = prepareFlushMode(session, definition.isReadOnly());
  7.    if (definition instanceof ResourceTransactionDefinition &&
  8.      ((ResourceTransactionDefinition) definition).isLocalResource()) {
  9.     // As of 5.1, we explicitly optimize for a transaction-local EntityManager,
  10.     // aligned with native HibernateTransactionManager behavior.
  11.     previousFlushMode = null;
  12.     if (definition.isReadOnly()) {
  13.      session.setDefaultReadOnly(true);
  14.     }
  15.    }
  16.    // ...
  17. }
  18. protected FlushMode prepareFlushMode(Session session, boolean readOnly) throws PersistenceException {
  19.     FlushMode flushMode = session.getHibernateFlushMode();
  20.     if (readOnly) {
  21.      // We should suppress flushing for a read-only transaction.
  22.      if (!flushMode.equals(FlushMode.MANUAL)) {
  23.       session.setHibernateFlushMode(Flusode.MANUAL);
  24.       return flushMode;
  25.      }
  26.     }
  27.     else {
  28.      // We need AUTO or COMMIT for a non-read-only transaction.
  29.      if (flushMode.lessThan(FlushMode.COMMIT)) {
  30.       session.setHibernateFlushMode(FlushMode.AUTO);
  31.       return flushMode;
  32.      }
  33.     }
  34.     // No FlushMode change needed...
  35.     return null;
  36. }
复制代码
在JpaDialect中,我们可以看到JpaDialect使用只读选项准备刷新模式。当 readOnly = true 时, JpaDialect 禁止刷新。此外,您还可以看到,在准备刷新模式后,session.setDefaultReadOnly(true)将session的readOnly属性设置为true。
  1. /**
  2. * Change the default for entities and proxies loaded into this session
  3. * from modifiable to read-only mode, or from modifiable to read-only mode.
  4. *
  5. * Read-only entities are not dirty-checked and snapshots of persistent
  6. * state are not maintained. Read-only entities can be modified, but
  7. * changes are not persisted.
  8. *
  9. * When a proxy is initialized, the loaded entity will have the same
  10. * read-only/modifiable setting as the uninitialized
  11. * proxy has, regardless of the session's current setting.
  12. *
  13. * To change the read-only/modifiable setting for a particular entity
  14. * or proxy that is already in this session:
  15. * @see Session#setReadOnly(Object,boolean)
  16. *
  17. * To override this session's read-only/modifiable setting for entities
  18. * and proxies loaded by a Query:
  19. * @see Query#setReadOnly(boolean)
  20. *
  21. * @param readOnly true, the default for loaded entities/proxies is read-only;
  22. *                 false, the default for loaded entities/proxies is modifiable
  23. */
  24. void setDefaultReadOnly(boolean readOnly);
复制代码
在Session接口中,通过将readOnly属性设置为true,将不会对只读实体进行脏检查,也不会维护持久状态的快照。此外,只读实体的更改也不会持久化。
总而言之,这些是在 Hibernate 中使用@Transactional(readOnly = true)所得到的结果

  • 性能改进:只读实体不进行脏检查
  • 节省内存:不维护持久状态的快照
  • 数据一致性:只读实体的更改不会持久化
  • 当我们使用主从或读写副本集(或集群)时,@Transactional(readOnly = true)使我们能够连接到只读数据库


2、当我们使用 JPA 时,是否应该总是将@Transactional(readOnly = true)添加到服务层的只读方法?有什么取舍吗?

我看到,当使用@Transactional(readOnly = true)时,我们可以有很多优势。但是,将@Transactional(readOnly = true)添加到服务层的只读方法是否合适?以下是我担心的事情

  • 无限制地使用事务可能会导致数据库死锁、性能和吞吐量下降。
  • 由于一个事务占用一个DB连接,所以@Transactional(readOnly = true)添加到Service层的方法可能会导致DB连接饥饿。
推荐一个开源免费的 Spring Boot 实战项目:
https://github.com/javastacks/spring-boot-best-practice
第一个问题很难重现,所以我做了一些测试来检查第二个问题。
  1. @Transactional(readOnly = true)
  2. public List<UserDto> transactionalReadOnlyOnService(){
  3.     List<UserDto> userDtos = userRepository.findAll().stream()
  4.             .map(userMapper::toDto)
  5.             .toList();
  6.     timeSleepAndPrintConnection();
  7.     return userDtos;
  8. }
  9. public List<UserDto> transactionalReadOnlyOnRepository(){
  10.     List<UserDto> userDtos = userRepository.findAll().stream()
  11.             .map(userMapper::toDto)
  12.             .toList();
  13.     timeSleepAndPrintConnection();
  14.     return userDtos;
  15. }
复制代码
我在服务层测试了两个方法,一个是@Transactional(readOnly = true),另一个是存储库层中的@Transactional (readOnly = true)(在 SimpleJpaRepository 中,它是 Jpa Respitory 的默认实现,在类的顶部有@Transformational(ready Only),因此 findAll()方法在默认情况下有@transactional(read only = True))。
我从DB中获取userInfo并保持线程5秒钟,然后检查该方法何时释放连接。
结果如下:
对于服务层方法中的@Transactional(readOnly = true),
  1. activeConnections:0, IdleConnections:10, TotalConnections:10
  2. start transactionalReadOnlyOnService!!
  3. Hibernate:
  4.     select
  5.         u1_0.id,
  6.         u1_0.email,
  7.         u1_0.name,
  8.         u1_0.profile_file_name
  9.     from
  10.         users u1_0
  11. activeConnections:1, IdleConnections:9, TotalConnections:10
  12. activeConnections:1, IdleConnections:9, TotalConnections:10
  13. activeConnections:1, IdleConnections:9, TotalConnections:10
  14. activeConnections:1, IdleConnections:9, TotalConnections:10
  15. activeConnections:1, IdleConnections:9, TotalConnections:10
  16. end transactionalReadOnlyOnService!!
  17. activeConnections:0, IdleConnections:10, TotalConnections:10
复制代码
对于存储库层方法中的@Transactional(readOnly = true),
  1. activeConnections:0, IdleConnections:10, TotalConnections:10
  2. start transactionalReadOnlyOnRepository!!
  3. Hibernate:
  4.     select
  5.         u1_0.id,
  6.         u1_0.email,
  7.         u1_0.name,
  8.         u1_0.profile_file_name
  9.     from
  10.         users u1_0
  11. activeConnections:0, IdleConnections:10, TotalConnections:10
  12. activeConnections:0, IdleConnections:10, TotalConnections:10
  13. activeConnections:0, IdleConnections:10, TotalConnections:10
  14. activeConnections:0, IdleConnections:10, TotalConnections:10
  15. activeConnections:0, IdleConnections:10, TotalConnections:10
  16. end transactionalReadOnlyOnRepository!!
  17. activeConnections:0, IdleConnections:10, TotalConnections:10
复制代码
正如您所看到的,@Transactional(readOnly = true)一旦查询结果到达,存储库层就会释放连接。
然而,@Transactional(readOnly = true)在服务层的方法中直到服务层的方法结束才释放连接。
因此,当服务层的方法有需要大量时间的逻辑时要小心,因为它可以长时间持有数据库连接,这可能会导致数据库连接匮乏。
3、回顾
很明显,@Transactional(readOnly = true)有很多优点。

  • 性能改进:只读实体不进行脏检查
  • 节省内存:不维护持久状态的快照
  • 数据一致性:只读实体的更改不会持久化
  • 当我们使用主从或读写副本集(或集群)时,@Transactional(readOnly = true)使我们能够连接到只读数据库
但是,您还应该记住,@Transactional(readOnly = true)在服务层的方法中可能会导致数据库死锁、性能低下和数据库连接匮乏!
当您需要将只读查询仅仅作为一个事务执行时,请毫不犹豫选择的在服务层的方法中使用@Transactional(readOnly = true),如果你的服务层的方法中有大量其他逻辑方法时,就要做取舍了!
近期热文推荐:
1.1,000+ 道 Java面试题及答案整理(2022最新版)
2.劲爆!Java 协程要来了。。。
3.Spring Boot 2.x 教程,太全了!
4.别再写满屏的爆爆爆炸类了,试试装饰器模式,这才是优雅的方式!!
5.《Java开发手册(嵩山版)》最新发布,速速下载!
觉得不错,别忘了随手点赞+转发哦!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

宝塔山

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表