Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hibernate not following JPA specifications when combined with Bean Validation API?

This question is a follow up to this one : JPA ConstraintViolation vs Rollback

I did some test about combination of JPA and validation API (JSR-303).

I found the following in JPA specifications (page 101-102):

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

...

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.

I setup the following test:

  • HibernateValidator as JSR-303 implementation
  • 2 PersistenceProvider Hibernate and EclipseLink
  • one entity NameNotNullWithDefaultGeneratedStrategy with an id generated with default strategy (@Generated) and @NotNull String name column
  • another entity NameNotNullWithTableGeneratedStrategy with an id generated with table strategy (@TableGenerated) and @NotNull String name column
  • the test try to persist one instance of each entity with a null name.
  • the expected results are a javax.validation.ConstraintViolationException thrown by the persist method and the transaction marked as rollback only (i.e. those assumptions are based on JPA specification quoted in this post).

The results are :

  • with eclipse link as a provider :
    • the persist method throws a javax.validation.ConstraintViolationException for both entities.
    • the transaction is marked as rollback only in both cases
  • with hibernate as a provider :
    • persist throws a javax.validation.ConstraintViolationException for entity NameNotNullWithDefaultGeneratedStrategy + transaction marked as rollback only
    • persist don't throw any exception for entity NameNotNullWithTableGeneratedStrategy + transaction not flagged as rollback only
    • commit for NameNotNullWithTableGeneratedStrategy fails with a RollbackException

The questions are :

  • is it really a violation of JPA specifications ? or I am missing something with particular case of table generated strategy ?
  • in case it is a violation: is there any existing bug report related to it ?

Here is the code for my test:

package com.example.jpa.validator;
import org.junit.Assert;
import org.junit.Test;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.RollbackException;

public class ConstraintViolationExceptionTest {

    @Test
    public void testHibernateDefaultStrategy() {  // Success
        testPersistWithNullName("pu-hibernate",new NameNotNullWithDefaultGeneratedStrategy());
    }

    @Test
    public void testHibernateTableStrategy() {
        testPersistWithNullName("pu-hibernate",new NameNotNullWithTableGeneratedStrategy());
        //this test fail with :
        //java.lang.AssertionError: Expecting a javax.validation.ConstraintViolationException, but persist() succeed !
    }

    @Test
    public void testEclipseLinkDefaultStrategy() {  // Success
        testPersistWithNullName("pu-eclipselink",new NameNotNullWithDefaultGeneratedStrategy());
    }

    @Test
    public void testEclipseLinkTableStrategy() {  // Success
        testPersistWithNullName("pu-eclipselink",new NameNotNullWithTableGeneratedStrategy());
    }

    private void testPersistWithNullName(String persistenceUnitName, Object entity){
        EntityManagerFactory emf = Persistence.createEntityManagerFactory(persistenceUnitName);
        EntityManager entityManager = emf.createEntityManager();
        try {
            final EntityTransaction transaction = entityManager.getTransaction();
            transaction.begin();
            try {
                try {
                    entityManager.persist(entity);
                    Assert.fail("Expecting a javax.validation.ConstraintViolationException, but persist() succeed !");
                } catch (javax.validation.ConstraintViolationException cve) {
                    //That's expected
                    Assert.assertTrue("According JPA specs transaction must be flagged as rollback only",transaction.getRollbackOnly());
                } catch (Exception e) {
                    Assert.assertTrue("According JPA specs transaction must be flagged as rollback only",transaction.getRollbackOnly());
                    e.printStackTrace();
                    Assert.fail("Expecting a javax.validation.ConstraintViolationException, but got " + e.getClass());
                }
                transaction.commit();
                Assert.fail("persisted with null name !!!");
            } catch (RollbackException e) {
                //That's expected
            }  catch (Exception e) {
                e.printStackTrace();
                Assert.fail("Unexpected exception :"+e.getMessage());
            }
        } finally {
            entityManager.close();
        }
    }
}

The entities

Default strategy

package com.example.jpa.validator;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.validation.constraints.NotNull;

@Entity
public class NameNotNullWithDefaultGeneratedStrategy {

    @Id @GeneratedValue private Long id;
    @NotNull public String name;
    public NameNotNullWithDefaultGeneratedStrategy() {}
}

Table stategy:

package com.example.jpa.validator;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.TableGenerator;
import javax.validation.constraints.NotNull;

@Entity
public class NameNotNullWithTableGeneratedStrategy {

