8

I have an entity Student and an entity Course. One student can be associated to 0 or more Courses. Viceversa, one Course can have associated 0 or more Students.

Student entity:

@Data
@Entity(name = "student")
public class Student {

    @Id
    private Integer id;

    private String name;

    @ManyToMany(fetch = EAGER)
    @JoinTable(name = "student_course",
            joinColumns = @JoinColumn(
                    name = "studentId",
                    referencedColumnName = "id",
                    insertable = false,
                    updatable = false
            ),
            inverseJoinColumns = @JoinColumn(
                    name = "courseId",
                    referencedColumnName = "id",
                    insertable = false,
                    updatable = false)
    )
    private Collection<Course> courses;
}

Course entity:

@Data
@Entity(name = "course")
public class Course {

    @Id
    private Integer id;

    private String name;

    @ManyToMany(mappedBy = "courses")
    private Collection<Student> students;
}

and the inverse association in the entity Course.

Both of those 2 @ManyToMany associations should be readonly. My problem is that when I try to save a Student Hibernate tries to update also the associated collection.

Here's what hibernate logs on a student update:

Hibernate: 
/* delete collection model.student.courses */ 
delete from `student_course` 
where `studentId`=?

Hibernate: 
/* insert collection row  */
insert into `student_course` (`studentId`, `courseId`) 
values (?, ?)

As you can see hibernate is trying to update also the table that stores the associations between the two entities. Those are the queries that I want to avoid.

desoss
  • 572
  • 4
  • 26
  • Have you tried cascading? – Syed Anas Dec 12 '18 at 12:42
  • @Anas yes I've tried it – desoss Dec 12 '18 at 12:44
  • Can you tell me which cascading you used – Syed Anas Dec 12 '18 at 12:47
  • I've used the cascading ALL. Nevertheless in my opinion the correct approach should be to avoid using any cascading type. – desoss Dec 12 '18 at 12:49
  • 1
    All would not work as All includes persist and delete so it would perform the respective operations, I'd suggest try using cascade Refresh only and see if it works. – Syed Anas Dec 12 '18 at 12:50
  • 1
    I've tried REFRESH but hibernate is still generating deletions and insertions for that association. – desoss Dec 12 '18 at 12:52
  • 1
    Allright... let me look for further solutions as well – Syed Anas Dec 12 '18 at 12:53
  • I found another solution, apparently making your relationships transient will stop their persist behaviour on database. Try making the relationship `@Transient` and let me know https://stackoverflow.com/a/2154640/2819935 – Syed Anas Dec 12 '18 at 12:57
  • I cannot make it transient otherwise hibernate doesn't initialise the relationship on select. – desoss Dec 12 '18 at 13:00
  • Please, provide classes `Student` and `Course` with related to mapping fields and annotations. – v.ladynev Dec 12 '18 at 13:00
  • @v.ladynev I've updated my question providing the classes I'm using. The Data annotation is of Lombock and it automatically creates getter and setters. – desoss Dec 12 '18 at 13:39
  • I know it sounds weird, but have you considered not using lombok, and just write getter and setters with an IDE template? – Vitor Santos Dec 12 '18 at 13:49
  • @VitorSantos yes I've tried, the problem is not Lombok – desoss Dec 12 '18 at 15:01
  • https://stackoverflow.com/a/7078678/2819935. I can already see you have mentioned the keywords informed in the link. Can you try changing the fetch type as suggested in the answer and also I'd like to know why you have used `@JoinTable` annotation instead of simple `@JoinColumn` – Syed Anas Dec 13 '18 at 06:18
  • 1
    @Anas because I'm using a ManyToMany relationship not a OneToMany. The associations between the 2 entities are stored in specific table, so I need to have multiple JoinColumns – desoss Dec 13 '18 at 16:14
  • @desoss understood. I mapped many to many in a different way ... Since I created a third table. I generated a separate entity and mapped both single entities in a one-to-many to that third table. In your case the third java class should be StudentCourse and the collection of Course would be changed to collection of StudentCourse – Syed Anas Dec 14 '18 at 06:53
  • @desoss I have also tried and tested `@Transient` in entity and while fetching it does get initialized. Can you also apply that and check? – Syed Anas Dec 14 '18 at 07:23

2 Answers2

0

Student class:

@ManyToMany(fetch = LAZY)
@JoinTable(
    name="STUDENT_COURSE"
    , joinColumns={
        @JoinColumn(name="STUDENT_ID")
        }
    , inverseJoinColumns={
        @JoinColumn(name="COURSE_ID")
        }
    )
private List<Course> courses;

Course class :

@ManyToMany(mappedBy="courses")
private List<Student> students;

As you khnow, from the JPA 2.0 spec, the defaults fetch are:

OneToMany: LAZY
ManyToOne: EAGER
ManyToMany: LAZY
OneToOne: EAGER

And in the latest version of hibernate fetch type is eager by deafult for all the mappings but if we are using JPA annotations then it aligns with JPA defaults.

Abder KRIMA
  • 3,418
  • 5
  • 31
  • 54
  • This doesn't solve my problem. Hibernate still perform DELETE and INSERT queries or on a save of a Student or on a save of a Course. While I want 2 readonly associations. – desoss Dec 12 '18 at 13:31
  • @desoss even if you try with fetch= Lazy ? – Abder KRIMA Dec 12 '18 at 13:38
  • Sure, I'm obtaining that query on a save(Student) (or on save(Course) depending on where the mappedBy is) not on the retrieval of the entities – desoss Dec 12 '18 at 13:41
0

The issue is that a list is ordered whereas a set is not. The generalized code for updating a list seems to assume that you wish to preserve that order and so it must delete the entire List of relations and reinsert it fresh to preserve the order through the relation PK ids. This would be explicitly true if you added an @OrderBy annotation on the join property. In a set this is not necessary so it can just do an insert.

@Entity
@Data
@EqualsAndHashCode(of="id")
@NoArgsConstructor
public class E1 {
    @Id @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;

    @ManyToMany
    Set<E2> e2s;

@Entity
@Data
@EqualsAndHashCode(of="id")
@NoArgsConstructor
public class E2 {
    @Id @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;

    @ManyToMany(mappedBy="e2s")
    Set<E1> e1s;

And to use them:

E1 e1 = new E1();
E2 e2 = new E2();
Set<E2> es2 = new HashSet<>();
es2.add(e2);
e1.setE2s(es2);

em.persist(e1);
em.persist(e2);
tx.commit();

// now modify the list
em.clear();

// fetch relations to avoid lazy fetch exception
e1 = em.createQuery("select e from E1 e left outer join fetch e.e2s where e.id = :id", E1.class)
        .setParameter("id", 1L)
        .getSingleResult();

tx.begin();
e2 = new E2();
e1.getE2s().add(e2);
em.persist(e2);
em.merge(e1);
tx.commit();

This gives me the following logs

create table E1_E2 (e1s_id bigint not null, e2s_id bigint not null, primary key (e1s_id, e2s_id))
create table E1 (id bigint generated by default as identity (start with 1), primary key (id))
create table E2 (id bigint generated by default as identity (start with 1), primary key (id))
alter table E1_E2 add constraint FKky9vffxlkk0u9t0ynqfsvanrt foreign key (e2s_id) references E2
alter table E1_E2 add constraint FKgnbwe4qtab0mt1caqxrrp8gqd foreign key (e1s_id) references E1
insert into E1 (id) values (default)
insert into E2 (id) values (default)
insert into E1_E2 (e1s_id, e2s_id) values (?, ?)
select e1x0_.id as id1_2_0_, e2x2_.id as id1_4_1_, e2s1_.e1s_id as e1_3_0__, e2s1_.e2s_id as e2_3_0__ from E1 e1x0_ left outer join E1_E2 e2s1_ on e1x0_.id=e2s1_.e1s_id left outer join E2 e2x2_ on e2s1_.e2s_id=e2x2_.id where e1x0_.id=?
insert into E2 (id) values (default)
insert into E1_E2 (e1s_id, e2s_id) values (?, ?)
K.Nicholas
  • 10,956
  • 4
  • 46
  • 66
  • The problem is that I want both ManyToMany relationships to be readonly. Hence I don't want to see any INSERT (nor any DELETE) for the children entities when a persist the parent entity occurs. – desoss Dec 13 '18 at 15:42
  • Yea that makes. A read only persistent object. Good luck with that. Say hi to Putin for me. – K.Nicholas Dec 13 '18 at 15:43
  • When I save A I just want that A would be saved not also also its children. This is the behaviour of the inverse ManyToMany relation (the one with mappedBy field). – desoss Dec 13 '18 at 15:46
  • That's nice, I guess, since I have no idea what you're talking about. – K.Nicholas Dec 13 '18 at 15:51
  • In your example if you try to persist a E2 entity with some e1s entries you shouldn't see any insert(/delete) into E1. I want this behaviour in both entities. – desoss Dec 13 '18 at 15:55
  • Persist or merge? If you define a relationship and add elements to it why wouldn't it persist? E1 is the owner so any relations added to it will be persisted. E2 is not the owner so it will ignore any new relations added there. The `e1s` set in E2 is for query only and makes the mapping bidirectional. You can't have a read-only unidirectional relation because the owner is by definition responsible for persisting the entity and its relations. – K.Nicholas Dec 13 '18 at 16:19
  • How I manage the update of the associations is out of the scope of this question. But you are right it seems that I cannot have a readonly owner of ManyToMany relationship even if the JoinColumns have insertable=false and updatable=false. – desoss Dec 13 '18 at 16:22
  • I assume those settings are overridden by the ownership and probably not intended for such purposes. – K.Nicholas Dec 13 '18 at 16:24