单向多对多只读关系

时间:2018-12-12 12:33:12

标签: java hibernate spring-boot jpa

我有一个实体 Student 和一个实体 Course 。一个学生可以与0个或多个课程相关联。反之,一个 Course 可以关联0个或多个 Students

学生实体:

@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;
}

课程实体:

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

    @Id
    private Integer id;

    private String name;

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

和实体 Course 中的逆关联。

这两个 @ManyToMany 关联均应为只读。 我的问题是,当我尝试保存 Student 时,Hibernate也会尝试更新关联的集合。

这是休眠记录学生更新的内容:

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

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

如您所见,hibernate也在尝试更新存储两个实体之间关联的表。 这些是我要避免的查询。

2 个答案:

答案 0 :(得分:0)

学生班:

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

课程:

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

如您所知,从JPA 2.0规范开始,默认获取为:

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

在最新版本的hibernate中,对于所有映射都急于获取类型,但是如果我们使用JPA批注,则它将与JPA默认值保持一致。

答案 1 :(得分:0)

问题在于,list是有序的,而set不是有序的。用于更新列表的通用代码似乎假设您希望保留该顺序,因此它必须删除整个关系列表,然后重新插入它以通过关系PK ID保留该顺序。如果在join属性上添加了@OrderBy批注,那么这将明确地成立。在一个集合中,这不是必需的,因此只需插入即可。

@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;

并使用它们:

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();

这给了我以下日志

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 (?, ?)