3

In a typical Java Spring Web APP:

we have the following layers:

Model [DB Models]

Repositories [where you have queries to DB]

Services [Business service where you have the @Transactional annotation]

Controllers [Rest endpoints]

So for a simple model e.g Car

@Entity
Car {
   Long id;
   String name;
   @ManyToOne(fetch = FetchType.LAZY) // Notice lazy here
   Engine engine;
}

CarRepo extends JpaRepository {....}

@Transactional
CarService {
 ....
}

@RestController
CarController{
  @GET
  public CarDto getCar(Long id) {
      ???
  }
}

??? : Here is the big dilemma, I use a mapstruct to convert objects to other formats, whenever I use it as in first following scenario I get LazyInitializationException:

Scenario#1 Get the model in controller (Which is not so good to do especially that models should be encapsulated from the view layer) and convert it to CarDto

CarController{
      @GET
      public CarDto getCar(Long id) {
          Car car= carService.getCar(id);
          return carMapper.toCarDto(car); // BAM `LazyInitializationException`, on `Engine` field!!! 
      }
    }

But here the problem, When mapper starts to convert Engine it will get LazyInitializationException since transaction was already committed and closed in service and Engine is lazy initialized

That moves us to Scenario#2 Ok so do the conversions in service then daa! while you still have the transaction opened, in service, so update the getCar method to return a CarDto instead:

  @Transactional
  CarService {
       CarDto getCar(Long id) {.... return mapper.toCarDto(car);} // Hurrah!!, no lazy exceptions since there is a transnational context wrapping that method
    }

But here is another problem, for other services in that uses Car suppose we have FactoryService and we want to get a car by id so that we can assign it to a factory model so we will diffidently need the Car model not the dto,

FactoryService {
    void createFactory() { 
      Factory factory = ....;
      Car car = carService.getCarModel...
      factory.addCar(car);
    }
}

so simple solution to this is to add another method with different name but will return model that time in the CarService

@Transactional
    CarService {
       CarDto getCar(Long id) {.... return mapper.toCarDto(car);}
       Car getCarModel(Long id) {.... return car;}
    }

But as you can see it is now ugly! to have the same function twice with same logic only with 2 different return types, that will also lead to have aloooot of same type logic method across the services

Eventually we have Scenario#3 and that is simply use Solution#1 but move the @Transactional annotation to the controller now we won't get the lazy exception when we use mapstruct in controller (But this is not very recommended thing to do since we are taking controlling transactions out of the service (business) layer)

@Transactional
CarController{
      @GET
      public CarDto getCar(Long id) {
          ???
      }
    
    }

So what would be the best approach to follow here

17
  • Making a whole service @Transactional doesn't seem (to me) the way to go. Usually, some methods are others are not. get**** usually are not.
    – Laiv
    Commented Jan 11, 2021 at 12:15
  • @Laiv Well, I just wanted to simplify the code in the post but usually, what I do is to have a Read Only transactional services e.g CarReadService annotated with @Transactional(readOnly = true) and a read/write transactional write service CarWriteService annotated by @Transactional
    – Youans
    Commented Jan 11, 2021 at 12:28
  • It doesn't change the fact that fetching the data from the DB for querying doesn't need to be transactional. Just provide CarReadService with non-modifier methods. That should be enough to ensure that nobody uses CarReadService to write/update the DB. Anyways, If the Service is only acting as a gateway to the repository, just call the repository from the Controller. Repositories are high level abstractions
    – Laiv
    Commented Jan 11, 2021 at 12:30
  • In Java with Spring it does, every call to DB should be wrapped into a Transaction context, this keeps connections to DB properly managed by the application container (Spring)
    – Youans
    Commented Jan 11, 2021 at 12:32
  • 2
    I have upvoted the question because now I'm interested in how other developers solve this.
    – Laiv
    Commented Jan 11, 2021 at 13:11

1 Answer 1

1

The first scenario seems a good approach. By default, the method getCar should just return the Car object without the Engine (as it is set to be fetched lazily). If the controller needs the Car and the Engine, an option is to provide an additional method CarService#getCarWithEngine. Such a method, will explicitly load and return the Car with the Engine (see this answer). This can be done in many ways, see here. For example, as suggested in the linked tutorial, you can create a method in CarRepo that uses Join Fetching:

public interface CarRepo extends JpaRepository<Car, Long> {

    @Query("SELECT c FROM Car c JOIN FETCH c.engine")
    Optional<Car> findCarByIdWithEngine(Long id);
}

This method can then be used by CarService#getCarWithEngine (with or without @Transactional) to implement scenario 1. Mapping the entity to the DTO in the controller layer keeps the service layer independent of the particular request so that it can be better reused and tested. Such an approach is also suggested here.

Scenario 2 makes CarService dependent on the DTO, which (as you have demonstrated) is not ideal. A new API that needs a different view of the Car, or even a slightly different response in the controller layer, will require CarService to change.

An additional approach is to keep the session open with spring.jpa.open-in-view=true, so that Hibernate can resolve lazy associations even after returning from an explicit @Transactional service. More info here.

There are more than one solution, it depends on the abstraction level that you need for your project, and if the additional complexity is worth it.

5
  • Thanks for your answer, For CarService#getCarWithEngine I like this but doing this for all models could be a pretty damn job to cover all view scenarios and also will pollute the service imagine that we have withEngine withEngineAndManufacturer withEngineAndManufacturerWithOriginCountry however I would think that this would be the best approach yet I can think of right now at least it will avoid issuing the query to load the lazy field since on that case it will be returned by the original query
    – Youans
    Commented Jan 11, 2021 at 15:08
  • 1
    For spring.jpa.open-in-view=true the OSIV is considered an Anti-Pattern check @Vlad Mihalcea answer here stackoverflow.com/a/37526397/1460591
    – Youans
    Commented Jan 11, 2021 at 15:08
  • @YouYou I am glad I could help. I see that there are a lot of discussions about OSIV. Even though it's considered an anti-pattern by many it's enabled by default in Spring Boot (with a warning). So I listed it as an additional approach since in some use cases can still be relevant.
    – Marc
    Commented Jan 11, 2021 at 15:49
  • Yeah, I just thought you might not know that, since I have just know it recently
    – Youans
    Commented Jan 11, 2021 at 16:56
  • to me this method is bypassing the LAZY configuration and force it to be EAGER. If I'm right you may as well set engine to EAGER.
    – Julien
    Commented Nov 29 at 10:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.