我叫两个方法,第一个方法更新一个表,第二个方法在另一个表中插入一条记录。当第二个事务失败时,EJB
不会回滚第一个事务。
这是我的后援豆:
@ManagedBean
@ViewScoped
public class TransactionTestBean implements Serializable {
@EJB
private TransactionTestService service;
public String loadView() {
return "/test/transactionTest";
}
public void test() {
try {
service.updateTest();
} catch (Exception e) {
}
}
}
EJB接口:
@Local
public interface TransactionTestService {
void updateTest() throws CustomException;
}
EJB类:
@Stateless
@TransactionManagement
public class TransactionTestServiceImpl implements TransactionTestService {
@Resource(mappedName = "java:jboss/datasources/xxxxxDS", shareable = true)
public DataSource dataSource;
private TransactionTestDAO dao;
@PostConstruct
public void init() {
dao = new TransactionTestDAOImpl();
}
@PreDestroy
public void destroy() {
dao = null;
}
@Override
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void updateTest() throws CustomException {
try (Connection connection = dataSource.getConnection()) {
dao.updateRecord(connection);
// dao.saveRecord(connection);
} catch (SQLException exception) {
throw new CustomException(exception, exception.getMessage());
}
}
}
还有我的自定义例外:
@ApplicationException(rollback = true)
public class CustomException extends Exception {
public CustomException(Throwable cause, String message) {
super(message, cause);
}
}
编辑:
已添加DAO类:
public class TransactionTestDAOImpl implements TransactionTestDAO {
@Override
public void updateRecord(Connection connection) throws CustomException {
PreparedStatement preparedStatement = null;
try {
preparedStatement = connection.prepareStatement("UPDATE table_x SET field_x = ? WHERE field_y = 1");
preparedStatement.setInt(1, 1);
preparedStatement.executeUpdate();
} catch (Exception exception) {
throw new CustomException(exception, exception.getMessage());
} finally {
if (preparedStatement != null) {
try {
preparedStatement.close();
} catch (SQLException sqlException) {
}
}
}
}
}
和DAO界面:
public interface TransactionTestDAO {
void updateRecord(Connection connection) throws CustomException;
}
在这种情况下,关键问题是某些JBoss版本的数据源中的默认错误。原始代码很好,并且可以在其他应用程序服务器(WebSphere App Server和轻量级WebSphere Liberty)中正常工作。
在JBoss中创建的数据源不是JTA-在管理控制台中未选中Use JTA
设置,在xml相关设置中为<datasource jta="false" ...
。将此设置更改为true
可解决此问题。 (JohnB,您曾写道,定义xa-datasource可以解决此问题,但是由于我没有看到带有数据源定义的原始xml,因此我相信在更改数据源期间,您还会更改有缺陷的jta =“ false”设置)。经过Grzesiek测试,它也将适用于非xa数据源。
这是非常糟糕的默认设置,因为它会导致事务不由容器管理,并且在EJB中获得的连接中引起错误的事务行为组件。
非常感谢Grzesiek D.帮助我诊断了此问题。
请尝试一下
@Override
public void updateTest() throws CustomException {
Connection connection = dataSource.getConnection();
try {
connection.setAutoCommit(false); // this should be the key
dao.updateRecord(connection);
dao.saveRecord(connection);
connection.commit();
} catch(Exception ex) {
connection.rollback();
throw new CustomException(ex, ex.getMessage());
} finally {
if(connection != null) {
connection.close();
}
}
}
我上面的答案有一个错误,因为上面的代码假定使用BMT(Bean-Managed Transactions)。但是,正如我们所看到的,您正在使用CMT(容器管理的事务)。因为@TransactionManagement
等于@TransactionManagement(TransactionManagementType.CONTAINER)
)。
以上代码片段将仅与BMT一起使用。使用CMT,您应该得到如下错误:
Caused by: java.sql.SQLException: You cannot set autocommit during a managed transaction!
但是,我的错误最终变成了有益的事情,因为当你写的时候
这很好用(...)
然后我们找到了答案:您认为您的EJB bean 使用CMT与JTA一起使用,但是由于某些错误,它没有。
在下面的评论中,我还建议您使用JPA,但是在这种简单的情况下,JDBC就足够了。 CMT事务也可以与JDBC自由使用。
这里的数据源类型也无关紧要。 CMT可以与非XA数据源(也称为本地数据源)和XA数据源自由使用。
User @Gas解决了following comment中的问题。为他表示敬意。
基本上:原始代码没有任何问题。问题在于数据源的配置(必须启用JTA)。因此,可以通过JBoss管理控制台编辑数据源配置,并设置复选框“ 使用JTA”。
UPDATE:不好的猜测,请参见answer below及其更新。
我几乎可以确定您的问题是由您造成的,因为您正在使用new
关键字创建DAO 手动
@PostConstruct
public void init() {
dao = new XxxxDAOImpl();
}
当您这样做时,您的ejb容器无法管理该对象的生命周期和事务边界。您应该让容器创建和管理dao(并为您注入)。这样,您将在所有EJB方法之间获得适当的事务传播-这样就可以解决您的问题。
要实现这一点,您可以简单地用@Stateless
注释DAO类,并使用以下命令将其注入到TransactionTestServiceImpl
类中:]
和destroy方法。@EJB private XxxxDAO dao;
然后,当然,删除init
为什么要使用单独的附加道层?在Java EE世界中,最方便的选择是仅使用EntityManager
。在大多数用例中,实体管理器在dao中的作用非常好。您的示例中的JDBC足够好,JPA(IMHO)更简单。
祝你好运
我认为问题在于Connection / DataSource根本不属于您当前事务的一部分。我建议不要注入JDBC连接: