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:
NameNotNullWithDefaultGeneratedStrategy
with an id generated with default strategy (@Generated
) and @NotNull String name
columnNameNotNullWithTableGeneratedStrategy
with an id generated with table strategy (@TableGenerated
) and @NotNull String name
columnpersist
one instance of each entity with a null name
.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 :
persist
method throws a javax.validation.ConstraintViolationException
for both entities.rollback only
in both casespersist
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 :
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>
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.
@NotNull validates that the annotated property value is not null. @AssertTrue validates that the annotated property value is true.
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.
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.
I filed a bug report for this : https://hibernate.onjira.com/browse/HHH-8028
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With