添加实体和持久单元后,EJB 定时器服务不再可用

EJB Timer Service no longer available after adding Entity and Persistent Unit

我正在玩弄 EJB 计时器,但在尝试 运行 同一项目中的计时器和持久实体时,我 运行 遇到了麻烦。在我的初始设置中,我只有计时器并且按预期触发:

@Stateless
public class TimerHandler {

    @Resource
    protected TimerService mTimerService;

    @PostConstruct
    public void init() {
        // could do cool stuff but choose not to
    }

    public Timer start(long aDuration) {
        TimerConfig conf = new TimerConfig();
        conf.setPersistent(false); // don't want the timer to be saved
        return mTimerService.createSingleActionTimer(aDuration, conf);
    }

    @Timeout
    public void timeOutAction(Timer aTimer) {
        // does fancy stuff
        System.out.println("So fancy :)");
    }

}

我在将计时器设为 运行 时遇到了一些麻烦,但我采用了蛮力方式并重新安装了 Payara (Glassfish)。在此之后使用 Timer 就可以了。我可以这样开始和取消它:

@Stateful
public class MyClass {

    @EJB
    private TimerHandler mTimerHandler;

    private Timer mTimer;

    public void startTimer(int aDuration) {
        mTimer = mTimerHandler.start(aDuration);
    }

    public void stopTimer() {
        try {
            mTimer.cancel();
        } catch (NoSuchObjectLocalException | NullPointerException ex) {
            System.out.println("There is no timer running.");
        }
    }
}

但是,在我尝试将实体添加到我的项目后,问题出现了。我的实体如下所示:

@Entity
public class TestEntity implements Serializable {

    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String testValue;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getTestValue() {
        return testValue;
    }

    public void setTestValue(String value) {
        testValue = value;
    }

    // removed standard code for @Override of equals(), 
    // hashCode() & toString()
}

我通过我的控制器 bean 操作的:

@Stateless
public class TestDBController {
    @PersistenceContext(unitName = "TimerTestWithDBPU")
    private EntityManager em;

    public long saveValue(String value) {
        TestEntity entity = new TestEntity();
        entity.setTestValue(value);
        em.persist(entity);
        em.flush();
        return entity.getId();
    }

    public String getValue(long aId) {
        TestEntity entity = em.find(TestEntity.class, aId);
        return entity.getTestValue();
    }
}

我的持久性单元 (persistence.xml) 设置如下:

<?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 name="TimerTestWithDBPU" transaction-type="JTA">
    <jta-data-source>jdbc/timer_test_pool</jta-data-source>
    <exclude-unlisted-classes>false</exclude-unlisted-classes>
    <properties>
      <property name="javax.persistence.schema-generation.database.action" 
                  value="create"/>
    </properties>
  </persistence-unit>
</persistence>

添加此实体和持久性单元后出现以下错误:

EJB Timer Service is not available. 
            Timers for application with id [XYZ] will not be deleted

这是为什么?您不能 运行 一个同时具有 ejb 计时器和持久实体的应用程序吗?

事实证明,你可以!谁会想到它...

Glassfish App Dev Guide 中的这句话为我指明了正确的方向。

Using the EJB Timer Service is equivalent to interacting with a single JDBC resource manager. If an EJB component or application accesses a database either directly through JDBC or indirectly (for example, through an entity bean’s persistence mechanism), and also interacts with the EJB Timer Service, its data source must be configured with an XA JDBC driver.

我将尝试描述我所做的事情(我是 Java EE 领域的新手,所以一些概念和功能名称可能不正确)

事实证明,在我的案例中,您需要在应用程序服务器 (Payara) 中配置一个资源,以便它使用 XA JDBC 驱动程序而不是普通的 JDBC 驱动程序。 (我不完全明白为什么,如果有人愿意详细说明,我很乐意听到)

为此:

  1. 创建一个数据库,给它一个名称并指定用户名密码
  2. 进入 Payaras 管理页面 -> 资源 -> JDBC -> JDBC 连接池 -> 新建
  3. 指定:
    • 一个名字,在我的例子中"TestPool"
    • 选择资源类型为:"javax.sql.XADataSource"
    • 选择您的供应商:我使用 JavaDB
  4. 点击下一步,让Datasource Classname为默认值
  5. 至少(我相信)指定以下属性:
    • 服务器名称(例如本地主机)
    • 端口号(例如 1527)
    • 密码(例如test)
    • 用户(例如测试)
    • URL(例如jdbc:derby://localhost:1527/Test)
    • 数据库名称(例如测试)
  6. 点击完成

ConnectionPool 需要连接到 JDBC 资源并为服务器获取 JNDI 名称才能使用它。在 Payara 的管理页面中:

  1. 转到 -> 资源 -> JDBC -> JDBC 资源 - 新
  2. 为其命名(例如 jdbc/TestPool)并选择您之前创建的池。
  3. 如果您愿意,请添加描述(描述很酷)
  4. 点击确定!

现在将新连接池添加为持久性单元 (persistance.xml) 中项目的数据源。

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1" xmlns="htt...>
  <persistence-unit name="TimerTestPU" transaction-type="JTA">
    <jta-data-source>jdbc/TestPool</jta-data-source> <!-- Magic Line -->
    .
    .
    .
  </persistence-unit>
</persistence>

至此我开始工作了。然而,似乎仍然存在问题,因为如果我 运行 其他应用程序使用计时器,同时仍然为数据库使用非 XA JBDC 驱动程序,它实际上破坏了我开始工作的东西。这表现为无法 Ping 默认的 __TimerPool 连接池,并出现以下错误(如果有人能阐明这一点,我会洗耳恭听):

java.lang.NoClassDefFoundError: Could not initialize class
        org.apache.derby.jdbc.EmbeddedDriver Could not initialize class 
        org.apache.derby.jdbc.EmbeddedDriver

我最终删除了 ejb 驱动程序 (__TimerPool) 的默认连接池,并使用与上述 (1-10) 相同的过程创建了一个新连接池,但是名称如 "TimerEJB"。为了让它正常工作,您需要 "install" 数据库,这意味着创建一个适合应用程序服务器的标准数据库。 Payara 和 Glassfish 为此提供了 SQL:

%install-path-of-payara%\glassfish\lib\install\databases\

在您创建的数据库上选择文件 ejbtimer_[VENDOR_NAME].sql 和 运行。在此之后,您应该将其设为 ejb 计时器的默认连接工具。在 Payaras 管理页面:

  1. 转到 -> 配置 -> server-config -> EJB 容器 -> EJB 定时器服务
  2. 输入您为 TimerEJB 连接池(第 8 步)创建的 JNDI 名称(资源名称)作为 "Timer Datasource"
  3. 保存

现在重新启动一切,或者至少重新启动 Payara 和数据库服务器,然后就可以开始了。

最后一部分的灵感来自@ejohansson's answer一个相关问题。

希望这对某人有所帮助:)