2010-08-06 50 views
1

我嘗試在一個DAO方法中執行多個查詢。測試失敗(數據未更新)。記錄沒有例外。 DAO執行的幾個操作

public List<Domain> getNewDomains(final int maxAllowedItems, final Date timestamp) { 

    return getJpaTemplate().execute(new JpaCallback<List<Domain>>() { 
     @SuppressWarnings("unchecked") 
     public List<Domain> doInJpa(EntityManager entityManager) throws PersistenceException { 
      Calendar dayBefore = Calendar.getInstance(); 
      dayBefore.setTime(timestamp); 
      dayBefore.add(Calendar.HOUR, -24); 

      List ids = entityManager.createQuery("SELECT d.id FROM domain d WHERE d.crawlDate IS NULL and (d.lastRead IS NULL OR d.lastRead <= :dayBefore) ") 
      .setParameter("dayBefore", dayBefore.getTime()) 
      .setMaxResults(maxAllowedItems) 
      .getResultList(); 

      LOG.debug("new domain IDS : " + ids.toString()); 

      if(ids.isEmpty()) { 
       return new ArrayList<Domain>(); 
      } 

      int result = entityManager.createQuery("UPDATE domain d SET d.lastRead = :timestamp WHERE d.id IN (:ids)") 
      .setParameter("timestamp", timestamp) 
      .setParameter("ids", ids).executeUpdate(); 

      LOG.debug("update result : " + result); 

      return entityManager.createQuery("SELECT d FROM domain d WHERE d.id IN (:ids) ") 
      .setParameter("ids", ids) 
      .setMaxResults(maxAllowedItems) 
      .getResultList(); 
     } 
    }); 
} 


首先選擇正確處理。但在更新「lastRead」字段狀態相同。

測試:

@Test 
public void testGetNewItems() { 
    List<Domain> items = domainDAO.getNewDomains(2, new Date()); 
    Assert.assertNotNull(items); 
    Assert.assertTrue(items.isEmpty()); 

    String domainName = "example.com"; 
    Domain domain1 = new Domain(domainName); 
    domainDAO.save(domain1); 

    String domainName2 = "example2.com"; 
    Domain domain2 = new Domain(domainName2); 
    domainDAO.save(domain2); 
    Domain domain2FromDB = domainDAO.getByName(domainName2); 
    Assert.assertEquals(domain2, domain2FromDB); 
    Assert.assertNull(domain2FromDB.getCrawlDate()); 

    domain2FromDB.setCrawlDate(new Date()); 
    domainDAO.update(domain2FromDB); 

    String domainName3 = "example3.com"; 
    Domain domain3 = new Domain(domainName3); 
    domainDAO.save(domain3); 

    items = domainDAO.getNewDomains(2, new Date()); 
    Assert.assertNotNull(items); 
    Assert.assertEquals(2, items.size()); 
    Assert.assertTrue(items.contains(domain1)); 
    Assert.assertTrue(items.contains(domain3)); 
    Assert.assertFalse(items.contains(domain2FromDB)); 

    for (Domain item : items) { 
     Assert.assertNotNull(item.getLastRead()); // FAILED assert 
    } 
} 

我應該更新後閃? 處理多個查詢的正確方法是什麼?

回答

1

更新&刪除查詢被視爲JPA中的批量更新,並具有不同的規則。批量更新直接在數據庫上執行,持久化上下文(EntityManager)將不會使用這些更改進行更新。所以當你查詢持久化上下文時,它會發現一個匹配的實體 - 不知不覺地返回陳舊的數據。

JPA規範提出這樣的:

注意應在執行批量更新或刪除操作,因爲它們可能導致數據庫和活躍的持久化上下文的實體之間 不一致時使用。一般而言,批量更新和刪除操作只應在新持久性文本中的事務中執行,或者在獲取或訪問狀態可能受此類操作影響的實體之前執行。

關於如何解決問題,您有幾個選項。

重寫第一個查詢以返回實體,而不僅僅是id。並修改實體。像這樣的東西應該工作

public List<Domain> doInJpa(EntityManager entityManager) throws PersistenceException { 
    Calendar dayBefore = Calendar.getInstance(); 
    dayBefore.setTime(timestamp); 
    dayBefore.add(Calendar.HOUR, -24); 

    List<Domain> domains = entityManager.createQuery("SELECT d FROM domain d WHERE d.crawlDate IS NULL and (d.lastRead IS NULL OR d.lastRead <= :dayBefore) ") 
     .setParameter("dayBefore", dayBefore.getTime()) 
     .setMaxResults(maxAllowedItems) 
     .getResultList(); 

    if(domains.isEmpty()) { 
     return new ArrayList<Domain>(); 
    } 

    for(Domain d : domains) { 
     d.setLastRead(timestamp); 
    } 

    return domains;  
} 

現在你到一個查詢,該實體將與持久化上下文同步,您的測試應該通過。

如果您未能通過調用每個域上的entityManager.refresh()來解決批量更新問題 - 刷新方法將使用數據庫中的最新狀態更新實體。