Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hibernate many to many - fetch method eager vs lazy

New to Hibernate.

I have User Group many to many relation. Three tables : User , Group and UserGroup mapping table.

Entities:

@Entity
@Table(name = "user")
public class User {

@Id
@Column (name = "username")
private String userName;

@Column (name = "password", nullable = false)
private String password;


@ManyToMany(cascade = {CascadeType.ALL}, fetch = FetchType.EAGER)
@JoinTable(name="usergroup", 
            joinColumns={@JoinColumn(name="username")}, 
            inverseJoinColumns={@JoinColumn(name="groupname")})
private Set<Group> userGroups = new HashSet<Group>();

... setter and getters



@Entity
@Table(name = "group")
public class Group {

@Id
@Column(name = "groupname")
private String groupName;

@Column(name = "admin", nullable = false)
private String admin;

@ManyToMany(mappedBy = "userGroups", fetch = FetchType.EAGER)
private Set<User> users = new HashSet<User>();

... setter and getters

Notice that in Group Entity I'm using fetch method EAGER. Now, when I'm calling my DAO to retrive all the groups in the system using the following criteria:

  Criteria criteria = session.createCriteria(Group.class);
  return criteria.list();

I'm getting all the rows from the mappgin table (usergroup) instead of getting the actual number of groups...

for example if i have in user table

 username password
 -----------------
 user1     user1
 user2     user2

in group table

 groupname admin
 ---------------
 grp1      user1
 grp2      user2

in usergroup table

 username groupname
 ------------------
 user1     grp1
 user2     grp2
 user1     grp2
 user2     grp1

The result will be the following list - {grp1,grp2,grp2,grp1}

Instead of {grp1,grp2}

If I change in Group Entity the fetch method to LAZY I'm getting the correct results but hibernate throws me LazyException in another place...

Please assist what fetch method should I use and why ?

Thanks!

like image 630
john Smith Avatar asked Jun 10 '14 20:06

john Smith


People also ask

What is the difference between fetch type eager and lazy?

FetchType. LAZY = This does not load the relationships unless you invoke it via the getter method. FetchType. EAGER = This loads all the relationships.

What is the difference between eager and lazy in Hibernate?

Eager Loading is a design pattern in which data initialization occurs on the spot. Lazy Loading is a design pattern that we use to defer initialization of an object as long as it's possible.

Which is better lazy loading or eager loading?

Use Eager Loading when you are sure that you will be using related entities with the main entity everywhere. Lazy Loading: In case of lazy loading, related objects (child objects) are not loaded automatically with its parent object until they are requested.

What is lazy loading and eager fetch in Hibernate?

Lazy and Eager are two types of data loading strategies in ORMs such as hibernate and eclipse Link. These data loading strategies we used when one entity class is having references to other Entities like Employee and Phone (phone in the employee).


2 Answers

Lazy people will tell you to always use FetchType.EAGER counter-intuitively. These are the people who generally don't worry about database performance and only care about making their development lives easier. I'm going to say you should be using FetchType.LAZY for the increased performance benefit. Because database access is usually the performance bottleneck of most applications, every little bit helps.

If you do actually need to get a list of users for a group, as long as your call getUsers() from within a transactional session, you won't get that LazyLoadingException that is the bane of all new Hibernate users.

The following code will get you all groups without populating the list of users for those groups

//Service Class
@Transactional
public List<Group> findAll(){
    return groupDao.findAll();
}

The following code will get you all groups with the users for those groups at the DAO level:

//DAO class
@SuppressWarnings("unchecked")
public List<Group> findAllWithUsers(){
    Criteria criteria = getCurrentSession().createCriteria(Group.class);

    criteria.setFetchMode("users", FetchMode.SUBSELECT);
    //Other restrictions here as required.

    return criteria.list();
}

Edit 1: Thanks to Adrian Shum for this code

For more info on the different types of FetchMode's see here

If you don't want to have to write a different DAO method just to access your collection object, as long as you are in the same Session that was used to fetch the parent object you can use the Hibernate.initialize() method to force the initialisation of your child collection object. I would seriously not recommend that you do this for a List<T> of parent objects. That would put quite a heavy load on the database.

//Service Class
@Transactional
public Group findWithUsers(UUID groupId){
    Group group = groupDao.find(groupId);

    //Forces the initialization of the collection object returned by getUsers()
    Hibernate.initialize(group.getUsers());

    return group;
}

I've not come across a situation where I've had to use the above code, but it should be relatively efficient. For more information about Hibernate.initialize() see here

I have done this in the service layer rather than fetching them in the DAO, because then you only have to create one new method in the service rather than making a separate DAO method as well. The important thing is that you have wrapped the getUsers() call within the transaction, so a session will have been created that Hibernate can use to run the additional queries. This could also be done in the DAO by writing join criteria to your collection, but I've never had to do that myself.

That said, if you find that you are calling the second method far more than you are calling the first, consider changing your fetch type to EAGER and letting the database do the work for you.

like image 65
JamesENL Avatar answered Sep 18 '22 05:09

JamesENL


Although answer from JamesENL is almost correct, it is lacking of some very key aspect.

What he is doing is to force the lazy-loading proxy to load when the transaction is still active. Although it solved the LazyInitialization error, the lazy loadings are still going to be done one by one, which is going to give extremely poor performance. Essentially, it is simply achieving the same result of FetchType.EAGER manually (and with a even worse way, because we missed the possibilities of using JOIN and SUBSELECT strategy), which even contradict with the concern of performance.

To avoid confusion: Using LAZY fetch type is correct.

However, in order to avoid Lazy Loading Exception, in most case, you should have your repository (or DAO?) fetch the required properties.

The most inefficient way is to do it by accessing the corresponding property and trigger the lazy loading. There are some really big drawbacks:

  1. Imagine what happen if you need to retrieve multiple level of data.
  2. If the result set is going to be big, then you are issuing n+1 SQLs to DB.

The more proper way is to try to fetch all related data in one query (or a few).

Just give an example using Spring-data like syntax (should be intuitive enough to port to handcraft Hibernate Repository/DAO):

interface GroupRepository {
    @Query("from Group")
    List<Group> findAll();

    @Query("from Group g left join fetch g.users")
    List<Group> findAllWithUsers();
}

Join fetching is equally simple in Criteria API (though seems only left join is available), quoted from Hibernate doc:

List cats = session.createCriteria(Cat.class)
    .add( Restrictions.like("name", "Fritz%") )
    .setFetchMode("mate", FetchMode.EAGER)
    .setFetchMode("kittens", FetchMode.EAGER)
    .list();
like image 38
Adrian Shum Avatar answered Sep 21 '22 05:09

Adrian Shum