【问题标题】:Spring DTO Projection query doesn't return all results due to inner join instead of left join由于内部联接而不是左联接,Spring DTO 投影查询不返回所有结果
【发布时间】:2019-07-31 12:36:15
【问题描述】:

我正在尝试对具有订单列表的实体User 进行简单的 DTO 投影。投影应该只包含用户名、姓和链接表中Orders 的数量。

User类:

@Entity
@Table(name = "user")
public class User {

    @Column(name = "firstName")
    private String firstName;

    @Column(name = "lastName")
    private String lastName;

    @OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
    private Set<Order> orders;

    // many other fields here

}

Order类:

@Entity
@Table(name = "order")
public class Order {

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

    // many other fields here

}

然后我有了 DTO 对象:

public class UserDetailOrderCountDto {

    private String firstName;
    private String lastName;
    private int orderCount;

    public UserDetailOrderCountDto(String firstName, String lastName, int orderCount) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.orderCount = orderCount;
    }

    // getters, setters, ...

}

最后是带有查询的存储库:

public interface UserRepository extends JpaRepository<User, Long> {

    @Query("select new a.b.c.UserDetailOrderCountDto(u.firstName, u.lastName, size(u.orders)) from User u group by u.firstName, u.lastName")
    List<UserDetailOrderCountDto> findUsersAndOrderCount();

}

数据库包含 2 个用户的 2 个订单。有很多用户没有任何订单(我仍然希望将 orderCount 设为 0 来接收)。存储库中的查询为 2 个用户返回 2 个 DTO,每个用户有 1 个订单(正确),但没有订单的用户会被跳过(因为它不是左连接的)。 Hibernate生成的查询如下:

select user0_.firstName as col_0_0_, user0_.lastName as col_1_0_, count(orders1_.user_id) as col_2_0_ from user user0_, orders orders1_ where user0_.id=orders1_.user_id group by user0_.firstName , user0_.lastName

如何强制 Hibernate 为我提供所有用户(也称为左连接,但如果可能的话没有本地查询)?或任何其他方法来获得我想要的解决方案?任何帮助表示赞赏。谢谢。

更新 1: 如果我尝试强制 Hibernate 使用 FetchMode.JOIN 连接表,它仍然使用内部连接。

@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
@Fetch(FetchMode.JOIN)
private Set<Order> orders;

查询如下所示:

select user0_.firstName as col_0_0_, user0_.lastName as col_1_0_, count(orders1_.user_id) as col_2_0_ from user user0_ cross join orders orders1_ where user0_.id=orders1_.user_id group by user0_.firstName , user0_.lastName

【问题讨论】:

    标签: java spring hibernate jpa spring-data-jpa


    【解决方案1】:

    JPA 查询

    您可以按照下一个方法指示左连接:

      @Query("SELECT new com.your.package.dto.UserDetailOrderCountDto(u.firstName, u.lastName, COUNT(o)) "
              + "FROM User u LEFT JOIN u.orders o group by u.firstName, u.lastName")
      List<UserDetailOrderCountDto> findUsersAndOrderCount();
    

    只要确保将你的类属性 orderCount 更改为 long:

    public class UserDetailOrderCountDto {
      private String firstName;
      private String lastName;
      private long orderCount;
    
      public UserDetail() {
      }
    
      public UserDetail(String firstName, String lastName, long orderCount) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.orderCount = orderCount;
      }
    
      // Getters and setters
    }
    

    注意,这适用于您的 User 类的以下配置:

    @OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
    private Set<Order> orders;
    

    使用原生查询

    您可以改用原生查询,因此您可以定义左连接:

    @Query(value = "select u.first_name as firstName, u.last_name as lastName, count(o.id) as orderCount from user u left join orders o on u.id = o.user_id  group by u.first_name, u.last_name;"
           , nativeQuery = true)
      List<UserDetailOrderCountDto> findUsersAndOrderCount();
    

    您只需要确保生成的列名称与您的 bean 的属性名称匹配。

    另外,在最新版本的spring上,你不需要创建bean,你可以定义一个接口,然后spring创建一个继承自接口的bean:

    public interface UserDetailOrderCountDto {
      public String getFirstName();
      public String getLastName();
      public int getOrderCount();
    }
    

    【讨论】:

    • 如果可能的话,我仍然希望避免使用本机查询,但如果我没有其他选择,我会使用它。根据this 的帖子,接口的使用也很注重性能。
    • @Tomask 我已经根据您的需要找到了解决方案,似乎还有离开的余地。请查看答案,让我知道它是否适合您。
    猜你喜欢
    • 1970-01-01
    • 2020-09-24
    • 1970-01-01
    • 1970-01-01
    • 2021-03-15
    • 1970-01-01
    • 1970-01-01
    • 2011-08-11
    • 1970-01-01
    相关资源
    最近更新 更多