【问题标题】:Why JPA Entity select result changes on each even query?为什么 JPA Entity 选择每个偶数查询的结果都会发生变化?
【发布时间】:2015-03-27 07:02:17
【问题描述】:

当相同的查询在每次调用后返回不同的结果时,我的问题与奇怪的读取/选择行为有关。我的情况描述如下:

我有以下代码,从数据库返回文档列表

@RequestMapping(value={"/docs"}, method = RequestMethod.GET)
    @ResponseBody
    public ArrayList<Document> getMetaData(ModelMap modelMap) {
        return (ArrayList<Document>)documentDAO.getDocuments();
    }

DocumentDAO.getDocuments 看起来像

public List<Document> getDocuments() {
        Query query = entityManager.createQuery("from Document");
        List<Document> list = query.getResultList();
        for(Document doc:list) System.out.println(doc.getName()+" "+doc.isSigned());
        return list;
}

在其他控制器中,我还提取 Document 并更改布尔属性

 Document doc = documentDAO.getDocumentById(id)
 doc.setSigned(true);
 documentDAO.updateDocument(doc); // IS IT NECESSARY??

getById 和 updateDocument 如下:

public Document getDocumentById(Long id) {
        return entityManager.find(Document.class, id);
 }

@Transactional
public void updateDocument(Document document) {
    entityManager.merge(document);
    entityManager.flush();
}

问题:

  1. 据我所知,设置托管对象的属性足以将更改传播到数据库。但我想立即刷新更改。我的额外调用更新方法是合适的解决方案还是调用setter足以立即更改数据库?通过额外更新我的意思是documentDAO.updateDocument(doc); // IS IT NECESSARY??
  2. JPA 如何存储托管对象——在一些内部数据结构中,还是简单地将它们保存在像Document doc; 这样的引用中?内部结构很可能使重复/相同 ID 托管对象成为不可能,引用很可能使具有相同 ID 和其他属性的多个托管对象成为可能。
  3. merge 如何在内部工作 - 尝试在内部存储中查找具有相同 ID 的托管对象,并在检测到的情况下刷新其字段或仅更新数据库?
  4. 如果内部存储确实存在(很可能这是持久性上下文,进一步说 PC),区分托管对象的标准是什么? @Id 注释 hibernate 模型的字段?

我的主要问题是entityManager.createQuery("from Document"); 的结果不同

System.out.println(doc.getName()+" "+doc.isSigned()); 在奇数调用时显示 isSigned true,在偶数调用时显示 false。

我怀疑第一个 select-all-query 返回带有 isSigned=false 的实体并将它们放到 PC 上,之后用户执行一些操作,通过 ID 获取实体,设置 isSigned=true 并且刚刚提取的实体与 PC 中已经存在的实体冲突。 第一个对象 isSigned=false,第二个对象 isSigned=true,PC 混淆并轮流返回不同的托管对象但这怎么可能呢?在我看来,PC 有机制通过为每个唯一 id 只保留一个托管对象来避免这种令人困惑的模棱两可的情况。

【问题讨论】:

    标签: java hibernate jpa persistence entitymanager


    【解决方案1】:

    首先,您希望在单个事务服务方法中同时注册读取和写入:

    @Transactional
    public void signDocument(Long id) {
        Document doc = documentDAO.getDocumentById(id)
        doc.setSigned(true);
    }
    

    所以这段代码应该驻留在服务端,而不是你的网络控制器中。

    1. 据我所知,设置托管对象的属性足以将更改传播到数据库。但我想立即刷新更改。是 我额外调用更新的方法是合适的解决方案或 调用 setter 是否足以立即更改数据库?通过额外 更新我的意思是 documentDAO.updateDocument(doc); // 有必要吗??

    这仅适用于托管实体,只要持久性上下文仍处于打开状态。这就是为什么您需要一个事务性服务方法。

    1. JPA 如何存储托管对象 - 在某些内部数据结构中,或者只是将它们保存在像 Document doc 这样的引用中?内部结构 很可能使重复/相同 ID 托管对象成为不可能, 引用最有可能拥有多个托管对象 具有相同的 id 和其他属性。

    JPA 一级缓存只是按原样存储实体,它不使用任何其他数据表示。在持久性上下文中,您可以有一个且只有一个实体表示(类和标识符)。在 JPA 持久性上下文的上下文中,托管的 entity equality is the same with entity identity

    合并如何在内部工作 - 尝试使用 内部存储中相同的 ID,并在检测的情况下刷新 是字段还是只是更新数据库?

    merge 操作对reattaching detached entities 有意义。在刷新期间,托管实体状态会自动与数据库同步。 automatic dirty checking mechanism 负责处理这个问题。

    1. 如果内部存储确实存在(很可能这是持久性上下文,更进一步的 PC),区分托管对象的标准是什么? @Id注解的hibernate模型字段?

    PersistenceContext 是一个会话级缓存。托管对象始终具有标识符和关联的数据库行。

    我怀疑第一个全选查询返回的实体是 isSigned=false 并将它们放到 PC 上,之后用户执行一些操作 通过 ID 获取实体的操作,设置 isSigned=true 并且只是 提取的实体与 PC 中已呈现的实体冲突。

    在同一 Persistence Context 范围内,这永远不会发生。如果您通过查询加载实体,该实体会在一级缓存中获得缓存。如果您尝试使用另一个查询或 EntityManager.find() 再次加载它,您仍将获得相同的对象引用,该引用已被缓存。

    如果第一个查询针对持久性上下文发生,并且第二个查询/查找将在第二个持久性上下文上发出,那么每个持久性上下文都必须缓存其自己的被查询实体版本。

    第一个对象有 isSigned=false,第二个有 isSigned=true 和 PC 混淆并轮流返回不同的托管对象。但是如何 有可能吗?

    这不可能发生。 Persistence Context 始终保持实体对象的完整性。

    【讨论】:

    • First of all you want to enrol both the read and the write in a single transactional service method - 你的意思是,我应该只在控制器中读取,创建非托管实例,用需要的值填充它并传递给 DAO 类中的一些事务方法? In the same Persistence Context scope this can't ever happen- 那么,我有多台电脑?按 F5 仅调用一种方法 - select-all-query - 并且打印 isSigned 属性为之前在其他用户操作中仅更改一次的实体提供 true-false-true-false。那是我怀疑我在使用 PC 时出现的错误。
    • 您可以在一个事务中读取,向用户显示结果并在新事务中应用更改。至于那个用例,我怀疑事务没有提交,因此更改实际上并没有持久化。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-02-13
    • 2015-05-18
    • 2021-09-16
    • 1970-01-01
    • 2012-11-26
    • 2021-05-05
    • 2019-05-23
    相关资源
    最近更新 更多