trzeciakp
trzeciakp

Reputation: 109

spring jpa - how to persist transitive bidirectional relationship

I have three classes (A, B, C) which have OneToMany relationships A <>-- B <>-- C code:

@Getter @Setter
@Entity
public class A {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @OneToMany(fetch = FetchType.EAGER, mappedBy = "a")
    private List<B> bList;

    private String name;
}

@Getter @Setter
@Entity
public class B {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @ManyToOne(fetch = FetchType.EAGER)
    private A a;

    @OneToMany(fetch = FetchType.EAGER, mappedBy = "b")
    private List<C> cList;

    private String name;
}

@Getter @Setter
@Entity
public class C {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String name;

    @ManyToOne(fetch = FetchType.EAGER)
    private B b;
}

How should I properly populate the data? When I'm trying something like this:

    A a = new A();
    a.setName("A-1");
    aRepository.save(a);

    B b1 = new B();
    b1.setName("B-1");
    b1.setA(a);
    bRepository.save(b1);

    B b2 = new B();
    b2.setName("B-2");
    b2.setA(a);
    bRepository.save(b2);

    for (int i = 1; i <= 9; i++ ) {
        C c = new C();
        c.setName("C-"+i);
        c.setB(b1);
        cRepository.save(c);
    }

I get properly populated data in the database:

+----+------+
| ID | NAME |
+----+------+
| 1  | A-1  |
+----+------+

+----+------+------+
| ID | NAME | A_ID |
+----+------+------+
| 1  | B-1  | 1    |
+----+------+------+
| 2  | B-2  | 1    |
+----+------+------+

+----+------+------+
| ID | NAME | B_ID |
+----+------+------+
| 1  | C-1  | 1    |
+----+------+------+
| 2  | C-2  | 1    |
+----+------+------+
| 3  | C-3  | 1    |
+----+------+------+
| 4  | C-4  | 1    |
+----+------+------+
| 5  | C-5  | 1    |
+----+------+------+
| 6  | C-6  | 1    |
+----+------+------+
| 7  | C-7  | 1    |
+----+------+------+
| 8  | C-8  | 1    |
+----+------+------+
| 9  | C-9  | 1    |
+----+------+------+

But when I'm trying to fetch data from the repository something is wrong:

These tests are ok:

    assertThat(cRepository.findOne(1l)).isNotNull();
    assertThat(cRepository.findOne(1l).getB()).isNotNull();
    assertThat(cRepository.findAll()).hasSize(9);
    assertThat(bRepository.findAll()).hasSize(2);
    assertThat(bRepository.findOne(1l).getCList().size()).isEqualTo(9);

but this one is failing:

    assertThat(aRepository.findOne(1l).getBList().size()).isEqualTo(2);

It returns 10 records. Query SELECT * FROM B WHERE A_ID = 1 returns 2 records, so could you please shed some light on what i am doing wrong?

Upvotes: 2

Views: 1617

Answers (2)

jgr
jgr

Reputation: 2931

Its strange hibernate behaviour which results from the fact that EAGER type using OUTER JOINS. You get duplicate B objects for each of C objects related with B. I had similiar problem and the only solution i found for that is change fetchType to LAZY or change List to Set.

Here you have better explanation: Hibernate Criteria returns children multiple times with FetchType.EAGER or Duplicates in OneToMany annotated List

Upvotes: 2

mh-dev
mh-dev

Reputation: 5503

You need to change it to the following:

B b1 = new B();
b1.setName("B-1");
b1.setA(a);
a.getBList().add(b1);
bRepository.save(b1);

B b2 = new B();
b2.setName("B-2");
b2.setA(a);
a.getBList().add(b2);
bRepository.save(b2);
aRepository.save(a);

Now a bit side information from my side. In theory it works also without the three extra lines, but only if you invalidate the hibernate cache. I think maybe it would return the correct result if you detach the a and dann get the a. General advice, avoid bidirectional mappings if possible. If you need it add the follwing in a

public void addB(B b) {
    b.setA(this);
    a.getBList().add(b);
}

Releated to the ManyToOne. Theres no need to add eager fetching because thats the default value.

Upvotes: 0

Related Questions