4

我想我刚刚发现两种不同的 JPA 实现对于违反约束和回滚的工作方式不同。

@Test(expectedExceptions = @@.class) // CVE or RB?
public void testXXX() {
    final EntityManager manager = LocalPU.createEntityManager();
    try {
        final EntityTransaction transaction = manager.getTransaction();
        transaction.begin();
        try {
            manager.persist(<wrong>); // this is where CVE coming from
            transaction.commit();     // this is where RB coming from
        } catch (RollbackException re) {
            // <---------------------------------------- hibernate here
            throw re;
        } catch (ConstraintViolationException cve) {
            // <---------------------------------------- eclipselink here
            transaction.rollback();
            throw cve;
        } catch (Exception e) {
            transaction.rollback();
            e.printStackTrace(System.err);
            Assert.fail(e.getMessage());
        }
    } finally {
        manager.close();
    }
}

哪个实施工作正确?

更新

NameMustNotBeNull.java

@Entity
@Table(name = "NAME_MUST_NOT_BE_NULL")
public class NameMustNotBeNull {

    protected NameMustNotBeNull() {
        this(null);
    }

    public NameMustNotBeNull(final String name) {
        super();

        this.name = name;
    }

    @Id
    @GeneratedValue(strategy = GenerationType.TABLE,
                    generator = "NAME_MUST_NOT_BE_NULL_ID_GENERATOR")
    @TableGenerator(name = "NAME_MUST_NOT_BE_NULL_ID_GENERATOR",
                    table = PrimaryKeyValue.TABLE,
                    pkColumnName = PrimaryKeyValue.PK_COLUMN_NAME,
                    valueColumnName = PrimaryKeyValue.VALUE_COLUMN_NAME,
                    pkColumnValue = "NAME_MUST_NOT_BE_NULL_ID")
    @NotNull
    @XmlTransient
    private Long id;

    @Basic(optional = false)
    @Column(name = "NAME", nullable = false)
    @NotNull
    private String name;
}

NameMustNotBeNullTest.java

public class NameMustNotBeNullTest {

    @Test(expectedExceptions = RollbackException.class)
    public void testNullName() {

        final EntityManager manager = LocalPU.createEntityManager();
        try {
            final EntityTransaction transaction = manager.getTransaction();
            transaction.begin();
            try {
                final NameMustNotBeNull entity = new NameMustNotBeNull(null);
                try {
                    manager.persist(entity);
                } catch (ConstraintViolationException cve) {
                    System.out.println(cve.toString());
                }
                transaction.commit();
                Assert.fail("persisted with null name");
            } catch (RollbackException re) {
                System.out.println(re.toString());
                throw re;
            } catch (Exception e) {
                transaction.rollback();
                e.printStackTrace(System.err);
                Assert.fail(e.getMessage());
            }
        } finally {
            manager.close();
        }
    }
}

持久性.xml

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">

  <persistence-unit name="localPU" transaction-type="RESOURCE_LOCAL">

    <!-- I'm testing with one of following providers uncommented -->
    <!--<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>-->
    <provider>org.hibernate.ejb.HibernatePersistence</provider>

    <class>....persistence.NameMustNotBeNull</class>

    <properties>

      <property name="javax.persistence.jdbc.driver"
                value="org.apache.derby.jdbc.EmbeddedDriver"/>
      <property name="javax.persistence.jdbc.url"
                value="jdbc:derby:memory:corrsDB;create=true"/>
      <!--<property name="javax.persistence.jdbc.user" value=""/>-->
      <!--<property name="javax.persistence.jdbc.password" value=""/>-->

      <!-- eclipselink -->
      <property name="eclipselink.create-ddl-jdbc-file-name" value="target/createDDL.jdbc"/>
      <property name="eclipselink.ddl-generation" value="create-tables"/>
      <property name="eclipselink.ddl-generation.output-mode" value="both"/>
      <property name="eclipselink.drop-ddl-jdbc-file-name" value="target/dropDDL.jdbc"/>
      <property name="eclipselink.logging.level.sql" value="INFO"/>
      <property name="eclipselink.logging.parameters" value="false"/>
      <property name="eclipselink.target-database" value="Derby"/>

      <!-- hibernate -->
      <property name="hibernate.archive.autodetection" value="class" />
      <property name="hibernate.format_sql" value="true" />
      <property name="hibernate.hbm2ddl.auto" value="create-drop"/>
      <property name="hibernate.show_sql" value="false" />
      <property name="hibernate.dialect" value="org.hibernate.dialect.DerbyDialect"/>

    </properties>
  </persistence-unit>
</persistence>

org.eclipse.persistence.jpa.PersistenceProvider

