【问题标题】:EJB Timer Service no longer available after adding Entity and Persistent Unit添加实体和持久单元后,EJB 计时器服务不再可用
【发布时间】:2017-01-20 18:19:18
【问题描述】:

我正在使用 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)。在此之后使用计时器很好。我可以这样开始和取消它:

@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 Unit (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 计时器和持久实体运行应用程序吗?

【问题讨论】:

    标签: jpa jakarta-ee timer payara


    【解决方案1】:

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

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

    使用 EJB Timer Service 相当于与单个 JDBC 资源管理器进行交互。如果 EJB 组件或应用程序通过 JDBC 直接或间接(例如,通过实体 bean 的持久性机制)访问数据库,并且还与 EJB Timer Service 交互,则其数据源必须配置有 XA JDBC 驱动程序。

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

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

    为此:

    1. 创建一个数据库,为其指定一个名称并指定用户名密码
    2. 进入 Payaras 管理页面 -> 资源 -> JDBC -> JDBC 连接池 -> 新建
    3. 指定:
      • 一个名称,在我的例子中是“TestPool”
      • 选择资源类型为:“javax.sql.XADataSource”
      • 选择您的供应商:我使用 JavaDB
    4. 点击下一步,让Datasource Classname为默认值
    5. 至少指定(我相信)以下属性:
      • 服务器名称(例如本地主机)
      • 端口号(例如 1527)
      • 密码(例如测试)
      • 用户(例如测试)
      • 网址(例如 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”。为了让它正常工作,您需要“安装”数据库,这意味着创建一个适合应用程序服务器的标准数据库。 Payara 和 Glassfish 为此提供了 SQL:

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

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

    1. 转到 -> 配置 -> 服务器配置 -> EJB 容器 -> EJB 计时器服务
    2. 输入您为 TimerEJB 连接池(第 8 步)创建的 JNDI 名称(资源名称)作为“计时器数据源”
    3. 保存

    现在重新启动一切,或者至少重新启动 Payara 和数据库服务器,您应该一切顺利。

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

    希望这可以帮助某人:)

    【讨论】:

    • 因为您使用的是非持久性计时器,这可能是 Payara/Glassfish 中的错误。非持久计时器不应该需要数据源。如果可以,请尝试使用新的 Payara Web Profile 分发,看看计时器是否开箱即用(Web Profile 根本不支持持久计时器,因此不需要计时器的数据源)。
    猜你喜欢
    • 1970-01-01
    • 2021-04-20
    • 1970-01-01
    • 1970-01-01
    • 2021-10-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多