java Hibernate JPA多对多Eager Loading不工作

jei2mxaa  于 5个月前  发布在  Java
关注(0)|答案(1)|浏览(36)
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个实体。用户和订阅有ManyToMany关系。当我保存用户时,它工作正常,在数据库中我可以看到用户的订阅。


的数据
当我调用方法findById来获取用户时,我得到订阅设置为空。我如何修复它?我使用Sping Boot v3.1.3,Java v17和PostgreSQL v14.3

2eafrhcq

2eafrhcq1#

有几个问题本可以在这里解决,但没有。所有的问题我都注意到了。

那么,让我们开始吧。如果我创建一个示例并执行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替换。您也可以通过使用List而不是Set来修复它,但我将把它作为读者找出原因的练习。现在我的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)])


最后,让我们摆脱Eager,因为它可以说是一个JPA-ANTIPATTERN. JPA and Hibernate FetchType EAGER is a code smell

@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给出了联合订阅并将它们包含在结果中。

相关问题