2017-02-02 5 views
0

Использование Spring Data REST и Spring Data JPA, я хочу обновить коллекцию дочерних сущностей по корню агрегата. В качестве примера для демонстрации предположим, что у меня есть объект Post, который имеет отношение «один ко многим» с объектом Comment. Post имеет собственный репозиторий данных Spring; Comment не потому, что он доступен только через Post.Spring Data REST/JPA - обновленная коллекция OneToMany с составным ключом

Отвратительный твист состоит в том, что Comment имеет составной ключ, включая внешний ключ, до Post из-за существующего дизайна базы данных. Следовательно, я не мог найти способ, чтобы внешний ключ был частью составного ключа в Comment без двунаправленной связи, хотя мне не нужно двунаправленное отношение.

Классы выглядеть следующим образом с Ломбок аннотаций:

@Entity 
@Data 
public class Post { 

    @Id 
    @GeneratedValue 
    private long id; 

    @OneToMany(mappedBy = "post", fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true) 
    private Set<Comment> comments = new HashSet<>(); 

    private String title; 
} 

А Комментарий:

@Entity 
@IdClass(Comment.CommentPk.class) 
@Data 
@EqualsAndHashCode(exclude = "post") 
@ToString(exclude = "post") 
public class Comment { 

    @Id 
    private long id; 

    @Id 
    @ManyToOne(fetch = FetchType.LAZY) 
    @RestResource(exported = false) 
    @JsonIgnore 
    private Post post; 

    private String content; 

    @Data 
    static class CommentPk implements Serializable { 
     private long id; 

     private Post post; 
    } 
} 

И репозиторий:

public interface PostRepository extends JpaRepository<Post, Long> { 
} 

Если я пытаюсь создать Post с Comment, произойдет исключение, которое POST_ID не может быть NULL. Другими словами, он не имеет обратной ссылки на родителя Post в Comment, который он пытается сохранить.

Это может быть решена путем добавления метода @PrePersist к Post, который поддерживает эту обратную ссылку:

@PrePersist 
private void maintainParentBackreference() { 
    for (Comment comment : this.comments) { 
     comment.setPost(this); 
    } 
} 

выше прекрасно работает при создании нового Post, но это не помогает при попытке добавить Comment к существующей Post (например, с помощью запроса PUT), потому что следующая ошибка возникает при попытке вставить комментарий:

NULL not allowed for column "POST_ID"; SQL statement: 
insert into comment (content, id, post_id) values (?, ?, ?) [23502-193] 

Напомним, что шаги, чтобы Repro дуче являются:

  1. опубликовать Post без каких-либо Comment сек
  2. PUT к созданному PostсComment

Что такое простейший способ, которым я могу достичь возможность обновить/добавить Comment s до существующего Post с использованием Spring Data REST?

образец проект, который демонстрирует это можно найти здесь: https://github.com/shakuzen/aggregate-child-update-sample/tree/composite-key

Эта конкретная установка в composite-key ветви репозитория.Для того, чтобы воспроизвести описанную выше неудачу с этим кодом, вы можете следить за действия вручную воспроизведение в README или запустить тест интеграции AggregateCompositeKeyUpdateTests.canAddCommentWithPut

ответ

0

Вы действительно не должны использовать @PrePersist и @PreUpdate обратные вызовы для управления этими бэк-ссылки, потому что их вызов часто зависит от того, управляется ли состояние Post или нет.

Вместо этого эти отношения должны быть чем-то, что вы манипулируете как часть какого-либо определенного для домена кода, который вызывает ваш контроллер или какой-либо бизнес-сервис. Я часто предпочитаю абстрагироваться этими типы отношений за более Domain Driven Design подхода:

public class Post { 
    @OneToMany(mappedBy = "Post", cascade = CascadeType.ALL, ...) 
    private Set<Comment> comments; 

    // Allows access to the getter, but it protects the internal collection 
    // from manipulation from the outside, forcing users to use the DDD methods. 
    public Set<Comment> getComments() { 
    return Collections.unmodifiableSet(comments); 
    } 

    // Do not expose the setter because we want to control adding/removing 
    // of comments through a more DDD style. 
    private void setComments(Set<Comment> comments) { 
    this.comments = comments; 
    } 

    public void addComment(Comment comment) { 
    if (this.comments == null) { 
     this.comments = new HashSet<Comment>(); 
    } 
    this.comments.add(comment); 
    comment.setPost(this); 
    } 

    public void removeComment(Comment comment) { 
    if (this.comments != null) { 
     for (Iterator<Comment> i = comments.iterator(); i.hasNext();) { 
     final Comment postComment = i.next(); 
     if (postComment.equals(comment)) { 
      // uses #getCompositeId() equality 
      iterator.remove(); 
      comment.setPost(null); 
      return; 
     } 
     } 
    } 
    throw new InvalidArgumentException("Comment not associated with post"); 
    } 

Как видно из кода, пользователи объекта Post лиц вынуждены использовать #addComment и #removeComment, если они хотят, чтобы манипулировать связанные комментарии. Эти методы обеспечивают правильную установку обратной ссылки.

final Comment comment = new Comment(); 
// set values on comment 
final Post post = entityManager.find(Post.class, postId); 
post.addComment(comment); 
entityManager.merge(post); 

UPDATE - Spring Data REST Решение

Для того, чтобы иметь Spring Data REST применить эту логику непосредственно, вы можете либо написать слушателя или класс обратного вызова.

Примером слушателя будет:

public class BeforeSavePostEventListener extends AbstractRepositoryEventListener { 
    @Override 
    public void onBeforeSave(Object entity) { 
    // logic to do by inspecting entity before repository saves it. 
    } 
} 

Пример аннотированный обработчика будет:

@RepositoryEventHandler 
public class PostEventHandler { 
    @HandleBeforeSave 
    public void handlePostSave(Post p) { 
    } 
    @HandleBeforeSave 
    public void handleCommentSave(Comment c) { 
    } 
} 

Далее вам просто нужно, чтобы убедиться, что этот компонент подобран либо указав один из различных стереотипов @Component через сканирование или вам нужно указать его как @Bean в классе конфигурации.

Наибольшее различие между двумя подходами состоит в том, что второе является безопасным по типу, а сущность-тип определяется первым аргументом для различных аннотированных методов.

Подробнее об этом here.

+0

Благодарим вас за углубленный отклик. Я был бы рад сделать то, что вы предлагаете, если бы я мог легко использовать Spring Data REST, используя эти методы при обработке соответствующих HTTP-запросов, но я не уверен, как лучше всего это сделать. –

+0

Добавлена ​​информация о том, как интегрировать мою идею с Spring Data REST с помощью прослушивателя или аннотированных обратных вызовов обработчика. – Naros

Смежные вопросы