Spring JPA 存储库 n+1 问题,如何将 n+1 选择减少为 1?

问题描述

我的 JPA 存储库有问题。我有实体:

    @Getter
    @Setter
    @Entity
    @NamedEntityGraph(name = "user-graph",includeAllAttributes = true,attributeNodes = {
                    @NamedAttributeNode("recruiter")
            })
    @Table(name = "users")
    public class User extends BaseEntity implements Serializable,UserDetails {
    
        private static final long serialVersionUID = 1L;
    
        private String name;
        private String lastName;
        @Column(nullable = false,unique = true)
        private String username;
        private String dateOfBirth;
        private String phone;
        private String facebook;
        private String twitter;
        private String instagram;
        private String description;
        private Long experience;
        @Column(columnDeFinition = "boolean default false")
        private boolean possibleRelocation;
        @Column(columnDeFinition = "boolean default false")
        private boolean remote;
        private boolean emailVisible;
    
        @Column(nullable = false)
        private String password;
        private boolean enabled;
        private boolean isExpired;
        private boolean isLocked;
        private boolean isCredentialsExpired;
        private boolean isSuperAdmin;
    
        @OnetoMany(mappedBy = "user",fetch = FetchType.LAZY)
        private Set<RecruiterFavouriteUsers> recruiterFavouriteUsers = new HashSet<>();
    
        @Transient
        @JsonIgnore
        @Column(nullable = false)
        private String passwordConfirm;
    
        @ManyToOne(fetch = FetchType.LAZY)
        @JoinColumn(name = "role_id",referencedColumnName = "id",nullable = false)
        private Role role;
    
        @OnetoOne(mappedBy = "user")
        private VerificationToken verificationToken;
    
        @OnetoOne(fetch = FetchType.LAZY)
        @JoinColumn(name = "company_id")
        private Company company;
    
        @OnetoOne
        @JoinColumn(name = "recruiter_id")
        private Recruiter recruiter;
    
        @OnetoOne(fetch = FetchType.LAZY)
        @JoinColumn(name = "address_id")
        private Address address;
    
        @OnetoOne(fetch = FetchType.LAZY)
        @JoinColumn(name = "avatar_id")
        private Avatar avatar;
    
        @OnetoOne(fetch = FetchType.LAZY)
        @JoinColumn(name = "background_id")
        private Background background;
    
        @ManyToOne(fetch = FetchType.LAZY)
        @JoinColumn(name = "position_level_id",referencedColumnName = "id")
        private PositionLevelDict positionLevel;
    
        @ManyToOne(fetch = FetchType.LAZY)
        @JoinColumn(name = "workplace_id",referencedColumnName = "id")
        private WorkplaceDict workplace;
    
        @OnetoMany(mappedBy = "user",fetch = FetchType.LAZY)
        private Set<Skill> skill;
    
        @OnetoMany(mappedBy = "user",fetch = FetchType.LAZY)
        private Set<Language> language;
    
        @OnetoMany(mappedBy = "user",fetch = FetchType.LAZY)
        private Set<Work> work;
    
        @OnetoMany(mappedBy = "user",fetch = FetchType.LAZY)
        private Set<School> school;
    
        @OnetoMany(mappedBy = "userTo",fetch = FetchType.LAZY)
        private Set<MessageGroup> messageGroupsTo;
    
        @OnetoMany(mappedBy = "userFrom",fetch = FetchType.LAZY)
        private Set<MessageGroup> messageGroupsFrom;
...
}

和存储库:

@Repository
@Transactional
public interface UserRepository extends JpaRepository<User,Long>,JpaSpecificationExecutor<User> {
    @EntityGraph("user-graph")
    Optional<User> findById(Long id);

    @EntityGraph("user-graph")
    User findByUsername(String username);

    @EntityGraph("user-graph")
    User findByUsernameAndEnabledTrue(String username);

    @EntityGraph("user-graph")
    User findByUsernameAndEnabledFalse(String username);
}

当我尝试使用 findById 方法时,我遇到了 n+1 问题:

