专栏名称: Java基基
一个苦练基本功的 Java 公众号,所以取名 Java 基基
目录
相关文章推荐
OFweek维科网  ·  宁德时代连签三大重磅合作! ·  昨天  
OFweek维科网  ·  谋求战略转型?这一激光大厂现重大并购! ·  3 天前  
半导体行业联盟  ·  英伟达财报来了!AI芯片霸主:营收大增78% ... ·  2 天前  
半导体行业联盟  ·  中国英伟达!沐曦回应裁员! ·  昨天  
51好读  ›  专栏  ›  Java基基

@Transactional(readOnly=true)真的是提高性能的灵丹妙药吗?

Java基基  · 公众号  ·  · 2024-07-14 18:01

正文

👉 这是一个或许对你有用 的社群

🐱 一对一交流/面试小册/简历优化/求职解惑,欢迎加入 芋道快速开发平台 知识星球。 下面是星球提供的部分资料:

👉 这是一个或许对你有用的开源项目

国产 Star 破 10w+ 的开源项目,前端包括管理后台 + 微信小程序,后端支持单体和微服务架构。

功能涵盖 RBAC 权限、SaaS 多租户、数据权限、商城、支付、工作流、大屏报表、微信公众号等等功能:

  • Boot 仓库:https://gitee.com/zhijiantianya/ruoyi-vue-pro
  • Cloud 仓库:https://gitee.com/zhijiantianya/yudao-cloud
  • 视频教程:https://doc.iocoder.cn
【国内首批】支持 JDK 21 + SpringBoot 3.2.2、JDK 8 + Spring Boot 2.7.18 双版本

来源:medium.com 丛林(Java知音)


今天,我想谈谈 Spring 提供的 @Transactional(readOnly = true)

之所以聊这个是因为我公司项目的代码里有很多 @Transactional(readOnly = true) ,用过的同学都说 @Transactional(readOnly = true) 提高了性能。先思考以下几点:

  • @Transactional(readOnly = true) 是如何工作的,为什么使用它可以提高性能?
  • 当我们使用 JPA 时,是否应该总是将 @Transactional(readOnly = true) 添加到服务层的只读方法?有什么取舍吗?

在开始之前,我们使用 Hibernate 来实现 JPA。

1. @Transactional(readOnly = true) 是如何工作的,为什么使用它可以提高性能?

首先,让我们看一下事务接口。

/**
* A boolean flag that can be set to {@code true} if the transaction is
* effectively read-only, allowing for corresponding optimizations at runtime.

Defaults to {@code false}.

This just serves as a hint for the actual transaction subsystem;
* it will not necessarily cause failure of write access attempts.
* A transaction manager which cannot interpret the read-only hint will
not throw an exception when asked for a read-only transaction
* but rather silently ignore the hint.
@see org.springframework.transaction.interceptor.TransactionAttribute#isReadOnly()
@see org.springframework.transaction.support.TransactionSynchronizationManager#isCurrentTransactionReadOnly()
*/


boolean readOnly() default false;

我们可以看到 readOnly = true 选项允许优化。事务管理器将使用只读选项作为提示。让我们看看用于事务管理器的 JpaTransactionManager

@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
 JpaTransactionObject txObject = (JpaTransactionObject) transaction;
  // .
  // Delegate to JpaDialect for actual transaction begin.
  int timeoutToUse = determineTimeout(definition);
  Object transactionData = getJpaDialect().beginTransaction(em,
    new JpaTransactionDefinition(definition, timeoutToUse, txObject.isNewEntityManagerHolder()));
  //...
}

JpaTransactionManager 中,doBegin方法委托JpaDialect来开始实际的事务,并在JpaDialect中调用 beginTransaction 。让我们来看看 HibernateJpaDialect 类。

@Override
public Object beginTransaction(EntityManager entityManager, TransactionDefinition definition)
  throws PersistenceException, SQLException, TransactionException 
{
   // ...
   // Adapt flush mode and store previous isolation level, if any.
   FlushMode previousFlushMode = prepareFlushMode(session, definition.isReadOnly());
   if (definition instanceof ResourceTransactionDefinition &&
     ((ResourceTransactionDefinition) definition).isLocalResource()) {
    // As of 5.1, we explicitly optimize for a transaction-local EntityManager,
    // aligned with native HibernateTransactionManager behavior.
    previousFlushMode = null;
    if (definition.isReadOnly()) {
     session.setDefaultReadOnly(true);
    }
   }
   // ...
}

protected FlushMode prepareFlushMode(Session session, boolean readOnly) throws PersistenceException {
    FlushMode flushMode = session.getHibernateFlushMode();
    if (readOnly) {
     // We should suppress flushing for a read-only transaction.
     if (!flushMode.equals(FlushMode.MANUAL)) {
      session.setHibernateFlushMode(Flusode.MANUAL);
      return flushMode;
     }
    }
    else {
     // We need AUTO or COMMIT for a non-read-only transaction.
     if (flushMode.lessThan(FlushMode.COMMIT)) {
      session.setHibernateFlushMode(FlushMode.AUTO);
      return flushMode;
     }
    }
    // No FlushMode change needed...
    return null;
}

