使用链接实体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