【问题标题】:Avoid stale data from DB when using JPA?使用 JPA 时避免数据库中的陈旧数据?
【发布时间】:2013-01-07 15:45:16
【问题描述】:

我面临的问题类似于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 关联的一级缓存引起的,如链接问题中所述。我们找到了两个解决方案:

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

两种解决方案都能满足我们的需求 - 我们获得了新数据。

问题:

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

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

【问题讨论】:

  • 我认为与多个应用程序共享一个数据库不是很常见?听起来你会因为不断地重新加载你的 EntityManager 而受到严厉的惩罚。您不能通过服务来提供所需的数据吗?或者在应用程序之间使用一些共享缓存?
  • @vertti:好点子。我没有意识到 JPA 在使用共享数据库时会出现这样的问题。当然,使用服务或类似服务是可能的,但这意味着重大变化,而且不太可能发生。

标签: database spring caching jpa eclipselink


【解决方案1】:

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

其实有两个问题:

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

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

短版

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

  • 为每个需要新数据的查询使用新的 EntityManager
  • 禁用二级缓存(persistence.xml 中的&lt;shared-cache-mode&gt;NONE&lt;/shared-cache-mode&gt;

【讨论】:

    【解决方案2】:
    1. 创建新的EntityManagers 是正确的,另一个不是(见下文)。

    2. 在单线程系统中调用EntityManager#clear() 非常危险。

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

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

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

    【讨论】:

    • EntityManager 不是线程安全的,因此无论如何您都不应该在两个线程中处理同一个 EntityManager。每个线程都应该注入自己的 EntityManager,因为它们代表不同的事务上下文。
    • 是的。只是查看了 OP 的代码 sn-p 以确保他确实以这种方式使用它。
    • @Chris:是的,是的。但是,我们使用 Spring 来注入 EntityManager,并且 Spring 为我们提供了“实际事务性 EntityManager 的共享、线程安全代理”(static.springsource.org/spring/docs/3.2.x/…)。所以线程安全不是问题。
    • 该链接暗示您正在进行交易,否则代理会在每次操作时使用新的 EM。如果这是正确的,您可能会在事务之外执行读取以获得更好的性能。还要注意 EclipseLink 有一个共享的二级缓存;验证您使用的是独立缓存设置
    • @Chris:实际上不,我们不在事务中(我相信 - 查询方法没有注释 @Transactional)。我们有时仍然会得到陈旧的数据。我还在调查这个...
    【解决方案3】:

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

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

    【讨论】:

    • 我们已经做到了。然而,问题似乎出在一级缓存上——正如所解释的,如果我们总是使用新的 EntityManager,一切都很好,所以共享缓存可能不是问题。
    【解决方案4】:

    这会变魔术 entityManager.getEntityManagerFactory().getCache().evict(MyDTO.class);

    【讨论】:

      猜你喜欢
      • 2019-09-29
      • 2013-09-10
      • 2014-11-22
      • 2018-01-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-10-08
      相关资源
      最近更新 更多