为什么JPA实体会在每个偶数查询中选择结果更改?

时间:2015-01-28 11:34:49

标签: java hibernate jpa persistence entitymanager

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

我有以下代码,从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. 据我所知,设置托管对象的属性足以将更改传播到DB。但我想立即刷新更改。我的额外调用更新的方法是适当的解决方案还是调用setter足以在DB中立即进行更改?额外更新我的意思是documentDAO.updateDocument(doc); // IS IT NECESSARY??
  2. JPA如何存储托管对象 - 在某些内部数据结构中,或者只是将它们保存在Document doc;等引用中?内部结构很可能使重复/同一ID托管对象不可能,引用最有可能使多个托管对象具有相同的id和其他属性。
  3. merge如何在内部工作 - 尝试在内部存储中找到具有相同 ID 的托管对象,并且在检测到的情况下,刷新其字段或只是更新数据库
  4. 如果内部存储确实存在(很可能这是持久性上下文,进一步的PC),区分管理对象的标准是什么? @Id带注释的hibernate模型字段?
  5. 我的主要问题是entityManager.createQuery("from Document");

    的不同结果

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

    我怀疑第一次select-all-query返回isSigned = false的实体并将它们放到PC上,之后用户执行一些操作来抓取实体byID,设置isSigned = true并且刚刚提取的实体冲突已经在PC中显示。 第一个对象的isSigned = false,第二个具有isSigned = true且PC混淆并返回不同的托管对象但它是如何可能的?在我看来,PC有机制通过为每个唯一ID只保留一个托管对象来不允许这种令人困惑的暧昧情境。

1 个答案:

答案 0 :(得分:1)

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

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

因此,此代码应位于服务端,而不是Web控制器中。

  
      
  1. 据我所知,设置托管对象的属性足以将更改传播到DB。但是我想立即更新。是   我额外调用更新的方法是适当的解决方案或   调用setter足以在DB中立即进行更改?额外的   更新我的意思是documentDAO.updateDocument(doc); //是否必要?
  2.   

这仅适用于托管实体,只要持久性上下文仍处于打开状态即可。这就是你需要一种交易服务方法的原因。

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

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

  

内部如何合并 - 尝试使用   内部存储中的相同ID,以及在检测时刷新的ID   它是字段还是只是更新DB?

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

  1. 如果内部存储确实存在(很可能这是持久性上下文,还有PC),区分被管对象的标准是什么? @Id注释hibernate模型的字段?
  2. PersistenceContext是会话级缓存。托管对象始终具有标识符和关联的数据库行。

      

    我怀疑第一次select-all-query返回实体   isSigned = false并将其放入PC,然后用户执行一些操作   抓取实体byID的操作,设置isSigned = true和just   提取的实体冲突已经在PC中呈现。

    在相同的持久化上下文范围内,这种情况永远不会发生。如果通过查询加载实体,则实体将在第1级缓存中获取缓存。如果您尝试使用另一个查询或使用EntityManager.find()再次加载它,您仍将获得相同的对象引用,该引用已经被缓存。

    如果第一个查询针对持久化上下文发生,第二个查询/查找将在第二个持久化上下文上发布,则每个持久化上下文都必须缓存自己正在查询的实体版本。

      

    第一个对象有isSigned = false,第二个有isSigned = true和PC   confused并返回不同的托管对象。但是怎么样   可能吗?

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