Running ...NameMustNotBeNullTest
1월 17, 2013 11:45:14 오전 org.hibernate.validator.internal.util.Version <clinit>
INFO: HV000001: Hibernate Validator 4.3.0.Final
javax.validation.ConstraintViolationException: Bean Validation constraint(s) violated while executing Automatic Bean Validation on callback event:'prePersist'. Please refer to embedded ConstraintViolations for details.
javax.persistence.RollbackException: Transaction rolled back because transaction was set to RollbackOnly.

org.hibernate.ejb.HibernatePersistence

Running ...NameMustNotBeNullTest
1월 17, 2013 11:50:14 오전 org.hibernate.validator.internal.util.Version <clinit>
INFO: HV000001: Hibernate Validator 4.3.0.Final
javax.persistence.RollbackException: Error while committing the transaction

如您所见,两个提供者似乎都启用了 Bean Validation。

EclipseLink 抛出 CVEEntityManager#persist()并标记回滚。
并且 Hibernate 将 RB 抛出EntityTransaction#commit()

4

2 回答 2

8

以下是有关您的行为的更详细信息。

根据JPA 2 规范(第 102 页)

如果 validate 方法返回的 ConstraintViolation 对象集不为空,则持久性提供程序必须抛出包含对返回的 ConstraintViolation 对象集的引用的 javax.validation.ConstraintViolationException,并且必须将事务标记为回滚。

休眠文档

如果发现实体无效,则约束违规列表由 ConstraintViolationException 传播,该异常公开了一组 ConstraintViolation。

当在提交时发生冲突时,此异常被包装在 RollbackException 中。否则, [由 Hibernate Validator] 返回 ConstraintViolationException(例如在调用 flush() 时。)

此外,来自 jpa 2 规范(第 101 页)

默认情况下,默认的 Bean Validation 组(组 Default)将根据 pre-persist 和 pre-update 生命周期验证事件进行验证

将所有这些放在一起,我有点困惑,因为在我看来 HibernatePersistenceProvider 的行为不遵循 JPA 2 规范,因为:

  • 必须在“pre-presist”上执行验证
  • 持久性提供者必须抛出ConstraintViolationException

显然,在您的情况下,调用时(以及使用 HibernatePersistenceProvider 时)ConstraintViolationException不会抛出。persist

所以根据我的理解并回答你的问题:

  • eclipselink是对的
  • 休眠是错误的

(注:希望别人能证实或不同意我的分析)


重要编辑

我对自己的结论感到困惑。所以我试图重现自己 OP 描述的行为,但我无法立即重现这种行为。

我所做的与OP所描述的非常相似:

  • 设置一个小项目,其中一个实体有一个@NotNull字段。
  • 试图@NotNull在一个简单的测试中坚持()一个字段为空的实例。
  • 断言persist()操作抛出javax.validation.ConstraintViolationException+ 标记事务为rollback only
  • 使用 eclipselink 作为持久性提供程序时执行此操作 --> 成功
  • 使用休眠作为持久性提供者时这样做->成功

我的测试和描述 OP 的测试之间的主要区别是 id 生成。在我成功的测试中,我使用了一个简单的@GeneratedValue.

将 id 生成策略更改为:

@GeneratedValue(strategy = GenerationType.TABLE,
        generator = "NAME_MUST_NOT_BE_NULL_ID_GENERATOR")
@TableGenerator(name = "NAME_MUST_NOT_BE_NULL_ID_GENERATOR",
        pkColumnValue = "NAME_MUST_NOT_BE_NULL_ID")

我发现了 OP 描述的确切行为:

  • 使用eclipselink时javax.validation.ConstraintViolationException抛出的。persist()
  • persist()使用休眠时根本不会抛出异常。

所以,当使用 Hibernate + strategy = GenerationType.TABLE: 时,行为是不同的。我很确定它不遵循 JPA2 规范。

于 2013-02-11T10:49:36.770 回答
3

两者都是正确的。JPA 允许提供者在持久化时抛出 EntityExistsException 或在刷新/提交时抛出另一个 PersistenceException,我一直认为这是覆盖数据库异常。我不知道 Hibernate 或您得到的完整错误,但我猜数据库异常正在发生并被包装在 RollbackException 中。

虽然这两个测试可能不等效 - ConstraintViolationException 不是来自 JPA,而是来自 prepersist 期间发生的验证 (JSR-303)。您必须在 EclipseLink 测试中启用一个 bean 验证实现(例如类路径上的 hibernate-validator-4.0.1.GA.jar),该实现可能在 Hibernate 测试中未启用。如果您从一个中删除 bean 验证或将其添加到另一个,它们的行为应该更相似。

于 2013-01-16T13:08:46.697 回答