我尝试在一个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();
}
});
}
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
}
}
更新后我应该闪一下吗? 处理多个查询的正确方法是什么?
答案 0 :(得分:1)
更新&amp;删除查询在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()来解决批量更新问题,则刷新方法将使用数据库中的最新状态更新实体。