Site icon Antonio's Blog

Bean Validation with JPA 1.0

For those of you who still don’t know Bean Validation, you should check the JSR 303 and the documentation of the reference implementation Hibernate Validator. In fact, like many other JSRs, Hibernate Validator existed on its own for quite a long time as an open source project (until version 3.1.x) and then got specified under the JSR 303 and became the reference implementation from its version 4.x. Bean Validation 1.0 was born and is now part of Java EE 6. But keep in mind that Bean Validation doesn’t need Java EE 6 to run (like other specs such as JPA 2.0, JSF 2.0, CDI 1.0…) and can be used outside any container.

So, what is it for ? Well, the short answer is to validate your POJOs. Imagine that you have a Book POJO and you want to check that the title of the book is not null and the author’s name is greater than 5 caracters and shorter than 20. Where do you validate these simple business rules ? In which later ? Different schools exist. In the Anemic Domain Model school, your model doesn’t have any intelligence and the validation should be done outside (typically in a service layer). In the Rich Object Model school, the object should validate itself and is the best place to encapsulate its own business logic. Bean Validation is part of the second school. So, how do you add validation constraints to your POJO with Bean Validation ? Through annotations. Bean Validation comes with existing annotations (in the javax.validation.constraints package) such as @Null, @NotNull, @Max, @Min, @Size, @Past, @Future… and gives you an API to create your own.

A Book POJO with Bean Validation annotations would look like this :

[sourcecode language=”java”]
public class Book {
@NotNull
private String title;

@NotNull
@Size(min = 5, max = 20)
private String author;

}
[/sourcecode]

Then, to validate it, you just need to use the Validator API, validate your POJO and get a set of ConstraintViolation back :

[sourcecode language=”java”]
@Test
public void shouldRaiseTwoConstraintViolations() {
// Creates an instance of book with null values
Book book = new Book(null, null);

// Validates the Book manually
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
Set<ConstraintViolation> constraintViolations = validator.validate(book);
assertEquals("Should raise 2 constraints because title and author are null", 2, constraintViolations.size());
}
[/sourcecode]

Download the code of the examples

Integration with JPA 2.0

One nice thing with Bean Validation is that it’s integrated with JPA 2.0 (Hibernate 3.5.x) as well as JSF 2.0. This means that without any specific code your entity is validated during the @PrePersist, @PreUpdate and @PreRemove lifecycle phases (this is by default, you can always change the phases if you want). So let’s take the Book POJO and turn it into a JPA 2.0 entity (by adding the @Entity and @Id annotations) :

[sourcecode language=”java”]
@Entity
public class Book {

@Id @GeneratedValue
private Long id;

@NotNull
private String title;

@NotNull
@Size(min = 5, max = 20)
private String author;

}
[/sourcecode]

How do you validate it ? Well, just by calling the EntityManager.persist() for example. No need to explicitly use the Validator API. As you can see in the test case below, persisting a non valid Book entity will throw a ConstraintViolationException :

[sourcecode language=”java”]
@Test(expected = ConstraintViolationException.class)
public void shouldThrowAnExceptionBecauseNotAValidBookWhenPersisting() {
// Creates an instance of book with null values
Book book = new Book(null, null);

// Persists the book to the database
tx.begin();
em.persist(book);
tx.commit();
}
[/sourcecode]

Making it work with JPA 1.0

Bean Validation is integrated with JPA 2.0 (Hibernate 3.5) but not with JPA 1.0 (Hibernate 3.4). So if you want to have the same features, you need to code it by hand. But this is easy with entity listeners. The custom code below is an entity listener that is triggered at @PrePersist, @PreUpdate and @PreRemove phase. It uses the Validator API and throws an exception if any validation constraints is violated.

[sourcecode language=”java”]
public class MyBeanValidationEventListener {

@PrePersist
@PreUpdate
@PreRemove
public void validate(Object entity) {
TraversableResolver tr = new MyTraversableResolver();
Validator validator = Validation.buildDefaultValidatorFactory().usingContext().traversableResolver(tr).getValidator();
final Set
if (constraintViolations.size() > 0) {
Set&gt; propagatedViolations = new HashSet&gt;(constraintViolations.size());
Set classNames = new HashSet();
for (ConstraintViolation violation : constraintViolations) {
propagatedViolations.add(violation);
classNames.add(violation.getLeafBean().getClass().getName());
}
StringBuilder builder = new StringBuilder();
builder.append("validation failed for classes ");
builder.append(classNames);
throw new ConstraintViolationException(builder.toString(), propagatedViolations);
}
}
}
[/sourcecode]

I also found a bug (that I can’t reproduce now) in the JPATraversableResolver (which is using the JPA 2.0 Persistence.getPersistenceUtil() method which is not part of JPA 1.0) so I wrote my own resolver (used above in the listener) :

[sourcecode language=”java”]
public class MyTraversableResolver implements TraversableResolver {

public boolean isReachable(Object traversableObject, Path.Node traversableProperty, Class rootBeanType, Path pathToTraversableObject, ElementType elementType) {
return traversableObject == null || Hibernate.isInitialized(traversableObject);
}

public boolean isCascadable(Object traversableObject, Path.Node traversableProperty, Class rootBeanType, Path pathToTraversableObject, ElementType elementType) {
return true;
}
}
[/sourcecode]

To use this listener on the Book entity, just use the @EntityListener annotation and you’ll get the job done. Automatically your entity will be validated when calling EntityManager.persist().

[sourcecode language=”java”]
@Entity
@EntityListeners(MyBeanValidationEventListener.class)
public class Book {

}
[/sourcecode]

Because you don’t want to repeat this annotation on every single entity of your application, instead you can use the XML metatdata to set this listener on all your entities :

[sourcecode language=”xml”]
<entity-mappings…>
<persistence-unit-metadata>
<persistence-unit-defaults>
<entity-listeners>
<entity-listener/>
<entity-listener class="org.beginningee6.sample.MyBeanValidationEventListener"/>
</entity-listeners>
</persistence-unit-defaults>
</persistence-unit-metadata>
</entity-mappings>
[/sourcecode]

Open question to Emmanuel Bernard

Emmanuel Bernard, who works for JBoss, is the spec lead of Bean Validation. Few months ago, before the spec was final, I was talking with him about the @PreRemove phase (see chapter 3.6 of the JPA 2.0 specification). For me it doesn’t make much sense to validate an entity before removing it. It should have been validated before getting inserted or updated to the database, but once it’s there, it should be easy to just remove it, without validation. At the time the topic was still in discussion and I can see now that the spec integrates validation before remove. Emmanuel, do you have a quick explanation about this ? Thanks.

Thanks to Emmanuel who answered on his blog
Download the code of the examples

Exit mobile version