1

I have some difficulties with Spring and Hibernate and lazy loading. There are a lot of questions and answers, but not really what i am looking for.

So lets say i have a Car class. With an id, name and one to many relationships with doors, windows and wheels. As they are oneToMany, they are default lazy loaded which is preferred because when i want to view the name of the car i dont want to see the tires and stuff. This works in my case, using the default findOne() methods of my repositories.

But when i want to view the tire pressure, i need to initialize the tires relationship. I used to do that with Hibernate.initialize(car.getTires()). This produces another SELECT query for the database. Now I want to improve my database queries and select only the car with the tires but leave out the windows and doors. (Which is possible in MySQL with joins). The option to make them load eager is out of question because i dont always want to load the tires relationship (I have some big relationships on my objects).

I tried the following approach:

@Query("SELECT c FROM Car c JOIN FETCH c.tires WHERE c.id = :id")
Car findOneWithTiresLoaded(@Param("id") Long id);

This does provide the right data. But after analyzing the object returned from the repository, i noticed all of the relationships are loaded. So this does not only return the car with the tires relationship, but also the doors and windows. The following gives me the same output (with the moneToMany relationship loaded)

@Query("SELECT c FROM Car c WHERE id = :id")
Car findOneWithTiresLoaded(@Param("id") Long id);

Something that is not wanted. I expected this to output only the Car object without all its lazy relationships.

Also suggested by people on the internet is to call Car.getTires().size(). Which will also produce another SELECT query.

Is there any way to just select the Car with only the Tires relation ship loaded? Without the fetch = FetchType.LAZY, Hibernate.initialize() or size() method? How is it not possible to just join one table? Also, i do not use XML for any configuration.

Thanks!

Jens Schauder
  • 77,657
  • 34
  • 181
  • 348
Fjarlaegur
  • 1,395
  • 1
  • 14
  • 33

1 Answers1

4

I would always suggest implementing this using entity graphs. I will be giving an example using Spring Data. Also lazy loading should always be used no matter what, all other relationships can be joined using specific graphs.

This way you can be very specific about your queries and ONLY fetch the data that is necessary for your business logic. You can even define sub-graphs to show what you want to select from tires entities as well. That would mean you always have lazy fetches on all Tire entity relationships. By default all you get is tires (as requested) and no other relationships from them. If you also want anything else from tires, then only thing left for you to do is define another set of graph definitions there and reference them from your repository where you make the query as sub-graphs.

@Entity
@Table(name = "car")
@NamedEntityGraph(name = Car.TIRES_GRAPH, attributeNodes = @NamedAttributeNode("tires"))
public class Car {

  public static final String TIRES_GRAPH = "Car.tires";

  @OneToMany(mappedBy = "car", fetch = FetchType.LAZY}
  private Set<Tire> tires = new HashSet<>();

}

And for your repository you can have a method

@Query("SELECT c FROM Car c")
@EntityGraph(Car.TIRES_GRAPH)
Set<Car> findAllWithTires();

Even if you are not using Spring Data, then the approach is the same and you can easily find good examples of that.

EDIT

Another tested working example. Just make sure your field names match from the domain for Spring Data to resolve them.

public interface CarRepository extends JpaRepository<Car, Long> {

  @EntityGraph(attributePaths = { "tires" })
  Set<Car> findAllWithTiresByCarId(Long id)
}

Link to documentation

Vaelyr
  • 2,841
  • 2
  • 21
  • 34
  • Do i need to annotate the Entity with the `@Table` annotation? As we dont do that right now and if its not needed it would be cleaner not to write it. Or is it used with the Object graphs? I will try this now. I will mark your answer as solution if this works for us. Thanks for the reply! – Fjarlaegur Dec 02 '16 at 13:11
  • No the `@Table` can be left out, I am just used to use it to define entities. – Vaelyr Dec 02 '16 at 13:14
  • I just discovered the class `Car` extends a class `Vehicle`. And so it uses the `@Inheritance` annotations. (Also an `@DiscriminatorValue('car') annotation). When i try to use a named graph in the `Car` entity it gives me an error when booting. `Unable to locate Attribute with the the given name [Tires] on this ManagedType [com.example.domain.Vehicle]`. But i query for the `Car` table in the `Car` repository. What could cause this? – Fjarlaegur Dec 02 '16 at 13:26
  • Okay, the extension is no problem. I used a capital T for `Tires` in the annotation. So it couldnt find a variable for that one as it stats with a lower T. Anyway, i now get an error at booting saying it can not autowire my repository as the `Car` object does not have a property findOneWithTires. `org.springframework.data.mapping.PropertyReferenceException: No property findOneWithTires found for type Car!` – Fjarlaegur Dec 02 '16 at 13:30
  • I think this exception is because Spring Data tries to build a query method out of it. Either the name of the method must be different or it should be annotated with `@Query` to exclude it from doing so. Can you try to play around with that. – Vaelyr Dec 02 '16 at 13:33
  • Too bad annotating with the @Query method and specifying `SELECT c FROM Car c WHERE c.id = :id` still gives me all relations initialized instead of just the `Tires` relationship in combination with the @EntityGraph. Will upvote tho for the help. – Fjarlaegur Dec 02 '16 at 14:46
  • It seems that the `Tire` entity does not have lazy fetches set then for other collections, it should never do that. – Vaelyr Dec 02 '16 at 14:56
  • Its not in the `Tire` class. I mean, when i use above mentioned method, it returns an object `Car` with all the `Tires` but also the `Windows` and `Doors`. Which are set to lazy. I guess that happens when i use the above mentioned @Query method with the query. – Fjarlaegur Dec 02 '16 at 15:02
  • Sorry yes I meant the other collections on the `Car`. But this should not happen in normal case, `@Query` must still respect the fetch type. Only case where it can seem to be not working, is if you are trying to access those collections in an open transaction, otherwise everything else should not be included in the first query. Would be interested to see how and where the repository method is used. Also you can look for examples in Spring documentation I linked above. – Vaelyr Dec 02 '16 at 15:24
  • I wish i could show you the real code. I made up names and such to keep the code private as i may not share it. But i have a Dao between my code and the repository. The Dao code is simple: `public Car findOneWithTires(Long id) { Car car = carRepository.findOneWithTires(id); return car; }` (Yeah its redundant, but for debugging purpose) When i breakpoint on `return car;` i see all is initialized for the Car class. – Fjarlaegur Dec 02 '16 at 15:42
  • I just made the exact sample and tested the approach and it works just fine. I also edited the reply with the second and easier approach where you only need to add the graph on repository level (no need to add anything to domain). See if it helps. – Vaelyr Dec 03 '16 at 09:26
  • I already tested the method you added. Which didn't help me either. Maybe it depends on versions or annotations we use. Could you set up a git repo with the project you created so we can compare it with our project? We would be really thankful for that. Your extensive help is much appreciated! Thanks! – Fjarlaegur Dec 05 '16 at 08:31
  • Ok... We solved it. Apparently annotating the method which calls the repository with `@Transactional` initializes all the relationships. When i write a custom query now, just to initialize the tires, it all works. All other relationships are not initialized. This was a search... But apparently the `@Transactional` messes it all up. Thanks for your help! – Fjarlaegur Dec 05 '16 at 15:51