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



我需要加载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*/
}  

PostVote

@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 */
}

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

对于我现在面临的问题,我提出了各种解决方案:没有一个对我来说完全有说服力


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.getVote(user)仍然会导致N+1选择问题。如果有一种方法可以有效地预取一些特定的PostVote,用于会话中的后续访问,那就太好了。(例如,调用from Post p left join fetch PostVote pv on p = pv.post and pv.user = :user,然后将结果存储在一级缓存中。或者可能涉及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类可能有点过头了,并破坏了ORM的目的。


3°溶液

我还没有测试过它,所以它可能有不正确的语法,但我们的想法是将PostPostVote实体封装在VotedPostDTO中。

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的标准双向关联是一个可维护但性能良好的解决方案。

为了减轻n+1的选择,可以使用例如:

  • @EntityGraph,以指定要加载哪些关联数据(例如,一个user及其所有posts和所有关联的votes在一个选择查询中(
  • 休眠@BatchSize,例如,当在@*ToOne3的所有posts上迭代时,一次为多个posts加载votes,而不是为每个postvotes的每个集合具有一个查询

当涉及到限制用户以性能较低的方式执行访问时,我认为应该由API来记录可能的性能影响,并为不同的用例提供性能替代方案。

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

最新更新