    @GeneratedValue(strategy = GenerationType.TABLE,
        generator = "NAME_MUST_NOT_BE_NULL_ID_GENERATOR")
    @TableGenerator(name = "NAME_MUST_NOT_BE_NULL_ID_GENERATOR")
    @Id @NotNull private Long id;
    @NotNull public String name;
    public NameNotNullWithTableGeneratedStrategy() {}
}

The 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="pu-hibernate" transaction-type="RESOURCE_LOCAL">
            <provider>org.hibernate.ejb.HibernatePersistence</provider>
            <class>com.example.jpa.validator.NameNotNullWithTableGeneratedStrategy</class>
            <class>com.example.jpa.validator.NameNotNullWithDefaultGeneratedStrategy</class>
            <properties>
                <property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
                <property name="javax.persistence.jdbc.url" value="jdbc:h2:mem:test_mem_hibernate"/>
                <property name="hibernate.hbm2ddl.auto" value="create-drop"/>
                <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
            </properties>
        </persistence-unit>
        <persistence-unit name="pu-eclipselink" transaction-type="RESOURCE_LOCAL">
            <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
            <class>com.example.jpa.validator.NameNotNullWithTableGeneratedStrategy</class>
            <class>com.example.jpa.validator.NameNotNullWithDefaultGeneratedStrategy</class>
            <properties>
                <property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
                <property name="javax.persistence.jdbc.url" value="jdbc:h2:mem:test_mem"/>
                <property name="eclipselink.ddl-generation" value="create-tables"/>
                <property name="eclipselink.target-database" value="org.eclipse.persistence.platform.database.H2Platform"/>
            </properties>
        </persistence-unit>
    </persistence>

The pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>

        <groupId>com.example</groupId>
        <artifactId>com.example.jpa.validator</artifactId>
        <version>1.0-SNAPSHOT</version>
        <properties>
            <hibernate.version>4.2.0.CR1</hibernate.version>
            <hibernate-validator.version>4.3.1.Final</hibernate-validator.version>
            <junit.version>4.11</junit.version>
            <h2.version>1.3.170</h2.version>
        </properties>

        <dependencies>
            <dependency>
                <groupId>org.hibernate</groupId>
                <artifactId>hibernate-validator</artifactId>
                <version>${hibernate-validator.version}</version>
            </dependency>
            <dependency>
                <groupId>com.h2database</groupId>
                <artifactId>h2</artifactId>
                <version>${h2.version}</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <scope>test</scope>
                <version>${junit.version}</version>
            </dependency>

            <dependency>
                <groupId>org.hibernate</groupId>
                <artifactId>hibernate-core</artifactId>
                <version>${hibernate.version}</version>
            </dependency>
            <dependency>
                <groupId>org.hibernate</groupId>
                <artifactId>hibernate-entitymanager</artifactId>
                <version>${hibernate.version}</version>
            </dependency>

            <dependency>
                <groupId>org.eclipse.persistence</groupId>
                <artifactId>org.eclipse.persistence.jpa</artifactId>
                <version>2.4.0</version>
            </dependency>
            <dependency>
                <groupId>org.eclipse.persistence</groupId>
                <artifactId>javax.persistence</artifactId>
                <version>2.0.0</version>
            </dependency>
        </dependencies>

        <repositories>
            <repository>
                <url>http://download.eclipse.org/rt/eclipselink/maven.repo/</url>
                <id>eclipselink</id>
                <layout>default</layout>
                <name>Repository for library EclipseLink (JPA 2.0)</name>
            </repository>
        </repositories>
    </project>
like image 553
ben75 Avatar asked Feb 20 '13 11:02

ben75


People also ask

What is the difference between javax validation and Hibernate Validator?

The Javax bean validation API provides the following most frequently used annotations. The Hibernate validator provides the following commonly used annotations for validation. In case of product or project development we must use both the annotations for bean validation.

What does @NotNull annotation Bean in Bean property?

@NotNull validates that the annotated property value is not null. @AssertTrue validates that the annotated property value is true.

Does NotBlank include NotNull?

So we might think that @NotBlank does allow null values, but it actually doesn't. The @NotNull class' isValid() method is called after the @NotBlank class' isValid(), hence forbidding null values.

How does Hibernate validation work?

Hibernate Validator allows to express and validate application constraints. The default metadata source are annotations, with the ability to override and extend through the use of XML. It is not tied to a specific application tier or programming model and is available for both server and client application programming.


1 Answers

I filed a bug report for this : https://hibernate.onjira.com/browse/HHH-8028

like image 133
ben75 Avatar answered Sep 26 '22 13:09

ben75