36

I have 2 entities as Parent and Child as OneToMany relation as

@Entity
public class Parent {

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;

private String name;

@OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
@IndexColumn(name = "index", base = 1)
@Cascade(org.hibernate.annotations.CascadeType.ALL)
@LazyCollection(LazyCollectionOption.EXTRA)
private List<Child> childs = new ArrayList<Child>();
// getter and setter

}

So here what is use of @LazyCollection(LazyCollectionOption.EXTRA) and when does it will come in picture, like for which operation with child list, it will be beneficial ?

Vlad Mihalcea
  • 103,297
  • 39
  • 432
  • 788
Rahul Agrawal
  • 8,583
  • 16
  • 42
  • 59

3 Answers3

42

EXTRA = .size() and .contains() won't initialize the whole collection

TRUE = initialize the whole collection on first access

FALSE = Eager-Loading

wutzebaer
  • 12,445
  • 18
  • 77
  • 144
  • Will `.get(0)` initialize the whole collection? – lakshayg Apr 02 '18 at 15:01
  • 1
    @LakshayGarg yes – wutzebaer Apr 02 '18 at 19:36
  • @LakshayGarg, it depends on the option you are using, EXTRA won't initialize the entire collection but will execute a query for this specific element. But it will initialize it if you instead use an iterator attached to this collection - like in stream or in for each loop. TRUE - will always initialize the whole collection. – Voland Mortes Jun 25 '20 at 10:47
26

There's actually no reason to use @LazyCollection.

The TRUE and FALSE values are not needed since the same behavior can be obtained with the JPA FetchType.LAZY or FetchType.EAGER.

The EXTRA value has no equivalent in JPA and was designed for very large collections. When you access an EXTRA lazy collection for the first time, the collection is not entirely loaded, as it's usually the case with any JPA collection.

Instead, each element is fetched one by one, using a secondary SELECT. This might sound like an optimization, but it's not because EXTRA lazy collections are prone to N+1 query issues.

Note that this only works for ordered collections, either List(s) that are annotated with @OrderColumn or Map(s). For bags (e.g. regular List(s) of entities that do not preserve any certain ordering), the @LazyCollection(LazyCollectionOption.EXTRA) behaves just like any other LAZY collection (the collection is fetched entirely upon being accessed for the first time).

If you have a very large collection, then you should not map it at all. Instead, you should map only the @ManyToOne side, and, instead of a parent-side collection, you should use a paginated JPQL query.

JPQL queries are much easier to tune because you can apply any filtering criteria, and you can paginate the result set.

Vlad Mihalcea
  • 103,297
  • 39
  • 432
  • 788
  • may I ask what is considered a very large collection? – Uri Loya Nov 07 '19 at 14:45
  • 1
    A very large collection is a collection that takes a significant time to be fetched from the database. – Vlad Mihalcea Nov 07 '19 at 17:31
  • 1
    You make the assumption in this answer, and in your article, that the items in the Collection are going to be iterated over. That isn't always the case. We have a couple of examples where we just want `.size()` on the Collection. With `@LazyCollection( LazyCollectionOption.EXTRA )` that's a simple `SELECT COUNT(id) FROM..`. Without the `LazyCollectionOption`, the operation becomes: select all matching rows, map them into objects, and then count the number of objects. That can be a difference of 1 or 2 seconds with a 50,000 item collection in the simple test that I ran. – Bernie Sep 23 '20 at 04:31
  • 1
    You make the assumptions that mapping collections is a duty. Collections are worth mapping only when their size is rather small. Use queries for large collections so that you can paginate the. For the collection size, just use a COUNT query. It works like a charm. – Vlad Mihalcea Sep 23 '20 at 06:08
  • @VladMihalcea No, I didn't make such assumptions. I am using a COUNT query, I've done so by annotating with `@LazyCollection( LazyCollectionOption.EXTRA )` and calling `.size()` on the mapped Collection. I prefer that over having to make a separate call to my Repository or DAO. – Bernie Oct 07 '20 at 22:07
  • Once you map a collection, someone will iterate it, in which case each element will be retrieved use a separate SQL statement. That's why I, as a Hibernate developer, don't use this Hibernate feature. – Vlad Mihalcea Oct 08 '20 at 02:41
  • @VladMihalcea That is a risk, but can be mitigated by not making the collection accessible from the Entity. If the Entity just has `getItemCount()` and no getter for the Collection itself then it wouldn't be possible to inadvertently iterate the Collection. – Bernie Oct 08 '20 at 21:43
  • It doesn't matter. Someone can still add a method in the entity to iterate the elements. – Vlad Mihalcea Oct 09 '20 at 04:28
  • 1
    @VladMihalcea So your argument boils down to "this feature is bad because someone could use it incorrectly and experience performance issues". I don't think that's a great criteria for evaluating the usefulness of a feature, and suggest to you that there are many features in Hibernate (and many other libraries [and Java and other languages]) which have the same flaw (if you consider such an attribute a flaw). – Bernie Oct 12 '20 at 05:01
  • As a top Hibernate project commiter, who worked as a Developer Advocate for the Hibernate project, yes, I'm telling everyone to avoid using this terrible "feature". But, you don't have to agree with me. It's your application after all. – Vlad Mihalcea Oct 12 '20 at 06:00
8

To give you a hint, it's mainly for performance reasons, you can start reading the following links:

Second Level Cache

Hibernate Documentation

Michael Schmidt
  • 8,258
  • 13
  • 53
  • 79
Maddy
  • 893
  • 5
  • 8