为什么在Hibernate中进行只读操作时需要事务支持?

129

为什么在Hibernate中进行只读操作时需要事务?

以下事务是否会在数据库中放置锁定?

从数据库获取的示例代码:

Transaction tx = HibernateUtil.getCurrentSession().beginTransaction(); // why begin transaction?
//readonly operation here

tx.commit() // why tx.commit? I don't want to write anything

我可以使用session.close()替代tx.commit()吗?

9
数据库本身需要事务处理。您可以在此处阅读有关自动提交模式的信息:https://community.jboss.org/wiki/Non-transactionalDataAccessAndTheAuto-commitMode - Bhesh Gurung
@BheshGurung 我想我们只需要在写操作时使用事务。 - user93796
4
你有没有在链接中阅读“揭穿自动提交神话”这一部分? - Bhesh Gurung
4个回答

156

读取事务的操作可能看起来很奇怪,通常人们在这种情况下不会为事务标记方法。但是JDBC将仍然创建事务,只是如果没有显式设置不同选项,它将在autocommit=true模式下工作。但是有实际理由标记只读事务:

对数据库的影响

  1. 只读标志可以让DBMS优化这样的事务或并行运行的事务。
  2. 跨多个SELECT语句的事务可以保证从可重复读或快照级别开始的正确隔离(例如,请参见PostgreSQL的可重复读)。否则,如果另一个事务并行提交,则2个SELECT语句可能会看到不一致的结果。在使用读取已提交时,这不相关。

对ORM的影响

  1. 如果您不显式开始/完成事务,则ORM可能会导致不可预测的结果。例如Hibernate会在第一个语句之前打开事务,但不会完成它。因此,连接将以未完成的事务返回到连接池中。那么会发生什么呢?JDBC保持沉默,因此这取决于实现:MySQL、PostgreSQL驱动程序会回滚这种事务,Oracle提交它。请注意,这也可以在连接池级别上进行配置,例如C3P0提供了这样的选项,默认情况下回滚。
  2. Spring会在只读事务的情况下设置FlushMode=MANUAL,这会导致其他优化,例如不需要进行脏检查。这可能会带来巨大的性能收益,具体取决于加载的对象数量。

对架构和代码清晰度的影响

没有保证您的方法不会写入数据库。如果将方法标记为@Transactional(readonly=true),则您将规定在此事务范围内是否“实际上”可以写入DB。如果您的架构繁琐,某些团队成员可能会选择把修改查询放在意料之外的地方,那么这个标志将指向有问题的地方。


1
谢谢回复。我正在使用 Hibernate(原生 Hibernate,而不是 JPA)。但是 Hibernate 强制我在进行任何数据库操作之前启动一个事务。如果我不启动一个事务,它会抛出错误,说没有活动的事务。我可以将事务标记为只读吗?如果可以,怎么做呢? - user93796
1
readonly标志实际上是为连接设置的,而不是为事务本身设置的,您不能像那样通过Hibernate访问它。您需要使用Spring事务支持,并使用注释或基于XML的事务配置。通过这样做,Spring接管了连接和事务管理,而不是Hibernate。 - Stanislav Bashkyrtsev
1
讲解得非常清楚!Mark Richards 的另一篇文章很好地解释了 Transactional 注释中只读标志的陷阱 - https://www.ibm.com/developerworks/java/library/j-ts1/index.html。 - Mahesh

66

所有数据库语句都在物理事务的上下文中执行,即使我们没有显式声明事务边界(例如 BEGIN、COMMIT、ROLLBACK)。

如果您不显式声明事务边界,则每个语句都必须在单独的事务中执行(自动提交模式)。这甚至可能导致每条语句打开和关闭一个连接,除非您的环境可以处理每个线程绑定一个连接。

将服务声明为 @Transactional 将为整个事务持续时间提供一个连接,并且所有语句将使用该单个隔离连接。这比一开始不使用显式事务要好得多。

在大型应用程序中,您可能有许多并发请求,减少数据库连接获取请求率肯定会提高整体应用程序性能。

JPA 不会对读操作强制执行事务。只有写入操作才会在您忘记启动事务上下文时引发 TransactionRequiredException。尽管如此,即使是只读事务也最好声明事务边界(在 Spring 中,@Transactional 允许您标记只读事务,这具有很大的性能优势)。


1
"...那么每个语句都必须在单独的事务中执行。"容器不会自动进行批处理吗?" - Arash
1
谢谢Vlad。我读了你的一些文章,它们真的很有用。 - Arash
这个https://dev59.com/s1sW5IYBdhLWcg3wmoJj似乎有点与您的说法相矛盾,即只读事务具有很大的性能优势。至少在Hibernate的上下文中似乎并非如此。 - nimai
1
不,它并不是。那只是关于设置只读,而我的问题是关于避免自动提交。 - Vlad Mihalcea

20

事务确实会对数据库进行锁定——好的数据库引擎以明智的方式处理并发锁定——并且在只读使用时非常有用,以确保没有其他事务添加数据,使您的视图不一致。您总是需要一个事务(尽管有时调整隔离级别是合理的,但最好不要从一开始就这样做);如果您在事务期间从未写入DB,则提交和回滚事务都将得出相同的结果(并且非常便宜)。

现在,如果您很幸运,对DB的查询是ORM始终将它们映射到单个SQL查询,那么您可以不需要显式事务,并依赖于DB的内置自动提交行为,但ORM是相对复杂的系统,因此除非您更加努力地检查实现操作,否则不安全。编写显式事务边界要容易得多(特别是如果您可以使用AOP或某些类似于ORM驱动的技术来执行它;从Java 7开始,也可以使用try-with-resources)。


16
无论您是否只是读取数据,数据库仍必须跟踪您的结果集,因为其他数据库客户端可能需要写入数据来更改您的结果集。
我曾见过有问题的程序杀死了大型数据库系统,因为它们只是读取数据,但从未提交,强制事务日志增长,因为在客户端几小时没有任何操作时,数据库无法释放事务数据,除非进行COMMIT或ROLLBACK。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接