【问题标题】:Have a property in an entity which contains the total number of children of a relationship entity在实体中拥有一个属性,该属性包含关系实体的子级总数
【发布时间】:2019-01-05 11:30:29
【问题描述】:

我有一个 Question 实体和一个 Answer 实体。我希望问题有一个包含答案数量的属性。

我知道我可以在 Question 中定义关系,以便将所有答案与问题一起加载,但这不是很有效。我在一个单独的请求中加载了一个问题的所有答案。

我知道我也可以计算一个问题有多少个答案(使用存储库方法),但我希望将该逻辑与 Question 实体耦合,以便理想情况下完成答案计数和加载问题对象在 JPA 的同一个 SQL 查询中,否则请求必须执行 20 次 SELECT COUNT 查询才能获得每个问题的答案数,因为每次加载 20 个问题。

这可能是用 Spring JPA 实现的吗?

【问题讨论】:

  • QuestionAnswers 集合吗?如果是,您可以在Question 上使用addAnswer 等方法来保留总数,然后将其作为属性保留。如果您处理的是对象而不是贫乏的数据结构,那么您可能无论如何都想要这个。
  • 但这将触发第一个问题:所有答案将与数据库中的问题一起加载,只是为了能够计算总数。
  • 如果使用 Hibernate - a.将@OneToMany Set<Answer> answers 添加到Question。湾。将@LazyCollection(LazyCollectionOption.EXTRA) 添加到answers。然后,question.getAnswers().size() 将简单地发出 SELECT COUNT(answer) ... 查询,而不加载每个问题的答案。
  • 嗨@EarthMind!需要注意的是,将数据库对象绑定到演示文稿并不是一个好主意,因为您最终会将数据库耦合到演示文稿,这是一种反模式,并且您已经遇到了一些问题。甚至有人说将业务逻辑与持久对象绑定也不好。我的建议,这也是为了您的项目的可维护性,创建更相似的数据结构的新对象以转换为 json。所以你有一个Question 和一个QuestionViewQuestionPresentation(找出最适合你的命名)。
  • @Augusto 你是对的,我不应该因为对 DRY 过于严格而对实体如此坚持,导致我的问题也以错误的方式被问到。专注于这一点的改进,我设法让它与 Page 对象一起工作,所以现在我的问题已经解决了。我稍后会发布一个答案,以便其他人可以有一个实际的例子来解决他们的问题。

标签: spring-boot spring-data-jpa


【解决方案1】:

经过大量搜索和尝试,在@marnish 和@Augusto 的帮助下,我找到了解决问题的方法。

我需要从我问错问题的问题开始,因为我想知道如何将这些数据实施到我的实体中,而这种思维方式导致我在寻找解决方案时遇到很多麻烦,因为这仅限于我能够实现的目标。最好将我的数据库对象与我的 JSON 对象分开,因为一个用于持久化数据,另一个用于呈现给客户端,而第一种方式几乎不链接到数据库列。瞬态属性对我来说也不是解决方案。

意识到(并接受)这一点,下一个问题是让新的表示对象与页面包装器很好地配合使用,因为我的问题需要分页。这是我写的解决方案:

问题作为链接到数据库表和列的实体

@Entity
@Table(name = "vragen")
// BaseEntity just contains the ID property
public class Question extends BaseEntity {
    @Column(name = "vraag")
    private String question;

    @Column(name = "bericht")
    private String message;

    @ManyToOne
    @JoinColumn(name = "gebruiker")
    private User user;

    @Column(name = "beantwoord", insertable = false)
    private boolean answered;

    @Column(name = "gepost_op", insertable = false)
    private Timestamp postedOn;

    @Column(name = "actief", insertable = false)
    @JsonIgnore
    private boolean active;

    @Column(name = "bekeken", insertable = false, updatable = false)
    private int viewed;

    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(
            name = "vragen_tags",
            joinColumns = @JoinColumn(name = "vraag_id", referencedColumnName = "id"),
            inverseJoinColumns = @JoinColumn(name = "tag_id", referencedColumnName = "id"))
    private Set<Tag> tags;

    @OneToMany(mappedBy = "question")
    // Thanks to @manish we can do it more efficiently instead of loading all the Answer objects
    @LazyCollection(LazyCollectionOption.EXTRA)
    @JsonManagedReference
    private Set<Answer> answers;


    public Question() {}

    // getters and setters
}

QuestionDTO 作为 JSON 表示对象

public class QuestionDTO {
    private Integer id;
    private String question;
    private String message;
    private User user;
    private boolean answered;
    private Timestamp postedOn;
    private int viewed;
    private Set<Tag> tags;
    private int totalAnswers;


    public QuestionDTO() {}

    // getters and setters
}

QuestionServiceImpl 中的方法:

public Page<QuestionDTO> getAllQuestions(int page, int size, String filter) {
    List<QuestionDTO> questions = new ArrayList<>();
    Page<Question> questionPage;

    switch (filter) {
        case "popular":
            questionPage = questionRepository.findAllByActiveTrueOrderByViewedDescPostedOnDesc(PageRequest.of(page, size));
            break;

        case "answered":
            questionPage = questionRepository.findAllByActiveTrueAndAnsweredTrueOrderByPostedOnDesc(PageRequest.of(page, size));
            break;

        default:
            questionPage = questionRepository.findAllByActiveTrueOrderByPostedOnDesc(PageRequest.of(page, size));
            break;
    }

    // Here the entity data is being converted to the presentation DTO. I'm sure this can be done cleaner with for example Mapstruct as objectmapper.
    for (Question q : questionPage) {
        QuestionDTO questionDTO = new QuestionDTO();

        questionDTO.setId(q.getId());
        questionDTO.setQuestion(q.getQuestion());
        questionDTO.setMessage(q.getMessage());
        questionDTO.setUser(q.getUser());
        questionDTO.setAnswered(q.isAnswered());
        questionDTO.setPostedOn(q.getPostedOn());
        questionDTO.setViewed(q.getViewed());
        questionDTO.setTags(q.getTags());
        questionDTO.setTotalAnswers(q.getAnswers().size());

        questions.add(questionDTO);
    }
    // We want to keep all the pagination data that the method returned, so we need to wrap the questionDTO in a Page wrapper and pass all the data from Page<Question>
    return new PageImpl<>(questions, questionPage.getPageable(), questionPage.getTotalElements());
}

回答实体:

@Entity
@Table(name = "antwoorden")
public class Answer extends BaseEntity {
    @Column(name = "bericht")
    private String message;

    @ManyToOne
    @JoinColumn(name = "gebruiker")
    private User user;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "vraag")
    @JsonBackReference
    private Question question;

    @Column(name = "gepost_op", insertable = false)
    private Timestamp postedOn;

    @Column(name = "actief", insertable = false)
    private boolean active;


    public Answer() {}

    // getters and setters
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-28
    • 2013-09-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多