Hibernate:选择 user0_.id 作为 id1_32_0_,user0_.create_date 作为 create_d2_32_0_,user0_.last_update as last_upd3_32_0_,user0_.address_id 为 address23_32_0_,user0_.avatar_id 为 avatar_24_32_0_,user0_.background_id 作为 backgro25_32_0_, user0_.company_id 为 company26_32_0_,user0_.date_of_birth 为 date_of_4_32_0_,user0_.description as descript5_32_0_,user0_.email_visible 为 email_vi6_32_0_,user0_.enabled 为 enabled7_32_0_,user0_.experience as experien8_32_0_,user0_.facebook 作为 facebook9_32_0_,user0_.instagram 作为 instagr10_32_0_, user0_.is_credentials_expired 为 is_cred11_32_0_,user0_.is_expired 为 is_expi12_32_0_,user0_.is_locked as is_lock13_32_0_,user0_.is_super_admin as is_supe14_32_0_,user0_.last_name as last_na15_32_0_,user0_.name as name16_32_0_,user0_.password as passwor17_32_0_,user0_.phone 作为 phone18_32_0_,user0_.position_level_id 为 positio27_32_0_, user0_.possible_relocation 为 possibl19_32_0_,user0_.recruiter_id 为 Recruit28_32_0_,user0_.remote 为 remote20_32_0_,user0_.role_id 为 role_id29_32_0_,user0_.twitter 为 twitter21_32_0_,user0_.username 为 usernam22_32_0_,user0_.workplace_id 作为 workpla30_32_0_,address1_.id 作为 id1_0_1_,address1_.create_date 作为 create_d2_0_1_, address1_.last_update 为 last_upd3_0_1_,address1_.city 为 city4_0_1_, address1_.country 为 country5_0_1_,address1_.flat_number 为 flat_num6_0_1_,address1_.house_number 为 house_nu7_0_1_, address1_.post_code 为 post_cod8_0_1_,address1_.street 为 street9_0_1_,avatar2_.id 为 id1_1_2_,avatar2_.create_date 为 create_d2_1_2_,avatar2_.last_update 为 last_upd3_1_2_,avatar2_.image 作为 image4_1_2_,avatar2_.mimetype 作为 mimetype5_1_2_,avatar2_.name 作为 name6_1_2_,background3_.id 为 id1_2_3_,background3_.create_date 为 create_d2_2_3_,background3_.last_update 为 last_upd3_2_3_, background3_.image 为 image4_2_3_,background3_.mimetype 为 mimetype5_2_3_,background3_.name as name6_2_3_,company4_.id as id1_3_4_,company4_.create_date 为 create_d2_3_4_, company4_.last_update 为 last_upd3_3_4_, company4_.company_creation_date 为 company_4_3_4_, company4_.company_size_id 为 company_7_3_4_,company4_.description 为 descript5_3_4_,company4_.name as name6_3_4_,language5_.user_id as user_id7_23_5_,language5_.id 为 id1_23_5_,language5_.id 为 id1_23_6_,language5_.create_date 为 create_d2_23_6_,language5_.last_update 为 last_upd3_23_6_,language5_.company_id 为 company_6_23_6_,language5_.level as level4_23_6_,language5_.name as name5_23_6_,language5_.user_id 为 user_id7_23_6_, messagegro6_.user_from_id 为 user_fro6_25_7_,messagegro6_.id 为 id1_25_7_,messagegro6_.id 为 id1_25_8_,messagegro6_.create_date 为 create_d2_25_8_,messagegro6_.last_update 为 last_upd3_25_8_,messagegro6_.deleted_user_id 为deleted_4_25_8_, messagegro6_.last_message 为 last_mes5_25_8_, messagegro6_.user_from_id 作为 user_fro6_25_8_,messagegro6_.user_to_id 作为 user_to_7_25_8_,messagegro7_.user_to_id 作为 user_to_7_25_9_, messagegro7_.id 为 id1_25_9_,messagegro7_.id 为 id1_25_10_, messagegro7_.create_date 为 create_d2_25_10_,messagegro7_.last_update 如last_upd3_25_10_,messagegro7_.deleted_user_id 为deleted_4_25_10_, messagegro7_.last_message 为 last_mes5_25_10_, messagegro7_.user_from_id 作为 user_fro6_25_10_,messagegro7_.user_to_id 作为 user_to_7_25_10_,positionle8_.id 作为 id1_14_11_, positionle8_.create_date 为 create_d2_14_11_,positionle8_.last_update as last_upd3_14_11_,positionle8_.name as name4_14_11_,recruiter9_.id 作为 id1_27_12_,recruiter9_.create_date 作为 create_d2_27_12_, Recruiter9_.last_update 为 last_upd3_27_12_,recruiter9_.payment_id 为 付款_4_27_12_,recruiter9_.recruiter_type_id 为recruite5_27_12_, Recruiterf10_.user_id 为 user_id5_28_13_,recruiterf10_.id 为 id1_28_13_,recruiterf10_.id 为 id1_28_14_,recruiterf10_.create_date 作为 create_d2_28_14_,recruiterf10_.last_update 作为 last_upd3_28_14_, 招聘人员f10_.recruiter_id 为recruite4_28_14_、recruiterf10_.user_id 作为 user_id5_28_14_,role11_.id 作为 id1_29_15_,role11_.create_date 作为 create_d2_29_15_,role11_.last_update 为 last_upd3_29_15_, role11_.name 为 name4_29_15_,school12_.user_id 为 user_id9_30_16_, school12_.id 为 id1_30_16_,school12_.id 为 id1_30_17_, school12_.create_date 为 create_d2_30_17_,school12_.last_update 为 last_upd3_30_17_,school12_.end_year 为 end_year4_30_17_,school12_.field_of_study 为 field_of5_30_17_,school12_.name 为 name6_30_17_,school12_.start_year 为 start_ye7_30_17_,school12_.title 为 title8_30_17_,school12_.user_id 为 user_id9_30_17_,skill13_.user_id 为 user_id7_31_18_,skill13_.id 为 id1_31_18_,skill13_.id 为 id1_31_19_,skill13_.create_date 为 create_d2_31_19_,skill13_.last_update 为 last_upd3_31_19_, Skill13_.company_id 为 company_5_31_19_,skill13_.level 为 level4_31_19_,skill13_.technology_id 为 technolo6_31_19_, Skill13_.user_id 为 user_id7_31_19_,work14_.user_id 为 user_id14_34_20_,work14_.id 为 id1_34_20_,work14_.id 为 id1_34_21_, work14_.create_date 为 create_d2_34_21_,work14_.last_update 为 last_upd3_34_21_,work14_.city 为 city4_34_21_,work14_.company_name 作为 company_5_34_21_,work14_.country 作为 country6_34_21_, work14_.description 为 descript7_34_21_,work14_.end_month 为 end_mont8_34_21_,work14_.end_year 为 end_year9_34_21_, work14_.start_month 为 start_m10_34_21_,work14_.start_year 为 start_y11_34_21_,work14_.still_working as still_w12_34_21_,work14_.user_id 为 user_id14_34_21_,work14_.workplace 为 workpla13_34_21_,workplaced15_.id 为 id1_17_22_, 工作场所d15_.create_date 为create_d2_17_22_, Workplaced15_.last_update 为 last_upd3_17_22_,workplaced15_.name 为 name4_17_22_,verificati16_.id 为 id1_33_23_, verificati16_.create_date 为 create_d2_33_23_, verificati16_.last_update 为 last_upd3_33_23_,verificati16_.enabled 作为启用4_33_23_,verificati16_.expiry_date 作为expiry_d5_33_23_, verificati16_.token 作为 token6_33_23_,verificati16_.user_id 作为 user_id8_33_23_,verificati16_.verification_token 作为 verifica7_33_23_ 从用户 user0_ 左外连接地址 address1_ 上 user0_.address_id=address1_.id 左外加入 avatar avatar2_ on user0_.avatar_id=avatar2_.id 左外加入背景 background3_ 在 user0_.background_id=background3_.id 上离开外部加入公司 company4_ on user0_.company_id=company4_.id 左外连接语言 language5_ on user0_.id=language5_.user_id 左外连接 message_group messagegro6_ on user0_.id=messagegro6_.user_from_id left 外加入message_group messagegro7_ on user0_.id=messagegro7_.user_to_id 左外连接 d_position_level positionle8_ on user0_.position_level_id=positionle8_.id 左外 加入招聘人员recruiter9_ on user0_.recruiter_id=recruiter9_.id left 外部加入Recruiter_favourite_usersRecruiterf10_ on user0_.id=recruiterf10_.user_id 内连接角色 role11_ on user0_.role_id=role11_.id 离开outer join school school12_ on user0_.id=school12_.user_id 左外连接技能 Skill13_ on user0_.id=skill13_.user_id 左外连接工作 work14_ on user0_.id=work14_.user_id 左外连接 d_workplaceworkplaced15_ on user0_.workplace_id=workplaced15_.id 左外连接 verify_token verificati16_ on user0_.id=verificati16_.user_id 哪里 user0_.id=?休眠:选择recruiter0_.id 作为id1_27_0_, Recruiter0_.create_date 为 create_d2_27_0_,recruiter0_.last_update 为 last_upd3_27_0_,recruiter0_.payment_id 作为 payment_4_27_0_, 招聘人员0_.recruiter_type_id 为recruite5_27_0_,user1_.id 为 id1_32_1_,user1_.create_date 为 create_d2_32_1_,user1_.last_update 作为last_upd3_32_1_,user1_.address_id 作为address23_32_1_, user1_.avatar_id 为 avatar_24_32_1_,user1_.background_id 为 backgro25_32_1_,user1_.company_id 为 company26_32_1_, user1_.date_of_birth 为 date_of_4_32_1_,user1_.description 为 descript5_32_1_,user1_.email_visible as email_vi6_32_1_,user1_.enabled as enabled7_32_1_,user1_.experience as experien8_32_1_,user1_.facebook 为 facebook9_32_1_,user1_.instagram 如 instagr10_32_1_,user1_.is_credentials_expired 为 is_cred11_32_1_, user1_.is_expired 为 is_expi12_32_1_,user1_.is_locked 为 is_lock13_32_1_,user1_.is_super_admin as is_supe14_32_1_,user1_.last_name 为 last_na15_32_1_,user1_.name 为 name16_32_1_, user1_.password 为 passwor17_32_1_,user1_.phone 为 phone18_32_1_, user1_.position_level_id 为 positio27_32_1_, user1_.possible_relocation 为 possibl19_32_1_,user1_.recruiter_id 为 Recruit28_32_1_,user1_.remote 为 remote20_32_1_,user1_.role_id 为 role_id29_32_1_,user1_.twitter 为 twitter21_32_1_,user1_.username 为 usernam22_32_1_,user1_.workplace_id 为 workpla30_32_1_, verificati2_.id 为 id1_33_2_,verificati2_.create_date 为 create_d2_33_2_,verificati2_.last_update 为 last_upd3_33_2_,verificati2_.enabled 为 enabled4_33_2_,verificati2_.expiry_date 为 expiry_d5_33_2_,verificati2_.token 作为 token6_33_2_, verificati2_.user_id 为 user_id8_33_2_, verificati2_.verification_token 作为 verifica7_33_2_ 来自招聘人员 招聘人员 0_ 左外加入用户 user1_ 上 Recruiter0_.id=user1_.recruiter_id 左外加入验证_token verificati2_ 在 user1_.id=verificati2_.user_id 上,其中recruiter0_.id=? 休眠:选择 technology0_.id 作为 id1_16_0_, technology0_.create_date 为 create_d2_16_0_,technology0_.last_update as last_upd3_16_0_,technology0_.name as name4_16_0_ from d_technology 技术0_在哪里技术0_.id=?休眠:选择 technology0_.id 作为 id1_16_0_,technology0_.create_date 作为 create_d2_16_0_,technology0_.last_update as last_upd3_16_0_,technology0_.name as name4_16_0_ 来自 d_technology technology0_ where technology0_.id=? 休眠:选择 technology0_.id 作为 id1_16_0_, technology0_.create_date 为 create_d2_16_0_,technology0_.name as name4_16_0_ from d_technology technology0_ where technology0_.id=?

如何将所有这些选择减少到一个

其他实体:

@Data
@Entity
public class VerificationToken extends BaseEntity {
    private String token;

    @OnetoOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;
    private boolean verificationToken;
    private boolean enabled;
    private Date expiryDate;

@Getter
@Setter
@Entity
@Table(name = "recruiter")
public class Recruiter extends BaseEntity {

    @OnetoOne(mappedBy = "recruiter")
    private User user;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "recruiter_type_id",referencedColumnName = "id")
    private RecruiterTypeDict recruiterType;

    @OnetoOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "payment_id")
    private Payment payment;

    @OnetoMany(mappedBy = "recruiter")
    private Set<RecruiterFavouriteUsers> recruiterFavouriteUsers = new HashSet<>();
}

@Entity
@Getter
@Setter
@Table(name = "skill")
public class Skill extends BaseEntity {

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "technology_id",nullable = false)
    private TechnologyDict technology;

    @Column(nullable = false)
    private Long level;

    @ManyToOne
    @JoinColumn(name = "user_id",referencedColumnName = "id")
    private User user;

    @ManyToOne
    @JoinColumn(name = "company_id",referencedColumnName = "id")
    private Company company;
}

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)