Как правильно изменить вложенные коллекции Hibernate без слишком большого количества побочных эффектов - PullRequest
0 голосов
/ 07 марта 2020

У меня есть следующий код:

У меня есть однонаправленное отношение один-ко-многим между Article и Comments:

@Entity
public class Article {

       @OneToMany(orphanRemoval=true)
       @JoinColumn(name = "article_id")
       private List<Comment> comments= new ArrayList<>();
        …
 }

Я использовал set ophanRemoval=true, чтобы отметить «дочерняя» сущность должна быть удалена, когда на нее больше нет ссылок из «родительской» сущности, например, когда вы удаляете дочернюю сущность из соответствующей коллекции родительской сущности.

Вот пример:

@Service
public class MyService {

public Article modifyComment(Long articleId) {

    Article article = repository.findById(articleId);

    List<Comments> comments = article.getComments();

    //Calls a method which modifies removes some comments from the collection based on some logic
    removeSomeComments(comments); //side effect

    modifyComments(comments); //side effect
    .....

    return repository.save(article);
    }
}

Итак, у меня есть несколько операторов, которые выполняют некоторые действия с коллекцией, которые затем сохраняются в базе данных. В приведенном выше примере я получаю статью из базы данных, выполняю некоторые мутации на объекте, удаляя / изменяя некоторые комментарии, а затем сохраняя ее в базе данных.

Я не уверен, каков самый чистый способ изменения коллекций объектов без большого количества побочных эффектов, что приводит к подверженному ошибкам коду (мой код более сложен и требует многократных мутаций в коллекции).

Поскольку я нахожусь внутри транзакции, любые изменения (добавление, удаление или изменение дочерних элементов) в коллекции будут сохраняться при следующем вызове EntityManager.commit().

Однако я попытался реорганизовать этот код и написать его в более выразительном функциональном стиле:

public Article modifyComment(Long articleId) {

    Article article = repository.findById(articleId);

    List<Comment> updatedComments = article.getComments().stream()
             filter(some logic..) //remove some comments from the list based on a filter
             sorted()
            .filter(again some logic) //do more stuff
            .collect(Collectors.toList());

    article.add(updatedComments);

    return repository.save(article);
}

Мне больше нравится этот подход, так как он короткий, лаконичный и более выразительный. Однако это не будет работать, так как выдает: A collection with cascade=“all-delete-orphan” was no longer referenced by the owning entity instance

Это потому, что я назначаю новый список (updatedComments). Если я хочу удалить или изменить дочерние элементы из родительского, я должен изменить содержимое списка, а не назначать новый список.

Поэтому мне пришлось сделать это в конце:

article.getComments().clear();
article.getComments().addAll(updatedComments);
repository.save(article)

Считаете ли вы второй пример хорошей практикой?

Я не уверен, как работать с коллекциями в JPA. Моя бизнес-логика c более сложная, и я хочу избежать 3-4 методов, которые изменяют данную коллекцию (присоединенную к сеансу гибернации), которая была передана как параметр.

Я думаю, что во втором примере есть меньший потенциал для побочных эффектов, потому что он не изменяет любой входной параметр. Как вы думаете?

(я использую Spring-Boot 2.2.5)

1 Ответ

0 голосов
/ 08 марта 2020

На самом деле вы можете попробовать повернуть логику предиката c, используемую в вашем фильтре

.filter(some logic..) //remove some comments from the list based on a filter

, для использования в removeIf, и выполнить модификацию следующим образом:

Article article = repository.findById(articleId);
article.getComments().removeIf(...inverse of some logic...) //this
return repository.save(article);
...