当前位置: 首页 > 知识库问答 >
问题:

JPA约束与回滚

易波涛
2023-03-14

我想我刚刚发现两种不同的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();
    }
}

哪个实现是正确的?

@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;
}
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 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>
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.
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在EntityTransaction#commit()上抛出RB。

共有2个答案

慕容嘉荣
2023-03-14

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

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

谢阳成
2023-03-14

这里有关于你行为的更详细的来源。

根据JPA 2规范(第102页)

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

从hibernate医生那里

如果发现实体无效,则ConstraintViolationException将传播约束冲突列表,该列表将公开ConstraintViolations集。

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

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

默认情况下,默认Bean验证组(组默认值)将在预持久化和预更新生命周期验证事件时进行验证

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

  • 验证必须在预准备上执行
  • 持久性提供者必须抛出约束违反异常

显然,在您的例子中,调用持久化时(以及使用HibernatePersistenceProvider时),不会抛出约束violationException

因此,根据我的理解并回答您的问题:

  • 日食是对的

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

重要编辑

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

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

  • 设置一个小项目,其中一个实体带有@NotNull字段
  • 在一个简单的测试中,尝试为@NotNull字段持久化()一个空的实例
  • 断言persist()操作抛出一个javax。验证。ConstraintViolationException将事务标记为仅回滚
  • 使用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.ConstraintViolationExc0019
  • 使用hibernate时,坚持()不会引发任何异常。

因此,当使用Hibernatestrategy=GenerationType时。表:行为不同。我很确定它没有遵循JPA2规范。

 类似资料:
  • 问题内容: 我正在尝试使用编写约束。我已经尝试过这样的事情: 虽然我知道这行不通。在以上语法中,如何添加要检查的集合/列表? 问题答案: seqs是集合。

  • 问题内容: 我正在JPA中对userid列强制执行唯一性约束检查,这是对user表中的所有记录强制执行的。 我的要求是,特定组织内的用户ID必须唯一,而不是在所有组织中都唯一。 如何执行此类检查? 问题答案: 您可以为唯一约束指定多个字段,请尝试: 通过这样做,您的约束条件将检查userid和Organizationid的组合是否唯一。 最好的祝福,费边

  • 问题内容: 有没有一种方法可以使用JPA指定在不同的列集上应该有多个唯一约束? 我已经看到了特定于hibernate的注释,但是由于我们仍在确定hibernate和数据核之间的关系,因此我试图避免使用特定于供应商的解决方案。 问题答案: 所述的属性实际接受的这些阵列。您的示例只是具有单个元素的数组的简写。否则,它看起来像: 只要唯一性约束仅基于一个字段,就可以在该列上使用。

  • 我正在做一个基本的例子来测试级联删除操作,但我得到了异常。这是我的实体

  • 我有一个函数,有一系列约束。当然,这些约束必须出现在使用的函数的签名中,所以我试图将约束包装在类型同义词。例如, 会成为 如果所有类型都公开,这将非常有效。但是,我使用函数依赖来生成约束列表中的一些类型,这些类型不会出现在的签名中。例如: GHC不接受,因为在LHS上没有绑定。我也不能使用,因为不在

  • 问题内容: 我想对Bean验证有一个限制,但这不是标准提供的。如果我要使用JPA ,则不会有独特的验证和错误报告机制。 有没有一种方法可以定义为Bean验证约束并将其与JPA结合使用,例如,JPA创建具有唯一约束的列并检查值是否唯一? 问题答案: 除非获得整个表的锁 ,否则基本上不可能使用SQL查询来检查唯一性(任何并发事务都可以在手动检查之后但在提交正在进行的事务之前修改数据)。换句话说,不可能