java - Spring Data JpaRepository「JOIN FETCH」が重複を返す

okwaves2024-01-25  7

私は単純な Spring Data JPA アプリケーションを作成しています。 MySQLデータベースを使用しています。 2 つの単純なテーブルがあります:

部門 従業員

各従業員は、何らかの部門 (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: それを修正する最善の方法は何ですか? (私は少なくとも 2 つの方法を見つけました: 1) Set<Department> を使用します。すべて取得(); 2) 「SELECT DISTINCT dep」を使用します。 @Query アノテーション内)



------------------------

FROMDepartment dep JOIN FETCH dep.employees emp 式は、プレーンな結果Department-Employeeを返すネイティブ クエリを生成します。すべての部門が dep.employees.size() 回返されます。これは JPA プロバイダーが予期する動作です (この場合は休止状態)。

distinct を使用して重複を削除するのは良い選択肢のようです。 <部門> を設定します。クエリ結果により、順序付けられた結果を取得できなくなるためです。

3

ありがとうございます!しかし、Spring は物事をシンプルにしてくれるはずですえー、もっと便利です。 Spring、OOP を使用する場合、なぜこのような低レベルの概念を気にする必要があるのでしょうか?

– ダニエル

2020 年 9 月 3 日 12:51

これは意図的な動作です。これにより、where 句で単一の従業員のエイリアスとして emp を使用できるようになります。たとえば、emp.name は「%John%」のようになります。

– オレクシー・ヴァルイスキー

2020 年 9 月 3 日 13:03

Spring は、シンプルさだけでなく柔軟性も与えます。

– オレクシー・ヴァルイスキー

2020 年 9 月 3 日 13:09



------------------------

DISTINCT だけでなく LEFT JOIN FETCH も使用するとよいでしょう。通常の JOIN FETCH では内部結合が使用されるため、従業員がいない部門はクエリによって返されません。 LEFT JOIN FETCH は代わりに外部結合を使用します。



------------------------

私の知識によれば、あなたが言及したユースケースでは、独自のメソッドを定義する必要はなく、代わりに CrudRepository を拡張し SimpleJpaRepository によって実装される PagingAndSortingRepository から継承される repository.findAll を使用します。ここを参照

したがって、以下のようにリポジトリ インターフェースを空白のままにしておきます。

@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();
    }
}

6

としてあなたのアプローチは 1) 部門.getEmployees() にアクセスするために @Transactional が必要であることは承知しています 2) n+1 選択問題が発生します

– ダニエル

2020 年 9 月 4 日 12:20

1. @Transactional がデータをフェッチする必要があるのはなぜですか? 2.FetchType.LAZY は、私が信じている n+1 の問題を処理します。あまり詳しくありませんが、詳しく教えていただければわかるかもしれません。ここで私が言いたいのは、何も実装する必要がなく、JPA によってすでに実装されているものを活用する必要がないことです。 stackoverflow.com/questions/27799455/… を参照する価値があります。

– スジットモハンティ30

2020 年 9 月 4 日 12:43

私の以前の質問 stackoverflow.com/questions/63675153 をご覧ください

– ダニエル

2020 年 9 月 4 日 12:50

読んだのですが、問題と解決策が不必要に複雑であるべきではないのに感じました。私は Oracle を使用していますが、通常は読み取り専用の目的でトランザクションが厳密に必要になることはありません。 @Transactional についての説明はこちら stackoverflow.com/questions をご覧ください。/10394857/… 。そして、これも stackoverflow.com/questions/54326306/…

– スジットモハンティ30

2020 年 9 月 4 日 13:05

DepartmentService.findAll() を使用して部門のリストを取得できます。しかし、Departmens.get(0).getEmployees() を呼び出すと、例外がスローされます。 @Transactional はこれを修正します。

– ダニエル

2020 年 9 月 4 日 13:17

総合生活情報サイト - OKWAVES
総合生活情報サイト - OKWAVES
生活総合情報サイトokwaves(オールアバウト)。その道のプロ(専門家)が、日常生活をより豊かに快適にするノウハウから業界の最新動向、読み物コラムまで、多彩なコンテンツを発信。