简体   繁体   English

Spring Jpa数据存储库使用LinkedEntity for ManyToMany关系进行保存(更新)

[英]Spring Jpa Data Repository save (update) with LinkedEntity for ManyToMany relationship

There are 2 entities (lets say Rule and Label) with many-to-many relationship using linked entity as per hibernate reference documentation 根据休眠参考文档,使用链接的实体有2个具有多对多关系的实体(让我们说规则和标签)
Rule enity: 规则实体:

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


Label entity: 标签实体:

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


Link entity: 链接实体:

@Entity
public class RuleLabel implements Serializable {

@Id
@ManyToOne
private Rule rule;

@Id
@ManyToOne
private Label label;
...


Repositories: 库:

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


Creating new entity via RuleRepository.save(Rule) works fine, but when I'm trying to update existing entity (the same method RuleRepository.save(Rule) , but entity to be saved contains id field) it leads to infinite loop of Hibernate: select... queries: 通过RuleRepository.save(Rule)创建新实体工作正常,但是当我尝试更新现有实体(相同的RuleRupository.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=?

and StackOverflowError as a result 结果是StackOverflowError

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

(LabelRepository acts in the same manner) (LabelRepository的行为方式相同)
How it can be fixed? 如何解决?
Update: After changing fetch strategy to Lazy 更新:将获取策略更改为惰性后

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

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

infinite loop problem has gone, but new one has appeared - related entities are not being populated and when Hibernate is trying to insert values into link table 无限循环问题已经消失,但是出现了一个新问题-相关实体没有被填充,并且当Hibernate试图在链接表中插入值时

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

we get 我们得到

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

Okay, well I've always used an EmbeddableId for link entities with JPA. 好了,好了我一直使用的EmbeddableId与JPA链接实体。 I haven't tried the hibernate example you refer to in terms of using cascade to do the work for me. 我还没有尝试过使用级联为我完成工作时提到的休眠示例。 It could be interesting but there are some differences between pure JPA and Spring Data Repositories. 可能很有趣,但是纯JPA和Spring Data Repository之间存在一些差异。 By using an EmbeddableId you can create a separate spring repository for the link entity. 通过使用EmbeddableId您可以为链接实体创建单独的spring存储库。 Then you manage the relationships yourself. 然后,您可以自己管理关系。 If you don't want to do that then you should use a ManyToMany annotation, but the link entity allows you to create link entity attributes, not shown here. 如果您不想这样做,则应使用ManyToMany批注,但是链接实体允许您创建链接实体属性,此处未显示。 This code will work for you and get you to point B and you can experiment from there: 该代码将为您工作,并使您到达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> {}

and to use it: 并使用它:

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

Yes because its what you are telling hibernate to do. 是的,因为您要告诉冬眠去做。

By default, all @ManyToOne and @OneToOne associations are EAGER loaded, so when it querying Rule then its also querying RuleLabel and then inside there is Rule again which is causing infinite select queries. 默认情况下,所有@ManyToOne@OneToOne关联都加载了EAGER ,因此在查询Rule时,它还会查询RuleLabel ,然后在里面再次存在Rule ,这会导致无限select查询。 It's better to have them LAZY loaded. 最好将它们加载为LAZY

You can do field lazy load like this @ManyToOne(fetch=FetchType.LAZY) 您可以像这样@ManyToOne(fetch=FetchType.LAZY)进行字段延迟加载

This is what JPA 2.0 spec say about defaults: 这是JPA 2.0规范对默认值的说明:

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

A good read on Lazy and Eager loading 很好的阅读懒惰和渴望加载

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM