【问题标题】:Execute task before EntityManager is closed在 EntityManager 关闭之前执行任务
【发布时间】:2014-08-27 03:47:15
【问题描述】:

我想在 EntityManager 关闭之前执行一项任务(例如,在关闭 AS 或取消部署应用程序时)并寻找 Hook 或 Listener 或类似的东西。

实际问题: 我想用我的应用程序在数据库中保存很多微小的数据。为了减轻数据库的负载,我将数据缓存在一个列表中,并希望在给定的时间间隔内保存所有数据。

到目前为止,这很好用,但如果 AS 关闭,数据将会丢失。这就是我想在 EntityManager 关闭之前保存数据的原因。

到目前为止我尝试了什么: 我尝试使用 @PreDestroy 注释在 bean 被销毁之前保存数据。不幸的是,EntityManager 的使用不起作用,正如我稍后阅读的那样,PreDestroy 方法中不允许使用。

@Singleton
@Startup
@DependsOn(value = "StatisticRepository")
public class StatisticService {
    private static final Logger LOG = Logger.getLogger(StatisticService.class.getName());

    @EJB
    private StatisticRepository repository;
    private List<Statistic> stats = new ArrayList<>();

    @PreDestroy
    public void destroy() {
        LOG.log(Level.INFO, "Saving before destroying Service.");
        for (Statistic stat : stats) {
            // ---> EntityManager in Repository already destroyed
            repository.create(stat);
        }
        stats.clear();
    }
...
}

.

@Singleton
@Startup
public class StatisticRepository extends BaseRepository<Statistic>{
    public StatisticRepository() {
        super(Statistic.class);
    }

    @PersistenceContext(unitName = PERSISTENCE_UNIT_NAME)
    EntityManager em;

    @Override
    protected EntityManager getEntityManager() {
        return em;
    }
    ...
}

.

@MappedSuperclass
public abstract class BaseRepository<T extends Serializable> {

    protected abstract EntityManager getEntityManager();
    private final Class<T> entityClass;

    public BaseRepository(Class<T> entityClass) {
        this.entityClass = entityClass;
    }

    public T create(T entity) {
        getEntityManager().persist(entity);
        getEntityManager().flush();
        return this.edit(entity);
    }
    ...
}

我在

上遇到了这个异常 信息:在销毁服务之前保存统计信息。 警告:RAR5114:分配连接时出错:[{ PoolInfo : (name=java:app/pool), (applicationName=AppName) }: Esist kein Poolmetadaten-Objekt mit dem Pool { PoolInfo : (name=java:app/pool) , (applicationName=AppName) } 验证。 Stellen Sie die Anwendung erneut bereit。 ] 警告:本地异常堆栈: 异常 [EclipseLink-4002] (Eclipse Persistence Services - 2.5.0.v20130507-3faac2b): org.eclipse.persistence.exceptions.DatabaseException 内部异常:java.sql.SQLException: { PoolInfo : (name=java:app/pool), (applicationName=AppName) }: Esist kein Poolmetadaten-Objekt mit dem Pool { PoolInfo : (name=java:app/pool) , (applicationName=AppName) } 验证。 Stellen Sie die Anwendung erneut bereit。 错误代码:0 ...

似乎池(应用范围的)已取消部署。

完整的堆栈跟踪:
here

