Сохранение (обновление) репозитория Spring Jpa с помощью LinkedEntity для отношений ManyToMany - PullRequest
0 голосов
/ 05 июля 2018

Есть 2 объекта (скажем, Rule и Label) с отношением «многие ко многим», использующих связанные объекты. согласно справочной документации по спящему режиму
Правило 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<>();
...


Метка сущности:

@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

Ответы [ 2 ]

0 голосов
/ 06 июля 2018

Хорошо, я всегда использовал EmbeddableId для связи сущностей с JPA. Я не пробовал пример гибернации, на который вы ссылаетесь, используя каскад для выполнения работы за меня. Это может быть интересно, но между чистым 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()));
0 голосов
/ 05 июля 2018

Да, потому что это то, что вы говорите в спящем режиме.

По умолчанию все @ManyToOne и @OneToOne ассоциации EAGER загружены, поэтому, когда он запрашивает Rule, он также запрашивает RuleLabel, а затем внутри снова появляется Rule, что вызывая бесконечные select запросов. Лучше, чтобы они были загружены LAZY .

Вы можете выполнять ленивую загрузку в поле следующим образом @ManyToOne(fetch=FetchType.LAZY)

Вот что JPA 2.0 spec говорит о значениях по умолчанию:

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

Хорошее чтение при загрузке Lazy and Eager

...