为什么需要在 Hibernate 中使用 Transaction 进行只读操作?

为什么需要在 Hibernate 中使用 Transaction 进行只读操作?

下面的事务是否在 DB 中放置了一个锁?

从 DB 获取的示例代码:

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()吗?

100592 次浏览

用于读取的事务可能看起来确实很奇怪,在这种情况下,人们通常不会标记事务的方法。但是 JDBC 无论如何都会创建事务,只是如果没有显式设置不同的选项,它将在 autocommit=true中工作。但是,对交易进行只读标记有一些实际原因:

对数据库的影响

  1. 只读标志可以让 DBMS 优化此类事务或并行运行的事务。
  2. Having a transaction that spans multiple SELECT statements guarantees proper Isolation for levels starting from Repeatable Read or Snapshot (e.g. see PostgreSQL 的可重复读取). Otherwise 2 SELECT statements could see inconsistent picture if another transaction commits in parallel. This isn't relevant when using Read Committed.

Impact on ORM

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

对体系结构和清晰代码的影响

There is no guarantee that your method doesn't write into the database. If you mark method as @Transactional(readonly=true), you'll dictate whether it's 事实上 possible to write into DB in scope of this transaction. If your architecture is cumbersome and some team members may choose to put modification query where it's not expected, this flag will point you to the problematic place.

事务确实在数据库上设置了锁ーー好的数据库引擎以合理的方式处理并发锁ーー而且在只读使用时非常有用,可以确保 其他事务不会添加使您的视图不一致的数据。你总是想要一个事务(虽然有时调优隔离级别是合理的,但最好不要这样做) ; 如果你从来没有在事务期间写入数据库,提交和回滚事务的结果是一样的(而且非常便宜)。

Now, if you're lucky and your queries against the DB are such that the ORM always maps them to single SQL queries, you can get away without explicit transactions, relying on the DB's built-in autocommit behavior, but ORMs are relatively complex systems so it isn't at all safe to rely on such behavior unless you go to a lot more work checking what the implementation actually does. Writing the explicit transaction boundaries in is far easier to get right (especially if you can do it with AOP or some similar ORM-driven technique; from Java 7 onwards try-with-resources could be used too I suppose).

无论只读还是不读,数据库都必须保持对结果集的跟踪,因为其他数据库客户端可能希望编写更改结果集的数据。

我曾经看到过错误的程序杀死庞大的数据库系统,因为它们只读数据,但从不提交,迫使事务日志增长,因为数据库不能在 COMMIT 或 ROLLback 之前释放事务数据,即使客户端在几个小时内什么都不做。

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

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

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

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

JPA 不对读操作强制执行事务。只有写操作才会抛出 TransactionRequiredException,以防您忘记启动事务上下文。尽管如此,即使对于只读事务,声明事务边界也总是更好(在 Spring @Transactional中,可以标记只读事务,这对性能有很大好处)。