在JPA(eclipselink)中禁用缓存

53

我想使用JPA(eclipselink)从我的数据库获取数据。该数据库被许多其他来源更改,因此我希望每次执行查找操作时都返回到数据库。我已经阅读了一些有关禁用缓存的帖子,但这似乎不起作用。有什么想法吗?

我正在尝试执行以下代码:

        EntityManagerFactory entityManagerFactory =  Persistence.createEntityManagerFactory("default");
        EntityManager em = entityManagerFactory.createEntityManager();

        MyLocation one = em.createNamedQuery("MyLocation.findMyLoc").getResultList().get(0);

        MyLocation two = em.createNamedQuery("MyLocation.findMyLoc").getResultList().get(0);    

        System.out.println(one==two);

one==two为真,但我希望它是假的。

我已尝试将以下每个/所有添加到我的persistence.xml文件中:

<property name="eclipselink.cache.shared.default" value="false"/>
<property name="eclipselink.cache.size.default" value="0"/>
<property name="eclipselink.cache.type.default" value="None"/>

我也尝试将@Cache注解添加到实体本身:

@Cache(
  type=CacheType.NONE, // Cache nothing
  expiry=0,
  alwaysRefresh=true
)

我是否理解有误?


在你对我的回答的评论中,James,请问你测试时缓存是否关闭了(<property name="eclipselink.cache.shared.default" value="false"/>)? - Justin
抱歉刚注意到这个问题,是缓存关闭了。我仍然遇到这个问题,离解决还很远。 - James
8个回答

44
这种行为是正确的,否则,如果您改变具有不同值的对象一和对象二,则在将它们持久化时会出现问题。发生的情况是调用加载对象二的操作会更新第一个调用中加载的实体。它们必须指向同一个对象,因为它们就是同一个对象。这确保了不能写入脏数据。
如果在两个调用之间调用em.clear(),则实体一应该变为分离状态,您的检查将返回false。然而,没有必要这样做,事实上EclipseLink正在将您的数据更新到最新状态,我猜这正是您想要的,因为它经常更改。
另外,如果您希望使用JPA更新此数据,则需要在Entity上获取悲观锁,以便底层数据在数据库中不能更改。
您还需要禁用查询缓存,因为您的缓存选项只是取消了对象缓存,而没有取消查询缓存,这就是为什么您没有得到新结果的原因:
在您的代码中:
em.createNamedQuery("MyLocation.findMyLoc").setHint(QueryHints.CACHE_USAGE, CacheUsage.DoNotCheckCache).getResultList().get(0);

Or in persistence.xml:

<property name="eclipselink.query-results-cache" value="false"/>

添加了一个关于悲观锁的信息链接。 - Justin
我已经尝试在代码和persistence.xml中进行所述的更改,但仍无法直接获取我更改的值。有什么想法吗? - James
请查看您的事务隔离级别。如果您的事务使用Serializable作为隔离级别(例如Oracle的默认值),那么T1将永远无法看到来自T2的更改(据我所知)。 - bennidi
DoNotCheckCache 确保不会缓存缓存。有可能确保查询返回的实体不被缓存吗? - Ced
@AliReza19330 这个答案已经有6年的历史了,如果它不再适用,请随意更新。然而,现在和以后仍然会有人点赞。 - Justin
显示剩余2条评论

31
final Query readQuery = this.entityManager.createQuery(selectQuery);
readQuery.setParameter(paramA, valueA);

// Update the JPA session cache with objects that the query returns.
// Hence the entity objects in the returned collection always updated.
readQuery.setHint(QueryHints.REFRESH, HintValues.TRUE);

entityList = readQuery.getResultList();

这对我有效。


1
这是我的问题的解决方案;我受到JTA的限制,在JTA内部不允许打开事务。没有事务,就没有refresh()。这个解决方案解决了我的问题。 - JayC667

28

如果您希望禁用缓存而不涉及特定供应商,您可以使用以下注释在您的域对象上进行标注:

@Cacheable(false)

这里有一个例子:

@Entity
@Table(name="SomeEntity")
@Cacheable(false)
public class SomeEntity {
    // ...
}

