Spring Jpa Data Repository使用LinkedEntity for ManyToMany关系保存(更新)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring Jpa Data Repository使用LinkedEntity for ManyToMany关系保存(更新)相关的知识,希望对你有一定的参考价值。

使用链接实体as per hibernate reference documentation有2个实体(比如规则和标签)与多对多关系 规则:

@Entity
@Table(name = "rule")
@JsonIdentityInfo(
    generator = ObjectIdGenerators.PropertyGenerator.class,
    property = "name")
public class Rule implements Serializable {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@NaturalId
@NotBlank
@Column(unique = true)
private String name;

@Lob
@Column(columnDefinition = "TEXT")
private String content;

@OneToMany(mappedBy = "rule", cascade = {CascadeType.PERSIST, 
    CascadeType.MERGE})
private List<RuleLabel> labels = new ArrayList<>();
...

标签实体:

@Entity
@Table(name = "label")
@JsonIdentityInfo(
    generator = ObjectIdGenerators.PropertyGenerator.class,
    property = "id")
public class Label implements Serializable {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@NotBlank
private String name;

@OneToMany(mappedBy = "label", cascade = {CascadeType.PERSIST, 
    CascadeType.MERGE})
private List<RuleLabel> rules = new ArrayList<>();
...

链接实体:

@Entity
public class RuleLabel implements Serializable {

@Id
@ManyToOne
private Rule rule;

@Id
@ManyToOne
private Label label;
...

库:

@Repository
public interface LabelRepository extends JpaRepository<Label, Long>
...
@Repository
public interface RuleRepository extends JpaRepository<Rule, Long>
...

通过RuleRepository.save(Rule)创建新实体工作正常,但是当我尝试更新现有实体时(同样的方法是RuleRepository.save(Rule),但要保存的实体包含id字段)它会导致Hibernate的无限循环:选择...查询:

Hibernate: select rule0_.id as id1_7_1_, rule0_.is_active as is_activ2_7_1_, rule0_.content as content3_7_1_, rule0_.is_deleted as is_delet4_7_1_, rule0_.import_section as import_s5_7_1_, rule0_.name as name6_7_1_, rule0_.rule_configuration as rule_con7_7_1_, labels1_.rule_id as rule_id1_8_3_, labels1_.label_id as label_id2_8_3_, labels1_.rule_id as rule_id1_8_0_, labels1_.label_id as label_id2_8_0_ from rule rule0_ left outer join rule_label labels1_ on rule0_.id=labels1_.rule_id where rule0_.id=?

结果是StackOverflowError

java.lang.StackOverflowError: null
at com.mysql.jdbc.ServerPreparedStatement.getInstance(ServerPreparedStatement.java:332)
...

(LabelRepository的行为方式相同) 怎么修好? 更新:将获取策略更改为Lazy后

@Id
@ManyToOne(fetch = FetchType.LAZY)
private Rule rule;

@Id
@ManyToOne(fetch = FetchType.LAZY)
private Label label;

无限循环问题已经消失,但新的问题已经出现 - 相关的实体没有被填充,当Hibernate试图将值插入到链接表中时

Hibernate: insert into rule_label (rule_id, label_id) values (?, ?)

我们得到

org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
...
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Column 'rule_id' cannot be null
答案

好吧,我总是使用EmbeddableId作为JPA的链接实体。我没有尝试过你在使用cascade为我做的工作方面提到的hibernate示例。它可能很有趣,但纯JPA和Spring Data Repositories之间存在一些差异。通过使用EmbeddableId,您可以为链接实体创建单独的spring存储库。然后你自己管理关系。如果您不想这样做,那么您应该使用ManyToMany注释,但链接实体允许您创建链接实体属性,此处未显示。此代码适用于您并让您指向B,您可以从那里进行实验:

@Entity
public class Label {
    @Id @GeneratedValue private Long id;
    @OneToMany(mappedBy = "ruleLabelId.labelId")
    private List<RuleLabel> rules = new ArrayList<>();

@Entity
public class Rule {
    @Id @GeneratedValue private Long id;
    @OneToMany(mappedBy = "ruleLabelId.ruleId")
    private List<RuleLabel> labels = new ArrayList<>();

@Entity
public class RuleLabel {
    @EmbeddedId
    private RuleLabelId ruleLabelId;

@SuppressWarnings("serial")
@Embeddable
public class RuleLabelId implements Serializable {
    private Long ruleId;
    private Long labelId;

public interface RuleRepository extends JpaRepository<Rule, Long> {
    @Query("from Rule r left join fetch r.labels where r.id = :id")
    public Rule getWithLabels(@Param("id") Long id);
}

public interface RuleLabelRepository extends JpaRepository<RuleLabel, RuleLabelId> {}

并使用它:

Rule rule = new Rule();
Label label = new Label();

ruleRepo.save(rule);
labelRepo.save(label);

RuleLabel ruleLabel = new RuleLabel();
RuleLabelId ruleLabelId = new RuleLabelId();
ruleLabelId.setRuleId(rule.getId());
ruleLabelId.setLabelId(label.getId());
ruleLabel.setRuleLabelId(ruleLabelId);

ruleLabelRepo.save(ruleLabel);

rule = ruleRepo.getWithLabels(1L);
System.out.println(rule + Arrays.toString(rule.getLabels().toArray()));
另一答案

是的,因为它告诉你hibernate要做什么。

默认情况下,所有@ManyToOne@OneToOne关联都是EAGER加载的,所以当它查询Rule然后它也查询RuleLabel然后在里面再次有Rule导致无限的select查询。让他们加载LAZY会更好。

你可以像这个@ManyToOne(fetch=FetchType.LAZY)那样进行现场延迟加载

这就是JPA 2.0 spec关于默认值的说法:

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

懒惰和渴望加载的好read

以上是关于Spring Jpa Data Repository使用LinkedEntity for ManyToMany关系保存(更新)的主要内容,如果未能解决你的问题,请参考以下文章

spring-data-jpa 和 spring-boot-starter-data-jpa 的区别

spring-data详解之spring-data-jpa:简单三步快速上手spring-data-jpa开发

spring data jpa怎么和solr整合

spring data jpa问题

Spring Data 系列 Spring+JPA(spring-data-commons)

spring-data-jpa软删除方案