有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
答案 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可以方便地进行延迟加载和加载