【问题标题】:Unidirectional ManyToMany readonly relationship单向多对多只读关系
【发布时间】:2019-05-13 13:31:34
【问题描述】:

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

学生实体:

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

和实体中的逆关联课程

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

这是 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 正在尝试更新存储两个实体之间关联的表。 这些是我想避免的查询。

【问题讨论】:

  • 你试过级联吗?
  • All 不起作用,因为 All 包括 persist 和 delete,因此它会执行相应的操作,我建议尝试仅使用级联 Refresh 并查看它是否有效。
  • 我已经尝试过 REFRESH,但 hibernate 仍在为该关联生成删除和插入。
  • 好吧...让我也寻找进一步的解决方案
  • @Anas 因为我使用的是多对多关系而不是单对多关系。 2 个实体之间的关联存储在特定的表中,所以我需要有多个 JoinColumns

标签: java hibernate spring-boot jpa


【解决方案1】:

学生班:

@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 中,fetch 类型对所有映射都是默认的,但如果我们使用 JPA 注释,那么它会与 JPA 默认值保持一致。

【讨论】:

  • 这并不能解决我的问题。 Hibernate 仍然执行 DELETE 和 INSERT 查询或保存学生或保存课程。虽然我想要 2 个只读关联。
  • @desoss 即使您尝试使用 fetch= Lazy ?
  • 当然,我是在 save(Student) 上(或在 save(Course) 上,取决于 mappedBy 在哪里)而不是在检索实体时获得该查询
【解决方案2】:

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

【讨论】:

  • 问题是我希望这两个多对多关系都是只读的。因此,当父实体发生持久化时,我不想看到子实体的任何 INSERT(或任何 DELETE)。
  • 是的。只读持久对象。祝你好运。替我向普京问好。
  • 当我保存 A 时,我只想保存 A 而不是它的孩子。这是反向多对多关系(具有 mappedBy 字段的关系)的行为。
  • 我想这很好,因为我不知道你在说什么。
  • 在您的示例中,如果您尝试使用一些 e1s 条目持久化 E2 实体,您不应该在 E1 中看到任何插入(/删除)。我希望这两个实体都有这种行为。
猜你喜欢
  • 1970-01-01
  • 2019-10-22
  • 2013-12-28
  • 1970-01-01
  • 1970-01-01
  • 2014-09-03
  • 1970-01-01
  • 1970-01-01
  • 2023-01-27
相关资源
最近更新 更多