Search code examples
hibernaterestjax-rsentitydto

LazyInitializationException and DTO in JAX-RS


In my RESTful Backend application there are two entities (A and B) with the following relationship: one entity A can be associated with many entities B, so in A we have a collection of B entities annotated with @OneToMany.

Implementing MVC pattern, I have:

  • Resource layer (interface between the user/frontend and my application)
  • Service layer (ejbs that call daos for performing query on the db)

I had a LazyInitializationException while retrieving the A entity in the resource layer. Searching around, I understood that the problem derives from the fact that when Service layer returns the entity A, it detatches it from the persistence layer and since by default the collections are LAZILY Loaded, if I try to get them in the resource layer, I receive the exception.

One of the most quoted solutions over the network is to use DTO objects and converting in the service layer the entities into DTO.

My first question is related to this last passage: Is this approach just a trick to use the "get" methods of entity and so forcing ORM to load the collection? I say that because when we map Entity to DTO we do the following operation:

objectADTO.setB(objectAEntity.getB());

Using this operation, we are retrieving from the DB the collection of B. We could achieve the same result by simply writing a line of code like this in the service layer: objectAEntity.getB(), and then directly return to the service layer the objectAEntity, which won't raise the exception since we used .getB() to retrieve the actual values.

Now, you could say: a DTO object allows you to have more versatility, but then my second question is: do I have to define a different mapping between entity A and DTO A, based on the methods in the service layer that I need? In other words, if I have two methods that returns different info of A, should I handle two kinds of DTO mapping? (consider for example that A contains two different collections of Images and the method 1 wants the first collection, and the second method wants the second. I cannot use the same DTO mapping that provides all of the collections to both of the methods, because it would return a too heavy object)

I hope I was clear while describing my problems.

Thanks in advance for the answers


Solution

  • You can implement DTOs in a way that only maps Java properties and triggers lazy loading during conversion, but that might lead you to the infamous N + 1 lazy loading issue. It would be a lot better performance wise to load only the data that the DTO really needs.

    A DTO is not always just a subset of an entity but sometimes contains transformed information or information about indirectly related entities. So having a dedicated type per use case is definitely something that is IMO desirable.

    do I have to define a different mapping between entity A and DTO A, based on the methods in the service layer that I need? In other words, if I have two methods that returns different info of A, should I handle two kinds of DTO mapping?

    Yes, each use case should have it's own DTO. You could achieve reusability through inheritance or composition if you need it.

    This is a perfect use case for Blaze-Persistence Entity Views.

    I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.

    A DTO model for your use case could look like the following with Blaze-Persistence Entity-Views:

    @EntityView(A.class)
    public interface ADto {
        @IdMapping
        Long getId();
        String getName();
        @Mapping("bCollection")
        Set<BDto> getBs();
    
        @EntityView(B.class)
        interface BDto {
            @IdMapping
            Long getId();
            String getName();
        }
    }
    

    Querying is a matter of applying the entity view to a query, the simplest being just a query by id.

    ADto a = entityViewManager.find(entityManager, ADto.class, id);

    The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features