我有一个持久保存数据的Web服务,其中一个方法需要在数据库事务中。实质上,它看起来像这样:
public abstract class ControllerBase {
//this is a method that needs to run in a transaction
void batchUpsert(/* ... */) {
try {
sql.query("BEGIN").execute();
sql.query("LOCK TABLE " + tableName + " IN EXCLUSIVE MODE").execute();
//do some stuff
} finally {
sql.query("COMMIT").execute();
}
}
}
Web服务在子类中定义并调用上面的方法:
@Path("symbology")
@Stateless
public class SymbologyController extends ControllerBase {
@PUT
@Path("upsert_symbology")
@Consumes(MediaType.APPLICATION_JSON)
public Response upsertSymbology(List<SymbologyRecord> symbology) {
batchUpsert(SYMBOLOGY, symbology, SYMBOLOGY.SHORT_NAME);
return Response.ok().build();
}
}
这感觉不对,因为交易部分应该由容器管理,而不是我发送BEGIN
和COMMIT
语句。
所以我删除了它们并在@TransactionAttribute
方法上添加了batchUpsert
注释:
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
void batchUpsert(/* ... */) {
sql.query("LOCK TABLE " + tableName + " IN EXCLUSIVE MODE").execute();
//do some stuff
}
但容器(wildfly)似乎没有创建一个事务,我得到一个例外:
错误:LOCK TABLE只能用于事务块
我也尝试了@TransactionAttribute
注释,但它也没有用。
我怎么能让wildfly知道它需要在数据库事务中包装我的方法?
修改
我刚刚意识到交易状态是活跃的:
@Resource TransactionSynchronizationRegistry tsr;
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
void batchUpsert(/* ... */) {
LOG.info("Transaction active? {}", tsr.getTransactionStatus() == Status.STATUS_ACTIVE);
//...
}
输出true。这不应该在数据库级别运行等效的BEGIN
语句吗?
答案 0 :(得分:1)
我通常将数据库逻辑分成不同的外观。
@Stateless
public void DBFacade{
@TransactionAttribute(REQUIRES_NEW(
public void doSomethingIntransaction(){}
}
@Stateless
@Path("/path")
public class MyFrontendService{
@EJB
private DBFacade dbFacade;
@PUT
@Path("upsert_symbology")
@Consumes(MediaType.APPLICATION_JSON)
public void updateUserData(){
dbFacade.doSomething();
}
}
你为什么要这样做?
因为如果你不这样做,容器就无法拦截batchUsert()
的调用,因为你直接调用了这个方法。但是,如果使用ejb注入,注入的实例是实际bean的代理,当您在代理上调用方法时,它们会拦截它,决定它是否需要活动事务,然后调用它持有的实际方法参考。这是唯一的ejb方式。您必须委派实际的调用。
答案 1 :(得分:1)
检查您的数据源是否在wildfly中启用了JTA声明:
<datasource jta="true" jndi-name="java:jboss/jdbc/ws" ...>