交易不会回滚

时间:2014-10-01 22:20:07

标签: java ejb jboss7.x rollback

我调用两个方法,第一个更新表,下一个在另一个表中插入记录。当第二个事务失败时,EJB没有执行第一个事务的回滚。

这是我的支持bean:

@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;
}

4 个答案:

答案 0 :(得分:8)

在这种情况下的关键问题是某些JBoss版本中数据源的默认值很差。原始代码很好,并且在其他应用程序服务器(WebSphere App Server和轻量级WebSphere Liberty)中正常工作。

在JBoss中创建的数据源不是JTA - 在管理控制台中,Use JTA设置未选中,xml相关设置为<datasource jta="false" ...。将此设置更改为true可解决问题。 (JohnB,你写的定义xa-datasource修复了,但由于我没有看到你的原始xml与数据源定义,我相信在更改数据源期间你也改变了这个有缺陷的jta =“false”设置)。它也适用于非xa数据源,正如Grzesiek测试的那样。

这是非常糟糕的默认值,因为它会导致事务不由容器管理,而会导致EJB 组件中的连接中出现错误的事务行为。

非常感谢帮助我诊断此问题的Grzesiek D.

答案 1 :(得分:5)

请尝试此

@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管理的事务)。但正如我们所看到的,您正在使用 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数据源一起使用。


更新2

用户@Gas解决了following comment中的问题。感谢他。

基本上:原始代码没有任何问题。问题在于数据源的配置(必须 JTA 启用)。因此,通过JBoss管理控制台编辑数据源配置,并设置一个复选框“使用JTA ”。

祝你好运。

答案 2 :(得分:4)

更新:错误猜测,请参阅answer below更新。


我几乎可以肯定您的问题是由于您使用new关键字手动创建DAO 造成的:

@PostConstruct
public void init() {
    dao = new XxxxDAOImpl();
}

当您执行此类操作时,您的ejb容器无法管理该对象的生命周期和事务边界 您应该让容器创建和管理dao(并为您注入)。这样,您将在所有EJB方法中获得正确的事务传播 - 因此您的问题将得到解决。

要实现这一点,您只需使用@Stateless注释DAO课程,并将其注入TransactionTestServiceImpl课程中:

@EJB
private XxxxDAO dao;

然后,当然,删除 init 销毁方法。

个人建议

为什么要使用单独的额外dao图层?在Java EE世界中,最方便的选择是使用EntityManager。实体管理器在大多数用例中扮演dao的角色。在你的例子中,Altough JDBC足够好,JPA(IMHO)更简单。

祝你好运

答案 3 :(得分:1)

我认为问题在于Connection / DataSource根本不是您当前事务的一部分。我建议不要注入JDBC连接:

  1. 在JTA类型的persistence.xml中为已定义的DataSoruce创建PersistenceUnit。
  2. 在EJB中注入相应的EntityManager
  3. 从步骤2中注入的Connection中解开EntityManager。没有标准方法,请检查this answer