我在 glassfish-resources.xml 上创建我的 JNDI 资源和连接池。因此,它不是应用服务器范围的资源。也许这就是重现错误的关键?

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE resources PUBLIC "-//GlassFish.org//DTD GlassFish Application Server 3.1 Resource Definitions//EN" "http://glassfish.org/dtds/glassfish-resources_1_5.dtd">
<resources>
  <!-- MySQL -->
  <jdbc-connection-pool allow-non-component-callers="false"
                        associate-with-thread="false"
                        connection-creation-retry-attempts="0"
                        connection-creation-retry-interval-in-seconds="10"
                        connection-leak-reclaim="false"
                        connection-leak-timeout-in-seconds="0"
                        connection-validation-method="auto-commit"
                        datasource-classname="com.mysql.jdbc.jdbc2.optional.MysqlDataSource"
                        fail-all-connections="false"
                        idle-timeout-in-seconds="170"
                        is-connection-validation-required="true"
                        is-isolation-level-guaranteed="true"
                        transaction-isolation-level="repeatable-read"
                        lazy-connection-association="false"
                        lazy-connection-enlistment="false"
                        match-connections="false"
                        max-connection-usage-count="0"
                        max-pool-size="100"
                        max-wait-time-in-millis="60000"
                        name="java:app/mysql_app_appPool"
                        non-transactional-connections="false"
                        ping="true"
                        pool-resize-quantity="2"
                        pooling="true"
                        res-type="javax.sql.DataSource"
                        statement-cache-size="0"
                        statement-leak-reclaim="false"
                        statement-leak-timeout-in-seconds="0"
                        statement-timeout-in-seconds="0"
                        steady-pool-size="20"
                        validate-atmost-once-period-in-seconds="0"
                        wrap-jdbc-objects="true">
    <property name="user" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="driverClass" value="com.mysql.jdbc.Driver"/>
  </jdbc-connection-pool>
  <jdbc-resource enabled="true" jndi-name="java:app/jdbc/app" object-type="user" pool-name="java:app/mysql_app_appPool"/>

.

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
  <!-- Persistence Unit for MySQL -->
  <persistence-unit name="com.app.web_app-webapp_war_1.0-SNAPSHOTPU" transaction-type="JTA">
    <jta-data-source>java:app/jdbc/app</jta-data-source>
    <exclude-unlisted-classes>false</exclude-unlisted-classes>
    <properties>
      <property name="javax.persistence.schema-generation.database.action" value="create"/>
      <property name="javax.persistence.schema-generation.create-source" value="metadata-then-script"/>
      <property name="javax.persistence.schema-generation.drop-source" value="metadata"/>
      <property name="javax.persistence.schema-generation-target" value="database"/>
    </properties>
  </persistence-unit>
</persistence>

问题: 您如何看待一般的机制? 缓存是否有意义,因为 JPA 可以自己更好地处理它? 有没有监听器或钩子?

【问题讨论】:

  • 你试过用@Transactional 注解装饰你的预销毁方法吗?另外,您为什么要自己执行数据缓存?您是否尝试过在您的 JPA 提供程序上配置缓存选项?
  • 另外,您可以使用带有 @Startup 注释的 Singleton ejb bean,但是您仍然需要澄清您是否正在为每个无状态调用缓存数据。
  • 你不应该重新发明轮子。通过使用 eclipselink,您可以获得大量 JPA 缓存选项。此外,如果您正在处理高可用性要求,您应该看看 Infinispan 以获取实际性能应用程序数据缓存。
  • 以防万一,删除“stats.clear();”线。该方法可能比 em.persist 操作更快,因此使实体无效(准备好被垃圾收集)。
  • persist-flush-merge 操作非常昂贵。仅当您不需要任何数据库生成的 ID 时才避免使用它们。单独使用 em.persist。

标签: jakarta-ee jpa glassfish eclipselink entitymanager


【解决方案1】:

在 eclipselink 上有很多缓存选项。看看here

另外,对于现实生活中的性能,我强烈建议使用 Infinspan。看看here

如果您的用例真的很简单,那么用@Startup 注释的@Singleton ejb bean 应该可以解决问题。像这样:

@Singleton
@Startup
public class StartupShutdownBean {

    @PostConstruct
    private void startup() {
        // your startup code here
    }

    @PreDestroy
    private void shutdown() {
        // your shutdown code here
    }

}

在这个 bean 上注入 EntityManager 应该没有任何问题。很可能,您不能在 bean 中使用实体管理器,因为事务在您执行“缓存更新”之前就结束了。

编辑:

在您调用@PreDestroy 之前,AS 似乎正在删除应用程序配置的资源,因此您应该在您的 asadmin 部署或重新部署命令中使用preserveAppScopedResources=true 参数,以便在这些操作期间保持池处于​​活动状态。

一些文档here

【讨论】:

  • 不幸的是,这对我不起作用。我也用“@DependsOn”对其进行了测试。顺便说一句,包含 entitymanager 的 bean 的所有其他操作都可以正常工作。似乎数据库的连接池(应用范围内)已在“@PreDestroy”上关闭。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-02-02
  • 2019-02-22
  • 2019-05-13
  • 2013-09-03
  • 2019-04-09
  • 2021-02-25
  • 2011-03-24
相关资源
最近更新 更多