当前位置: 首页 > 知识库问答 >
问题:

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

松阳泽
2023-03-14

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

@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*/
}  
@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的原因是集合太大,需要在访问之前进行适当的分页:不向我的实体中添加与任何实体的关联意味着防止任何人天真地执行类似于for(PostVote pv:post.getPostVoices())的操作。

对于我现在面临的问题,我有各种各样的解决方案:在我看来,没有一个是完全令人信服的。

我可以将@OneTo众多关联表示为只能通过键访问的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(用户)在每个帖子上仍然会导致N 1选择问题。如果有一种方法可以有效地预取某些特定的投票后s,以便在会话中进行后续访问,那就太好了。(例如,在p=pv.Post和pv.user=:user上从Post p left join fetch PostVote pv调用,然后将结果存储在一级缓存中。或者可能涉及EntityGraph

一个简单的解决方案可能是:

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个参数长的可能是个好主意。另外,为投票后设置一个单独的存储库类可能有点过火,破坏了ORMs的用途。

我没有测试它,所以它可能有错误的语法,但我们的想法是将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实现。


共有1个答案

隗和裕
2023-03-14

我可以想象使用@OneToMany的标准双向关联是一个可维护但性能良好的解决方案。

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

  • @EntityGraph,用于指定要加载的关联数据(例如,一个用户及其所有帖子和一个select查询中所有关联的投票
  • Hibernate@BatchSize,例如,当迭代一个用户的所有帖子时,一次加载多个帖子的投票,而不是对每个帖子的投票集合进行一次查询

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

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

 类似资料:
  • 我找了很多,但没有找到任何答案,所以我希望你能帮助我! 我有三个表:post、users和post\U users。Post_users包含两列:user_id和Post_id。 我需要得到一个有口才的用户的所有帖子。有什么想法吗? 非常感谢你! (如果这个问题已经存在,我很抱歉,但我还没有找到它)

  • 我想找到一种方法,使悬停属性在投票后保持活动状态,这是状态,并让Javascript或Firebase记住为该用户保持投票图标处于该状态。所以当一个用户投了几个名字,然后再回到页面时,他们会看到他们投了哪些名字,他们投的是什么。 我正在寻找如何实现这一目标的想法。 目前,在firebase中,我将用户的身份验证ID(uid)保存在他们投票的每个人的名字中,或者是1或者是-1表示支持或支持。我确信可

  • 一、简介 系统的投票功能提供了两种投票类型,第一个是单选投票.第二种是多选投票.网站编辑人员可以根据实际的需求,选择类型进行操作。 何处使用投票: 常用于首页、内容页、及专题页面。所有你想放投票的区域。 如何使用: 只需要根据投票所放位置不同,复制对应代码到模版里即可。 系统信息发布页 和 专题管理内置提供了投票选择功能,只需手动点选,即可添加投票。 针对不同位置CSS样式不同,系统提供了三种常用

  • 现在我们的系统更完善了,但是想要找到最受欢迎的帖子有点难。我们需要一个排名系统来给我们的帖子排个序。 我们可以建立一个基于 karma 的复杂排名系统,权值随着时间衰减,和许多其他因素(很多功能都在 Telescope 中实现了,他是 Microscope 的大哥)。但是对于我们的例子 app, 我们尽量保持简单,我们只按照帖子收到的投票数为它们排序。 让我们实现一个给用户为帖子投票的方法。 数据

  • 获取投票信息 Mudu.Room.Vote.Get(function (response) { response = JSON.parse(response) if (response.status === 'y') { console.log('获取成功,数据为:', response.data) } if (response.status === 'n') {

  • 与JPA Consumer、transform和其他中间路线操作一起制作骆驼ETL路线的示例。这包括针对另一个JPA实体的额外步骤。PollRich步骤应该基于定义的带动态参数的namedQuery(通过先前交换中的#params注册表映射传递)来使用实体数据。它完全忽略namedQuery。它也会忽略任何在线查询——不管查询有多简单——并简单地将初始记录轮询到maximumResults。