August 18, 2019
This is a note on working with JPA in BeanValidation custom validators. In this example, I've created a custom ConstraintValidator that will check a value from an incoming request against the database. If the item is found, then the field is deemed valid. It's simple to hookup the custom validator -- just add a single annotation to a JavaBean field -- however, an additional step is required to keep the custom JPA validator from affecting em.persist() calls.
FlushMode is a setting in JPA that governs a rule for when you want to serialize data with the database from your EntityManager. It's set to AUTO by default. This means that when you run a query in JPA, say with EntityManager.createQuery(), you'll flush out any pending writes. The other value you can specify is COMMIT. COMMIT will flush the transaction state, bringing the Entities in sync with the queries up to that point.
An interesting case came up in a web service I coded that used both JPA and Bean Validation. I wrote a Stateless EJB to save a JAX-RS request. Both the JAX-RS request and the EJB communicated with a JPA Entity that was serialized from the incoming payload. I used Bean Validation to automatically check the incoming payload for a missing field and a make sure that another field contained a value found in the database. The first validation -- the missing field -- was implemented with the standard @NotNull annotation. For checking a value against the database, I wrote a customer validator.
To keep the data access code consistent, I implemented the customer validator with an EJB and a different JPA Entity.
This UML class diagram shows the design of the program. An AccessoriesResource is the entry point, provided by JAX-RS. The JAX-RS endpoint uses a Stateless EJB, AccessoriesBean, to issue the em.persist() call. Through the AccessoriesResource, the customer validator CheckAccessoryTypeValidator is invoked. That validator uses a second Stateless EJB and a different Entity to check the incoming String value.
This code listing shows the top of the AccessoriesResource class. There is a createAccessory() method that accepts an Accessory argument. This will be serialized from the application/json body of an HTTP POST method. Marking the Accessory bean with the @Valid argument triggers Bean Validation.
@Path("/accessories")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class AccessoriesResource {
@EJB
private AccessoryBean accessoryBean;
@POST
public Long createAccessory(@Valid Accessory accessory) {
return accessoryBean.createAccessory(accessory);
}
...
This listing shows the Accessory JavaBean.
@Entity
public class Accessory {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotNull
private String name;
@CheckAccessoryType
private String accessoryType;
private String manufacturer;
private String price;
...
The Accessory JavaBean is a JPA Entity. It's also marked with instructions for Bean Validation. @NotNull is a standard validation while @CheckAccessoryType is one that I have written. It is the unified transport between the JAX-RS and the EJB layers.
Notice the lack of annotations related to JSON. JAX-RS will automatically serialize the text payload into this data structure.
This is the code listing for the AccessoriesBean. By my convention, my createAccessory() method returns the Long identifier. The code is trivial, issuing a single em.persist() call.
For the Bean Validation custom validator to work, you need three things.
This code listing shows the CheckAccessoryType annotation. It contains only the required members of the payload and the message. In this capacity, the annotation is merely a marker interface.
@Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = CheckAccessoryTypeValidator.class)
@Documented
public @interface CheckAccessoryType {
String message() default "{us.bekwam.ws.ejb.CheckAccessoryType.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
The implementation of the custom validator uses a second EJB, AccessoryTypeBean.
public class CheckAccessoryTypeValidator
implements ConstraintValidator<CheckAccessoryType, String> {
@EJB
private AccessoryTypeBean accessoryTypeBean;
@Override
public void initialize(CheckAccessoryType checkAccessoryType) {
}
@Override
public boolean isValid(String atype, ConstraintValidatorContext constraintValidatorContext) {
if( atype == null ) {
return true;
}
return accessoryTypeBean.findAccessoryTypeByName(atype).isPresent();
}
}
The JPA code supporting looking up the AccessoryType is listed below. The default flush mode for the findAccessoryByName query is AUTO.
@Stateless
public class AccessoryTypeBean {
@PersistenceContext
private EntityManager em;
public Optional<AccessoryType> findAccessoryTypeByName(String name) {
try {
String jpql = "SELECT atype FROM AccessoryType atype WHERE atype.name = :name";
TypedQuery<AccessoryType> at =
em.createQuery(jpql, AccessoryType.class)
.setParameter("name", name);
return Optional.of( at.getSingleResult() );
} catch(NoResultException exc) {
return Optional.empty();
}
}
...
The final listing is the AccessoryType Entity.
@Entity
public class AccessoryType {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
...
In testing the service, I received the following error. This was thrown by the isValid() method.
Caused by: org.hibernate.AssertionFailure: null id in us.bekwam.ws.ejb.Accessory entry (don't flush the Session after an exception occurs)
What happened was the AUTO flush was attempting to synchronize my EntityManager Java object with the database. This meant that the convenient Accessory parameter I was passing around was attempting to be serialized prematurely. I simply wanted to run the AccessoryType query. However, running the query attempted to merge in the Accessory parameter into the EntityManager. This was not valid because em.persist() had not been called yet. So, there was no id for the merge/update and an error was thrown.
My fix was to tweak the findAccessoryTypeByName() query to defer any syncronization steps. This allowed the em.persist() to be run on the Accessory parameter. So, rather than an implied update, my code was able to hit the explicit insert step. Along the way, the query was called and if the payload didn't find a matching AccessoryType in the database, it would have failed.
The following is the revised AccessoryTypeBean.
@Stateless
public class AccessoryTypeBean {
@PersistenceContext
private EntityManager em;
public Optional<AccessoryType> findAccessoryTypeByName(String name) {
try {
String jpql = "SELECT atype FROM AccessoryType atype WHERE atype.name = :name";
TypedQuery<AccessoryType> at =
em.createQuery(jpql, AccessoryType.class)
.setParameter("name", name)
.setFlushMode(FlushModeType.COMMIT); // *** NEW
return Optional.of( at.getSingleResult() );
} catch(NoResultException exc) {
return Optional.empty();
}
}
...
It's important to have consistent validations in your application when building a RESTful API. In many cases, this API will be called by external organizations. If the API is well validated, then the callers can find their way to a successful transmission through trial-end-error and may be able to work independently with the project's documentation. Bean Validation is an important technology in JavaEE. This example showed how the use a secondary JPA query to enforce business rules by tweaking the FlushMode of a JPA Query.
By Carl Walker
President and Principal Consultant of Bekwam, Inc