【问题标题】:Spring Data JpaRepository "JOIN FETCH" returns duplicatesSpring Data JpaRepository“JOIN FETCH”返回重复项
【发布时间】:2020-12-22 13:46:42
【问题描述】:

我正在编写一个简单的 Spring Data JPA 应用程序。我使用 MySQL 数据库。有两个简单的表:

  • 部门
  • 员工

每个员工都在某个部门工作 (Employee.department_id)。

@Entity
public class Department {
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Id
    private Long id;

    @Basic(fetch = FetchType.LAZY)
    @OneToMany(mappedBy = "department")
    List<Employee> employees;
}

@Entity
public class Employee {
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Id
    private Long id;

    @ManyToOne
    @JoinColumn
    private Department department;
}

@Repository
public interface DepartmentRepository extends JpaRepository<Department, Long> {
    @Query("FROM Department dep JOIN FETCH dep.employees emp WHERE dep = emp.department")
    List<Department> getAll();
}

getAll 方法返回一个包含重复部门的列表(每个部门重复的次数与该部门的员工数量一样多)。

问题 1:我是否认为这是一个与 Spring Data JPA 无关但与 Hibernate 相关的功能?
问题2:修复它的最佳方法是什么? (我发现至少有两种方法:1)使用Set&lt;Department&gt; getAll(); 2)在@Query注解中使用"SELECT DISTINCT dep"

【问题讨论】:

    标签: java sql spring spring-data-jpa


    【解决方案1】:

    我不确定在这里使用@Query。如果您通过注释 @OneToMany@ManyToOne 映射这两个实体,则此连接是多余的。 第二个时刻,为什么@Basic(fetch = FetchType.LAZY) 在这里?也许您应该在 @OneToMany 中设置惰性初始化? Check out this resource, I hope you'll find it helpfull

    【讨论】:

      【解决方案2】:

      FROM Department dep JOIN FETCH dep.employees emp 表达式生成本机查询,该查询返回纯结果Department-Employee。每个Department 将返回dep.employees.size() 次。这是 JPA 提供者的预期行为(在您的情况下为 Hibernate)。

      使用distinct 去除重复项似乎是一个不错的选择。 Set&lt;Department&gt; 作为查询结果,无法得到有序结果。

      【讨论】:

      • 谢谢!但是 Spring 应该让事情变得更简单、更方便。为什么在使用 Spring、OOP 时需要为这些低级概念烦恼?
      • 这是故意行为。它允许您在 where 子句中使用 emp 作为单个员工的别名。例如,where emp.name like "%John%"
      • Spring 不仅给你简单,而且给你灵活性。
      【解决方案3】:

      据我所知,您提到的用例不需要定义自己的方法,而是使用继承自 PagingAndSortingRepositoryrepository.findAll,它扩展了 CrudRepository 并由 SimpleJpaRepository 实现。见here

      所以只需将存储库界面留空,如下所示,

      @Repository
      public interface DepartmentRepository extends JpaRepository<Department, Long> {
      
      }
      

      然后在任何需要的地方注入并使用它,例如让我们说 DepartmentService

      public interface DepartmentService {
          
          List<Link> getAll();    
      }
      
      @Component
      public class DepartmentServiceImpl implements DepartmentService {
      
          private final DepartmentRepository  repository;    
      
          // dependency injection via constructor
          @Autowired
          public DepartmentServiceImpl(DepartmentRepository repository) {
              this.repository = repository;
          }
      
          @Override
          public List<Department> getAll() {  
              return repository.findAll();
          }
      }
      

      【讨论】:

      • 据我所知,您的方法 1) 需要 @Transactional 才能访问 department.getEmployees() 2) 导致 n+1 select problem
      • 1.为什么需要@Transactional 来获取数据? 2.FetchType.LAZY 将解决我相信的n+1 问题。知道的不多,如果你能详细说明一下。我的观点是findAll,您不需要实现任何东西并利用 JPA 已经实现的功能。值得一看stackoverflow.com/questions/27799455/…
      • 你可以阅读我之前的问题stackoverflow.com/questions/63675153
      • 我读过它,但我觉得问题和解决方案在不应该的地方过于复杂。我使用 Oracle,通常出于只读目的,从不严格要求事务性。请在此处查看有关@Transactional 的说明stackoverflow.com/questions/10394857/…。还有这个stackoverflow.com/questions/54326306/…
      • 我可以使用 departmentService.findAll() 来获取部门列表。但是当我调用departmens.get(0).getEmployees() 时会抛出异常。 @Transactional 解决了这个问题。
      猜你喜欢
      • 2015-01-30
      • 1970-01-01
      • 2021-10-19
      • 2017-08-21
      • 2016-10-12
      • 2014-06-13
      • 2017-01-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多