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



我在玩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.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应用程序开发指南中的这句话为我指明了正确的方向。

使用EJB定时器服务相当于与单个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. 单击下一步并将数据源类名设为默认值
  5. 至少(我相信)指定以下属性:
    • ServerName(例如localhost)
    • 端口号(例如1527)
    • 密码(例如测试)
    • 用户(例如测试)
    • URL(例如jdbc:derby://localhost:1527/Test)
    • 数据库名称(例如测试)
  6. 单击"完成">

ConnectionPool需要连接到JDBC资源,并获得服务器的JNDI名称才能使用它

  1. 转到->资源->JDBC->JDBC资源-新建
  2. 给它一个名称(例如jdbc/TestPool),然后选择您之前创建的池
  3. 如果您愿意,请添加说明(说明很酷)
  4. 单击"确定">

现在在PersistenceUnit(Persistence.xml)中添加新的连接池作为项目的DataSource。

<?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)相同的过程创建了一个新的连接池,但名称类似于"TimerJB"。要使其正常工作,您需要"安装"数据库,这意味着创建一个适合应用程序服务器的标准数据库。Payara和Glassfish为此提供了SQL:

%install-path-of-payara%glassfishlibinstalldatabases

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

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

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

最后一部分的灵感来自@ejohansson对一个相关问题的回答。

希望这能帮助到某人:)

最新更新