12

I am facing issues while retrieving data for entities having bi-directional many-to-many relationship. If I use List for storing entities, I get unable to fetch multiple bags simultaneously error. If i change my code to use Set, I get stackoverflow error.

Details :

  • Spring 3.0.3
  • Hibernate-core : 3.5.1-Final
  • Hibernate-annotations : 3.5.1-Final
  • hibernate-common-annotations : 3.2.0-Final
  • hibernate-entitymanager : 3.5.1-Final
  • Mysql database
  • Junit 4

User has Many Bank Accounts; Bank Account can have many users

User.java

@ManyToMany(fetch = FetchType.EAGER, mappedBy="user") 
private List<BankAccount> bankAccounts = new ArrayList<BankAccount>();

BankAccount.java

@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "user_bankaccount", 
           joinColumns = @JoinColumn(name="bank_account_id"), 
           inverseJoinColumns = @JoinColumn(name = "user_id")
)
private List<User> user = new ArrayList<User>();

DB Tables

Users
user_id PK

Bankaccount
bank_account_id PK

user_bankaccount
bank_account_id PK ( references bankaccount.bank_account_id )
user_id PK ( references user.user_id )

issues

  1. when I try to get all the users data (getAllUsers) using a JUnit test case, I get unable to fetch multiple bags simultaneously error.
  2. If I use Set and HashSet instead of List and ArrayList respectively, I get stackoverflow error.

Please help me and let me know if code is wrong or its a known hibernate issue with specific version of libs that I am using.

Pascal Thivent
  • 562,542
  • 136
  • 1,062
  • 1,124
Suyash
  • 161
  • 1
  • 2
  • 7

5 Answers5

31

I've faced the similar issue and the root cause is a Lombok generation code as Vjeetje mentioned above. My code uses the annotation @Data, which generates hashCode and ToString methods with cross-dependent fields and this structure leads to Hibernate stuck. So, one should care to avoid these infinite loop calls, in my case I just added the exclusion parameters, like @EqualsAndHashCode(exclude="dependent_list") and @ToString(exclude = "dependent_list"). It solves stack overflow issue.

kolya_metallist
  • 589
  • 9
  • 20
3

You cannot map the many to many relationship on both of the lists, hibernate will then try to fetch a collection for every nested element i.e. every user in the users list has a list of bank accounts which have a list of users... . think of it of a never ending recursion.

Noam Nevo
  • 3,021
  • 10
  • 35
  • 49
  • Hi Noam thanks for your reply. But dont we need Collection on both sides since its a many-to-many relationship ? – Suyash Oct 21 '10 at 15:08
  • in order for hibernate to map the collections only one side is required. from the hibernate annotaion docs: "As seen previously, the other side don't have to (must not) describe the physical mapping: a simple mappedBy argument containing the owner side property name bind the two." – Noam Nevo Oct 21 '10 at 15:46
  • 4
    I was able to resolve the issue by removing references of other entity from toString() method. i.e removed references of BankAccount from User object's toString method and vice versa. I also removed fetch=fetchType.EAGER from all the entities.However, i thought that hibernate was able to detect such a cyclic dependency and not throw stackoverflow exception. – Suyash Oct 27 '10 at 08:32
2

(Let's leave the fetch attribute aside for now). Your mapping is perfectly valid and is the right way to map a bidirectional many-to-many relationship. From the JPA 2.0 specification:

2.10.4 Bidirectional ManyToMany Relationships

...

Example:

@Entity
public class Project {
    private Collection<Employee> employees;

    @ManyToMany
    public Collection<Employee> getEmployees() {
        return employees;
    }

    public void setEmployees(Collection<Employee> employees) {
        this.employees = employees;
    }
    ...
}

@Entity
public class Employee {
    private Collection<Project> projects;

    @ManyToMany(mappedBy="employees")
    public Collection<Project> getProjects() {
        return projects;
    }
    public void setProjects(Collection<Project> projects) {
        this.projects = projects;
    }
    ...
}

In this example:

  • Entity Project references a collection of Entity Employee.
  • Entity Employee references a collection of Entity Project.
  • Entity Project is the owner of the relationship.

...

That being said, I'm unsure of the behavior when using EAGER fetching on both sides (will it lead to an infinite cycle?), the JPA specification is pretty blurry about this and I can't find any clear mention that it is forbidden. But I bet that it's part of the problem.

But in the particular case of Hibernate, I'd expect Hibernate to be able to handle cycles as mentioned in this comment from Emmanuel Bernard:

LAZY or EAGER should be orthogonal to an infinite loop issue in the codebase. Hibernate knows how to handle cyclic graphs

Funnily enough, I've answered a very similar question recently (very close problem). Maybe an hint that something is wrong in Hibernate (my understanding of the above comment is that using EAGER fetching on both sides should work).

I'll thus conclude my answer in the same way: if you can provide a test case allowing to reproduce the problem, open a Jira issue.

Community
  • 1
  • 1
Pascal Thivent
  • 562,542
  • 136
  • 1,062
  • 1,124
  • I have added JIRA for this issue alongwith complete source code http://opensource.atlassian.com/projects/hibernate/browse/HHH-5691 – Suyash Oct 26 '10 at 07:34
  • 6
    I had a very similar issue using Lombok's ToSring annotation on a many-to-many relationship between two entities. Solution: use the exclude attribute of the ToString annotation. Anyway, Hibernate can work fine with a many-to-many relationship where both sides are fetched eagerly. – Vjeetje Jan 28 '16 at 18:57
1

I was also facing the same issue.

The problem was with my @Data annotation from the lombok library.

To fix it, I replaced @Data with @Getter, @Setter. And defined a custom toString() method on the class.

Prathamesh Beri
  • 131
  • 1
  • 5
0

The Problem,

Classes go in recursion.

The solution : Remove the default toString() and hashCode() method, from Lombok and how to remove it.

Add two annotations to mention the customization.

@ToString(exclude = "components")
@EqualsAndHashCode(exclude = "components")

Complete example.

package com.oracle.dto2;

import lombok.*;

import javax.persistence.*;
import java.util.List;

@Entity
@Data
@ToString(exclude = "components")
@EqualsAndHashCode(exclude = "components")
public class Device {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO,generator = "ds")
    @SequenceGenerator(name = "ds",sequenceName = "dev_seq",allocationSize = 1,initialValue = 1)
    @Column(name = "device_Id",updatable = false,nullable = false)
    @Setter(AccessLevel.NONE)
    private Integer deviceId;
    @OneToMany
    List<Component> components;
}

package com.oracle.dto2;

import lombok.AccessLevel;
import lombok.Data;
import lombok.Setter;

import javax.persistence.*;

@Entity
@Data
public class Component {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO,generator = "cs")
    @SequenceGenerator(name = "cs",sequenceName = "comp_seq",allocationSize = 1,initialValue = 1)
    @Column(name = "component_Id",updatable = false,nullable = false)
    @Setter(AccessLevel.NONE)
    private Long componentId;
    @Column(name="component_price",nullable = false)
    private Double price;
    @Column(name="component_manufacturer",nullable = true)
    private String manufacturer;
}

Pratik Gaurav
  • 661
  • 7
  • 8