java - 为什么我在Hibernate中需要Transaction才能进行只读操作?




database transactions database-connection (5)

为什么我在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()吗?


Answers

实际上,您可能有理由将事务标记为只读。

  1. 阅读交易看起来确实很奇怪,在这种情况下,人们通常不会为交易标记方法。 但是JDBC无论如何都会创建事务,如果没有明确设置不同的选项,它只会在autocommit=true工作。
  2. 但是无法保证您的方法不会写入数据库。 如果将方法标记为@Transactional(readonly=true) ,Spring会将JDBC事务设置为只读模式,因此您将决定是否可以在此事务的范围内写入DB。 如果您的架构非常繁琐,并且某些团队成员可能不遵循合理的路径,那么此标志会指向您有问题的地方。
  3. 此外,只读事务可以由DB优化,但这当然是特定于数据库的。 例如,MySQL仅在InnoDB中添加了对5.6.4版本的支持。
  4. 如果您不是直接使用JDBC,而是使用ORM,则可能会出现问题。 例如,Hibernate社区表示在事务之外工作可能会导致不可预测的行为。 这是因为Hibernate将打开事务,但它不会自己关闭它,因此连接将返回到连接池,而事务未被提交。 那么会发生什么? JDBC保持沉默,因此这是特定于实现的(MySQL回滚事务,Oracle afair提交它)。 这也可以在连接池级别配置(例如,C3P0为您提供了这样的选项,默认情况下为回滚)。
  5. 另一方面,就Hibernate而言,在只读事务的情况下,Spring将FlushMode设置为MANUAL,这导致其他优化,例如不需要脏检查。
  6. 您可能希望显式覆盖或设置事务隔离级别。 这会影响读取事务,因为您确实或不想读取未提交的更改,暴露于幻像读取等。

总结一下 - 你可以双向进行,但你需要了解后果。


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

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

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

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

JPA不会对读取操作强制执行事务。 只有在忘记启动事务上下文时,才会写入最终抛出事务所需的异常。 然而,即使对于只读事务,声明事务边界总是更好(在Spring @Transactional允许您标记只读事务,这具有很大的性能优势)。


无论您是否只读取 - 数据库必须仍然跟踪您的结果集,因为其他数据库客户端可能希望写入会更改结果集的数据。

我已经看到了错误的程序来杀死庞大的数据库系统,因为它们只是读取数据,但从不提交,迫使事务日志增长,因为DB无法在COMMIT或ROLLBACK之前释放事务数据,即使客户端什么也没做用了几个小时。


事务确实将锁定放在数据库上 - 良好的数据库引擎以合理的方式处理并发锁定 - 并且对于只读使用非常有用,以确保没有其他事务添加使您的视图不一致的数据。 你总是想要一个交易(虽然有时调整隔离级别是合理的,但最好不要这样做); 如果您在交易期间从未写过数据库,那么提交和回滚交易的工作都是相同的(并且非常便宜)。

现在,如果你很幸运,你对DB的查询是这样的,ORM总是将它们映射到单个SQL查询,你可以在没有显式事务的情况下逃脱,依赖于DB的内置自动提交行为,但ORM是相对复杂的系统所以依靠这种行为是不安全的,除非你去做更多的工作来检查实现实际上做了什么。 编写显式事务边界更容易正确(特别是如果你可以使用AOP或类似的ORM驱动技术;从Java 7开始,我认为也可以使用try-with-resources)。


我不明白为什么foo的内容取决于你从哪个类访问它。

基本上这是类型初始化的问题。 Sub初始化时, foo的值设置为"bar" 。 但是,在您的Testing类中,对Sub.foo的引用实际上被编译为对Super.foo的引用,因此它不会最终初始化Sub ,因此foo永远不会成为"bar"

如果您将测试代码更改为:

public class Testing {
    public static void main (String[] args) {
        Sub.main(args);
        System.out.println(Super.foo);
        System.out.println(Sub.foo);
        System.out.println(Super.foo);
    }
}

然后它将打印出“bar”四次,因为第一个语句将强制Sub被初始化,这将改变foo的值。 这根本不是从哪里进入的问题。







java database hibernate transactions database-connection