JPA ConstraintViolation VS回滚(JPA ConstraintViolati

2019-07-18 06:30发布

我想我只是发现有两个不同的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();
    }
}

该实施工作的权利?

UPDATE

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();
        }
    }
}

persistence.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验证似乎需要为提供者启用。

的EclipseLink抛出的CVE EntityManager#persist()注明回滚。
和Hibernate抛出的RB EntityTransaction#commit()

Answer 1:

以下是关于你的行为更加详细的来源。

根据JPA 2规范 (第102页)

如果集合ConstraintViolation的对象由validate方法返回不为空,持久性提供必须抛出包含到返回set ConstraintViolation对象的参考的javax.validation.ConstraintViolationException,并且必须标记为回滚事务。

而从休眠DOC

如果一个实体被发现是无效的,违反约束的名单是由公开组ConstraintViolations的ConstraintViolationException传播。

当违规行为,会在提交时此异常被包裹在一个RollbackException。 否则(调用flush()时例如)的ConstraintViolationException返回[由Hibernate验证]

另外,从JPA 2种规格(101页)

默认情况下,默认Bean验证组(组默认)将在预坚持和更新前的生命周期验证事件进行验证

把所有的这一起,我有点困惑,因为它在我看来,HibernatePersistenceProvider的行为不符合JPA 2规范,因为:

  • 验证必须在“预presist”进行
  • 持久性提供者必须抛出 ConstraintViolationException

很显然,你的情况ConstraintViolationException并不时抛出persist被调用(使用HibernatePersistenceProvider时)。

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

  • 的EclipseLink是正确的
  • Hibernate是错误的

(注:我希望别人能证实或与我的分析持反对意见)


重要EDIT

我用我自己的结论混淆。 所以,我想重现我被这个OP所描述的行为,我无法立即重现此问题。

我所做的就是真的类似于在OP的描述:

  • 设置一个小项目,与一个实体@NotNull场。
  • 努力坚持()与空的一个实例@NotNull场在一个简单的测试。
  • 声称将persist()操作抛出一个javax.validation.ConstraintViolationException +事务标记为rollback only
  • 使用的EclipseLink作为持久性提供时,这样做 - >成功
  • 采用Hibernate持久性提供时,这样做 - >成功

我的测试和描述的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描述:

  • 一个javax.validation.ConstraintViolationException通过抛出persist()使用的EclipseLink时。
  • 也不例外,在所有抛出persist()使用Hibernate的时候。

因此,使用Hibernate +时strategy = GenerationType.TABLE :行为是不同的。 我敢肯定它不是以下JPA2规格。



Answer 2:

两者都是正确的。 JPA允许供应商抛出EntityExistsException在持续或冲洗另一个的PersistenceException /提交,这是我一直以为盖数据库例外。 我不知道休眠或您获得完整的错误,但我想这该数据库异常是发生和被包裹在一个RollbackException。

这两个测试可能并不等同,但 - ConstraintViolationException不是从JPA而是验证(JSR-303)出现以后,prepersist期间发生。 您必须在的EclipseLink测试启用(如Hibernate验证器,4.0.1.GA.jar在classpath)的Bean验证实现,可能无法在Hibernate测试启用。 如果从一个删除Bean验证或添加到其他的,他们应该表现得更加相似。



文章来源: JPA ConstraintViolation vs Rollback