为什么 JPA Entity select 结果在每个偶数查询上都会发生变化?

Why JPA Entity select result changes on each even query?

我的问题与奇怪的 read/select 行为有关,即每次调用后相同查询 returns 不同的结果。我的情况描述如下:

我有以下代码,从 DB

返回文档列表
@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 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. 据我所知,设置托管对象的 属性 足以将更改传播到数据库。但是我想 flush 立即更改。我额外调用更新的方法是合适的解决方案还是调用 setter 足以立即更改数据库?通过额外更新,我的意思是 documentDAO.updateDocument(doc); // IS IT NECESSARY??
  2. JPA 如何存储托管对象 - 在某些内部数据结构中或只是将它们保存在 Document doc; 之类的引用中?内部结构很可能使 duplicate/sameID 托管对象成为不可能,引用很可能使具有相同 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 returns 带有 isSigned=false 的实体并将它们放入 PC,然后用户执行一些操作,通过 ID 获取实体,设置 isSigned=true 和刚刚提取的实体与 PC 中已经存在的实体冲突。 第一个对象有 isSigned=false,第二个对象有 isSigned=true 和 PC 混淆和 returns 轮流管理的不同对象但这怎么可能呢?在我看来,PC 有一种机制可以通过为每个唯一 ID 只保留一个托管对象来防止出现这种令人困惑的模棱两可的情况。

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

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

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

  1. As far as I know, setting property of managed object is enough to propagate changes to DB. But I want to flush changes immediately. Is my approach with extra call of update is appropriate solution or calling setter is enough for making immediate changes in DB? By extra update I mean documentDAO.updateDocument(doc); // IS IT NECESSARY??

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

  1. How JPA stores managed objects - in some internal data structure or simply keeps them in references like Document doc;? Internal structure most likely makes duplicate/sameID managed object impossible, references most likely makes possible to have multiple managed objects with same id and other properties.

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

How merge works internally - tries to find managed object with the same ID in internal storage and, in the case of detecting, refreshes it's fields or simply updates DB?

merge 操作很有意义,因为 reattaching detached entities. A managed entity state is automatically synchronized with the database during flush-time. The automatic dirty checking mechanism 会处理这个问题。

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

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

I suspect that first select-all-query returns entities with isSigned=false and put them to PC, after that user performs some operation which grabs entity byID, sets isSigned=true and just extracted entity conflicts with already presented in PC.

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

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

First object has isSigned=false, second has isSigned=true and PC confused and returns different managed objects in rotation. But how its possible?

这不可能发生。持久性上下文始终保持实体对象的完整性。