Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
670 views
in Technique[技术] by (71.8m points)

hibernate - JPA ConstraintViolation vs Rollback

I think I just found that two different JPA implementations work differently for constraint violations and rolling-backs.

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

which implementation is working right?

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

As you can see, Bean Validation seems to be enabled for both providers.

EclipseLink throws CVE on EntityManager#persist() with rollback marked.
And Hibernate throws RB on EntityTransaction#commit().

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Here are more detailed sources about the behavior you have.

According JPA 2 specification (page 102)

If the set of ConstraintViolation objects returned by the validate method is not empty, the persistence provider must throw the javax.validation.ConstraintViolationException containing a reference to the returned set of ConstraintViolation objects, and must mark the transaction for rollback.

And from the hibernate doc

If an entity is found to be invalid, the list of constraint violations is propagated by the ConstraintViolationException which exposes the set of ConstraintViolations.

This exception is wrapped in a RollbackException when the violation happens at commit time. Otherwise the ConstraintViolationException is returned [by Hibernate Validator] (for example when calling flush().)

Additionally, from jpa 2 specs (page 101)

By default, the default Bean Validation group (the group Default) will be validated upon the pre-persist and pre-update lifecycle validation events

Putting all of this together, I'm little confused because it seems to me that the behavior of HibernatePersistenceProvider doesn't follow the JPA 2 specs since:

  • validation must be performed on "pre-presist"
  • persistence provider MUST throw ConstraintViolationException

And obviously, in your case the ConstraintViolationException is not thrown when persist is called (and when using HibernatePersistenceProvider).

So according my understanding and to answer your question :

  • eclipselink is right
  • hibernate is wrong

(note: I hope that someone else can confirm or disagree with my analysis)


IMPORTANT EDIT

I was confused with my own conclusion. So I tried to reproduce myself the behavior described by the OP and I was unable to reproduce this behavior immediately.

What I did was really similar to what the OP is describing:

  • setup a little project, with one entity with a @NotNull field.
  • trying to persist() an instance with null for @NotNull field in a simple test.
  • asserting that the persist() operation throw a javax.validation.ConstraintViolationException + mark the transaction as rollback only
  • doing this when using eclipselink as persistence provider --> successful
  • doing this when using hibernate as persistence provider --> successful

The major difference between my test and the test described the OP was the id generation. In my successful test, I was using a simple @GeneratedValue.

After changing the id generation strategy to :

@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")

I found the exact behavior describe by the OP :

  • a javax.validation.ConstraintViolationException thrown by persist() when using eclipselink.
  • no exception at all thrown by persist() when using hibernate.

So, when using Hibernate + strategy = GenerationType.TABLE : the behavior is different. I'm quite sure it's not following JPA2 specifications.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...