我想我刚刚发现,两种不同的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抛出RB EntityTransaction#commit()。
EntityManager#persist()
EntityTransaction#commit()
这里是有关您的行为的更多详细信息。
根据JPA 2规范(第102页)
如果validate方法返回的ConstraintViolation对象集不为空,则持久性提供程序必须抛出javax.validation.ConstraintViolationException,其中包含对返回的ConstraintViolation对象集的引用,并且必须将事务标记为回滚。
并从hibernate文档
如果发现一个实体无效,则ConstraintViolationException传播约束违例列表,该约束公开了ConstraintViolations集。 当在提交时发生违规时,此异常包装在RollbackException中。否则,[由Hibernate Validator]返回ConstraintViolationException(例如,在调用flush()时)。
如果发现一个实体无效,则ConstraintViolationException传播约束违例列表,该约束公开了ConstraintViolations集。
当在提交时发生违规时,此异常包装在RollbackException中。否则,[由Hibernate Validator]返回ConstraintViolationException(例如,在调用flush()时)。
此外,从jpa 2规格开始(第101页)
默认情况下,默认Bean验证组(默认组)将在持久化和更新前生命周期验证事件时进行验证
将所有这些放在一起,我几乎不会感到困惑,因为在我看来,HibernatePersistenceProvider的行为不遵循JPA 2规范,因为:
显然,在您的情况下,调用(以及使用HibernatePersistenceProvider时)ConstraintViolationException不会引发persist。
ConstraintViolationException
persist
因此,根据我的理解并回答您的问题:
(注意:我希望其他人可以确认或不同意我的分析)
重要编辑
我对自己的结论感到困惑。因此,我尝试重现OP描述的行为,但无法立即重现此行为。
我所做的与OP描述的内容非常相似:
@NotNull
persist()
javax.validation.ConstraintViolationException
rollback only
我的测试与描述OP的测试之间的主要区别是id生成。在成功的测试中,我使用了一个简单的@GeneratedValue。
@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描述的确切行为:
因此,当使用Hibernate +时 strategy = GenerationType.TABLE:行为是不同的。我很确定它没有遵循JPA2规范。
strategy = GenerationType.TABLE