【问题标题】:EntityManager.flush() commits the transaction in a Java web serviceEntityManager.flush() 在 Java Web 服务中提交事务
【发布时间】:2023-03-03 17:26:01
【问题描述】:

编辑感谢大家的回答,但问题出在我的数据源配置上,它实际上处于自动提交模式。详情请见my answer below

EntityManager.flush() 方法的 Javadoc 和在 Google 中搜索它似乎都表明 flush 方法只将待处理的语句发送到数据库而不提交事务。但是我创建的一个简单的测试 Web 服务(在 Java 7、Oracle 11gR2、JBoss 7.1 中并且 Web 服务被打包为一个 jar 文件)似乎表明并非如此:

这是表创建脚本:

CREATE TABLE test(
    id INTEGER NOT NULL,
    name VARCHAR2(20), 
    CONSTRAINT test_pk PRIMARY KEY ("ID")
);
CREATE SEQUENCE test_seq;

这是对应的实体:

@Entity @Table(name = "TEST")
public class Test implements Serializable {

    private static final long serialVersionUID = 9192814682033048425L;

    @Id @Column(name = "ID")
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "TEST_SEQ")
    @SequenceGenerator(name="TEST_SEQ",sequenceName="TEST_SEQ", allocationSize = 1)
    private Integer id;

    @Column(name = "NAME")
    private String name;

    // Getters and setters...
}

还有测试网络服务:

@Stateless @WebService(serviceName = "TestService")
@TransactionManagement(TransactionManagementType.CONTAINER)
public class TestServiceBean implements TestService {

    @PersistenceContext
    private EntityManager entityManager;

    @Override
    public void createTest(String name) {
        Test test = new Test();
        test.setName(name);
        entityManager.persist(test);
        entityManager.flush();

        throw new RuntimeException();
    }
}

我的理解是:

  • 当调用createTest 方法时,应用程序启动一个新事务
  • persist() 方法生成要发送到数据库的 INSERT 语句
  • flush() 方法将 INSERT 语句发送到数据库但不提交事务!
  • RuntimeException 导致事务回滚。

但显然我的理解是错误的:每次我运行 Web 服务方法时,我都会在表中得到一个新行。此外,使用调试器单步执行此方法会显示在调用 flush() 方法时插入了该行(我可以使用 SQL Developer 从另一个数据库会话“看到”该行)。

有人可以解释一下这种行为吗?

【问题讨论】:

    标签: java oracle jpa jax-ws ejb-3.0


    【解决方案1】:

    看来flush()毕竟没有什么问题。问题是我没有在 JBoss 中正确设置数据源。这里的教训是,如果你想在 EBJ 中使用容器管理的事务,那么你需要:

    • 在 JBoss 中,选中数据源配置中的 Use JTA? 复选框。
    • 在 Weblogic 中,选中数据源配置的“事务”选项卡中的支持全局事务复选框。

    此外,为了消除任何混淆,我的代码中的事务管理是正确的。抛出 RuntimeException 确实回滚异常。这是为什么?嗯,from the Java EE 6 教程我们有:

    如果抛出系统异常,容器会自动回滚事务。

    但是什么是系统异常?本教程似乎没有进一步涉及该主题,因此让我们搜索EJB spec。在第 382 页,我们有:

    系统异常是 java.rmi.RemoteException(或其子异常之一) classes) 或不是应用程序异常的 RuntimeException。

    好的,那么也许 RuntimeException 是应用程序异常?不,不是,因为在第 380 页我们有这个:

    作为检查异常的应用程序异常可以通过在 bean 的业务接口、无接口视图、主接口、组件接口和 Web 服务端点的方法的 throws 子句中列出来定义。作为未经检查的异常的应用程序异常被定义为应用程序异常,方法是使用 ApplicationException 元数据注释对其进行注释,或者在部署描述符中使用 application-exception 元素对其进行表示。

    所以,因为我没有做上面列出的任何事情,所以我在代码中抛出的异常确实是系统异常,并且确实会回滚事务如果您已将数据源设置为使用 JTA

    【讨论】:

      【解决方案2】:

      要在 EJB 方法中回滚事务,您应该调用 setRollbackOnly() 方法,否则即使抛出异常退出方法也会导致事务被提交。 更详细的解释可以参考The Java EE 6 Tutorial

      【讨论】:

      • 并非如此。如果我删除 throw 语句,将 EJBContext 注入到我的 bean 并调用 context.setRollbackOnly(); 我仍然会得到表中的行。
      • 这听起来很奇怪。您的配置中必须有一些东西将数据库标记为自动提交或类似的东西。 flush 方法将实体与数据库同步,但在事务边界内,因此插入的行应该在事务内可见并且一旦提交,否则它们应该消失。仔细检查您的配置(JDBC 驱动程序、持久性单元中的自动提交属性等)。
      • 谢谢,你的评论让我找到了我在另一个答案中写的实际问题。
      • 那么请改进你的答案,因为它是错误的。我在我的问题中编写的代码正确回滚了事务(如果设置正确!)并且不需要调用 setRollbackOnly()
      • 没关系,没关系,反正Java EE 6规范是这么规定的,可能JBoss的实现不完全符合。
      【解决方案3】:

      引用 JPA 规范第 23 页的 JSR-317:

      属性访问器方法抛出的运行时异常会导致当前事务被标记为回滚。当持久性运行时用于加载或存储持久性状态时,此类方法引发的异常会导致持久性运行时将当前事务标记为回滚,并引发包装应用程序异常的 PersistenceException。

      因此,您抛出的 RuntimeException 应该从实体 bean setter 中抛出,而不是从 EJB 中抛出。

      我确定您没有得到包装 PersistenceException - 表示回滚标记 - 而是得到了您抛出的 RuntimeException。

      尝试从 EJB 中抛出 RollbackException 而不是 RuntimeException!!!

      或者按照规范从实体 bean 设置器中抛出 RuntimeException。

      【讨论】:

      • 我已经验证了 @mavroprovato 在 Glassfish 3.1.2.2 容器上编写的代码,它成功回滚了事务。 Java EE 6 规范说:If the bean throws an application exception, the rollback is not automatic but can be initiated by a call to setRollbackOnly. 不管怎样,就是这样,你从 EJB 中抛出一个 RuntimeException 并且事务回滚。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-24
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多