38

我有一个 PostgreSQL 8.4 数据库,其中包含一些表和视图,它们本质上是一些表的连接。我使用 NetBeans 7.2(如此所述)创建从这些视图和表派生的基于 REST 的服务,并将它们部署到 Glassfish 3.1.2.2 服务器。

还有另一个进程异步更新用于构建视图的某些表中的内容。我可以直接查询视图和表并查看这些更改是否正确发生。但是,当从基于 REST 的服务中提取时,这些值与数据库中的值不同。我假设这是因为 JPA 已经在 Glassfish 服务器上缓存了数据库内容的本地副本,并且 JPA 需要刷新关联的实体。

我尝试向 NetBeans 生成的 AbstractFacade 类添加几个方法:

public abstract class AbstractFacade<T> {
    private Class<T> entityClass;
    private String entityName;
    private static boolean _refresh = true;

    public static void refresh() { _refresh = true; }

    public AbstractFacade(Class<T> entityClass) {
        this.entityClass = entityClass;
        this.entityName = entityClass.getSimpleName();
    }

    private void doRefresh() {
        if (_refresh) {
            EntityManager em = getEntityManager();
            em.flush();

            for (EntityType<?> entity : em.getMetamodel().getEntities()) {
                if (entity.getName().contains(entityName)) {
                    try {
                        em.refresh(entity);
                        // log success
                    }
                    catch (IllegalArgumentException e) {
                        // log failure ... typically complains entity is not managed
                    }
                }
            }

            _refresh = false;
        }
    }

...

}

然后,我从NetBeans 生成doRefresh()的每个方法中调用。find通常发生的情况IllegalArgumentsException是抛出类似Can not refresh not managed object: EntityTypeImpl@28524907:MyView [ javaType: class org.my.rest.MyView descriptor: RelationalDescriptor(org.my.rest.MyView --> [DatabaseTable(my_view)]), mappings: 12].

所以我正在寻找一些关于如何正确刷新与视图关联的实体的建议,以便它是最新的。

更新:原来我对潜在问题的理解是不正确的。这与我之前发布的另一个问题有些相关,即视图没有可用作唯一标识符的单个字段。NetBeans 要求我选择一个 ID 字段,所以我只选择了应该是多部分键的一部分。这表现出具有特定 ID 字段的所有记录都相同的行为,即使数据库具有具有相同 ID 字段但其余部分不同的记录。JPA 只是查看了我告诉它的唯一标识符,并简单地提取了它找到的第一条记录。

我通过添加一个唯一标识符字段解决了这个问题(永远无法让多部分密钥正常工作)。

4

4 回答 4

56

我建议添加一个@Startup @Singleton与 PostgreSQL 数据库建立 JDBC 连接并使用LISTENNOTIFY处理缓存失效的类。

更新这是另一种有趣的方法,使用 pgq 和一组工作人员进行 invalidation

失效信号

在正在更新的表上添加一个触发器,该触发器在NOTIFY实体更新时发送一个。在 PostgreSQL 9.0 及更高版本上,它NOTIFY可以包含有效负载,通常是行 ID,因此您不必使整个缓存无效,只需使已更改的实体无效。在不支持有效负载的旧版本上,您可以将无效的条目添加到带时间戳的日志表中,当助手类获取时查询该日志表NOTIFY,或者只是使整个缓存无效。

您的助手类现在在触发器发送LISTEN的事件上。NOTIFY当它收到一个NOTIFY事件时,它可以使单个缓存条目无效(见下文),或刷新整个缓存。您可以使用PgJDBC 的监听/通知支持来监听来自数据库的通知。您将需要打开任何连接池管理java.sql.Connection器以访问底层 PostgreSQL 实现,以便您可以将其转换为org.postgresql.PGConnection并调用getNotifications()它。

LISTEN作为and的替代方法NOTIFY,您可以在计时器上轮询更改日志表,并在问题表上设置触发器,将更改的行 ID 和更改时间戳附加到更改日志表中。除了需要为每种数据库类型使用不同的触发器之外,这种方法将是可移植的,但它效率低且不及时。它需要频繁的低效轮询,并且仍然存在侦听/通知方法所没有的时间延迟。在 PostgreSQL 中,您可以使用UNLOGGED表来稍微降低这种方法的成本。

缓存级别

EclipseLink/JPA 有几个级别的缓存。

一级缓存在EntityManager级别。如果实体附加到EntityManagerby persist(...)merge(...)find(...)等,则在同一会话中再次访问该实体时,无论您的应用程序是否仍然具有对它的引用,EntityManager都需要返回该实体的相同实例。如果您的数据库内容已更改,则此附加实例将不是最新的。

二级缓存是可选的,属于一级缓存,是EntityManagerFactory比较传统的缓存。目前尚不清楚您是否启用了二级缓存。检查 EclipseLink 日志和persistence.xml. EntityManagerFactory.getCache()您可以使用;访问二级缓存。见Cache

@thedayofcondor 展示了如何刷新二级缓存:

em.getEntityManagerFactory().getCache().evictAll();

但您也可以通过以下evict(java.lang.Class cls, java.lang.Object primaryKey)调用逐出单个对象:

em.getEntityManagerFactory().getCache().evict(theClass, thePrimaryKey);

您可以从@Startup @Singleton NOTIFY侦听器中使用它来仅使那些已更改的条目无效。

一级缓存并不容易,因为它是应用程序逻辑的一部分。您将想了解EntityManager、附加和分离实体等如何工作。一种选择是始终对有问题的表使用分离的实体,EntityManager每当您获取实体时都使用新的实体。这个问题:

使 JPA EntityManager 会话无效

对处理实体管理器缓存的失效进行了有用的讨论。但是,缓存不太可能EntityManager是您的问题,因为 RESTful Web 服务通常使用短EntityManager会话来实现。EntityManager如果您使用扩展的持久性上下文,或者如果您正在创建和管理自己的会话而不是使用容器管理的持久性,这可能只是一个问题。

于 2012-11-07T00:11:24.457 回答
8

您可以完全禁用缓存(请参阅:http ://wiki.eclipse.org/EclipseLink/FAQ/How_to_disable_the_shared_cache%3F ),但要准备好相当大的性能损失。

否则,您可以通过编程方式执行清除缓存

em.getEntityManagerFactory().getCache().evictAll();

您可以将其映射到 servlet,以便在外部调用它 - 如果您的数据库很少在外部修改并且您只想确保 JPS 会选择新版本,这会更好

于 2012-11-06T20:43:09.263 回答
0

只是一个想法,但是您如何收到您的 EntityManager/Session/whatever?

如果您在一个会话中查询实体,它将在下一个会话中分离,您必须将其合并回持久性上下文中才能再次对其进行管理。

尝试使用分离的实体可能会导致那些非托管异常,您应该重新查询实体,或者您可以尝试使用合并(或类似方法)。

于 2012-11-06T20:57:47.350 回答
-4

JPA 默认不做任何缓存。您必须明确配置它。我相信这是您选择的架构风格的副作用:REST。我认为缓存发生在 Web 服务器、代理服务器等。我建议您阅读内容并进行更多调试。

于 2012-11-06T20:44:25.557 回答