【问题标题】:Grails Transactions and the SessionGrails 事务和会话
【发布时间】:2016-09-02 16:07:29
【问题描述】:

假设您在 Grails 2.5.5 应用程序中有以下控制器:

def index() {
        bookService.method()
        Book bako = Book.findById(4)
        System.out.println(bako.title);
}

在 bookService 内部(使用 Grails 默认事务管理),您有以下方法:

class BookService
    def method() {
        Book bako = Book.findById(4)
        System.out.println(bako.title);

        // MANUAL UPDATE OF DB HAPPENS HERE

        Book bako = Book.findById(4)
        System.out.println(bako.title);
    }
}

而且您的数据库确实有一本 ID 为 4 的书,名为“指环王”。

如果您随后在所有 System.out.println() 上设置断点,并且在执行第一个 findById 之后,您手动将该对象的标题编辑为“哈利波特与火”,我预计:

  • 在 bookService.method() 中的 findById(4) 都读取相同的值,毕竟它们是在同一个事务中隔离执行的,如果该事务在该状态下读取它,则第二个也应该读取它.
  • 在手动更新后,控制器中执行的 findById(4) 已经看到对象的新状态。毕竟,第一个事务已经提交,第二个查找,即使在服务之外完成,也应该创建一个新事务。

但是,输出将始终是处于与开始时相同状态的对象。

更新:输出为:

The Lord Of The Rings
The Lord Of The Rings
The Lord Of The Rings

在任何情况下,如果您修改控制器,以便:

def index() {
    bookService.method()
    Book.withNewTransaction {
        Book bako = Book.findById(4)
        System.out.println(bako.title);
    }

}

结果还是一样。

更新:输出为:

The Lord Of The Rings
The Lord Of The Rings
The Lord Of The Rings

仅当您将其修改为:

def index() {
    bookService.method()
    Book.withNewSession {
        Book bako = Book.findById(4)
        System.out.println(bako.title);
    }

}

是否会出现正确的行为。

更新:输出为:

The Lord Of The Rings
The Lord Of The Rings
Harry Potter and the Goblet of Fire

谁能解释一下原因:

  1. 仅仅在事务之外读取处于某种状态的对象是不足以读取新数据的;
  2. 即使强制执行新事务也不足以读取具有最新状态的对象;
  3. 为什么新会话允许我们这样做。

【问题讨论】:

    标签: grails grails-orm


    【解决方案1】:

    首先Book bako = Book.findById(4) findById 应在极少数情况下使用,请参阅Book.get(1L) Book.load(1L) Book.read(1L)

    当您本可以运行 .get 时,您正在启动查询以查找 id

    实际问题

    经过大量讨论,无论服务有多少是事务性的。如果您决定手动使用 mysql 更新数据库。您将打破休眠缓存。您可以尝试禁用 - first / second level cache。首先是如果您在域类映射中声明缓存。

    这确实是不明智的,并且会影响应用程序。现实情况是事务服务应该为您进行更新。如果需要手动更新数据库。停止应用更新/启动应用。就是这么简单

    我一直试图通过示例项目将您推到尝试此场景的路线上是有原因的。

    为什么? 因为它有助于回答任何猜测。我已经复制了您的示例项目,并在演示中添加了一些实际记录更新。 https://github.com/vahidhedayati/grails-transactions

    我还对您的版本提出了拉取请求,以便您可以合并它并在本地测试它。

    基本上flush:true 不需要。 .get(id) 不需要。

    从下面的结果中可以看出,在方法 1 的 .save() 之后的服务中,结果已更新。在控制器中,它在服务返回后使用 method() 返回正确的结果。

    -- transactions.Book : 1 1 added
    | Server running. Browse to http://localhost:8080/transactions
    2016-09-05 18:12:48,520 [http-bio-8080-exec-4] DEBUG hibernate.SQL  - select book0_.id as id1_0_0_, book0_.version as version2_0_0_, book0_.title as title3_0_0_ from book book0_ where book0_.id=?
    --method1: before update: ------------------------------> TITLE_SET_BY_BOOTSTRAP
    --method1 before get: ---------------------------------> New title from method1
    method1  after get: ----------------------------------> New title from method1
    2016-09-05 18:12:48,618 [http-bio-8080-exec-4] DEBUG hibernate.SQL  - update book set version=?, title=? where id=? and version=?
    After service1 call 1 New title from method1
    --method2  update: ------------------------------> New title from method1
    2016-09-05 18:12:48,687 [http-bio-8080-exec-4] DEBUG hibernate.SQL  - update book set version=?, title=? where id=? and version=?
    --method2 before get: --------------------------> New title from method2
    method2 after get:  ----------------------------> New title from method2
    After service call 2 New title from method2
    --method3 before update: ---------------------------> New title from method2
    2016-09-05 18:12:48,795 [http-bio-8080-exec-4] DEBUG hibernate.SQL  - update book set version=?, title=? where id=? and version=?
    --method3 updated before get: -------------------------> New title from method3
    --method3 after get: -----------------------------------> New title from method3
    After service call 3 New title from method3
    

    在回顾了用户问题并了解他们手动更新数据库记录之后,期望屏幕显示相同的结果。

    简而言之,如果您没有在应用程序中启用缓存,那么是的,它应该可以正常工作。如果您启用了某种形式的 Hibernate 缓存或 ehcache,那么您很可能会查看某个缓存对象。我曾建议重新启动应用程序以确保您拥有最新版本。但如果你只是:

    包装一个

    DomainClass.withNewTransaction { 
      //get the latest copy
      DomainClass clazz = DomainClass.get(recordId)
      println "-- ${clazz.value}"
    }
    

    这应该确保您从数据库中获得最新的,它不会提高速度,但如果您希望手动更新数据库,您可以始终确保最新的在上面。..

    【讨论】:

    • 但是flush:true 确实违背了我在grails 中应该如何工作的心理模型。如果代码在事务方法中,则应该(几乎)永远不需要 flush:true,除非在更新对象的给定事务方法中,您需要读取其最新状态。但是,如果您在一个事务方法中更新一个对象,并在另一个方法中读取它,则第一个事务应该已被刷新,您应该能够在第二个事务中读取最新状态。这是我问题的核心。参考:stackoverflow.com/a/28302047/6788384
    • 事务问题已经发生,并且一直与我的思维模型相矛盾,所以我真的用上面显示的代码创建了一个示例测试应用程序只是 .这是回购:github.com/miguelpais/grails-transactions
    • 你不应该在服务方法上方需要@Transactional,如果你什么都不说,它们默认是事务性的。是的,我有评论你应该直接在数据库上手动更改对象,或者从其他地方更新它。如果您在服务方法的 findById 之间更改对象,我的心智模型告诉我返回的对象处于相同状态是正确的,因为两个 findByid 都发生在同一个事务中。但是,稍后在控制器中完成的 findById(事务提交后)应该返回新状态,而不是旧状态。
    • 我希望这有助于解答你所有的谜题
    • 谢谢您,您确实发送了很多有用的信息!然而,这条神奇的线只是我在 MySql 工作台或任何其他等效应用程序上运行,并在 bookService 中的第二个 get(1) 执行之前自己更新记录。在我的 MysqlLog 中,我会清楚地看到第一个查找、更新(手动)提交和第二个查找在同一初始事务中返回相同(旧)数据。然而,在控制器中,我找不到任何借口解释为什么第三个 find 不会返回最新信息而是似乎返回缓存版本。
    猜你喜欢
    • 2015-07-01
    • 2012-03-21
    • 2012-12-08
    • 2016-12-17
    • 1970-01-01
    • 2011-10-27
    • 2011-01-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多