Hibernate JPA 多对多预加载不起作用

问题描述 投票:0回答:1
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Set;

@Entity
@Table(name = "users")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    @Id
    @GeneratedValue
    private Long id;
    @Column(unique = true, nullable = false)
    private String username;
    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(name = "user_subscriptions",
    joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"),
    inverseJoinColumns = @JoinColumn(name = "subscription_id", referencedColumnName = "id"))
    private Set<Subscription> subscriptions;
}


import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Set;

@Entity
@Table(name = "subscriptions")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Subscription {
    @Id
    @GeneratedValue
    private Long id;
    @Column(unique = true, nullable = false)
    private String name;
    @OneToMany(mappedBy = "subscription")
    private Set<Notification> notifications;
    @ManyToMany(mappedBy = "subscriptions")
    private Set<User> users;
}


import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Entity
@Table(name = "notifications")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Notification {
    @Id
    @GeneratedValue
    private Long id;
    @Column(nullable = false)
    private String title;
    @Column(nullable = false)
    private String message;
    @Column(name = "is_read", nullable = false)
    private boolean isRead;
    @ManyToOne
    @JoinColumn(name = "subscription_id")
    private Subscription subscription;
}

我创建了这 3 个实体。用户和订阅具有多对多关系。当我保存用户时,它工作正常,在数据库中我可以看到用户的订阅。

当我调用方法 findById 来获取用户时,我的订阅设置为空。我该如何修复它? 我正在使用 Spring Boot v3.1.3、Java v17 和 PostgreSQL v14.3

java spring-boot spring-data-jpa many-to-many
1个回答
0
投票

叹息。很多本来可以做但没有做的事情。

那么,让我们开始吧。如果我创建一个示例并执行

findById
我会得到以下 SQL。

Hibernate: select u1_0.id,s1_0.user_id,s1_1.id,s1_1.name,u1_0.username from users u1_0 left join (user_subscriptions s1_0 join subscriptions s1_1 on s1_1.id=s1_0.subscription_id) on u1_0.id=s1_0.user_id where u1_0.id=?
Hibernate: select n1_0.subscription_id,n1_0.id,n1_0.is_read,n1_0.message,n1_0.title from notifications n1_0 where n1_0.subscription_id=?
Hibernate: select u1_0.subscription_id,u1_1.id,u1_1.username from user_subscriptions u1_0 join users u1_1 on u1_1.id=u1_0.user_id where u1_0.subscription_id=?
Hibernate: select s1_0.user_id,s1_1.id,s1_1.name from user_subscriptions s1_0 join subscriptions s1_1 on s1_1.id=s1_0.subscription_id where s1_0.user_id=?
Hibernate: select n1_0.subscription_id,n1_0.id,n1_0.is_read,n1_0.message,n1_0.title from notifications n1_0 where n1_0.subscription_id=?
Hibernate: select u1_0.subscription_id,u1_1.id,u1_1.username from user_subscriptions u1_0 join users u1_1 on u1_1.id=u1_0.user_id where u1_0.subscription_id=?

这就是你的想法吗?我希望不是。让我们修复它吧。

@Entity
@Table(name = "subscriptions")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Subscription {

请注意,

@Data
已被删除并替换为
@Getter
@Setter
。现在我的sql只剩下原来的第一行了。

Hibernate: select u1_0.id,s1_0.user_id,s1_1.id,s1_1.name,u1_0.username from users u1_0 left join (user_subscriptions s1_0 join subscriptions s1_1 on s1_1.id=s1_0.subscription_id) on u1_0.id=s1_0.user_id where u1_0.id=?

注意您已加入订阅表

left join (user_subscriptions s1_0 join subscriptions s1_1 on s1_1.id=s1_0.subscription_id)

但您尚未将其包含在

User
中使用。

select u1_0.id,s1_0.user_id,s1_1.id,s1_1.name,u1_0.username

让我们解决这个问题。

@Query("from User u left join fetch u.subscriptions where u.id = :id")
Optional<User> findByIdWithSubscriptions(@Param("id") Long id);

这给了我

Hibernate: select u1_0.id,s1_0.user_id,s1_1.id,s1_1.name,u1_0.username from users u1_0 left join (user_subscriptions s1_0 join subscriptions s1_1 on s1_1.id=s1_0.subscription_id) on u1_0.id=s1_0.user_id where u1_0.id=?
User(id=1, username=un1, subscriptions=[Subscription(id=1, name=sub1), Subscription(id=2, name=sub2)])

这没有解决

Notifications
,但你的问题不包括这一点。最后,让我们摆脱
Eager
,因为这可以说是 JPA-ANTIPATTERN。

@ManyToMany
@JoinTable(name = "user_subscriptions",
        joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"),
        inverseJoinColumns = @JoinColumn(name = "subscription_id", referencedColumnName = "id"))
private Set<Subscription> subscriptions;

现在我们有了一个明智的

Repository
,其中
findById
只为我们提供了所请求的实体

Hibernate: select u1_0.id,u1_0.username from users u1_0 where u1_0.id=?
方法 

findByIdWithSubscriptions

 为我们提供了加入的订阅并将它们包含在结果中。

© www.soinside.com 2019 - 2024. All rights reserved.