2013-01-07 52 views
3

我面对类似于Invalidating JPA EntityManager session描述的一个问题:使用JPA时避免来自数据库的陈旧数据?

问题:让旧数据

我们正在运行JPQL的查询也同时由不同的应用程序更改SQL数据库上。我们使用在Tomcat下运行的JSF + Spring + EclipseLink。

类做JPQL查询是一个单独的Spring bean,并使用注射EntityManager

@Component 
public class DataRepository{ 
    @PersistenceContext 
    private EntityManager entityManager; 
    public List<MyDTO> getStuff(long id) { 
     String jpqlQuery ="SELECT new MyDTO([...])"; 
     TypedQuery<MyDTO> query = entityManager.createQuery(jpqlQuery,MyDTO.class); 
     return query.getResultList(); 
    } 
[...] 

(代码转述)。

问题是此代码没有看到直接在数据库上执行的更改。如果Tomcat实例重新启动,这些更改只会变得可见。

我们已经尝试

我们假设该行为是由相关的EntityManager一级高速缓存中引起的,如链接的问题进行说明。我们发现了两个解决方案:

  • 呼叫entityManager.clear()调用createQuery之前(这是建议的链接的问题)
  • 注入的EntityManagerFactor(使用@PersistenceUnit),然后创建并关闭新EntityManager为每个查询

这两种解决方案都能做到我们想要的 - 我们获得新的数据。

问题:

  • 是这两种解决方案是否正确?哪一个更好?
  • 特别是,我们可以安全地在注入的EntityManager上调用entityManager.clear(),或者这会以某种方式影响也使用注入的EntityManager的其他代码(在相同或不同的类中)?
  • 有没有不同的更好的方法?我们可以以某种方式声明我们想刷新缓存,或者我们想要一个新的EntityManager

我想这一定是一个相当普遍的问题(因为它发生时多个应用程序共享一个数据库),所以我想必须有一个简单的解决方案...

+0

我认为这不是很常见的共享与多个应用程序的数据库?这听起来像你会不断重新加载你的EntityManager而受到严重惩罚。你不能通过服务提供所需的数据吗?或者在应用程序之间使用一些共享缓存? – vertti

+0

@vertti:好点。我没有意识到JPA在使用共享数据库时存在这样的问题。当然,使用服务或类似服务是可能的,但这意味着一个重大变化,而且不太可能发生。 – sleske

回答

2
  1. 创建新EntityManager小号是正确的,另一个不是(见下文)。

  2. 调用EntityManager#clear()对于除单线程系统之外的所有系统都是非常危险的。

    ...导致所有被管实体分离...

    因此,如果一个线程与附属实体一起工作,而“您的”线程清除实体管理器,则会产生严重的副作用。

  3. 嗯,很难说。如果在您的应用程序之外修改的实体数量很少,我会直接使用数据源,并针对相应的操作使用JdbcTemplate

+0

EntityManagers不是线程安全的,因此无论如何您都不应该在两个线程中使用同一个EntityManager。每个线程都应该有自己的EntityManager注入,因为它们表示不同的事务上下文。 – Chris

+0

是的。只是看看OP的代码片段,以确保他确实以这种方式使用它。 –

+0

@克里斯:是的,没错。然而,我们使用Spring来注入EntityManager,并且Spring为我们提供了“一个共享的,线程安全的代理,用于实际的事务性EntityManager”(http://static.springsource.org/spring/docs/3.2.x/spring-framework -reference/html/orm.html#orm-jpa-straight)。所以线程安全不是问题。 – sleske

4

看起来我们已经解决了这个问题。

实际上有两个问题:

  1. 在调试问题,在某些情况下,我们并没有使用新的EntityManager,每次查询。当使用和重新使用相同的EntityManager时,实体显然会从一旦检索到它们就不刷新(除非使用EntityManager.refresh()明确刷新)。这显然是因为一旦一个实体被加载,它就被存储在EntityManager的持久性上下文(又名一级缓存)中。这样做是必要的,因为JPA规范要求对同一个实体的后续查询返回相同的对象实例。换句话说:只要你使用相同的EntityManager,你会得到陈旧的数据,除非你明确地刷新。

  2. 当我们确实为每个查询使用一个新的EntityManager时,通常情况下工作。但是,我们遇到了a bug in EclipseLink,其中针对某些JPQL查询返回陈旧数据(涉及构造函数表达式和JOIN FETCH)。

短版

如果你想永远从自修改程序之外的数据库得到的最新数据,然后

  • 使用新鲜的EntityManager对于每个查询,你想要新数据
  • 禁用二级缓存(<shared-cache-mode>NONE</shared-cache-mode>位于persistence.xml中)
0

这将做魔术 entityManager.getEntityManagerFactory().getCache().evict(MyDTO.class);