在JpaDialect中,我们可以看到JpaDialect使用只读选项准备刷新模式。当 readOnly = true 时, JpaDialect 禁止刷新。此外,您还可以看到,在准备刷新模式后, session.setDefaultReadOnly(true) 将session的readOnly属性设置为true。

/**
 * Change the default for entities and proxies loaded into this session
 * from modifiable to read-only mode, or from modifiable to read-only mode.
 *
 * Read-only entities are not dirty-checked and snapshots of persistent
 * state are not maintained. Read-only entities can be modified, but
 * changes are not persisted.
 *
 * When a proxy is initialized, the loaded entity will have the same
 * read-only/modifiable setting as the uninitialized
 * proxy has, regardless of the session's current setting.
 *
 * To change the read-only/modifiable setting for a particular entity
 * or proxy that is already in this session:
 * @see Session#setReadOnly(Object,boolean)
 *
 * To override this session's read-only/modifiable setting for entities
 * and proxies loaded by a Query:
 * @see Query#setReadOnly(boolean)
 *
 * @param readOnly true, the default for loaded entities/proxies is read-only;
 *                 false, the default for loaded entities/proxies is modifiable
 */

void setDefaultReadOnly(boolean readOnly);

在Session接口中,通过将readOnly属性设置为true,将不会对只读实体进行脏检查,也不会维护持久状态的快照。此外,只读实体的更改也不会持久化。

总而言之,这些是在 Hibernate 中使用 @Transactional(readOnly = true) 所得到的结果

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

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/ruoyi-vue-pro
  • 视频教程:https://doc.iocoder.cn/video/

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

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

  1. 无限制地使用事务可能会导致数据库死锁、性能和吞吐量下降。
  2. 由于一个事务占用一个DB连接,所以 @Transactional(readOnly = true) 添加到Service层的方法可能会导致DB连接饥饿。

第一个问题很难重现,所以我做了一些测试来检查第二个问题。

@Transactional(readOnly = true)
public List transactionalReadOnlyOnService(){
    List userDtos = userRepository.findAll().stream()
            .map(userMapper::toDto)
            .toList();
    timeSleepAndPrintConnection();
    return userDtos;
}

public List transactionalReadOnlyOnRepository(){
    List userDtos = userRepository.findAll().stream()
            .map(userMapper::toDto)
            .toList();
    timeSleepAndPrintConnection();
    return userDtos;
}

我在服务层测试了两个方法,一个是 @Transactional(readOnly = true) ,另一个是存储库层中的 @Transactional (readOnly = true) (在 SimpleJpaRepository 中,它是 Jpa Respitory 的默认实现,在类的顶部有 @Transformational(ready Only) ,因此 findAll() 方法在默认情况下有 @transactional(read only = True) )。

我从DB中获取userInfo并保持线程5秒钟,然后检查该方法何时释放连接。

结果如下:

对于服务层方法中的 @Transactional(readOnly = true)

activeConnections:0, IdleConnections:10, TotalConnections:10
start transactionalReadOnlyOnService!!
Hibernate: 
    select
        u1_0.id,
        u1_0.email,
        u1_0.name,
        u1_0.profile_file_name 
    from
        users u1_0
activeConnections:1, IdleConnections:9, TotalConnections:10
activeConnections:1, IdleConnections:9, TotalConnections:10
activeConnections:1, IdleConnections:9, TotalConnections:10
activeConnections:1, IdleConnections:9, TotalConnections:10
activeConnections:1, IdleConnections:9, TotalConnections:10
end transactionalReadOnlyOnService!!
activeConnections:0, IdleConnections:10, TotalConnections:10

对于存储库层方法中的 @Transactional(readOnly = true)

activeConnections:0, IdleConnections:10, TotalConnections:10
start transactionalReadOnlyOnRepository!!
Hibernate: 
    select
        u1_0.id,
        u1_0.email,
        u1_0.name,
        u1_0.profile_file_name 
    from
        users u1_0
activeConnections:0, IdleConnections:10, TotalConnections:10
activeConnections:0, IdleConnections:10






请到「今天看啥」查看全文


推荐文章
OFweek维科网  ·  宁德时代连签三大重磅合作!
昨天
半导体行业联盟  ·  中国英伟达!沐曦回应裁员!
昨天
经典短篇阅读小组  ·  早安 |千万不要跟悲观的人做朋友!
8 年前
经典短篇阅读小组  ·  早安 |千万不要跟悲观的人做朋友!
8 年前
程序猿  ·  MySQL SQL语句优化技巧
8 年前
拳皇98终极之战OL  ·  【同人小说】合力战无界(73)
7 年前