这肯定是有效的答案! - Imad El Hitti
上帝保佑您,先生。这个问题一直让我抓狂,而您只用了5秒钟就为我解决了。真希望我有更多的模块点数。 - user64141
这绝对是这里最好的答案。 - ayez1389

14

参见:

http://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Basic_JPA_Development/Caching

对于同一EntityManager,JPA始终要求one==two,因此无论您的缓存选项如何(这是L1缓存或事务缓存,它强制执行您的事务隔离并维护对象标识),都是正确的。

要强制刷新查询(并撤消您所做的任何更改),可以使用查询提示“eclipselink.refresh”=“true”。 或者更好的方法是为每个查询/请求使用新的EntityManager,或在您的EntityManager上调用clear()。

<property name="eclipselink.cache.shared.default" value="false"/>

禁用共享缓存(L2缓存)的正确方法是什么?请删除所有其他设置,因为它们不正确并可能会导致问题。

EclipseLink默认情况下不维护查询缓存,因此这些设置将不起作用。CacheUsage也不正确,请勿使用它(它适用于内存查询)。


1
请注意,在当前版本的EclipseLink中,“eclipselink.refresh” =“true”存在故障(https://bugs.eclipse.org/bugs/show_bug.cgi?id=398074);此外,它是EclipseLink特有的,而不是JPA的一部分。因此,我建议每当您需要新鲜数据时,只需获取一个新的EntityManager - 这是使用JPA的工作方式。 - sleske
获取一个新的EntityManager并不会直接从数据库中获取一个新的对象。缓存工作在EntityManagerFactory级别上,通常具有应用程序的生命周期。 - OliCoder

6

默认情况下启用了第一级缓存,您无法禁用它。也就是说,无论您在persistence.xml文件中进行哪些设置,都无法禁用第一级缓存。

您只能通过调用清除所有实体管理器对象来清除它们。

entityManager.clear()

这将使后续查询首先访问数据库,然后再将对象存储在缓存中。

您可以通过调用以下方法来直接强制每个查询访问数据库:

query.setHint("javax.persistence.cache.storeMode", CacheStoreMode.REFRESH);

5

我知道这篇文章可能有点老了,但我写这篇文章是为了帮助其他需要帮助的人。 我曾经也遇到过这样的问题,最终我通过以下代码解决了它:

em.createNamedQuery("findAll").setHint(QueryHints.CACHE_RETRIEVE_MODE, CacheRetrieveMode.BYPASS).getResultList();

它的表现非常好。并且我们可以在BYPASS枚举的javadoc中看到,上面写着:

绕过缓存:直接从数据库获取数据。

请注意,我使用的是Weblogic 12c和TopLink作为JPA实现。


上述的刷新提示对你没有起作用吗? - cen
我没有尝试过那个,因为我只想在某些查询中绕过JPA缓存。 - Ali Abazari

4
如果手动修改对象属性,例如MyLocation。上述技巧(CACHE_USAGE = CacheUsage.DoNotCheckCacheeclipselink.query-results-cache = false)似乎不起作用,因为我已经尝试过。
所以我尝试设置另一个提示,即eclipselink.refresh,将其设置为true。然后它就有效了。我的意思是手动更改的属性被检索到了。
因此,据我所知,上述技巧仅确保获得正确的对象。但是,如果这些对象已经被缓存,eclipselink仅会返回它们,而不会检查对象内容的新鲜程度。只有当提示eclipselink.refresh设置为true时,这些对象才会刷新以反映最新的属性值。

4
你是否在persistence.xml中设置了这个?如果我在查询中添加这个提示,它可以工作。但是如果我只在persistence.xml中定义它,它不能正常工作。 - Christian

0

当您通过实体管理器创建查询时,将调用第一级缓存。实体将变旧。 假设我有一个键,在数据库中手动更新后尝试检索:

Key result = entityManager
                .createQuery("SELECT k FROM Key k WHERE k.linkKey = :key", Key.class)
                .setParameter("key", key)
                .getSingleResult();

以下的键将保持不变,因为它仍然被缓存,我的更新将不会出现。要刷新实体,您需要调用以下内容:

 entityManager.refresh(result);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接