读取数据时需要进行数据库事务吗?

59

当我尝试从数据库中读取数据,至少使用以下代码

((Session)em.getDelegate()).createCriteria()

时,会抛出一个异常,说没有事务。

当我添加以下注释:

@Transactional(
    value = SomeClass.TRANSACTIONAL_MANAGER, 
    propagation = Propagation.SUPPORTS, 
    readOnly = true
)

它工作得很好。

但是,由于读取会以每秒数百万次的速度发生以访问和读取数据,我希望确保我们的环境不会不必要地被拥塞。

如果没有,创建一个只读的 Propagation.Supports 事务的成本是多少?

在使用Spring的情况下,我能不能创建一个没有事务的Hibernate Criteria查询?


可能是 https://dev59.com/u3A65IYBdhLWcg3wogKe 的重复问题。 - OO7
是的,确实是这样。我不小心评论了错误的帖子。现在你应该能看到我的评论了。但我已经接受了这个事实。 - mjs
2个回答

198

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

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

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

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

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

现在,如果您使用声明性事务边界(例如@Transactional),则需要确保数据库连接获取被延迟直到有JDBC语句需要执行。在JTA中,这是默认行为。当使用RESOURCE_LOCAL时,您需要设置hibernate.connection.provider_disables_autocommit配置属性,并确保底层连接池设置为禁用自动提交模式。


1
请看这个链接:ibm.com/developerworks/library/j-ts1。它说:“更好的方法是,在执行读操作时完全避免使用@Transactional注释,如清单10所示:”。因此我得出的印象是不需要使用它。考虑到这是关于执行读取SQL语句的操作,我不认为有必要增加额外的负担。 - mjs
5
其他系统在非常只读的单语句事务中使用自动提交模式。问题出现在您的服务方法中有一个逻辑事务中有多个语句的情况下。 - Vlad Mihalcea
1
连接开销是一件事,但更重要的是ACID中的D保证 - 在自动提交模式下每个语句提交后!- 数据库必须保证您的更改命中磁盘(而不仅仅是磁盘缓存!)。在打开的事务中,已读取的数据库块上的操作仅在DMBS内存中执行,直到执行提交为止。顺便说一句,回滚是简单的过程,即丢弃脏内存块,几乎没有成本。 - rgielen
4
数据库仅在提交后同步重做日志,而不是在检查点期间刷新的整个缓冲池。回滚并非一定是免费的。在Oracle和MySQL上,需要从回滚段重新构建元组。此外,索引也必须重新平衡。 - Vlad Mihalcea
5
即使在自动提交模式下,你使用关系型数据库(RDBMS)时总是需要事务,即使你没有明确声明。自动提交会使用默认的隔离级别,并将语句包装在一个事务中。 - Vlad Mihalcea
显示剩余13条评论

4
根据我在J2EE中使用JPA实现的经验,为了保证CRUD操作的安全性并确保数据完整性,总是需要一个事务管理器来执行回滚。
企业应用程序使用不同的资源来保存数据和发送消息,如数据库或消息队列。如果我们想按顺序查询这些资源,并在出现问题时取消整个操作,则必须将此查询放入工作单元中,以便整体执行。
你可以将其定义为:
  • by using related annotations (as shown in the questions); in this way, the container loads automatically the transaction manager for a given persistence context;

  • by injecting manually the transaction manager, as follows:

    public class sample {
    
        @PersistenceContext
        EntityManager em;
    
        // Injected transaction manager
        @Inject
        UserTransaction utx;
    
        private static final String[] GAME_TITLES = {
            "Super Mario Brothers",
            "Mario Kart",
            "F-Zero"
        };
    
        private void clearData() throws Exception {
            utx.begin();
            em.joinTransaction();
            System.out.println("Dumping old records...");
            em.createQuery("delete from Game").executeUpdate();
            utx.commit();
        }
    
        private void insertData() throws Exception {
            utx.begin();
            em.joinTransaction();
            System.out.println("Inserting records...");
            for (String title : GAME_TITLES) {
                Game game = new Game(title);
                em.persist(game);
            }
            utx.commit();
            // clear the persistence context (first-level cache)
            em.clear();
        }
    
        // ...
    
    }
    
作为JPA规范的实现,Spring Data 可能会采用相同的方法。
您可以通过阅读以下文章了解更多信息:Java_Persistence/Transactions

最初的背景是在读取数据。 - Simon Logic

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