How to prevent the List.size () method from executing lazyload with Hibernate and JPA

6
When I make a query in the database and an object is returned, that object has a collection, which by default is Lazyload, if I do object.getColecao().size() it runs the lazyload to bring the records, whether the session or the entitymanager has been closed, of course LazyLoadException will happen.

My question is , how would I get the query object, even if I do object.getColecao.size() it does not execute lazy load?

The problem is that I do the query and need to serialize the object to JSON, but the main JSON processing frameworks like Jackson and Gson implicitly execute the size() for collections, so LazyLoadException happens.

I can not configure the frameworks to ignore the litas because if the lists are not null it will have to serialize, that is, if it is null, it does not serialize, otherwise serialize. The configuration to not serialize nulls of these two works but not for lists, even so they execute size() to check if it has content, so the solution I see is not to execute lazy load when calling method size() . >

I have already tried to clone the objects using SpringBeansUtil, but even so when running size() a lazyload attempt is made.

    
asked by anonymous 04.02.2014 / 23:13

1 answer

3

Solution ignoring the attribute when serializing

After seeing the comment and getting a better understanding of the problem, I think the solution is to ask the Json library to ignore the attributes with the serialization collections only when needed, since you mentioned that in some situations you will want to include the collections .

Solution with Gson

Based on this SOEN issue , there are two ways to exclude certain fields.

The first is to note the fields that always will be serialized with the @Expose annotation. When it is necessary to serialize all fields, the library is normally used, but if we only want to serialize the fields with the annotation, we can do this:

Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();

The second way is by implementing a ExclusionStrategy . Here's an example:

public class ClienteExclusionStrategy implements ExclusionStrategy {

    public boolean shouldSkipClass(Class<?> arg0) {
        return false;
    }

    public boolean shouldSkipField(FieldAttributes f) {
        return f.getDeclaringClass() == Cliente.class
            && (f.getName().equals("enderecos")) || f.getName().equals("contatos"));
    }

}

So when you want to ignore addresses and contacts, do so:

Gson gson = new GsonBuilder().setExclusionStrategies(new ClienteExclusionStrategy()).create();

Solution with Jackson

To override the original behavior of a class at the time of serialization you can use Jackson's Mixins . This is an abstract class where you define the behavior you want without changing the original class.

See the example:

abstract class ClienteMixIn {
     @JsonIgnore List<Contato> getContatos();
     @JsonIgnore List<Endereco> getEnderecos();
}

And finally add the mixin to the serialization when needed:

objectMapper.getSerializationConfig().addMixInAnnotations(Cliente.class, ClienteMixIn.class);

Solution retrieving relationship data

If you understand correctly you want to execute size() without executing the queries in lazy mode. This is simply not possible, after all how could Hibernate know how many records join will return?

Continuing this reasoning, to know if there are items in the collection and use them when necessary you will always have to check the items in the collection.

One of the solutions to force the collection to read while the EntityManager is open, even if the entity relationship is set to lazy , is to use a join fetch .

join fetch is often used to improve performance when we know in advance that we need to access that relationship. Internally, Hibernate will fetch the data from the parent entity and perform a native OUTER JOIN on the database to fetch the records from the collection. For more details, see the documentation for the different optimization strategies here .

An example of this using Criteria is:

CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Entidade> criteriaQuery = criteriaBuilder.createQuery(Entidade.class);
Root<Entidade> root = criteriaQuery.from(Entidade.class);
root.fetch("colecao", JoinType.LEFT); // força um 'LEFT OUTER JOIN' nativo

With the above code, items in the collection will be loaded immediately and no additional queries will be required, meaning nothing of LazyLoadException and only a call to the bank.

Alternatively, another way to initialize a collection forcibly when it is required is to use the static method Hibernate.initialize() .

This method forces the proxy of the collection to load everything that is required to run when the EntityManager is closed.

See an example:

Hibernate.initialize(object.getColecao());

Solution de-encapsulating the proxy entity

Hibernate creates a proxy class to intercept calls to lazy attributes. Another idea would be to get the original entity, without a proxy, because in that case we would not have the problem of LazyLoadingException .

I found two SOAP issues with codes that purports to do this. See one of them:

public static <T> T initializeAndUnproxy(T entity) {
    if (entity == null) {
        throw new 
           NullPointerException("Entity passed for initialization is null");
    }

    Hibernate.initialize(entity);
    if (entity instanceof HibernateProxy) {
        entity = (T) ((HibernateProxy) entity).getHibernateLazyInitializer()
                .getImplementation();
    }
    return entity;
}

With solution it would not be necessary to modify the Json generation. Just apply it right after doing the detach of the entity.

    
05.02.2014 / 00:41