使用JPA时,请避免来自DB的陈旧数据?

时间:2013-01-07 15:45:16

标签: database spring caching jpa eclipselink

我遇到的问题类似于Invalidating JPA EntityManager session中描述的问题:

问题:获取陈旧数据

我们在SQL数据库上运行JPQL查询,该数据库也由不同的应用程序同时更改。我们正在使用在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

我认为这必定是一个相当普遍的问题(因为只要多个应用程序共享一个数据库就会出现这种情况),所以我认为必须有一个简单的解决方案......

4 个答案:

答案 0 :(得分:4)

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

实际上有两个问题:

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

  2. 当我们 为每个查询使用新的EntityManager时,通常会有效。但是,我们遇到a bug in EclipseLink,其中为某些JPQL查询返回过时数据(涉及构造函数表达式和JOIN FETCH)。

  3. 简短版

    如果您想要始终从程序外部修改的数据库中获取新数据,那么

    • 为您想要新数据的每个查询使用新的EntityManager
    • 禁用二级缓存(persistence.xml中的<shared-cache-mode>NONE</shared-cache-mode>

答案 1 :(得分:2)

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

  2. 除了单线程系统外,调用EntityManager#clear()非常危险。

      

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

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

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

答案 2 :(得分:1)

要在EclipseLink中禁用共享缓存,请参阅

http://wiki.eclipse.org/EclipseLink/FAQ/How_to_disable_the_shared_cache%3F

答案 3 :(得分:0)

这将是神奇的 entityManager.getEntityManagerFactory().getCache().evict(MyDTO.class);