我正在开发我的应用程序中的聊天功能。当我试图从数据库中检索Chat
对象时,出现了问题,所有消息都被检索了两次。
项目结构
以下是简化的结构:
x1c 0d1x的数据
这是实体(简化):
@Entity
public class Chat {
@Id
@GeneratedValue
private Long id;
@ManyToMany
@JoinTable(
name = "user_chat",
joinColumns = @JoinColumn(name = "chat"),
inverseJoinColumns = @JoinColumn(name = "user")
)
private Set<User> users = new HashSet<>();
@OneToMany(mappedBy = "chat", cascade = CascadeType.ALL)
private List<Message> messages = new ArrayList<>();
public void addMessage(Message message){
this.messages.add(message);
}
}
@Entity
public class Message {
private @Id @GeneratedValue Long id;
@ManyToOne
@JoinColumn(name="senderId", nullable=false)
private User sender;
@Column(name="value")
private String value;
@ManyToOne()
@JoinColumn(name = "chat")
private Chat chat;
}
@Entity
public class User implements UserDetails {
private @Id
@GeneratedValue
Long id;
@OneToMany(mappedBy = "sender", cascade = CascadeType.ALL)
private List<Message> messagesSent;
@ManyToMany(mappedBy = "users")
private Set<Chat> chats = new HashSet<>();
}
字符串
这就是失败的仓库:
public interface ChatRepository extends JpaRepository<Chat,Long> {
@Query("SELECT c FROM Chat c JOIN c.users u WHERE u.id IN :usersIDs GROUP BY c HAVING COUNT(DISTINCT u) = :numberOfUsers")
Optional<Chat> getChat(@Param("usersIDs") List<Long> usersIDs, int numberOfUsers);
}
型
问题
在这里,您可以看到如何检索对象的示例:
的
可以看到,每个Message
对象都是重复的
我想问题是
在研究和执行对数据库的原始SQL查询时,我假设问题可能是这样的:Hibernate是这样做的查询:加入聊天和用户之间,然后加入结果和消息之间。这会使消息重复,因为它将每条消息与Chat-User 1和Chat-User 2行连接起来。就这样:
SELECT message.id as messageID, chat.id as chatID, user.id as userID FROM chat JOIN user JOIN message;
型
的
我一直在努力
作为对我之前解释的假设的回应,我尝试以这种方式更改存储库查询:
public interface ChatRepository extends JpaRepository<Chat,Long> {
@Query("SELECT c FROM Chat c JOIN c.users u LEFT JOIN c.messages m ON m.sender = u AND m.chat = c WHERE u.id IN :usersIDs GROUP BY c HAVING COUNT(DISTINCT u) = :numberOfUsers")
Optional<Chat> getChat(@Param("usersIDs") List<Long> usersIDs, int numberOfUsers);
}
型
查询明细:SELECT c FROM Chat c JOIN c.users u
聊天和用户已加入LEFT JOIN c.messages m ON m.sender = u AND m.chat = c
结果和消息之间的连接WHERE u.id IN :usersIDs
筛选刚刚请求的用户GROUP BY c HAVING COUNT(DISTINCT u) = :numberOfUsers")
检索所有用户都请求的聊天
正如您所看到的,差异在于ON m.sender = u AND m.chat = c
。这样,Messages
也必须加入User
,如下面的查询:
SELECT message.id as messageID, chat.id as chatID, user.id as userID FROM chat JOIN user JOIN message ON message.sender_id = user.id and message.chat = chat.id;
型
的
而JOIN之前的LEFT
则是为了获取没有发消息到聊天中但已经是成员的用户。
但是,此查询并不能解决问题。返回带有复制消息的聊天。
这是我尝试过的其他事情的列表:
- 在
private List<Message> messages = new ArrayList<>();
之前添加@Fetch (FetchMode.SELECT)
- 在查询前使用
@Transactional
- 覆盖
Message
中的equals(Object o)
下面是message.equals()
方法的样子:
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Message message = (Message) o;
return Objects.equals(id, message.id) && Objects.equals(sender, message.sender) && Objects.equals(value, message.value) && Objects.equals(chat, message.chat);
}
@Override
public int hashCode() {
return Objects.hash(id, sender, value, chat);
}
型
我看过的其他SO帖子
JPA - EntityManager find method returns duplicates
Multiple fetches with EAGER type in Hibernate with JPA的
1条答案
按热度按时间dgiusagp1#
当你的查询执行时,你的模型将总是返回一个由3个表组成的笛卡尔图。
我相信在这种情况下,你应该做的是在你的投影中实现
DISTINCT
子句,这样Hibernate就能够比较对象并将每个不同消息中的一个添加到消息列表中。字符串
我还在join中添加了
FETCH
,以将消息检索到单个快照中,但这也会使查询更大,并添加另一个user表示例来检索消息的用户,而不是重用聊天的用户。除了distinct对数据库查询没有影响之外,Hibernate还知道结果必须经过一个归约过程来消除重复。
另外,这篇文章可能会有所帮助:https://vladmihalcea.com/jpql-distinct-jpa-hibernate/