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

时间:2018-07-05 13:27:46

标签: java hibernate spring-data-jpa

有2个使用链接的实体具有多对多关系的实体(让我们说规则和标签) as per hibernate reference documentation
规则实体:

@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: 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的行为方式相同)
如何解决?
更新: 将抓取策略更改为“懒惰”后

@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

2 个答案:

答案 0 :(得分:1)

好的,我一直使用EmbeddableId来创建带有JPA的链接实体。我还没有尝试过使用级联为我完成工作时提到的休眠示例。可能很有趣,但是纯JPA和Spring Data Repository之间存在一些差异。通过使用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()));

答案 1 :(得分:-1)

是的,因为这是您要告诉休眠的操作。

默认情况下,所有@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可以方便地进行延迟加载和加载