Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Correct way to do an EntityManager query during Hibernate Validation

I'm a bit of a Java EE/EJB noob, but from the docs and other posts I've gathered you cannot query the database using the same entitymanager/session during entity validation.

In general, the lifecycle method of a portable application should not invoke EntityManager or Query operations, access other entity instances, or modify relationships within the same persistence context.[43] A lifecycle callback method may modify the non-relationship state of the entity on which it is invoked.

Translation please?

This is pretty abstract...can it be explained in more concrete terms? It leads to more questions than it answers. For example, if my entity has a lazy-loaded collection am I allowed to access it during validation? The collection is 'another entity' and will require a DB query which seems to be in violation of the docs.

This 'lifecycle requirement' seems odd because it's just a fact of life that certain validations do indeed require querying the database.

From other posts I've also seen people get around this querying issue by creating a new entitymanager/session using the entitymanagerfactory.

This leads me to two questions about using EntityManagers and Hibernate Validation:

  1. Is it possible I have some sort of design flaw or am misusing Hibernate Validation because I need to query the database during validation?
  2. Given that I'm using Java EE with JBoss, how do I inject my validator with an EntityManagerFactory?

I've tried something like this:

@Stateless
public class UserValidator implements ConstraintValidator<ValidUser, User> {
    @PersistenceUnit(unitName="blahblah")
    EntityManagerFactory emf;

    ...
}

But the EMF never gets injected. I'm guessing the @Stateless tag becomes irrelevant because I'm implementing a ConstraintValidator interface which is needed for the Hibernate Validator stuff to work.

So what's the general pattern for getting at an EntityManagerFactory from a Validator?

Thanks!

like image 515
lostdorje Avatar asked Aug 16 '13 06:08

lostdorje


People also ask

What is the use of EntityManager in Hibernate?

The EntityManager API is used to access a database in a particular unit of work. It is used to create and remove persistent entity instances, to find entities by their primary key identity, and to query over all entities.

How does Hibernate Validator 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.

What is EntityManager used for?

In JPA, the EntityManager interface is used to allow applications to manage and search for entities in the relational database. The EntityManager is an API that manages the lifecycle of entity instances. An EntityManager object manages a set of entities that are defined by a persistence unit.


1 Answers

Through some of the comments and enough scrounging around, I finally figured out a somewhat 'canonical' way to answer my question.

But to clear things up, my question really was asking two things which have 2 distinct answers:

  1. How do you inject things into Validators that are used in the Hibernate Validation framework?
  2. Assuming we can inject things is it safe to inject an EntityManagerFactory or EntityManager and use them for querying during a JPA lifecycle event?

Answering the second question first I'll simply say, it is strongly encouraged to use a second EntityManager to do queries during validation. That means you should be injecting an EntityManagerFactory and creating a new EntityManager for queries (rather than injecting an EntityManager which will be the same one that created the lifecycle event to begin with).

Generally speaking, for validation purposes, you'll only be querying the database anyway and not inserting/updating so this should be fairly safe to do.

I asked a very related SO question here.

Now to answer question 1.

Yes it is completely possible to inject things into Validators used in the Hibernate Validation framework. To accomplish this you need to do 3 things:

  1. Create a custom ConstraintValidatorFactory that will create the validators used in the framework (overriding Hibernate's default factory). (My example uses Java EE, not Spring so I use BeanManager, but in Spring you'd probably use ApplicationContext for this).
  2. Create a validation.xml file which tells the Hibernate Validation framework which class to use for the ConstraintValidatorFactory. Make sure this file ends up on your classpath.
  3. Write a Validator that injects something.

Here is an example custom ConstraintValidatorFactory that uses 'managed' (injectable) validators:

package com.myvalidator;

public class ConstraintInjectableValidatorFactory implements ConstraintValidatorFactory {

    private static BeanManager beanManager;

    @SuppressWarnings(value="unchecked")
    @Override
    public <T extends ConstraintValidator<?, ?>> T getInstance(Class<T> clazz) {
        // lazily initialize the beanManager
        if (beanManager == null) {
            try {
                beanManager = (BeanManager) InitialContext.doLookup("java:comp/BeanManager");
            } catch (NamingException e) {
                // TODO what's the best way to handle this?
                throw new RuntimeException(e);
            }
        }

        T result = null;

        Bean<T> bean = (Bean<T>) beanManager.resolve(beanManager.getBeans(clazz));
        // if the bean/validator specified by clazz is not null that means it has
        // injection points so get it from the beanManager and return it. The validator
        // that comes from the beanManager will already be injected.
        if (bean != null) {
            CreationalContext<T> context = beanManager.createCreationalContext(bean);
            if (context != null) {
                result = (T) beanManager.getReference(bean, clazz, context);
            }
        // the bean/validator was not in the beanManager meaning it has no injection
        // points so go ahead and just instantiate a new instance and return it
        } else {
            try {
                result = clazz.newInstance();
            } catch (Throwable t) {
                throw new RuntimeException(t);
            }
        }

        return result;
    }
}

Here is an example validation.xml file that tells Hibernate Validator which class to use as the ValidatorFactory:

<?xml version="1.0" encoding="UTF-8"?>
<validation-config
    xmlns="http://jboss.org/xml/ns/javax/validation/configuration"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://jboss.org/xml/ns/javax/validation/configuration validation-configuration-1.0.xsd">
    <constraint-validator-factory>
        com.myvalidator.ConstraintInjectableValidatorFactory
    </constraint-validator-factory>
</validation-config>

And finally a validator class with injection points:

public class UserValidator implements ConstraintValidator<ValidUser, User> {

    @PersistenceUnit(unitName="myvalidator")
    private EntityManagerFactory entityManagerFactory;

    private EntityManager entityManager;

    @Override
    public void initialize(ValidUser annotation) {
    }

    @Override
    public boolean isValid(User user, ConstraintValidatorContext context) {
        // validation takes place during the entityManager.persist() lifecycle, so
        // here we create a new entityManager separate from the original one that
        // invoked this validation
        entityManager = entityManagerFactory.createEntityManager();

        // use entityManager to query database for needed validation

        entityManager.close();
    }
}
like image 81
lostdorje Avatar answered Nov 16 '22 03:11

lostdorje