JPA:获取特定用户投票的帖子

JPA: fetch posts with vote cast by a specific user

我需要加载 Post 实体以及代表特定用户(当前登录用户)投票的 PostVote 实体。这是两个实体:

Post

@Entity
public class Post implements Serializable {
    public enum Type {TEXT, IMG}

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    protected Integer id;

    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    @JoinColumn(name = "section_id")
    protected Section section;

    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    @JoinColumn(name = "author_id")
    protected User author;

    @Column(length = 255, nullable = false)
    protected String title;

    @Column(columnDefinition = "TEXT", nullable = false)
    protected String content;

    @Enumerated(EnumType.STRING)
    @Column(nullable = false)
    protected Type type;

    @CreationTimestamp
    @Column(nullable = false, updatable = false, insertable = false)
    protected Instant creationDate;
    
    /*accessor methods*/
}  

Post投票

@Entity
public class PostVote implements Serializable {

    @Embeddable
    public static class Id implements Serializable{

        @Column(name = "user_id", nullable = false)
        protected int userId;

        @Column(name = "post_id", nullable = false)
        protected int postId;

        /* hashcode, equals, getters, 2 args constructor */
    }

    @EmbeddedId
    protected Id id;

    @ManyToOne(optional = false)
    @MapsId("postId")
    protected Post post;

    @ManyToOne(optional = false)
    @MapsId("userId")
    protected User user;

    @Column(nullable = false)
    protected Short vote;

    /* accessor methods */
}

所有关联都是单向的@*ToOne。我不使用 @OneToMany 的原因是因为集合太大并且在访问之前需要适当的分页:不将 @*ToMany 关联添加到我的实体意味着防止任何人天真地做类似 for (PostVote pv : post.getPostVotes()).

对于我现在面临的问题,我提出了各种解决方案:none 其中看起来完全有说服力。


1°解

我可以将 @OneToMany 关联表示为只能通过键访问的 Map。这样就不会出现迭代集合引起的问题。

@Entity
public class Post implements Serializable {
    [...]

    @OneToMany(mappedBy = "post")
    @MapKeyJoinColumn(name = "user_id", insertable = false, updatable = false, nullable = false)
    protected Map<User, PostVote> votesMap;

    public PostVote getVote(User user){
        return votesMap.get(user);
    }
    
    [...]
}  

这个解决方案看起来非常酷并且非常接近 DDD 原则(我猜?)。但是,在每个 post 上调用 post.getVote(user) 仍然会导致 N+1 选择问题 。如果有一种方法可以有效地预取某些特定的 PostVotes 以供会话中的后续访问使用,那就太好了。 (例如调用 from Post p left join fetch PostVote pv on p = pv.post and pv.user = :user 然后将结果存储在 L1 缓存中。或者可能涉及 EntityGraph


2°解

一个简单的解决方案如下:

public class PostVoteRepository extends AbstractRepository<PostVote, PostVote.Id> {
    public PostVoteRepository() {
        super(PostVote.class);
    }

    public Map<Post, PostVote> findByUser(User user, List<Post> posts){
        return em.createQuery("from PostVote pv where pv.user in :user and pv.post in :posts", PostVote.class)
                .setParameter("user",user)
                .setParameter("posts", posts)
                .getResultList().stream().collect(Collectors.toMap(
                        res -> res.getPost(),
                        res -> res
                ));
    }
}

服务层负责调用 PostRepository#fetchPosts(...)PostVoteRepository#findByUser(...),然后将结果混合在 DTO 中发送到上面的表示层。

这是我目前使用的解决方案。但是,我不认为使用 ~50 个参数长 in 子句可能是个好主意。此外,为 PostVote 使用单独的 Repository class 可能有点矫枉过正,破坏了 ORM 的目的。


3°解

我还没有测试它,所以它可能有一个不正确的语法,但我的想法是将 PostPostVote 实体包装在 VotedPost DTO 中。

public class VotedPost{
    private Post post;
    private PostVote postVote;

    public VotedPost(Post post, PostVote postVote){
        this.post = post;
        this.postVote = postVote;
    }

    //getters
}  

我通过这样的查询获取对象:

select new my.pkg.VotedPost(p, pv) from Post p 
left join fetch PostVote pv on p = pv.post and pv.user = :user  

这比基于 Object[]Tuple 查询结果的解决方案更安全。看起来比解决方案 2 更好,但以有效的方式采用解决方案 1 将是最好的。

一般来说,解决此类问题的最佳方法是什么?我正在使用 Hibernate 作为 JPA 实现。

我可以想象使用 @OneToMany 的标准 bi-directional 关联是一个可维护但高性能的解决方案。

为了减轻 n+1 selects,可以使用例如:

  • ,以指定要加载的关联数据(例如,一个 user 及其所有 posts 和所有关联的 votes 在一个 select查询)
  • 休眠 @BatchSize,例如在遍历 user 的所有 posts 时一次为多个 posts 加载 votes,而不是对每个 [=] 的 votes 的每个集合进行一次查询22=]

当谈到限制用户以性能较低的方式执行访问时,我认为应该 API 记录可能的性能影响并为不同 use-cases 提供高性能替代方案.

(作为 API 的用户,可能总能找到以最低性能方式实现事物的方法:)