【Spring Boot/JPA】外部キー条件検索の実装



# はじめに
　はじめまして。現在研修中の新卒エンジニアです。

　入社からもうすぐ2ヶ月経つところで、Springのアプリケーション開発に取り組んでます。
　その中で、JPAの外部キーを用いた検索の実装方法をいくつか覚えたので、学習記録の意味も含めそちらを内容として書いていこうと思います。
　具体的には、`外部キーを使った条件検索について、リクエストパラメータで値を受け取ったケースでの記述法`をいくつか紹介します。

　こちらの記事はSpring Bootの基礎知識が前提の内容になります。
　Springの基礎を学習中の方や、JPAの外部参照について知りたい方に役立つ内容になれば幸いです。

# 実装する処理の概要

　外部参照関係にある2つのテーブルを使った一覧表示、検索を行う処理です。
　ドロップダウンリストから絞りたい部署名を検索します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/550496/be1a32ff-ed7f-49ca-1565-da2c1b09eb87.png)

###テーブル
主キーはそれぞれidになります
従業員テーブルのdepartment_idは部署テーブルのidを外部参照しています

#####部署テーブル(department)
| id | dep_name |
|:-----------|------------:|
| 1       |    人事部     |
| 2     |    経理部   |
| 3       |     営業部    | 



#####従業員テーブル(emp_sample)　　　　

| id | emp_name | department_id |
|:-----------|------------:|:------------:|
| 1       | sato        | 2         |
| 2     | suzuki      | 2       |
| 3       | watanabe        | 1         |
| 4       | mikami        | 3         |
| 5     | tomita      | 1       |
| 6       | sasaki        | 2         |


###エンティティ

```java:Department.java
@Entity
@Table(name="department")

public class Department {
	
	
	@Id
	private Integer id;
	
	@Column
	private String depName;

	public Integer getId() {
		return id;
	}

	public void setId(Integer id) {
		this.id = id;
	}

	public String getDepName() {
		return depName;
	}

	public void setDepName(String depName) {
		this.depName = depName;
	}


}
```
```java:EmpSample.java
@Entity
@Table(name="emp_sample")
public class EmpSample {

	@Id
	private Integer id;
	
	@Column
	private String empName;
	
	@ManyToOne()
	@JoinColumn(name = "department_id", referencedColumnName = "id")
	private Department department;

	
	public Integer getId() {
		return id;
	}

	public void setId(Integer id) {
		this.id = id;
	}

	public String getEmpName() {
		return empName;
	}

	public void setEmpName(String empName) {
		this.empName = empName;
	}

	public Department getDepartment() {
		return department;
	}

	public void setDepartment(Department department) {
		this.department = department;
	}
}
```



###リポジトリ
```java:DepartmentRepository.java
public interface DepartmentRepository extends JpaRepository<Department, Integer>{

}
```
```java:EmpSampleRepository.java
public interface EmpSampleRepository  extends JpaRepository<EmpSample, Integer>{
	
	List<EmpSample> findByDepartment(Department dep);

}
```


#社員一覧表示、検索ホームの処理
###ビュー
```html:view.html
<body>
	<form th:action="@{/doSearchByDep}" method="get">
		部署名検索: <select  name="depId">
					<option th:each="dep : ${session.deps}"
						th:value="${dep.id}"
						th:text="${dep.depName}"></option>
				</select> 
				<input type="submit" value="検索">
	</form>
	<table>
		<tr>
			<th>社員ID</th>
			<th>社員名</th>
			<th>部署名</th>
		</tr>
		<tr th:each="emp : ${emps}">
			<td th:text="${emp.id}"></td>
			<td th:text="${emp.empName}"></td>
			<td th:text="${emp.department.depName}"></td>
		</tr>
	</table>
</body>
```


###コントローラー
```java:EmpController.java
@Controller
public class EmpController{
	
	@Autowired
	EmpSampleRepository empRepository;
	
	@Autowired
	DepartmentRepository depRepository;
	
	// 社員一覧表示処理
	@GetMapping("/showView") 
	public String showView(Model model, HttpSession session) {
		
		//部署テーブルから全レコードを取得、セッションスコープに保存
		session.setAttribute("deps", depRepository.findAll());
		//内部結合した社員情報の全レコード取得、リクエストスコープに保存
		model.addAttribute("emps", empRepository.findAll());
		return "view";
	}
	
	//検索処理
	@GetMapping("/doSearchByDep")
	public String doSearchByDep(Model model, Integer depId) {
		
		//フォームで選択された部署のidで初期化
		Department dep = new Department();
		dep.setId(depId);
		model.addAttribute("emps", empRepository.findByDepartment(dep));
		
		return "view";
	}
}
```
　社員一覧表示処理では、それぞれスコープに保存し、ビューにフォワードして表示します。
　ビューで表示される一覧の部署名は、EmpSampleの外部キーフィールドからさらにそのフィールドであるdepNameを参照しています。

　検索処理ではフォームで受け取った値をを使用し、EmpSampleRepositoryで定義したメソッドを使って条件検索を行っています。
　フォームのvalue属性の値は、name属性で指定した文字列を、引数で同じ名前を宣言することで使用することができます。

　findByDepartmentで条件検索を行うにはエンティティであるDepartment型のオブジェクトを引数に入れる必要があります。
　そのため、Departmentをインスタンス生成しています。
　なお、DepartmentのidフィールドはEmpSampleの外部参照として紐づいているので、これを初期化して引数に入れるだけで検索が行えます。

#１行で書く
上記の検索処理はインスタンス生成、初期化など複数行で記述しましたが、１行で書くことも可能です。

```java:EmpController.java
//検索処理
	@GetMapping("/doSearchByDep")
	public String doSearchByDep(Model model, Integer depId) {
		
//		//フォームで選択された部署のidで初期化
//		Department dep = new Department();
//		dep.setId(depId);
//		model.addAttribute("emps", empRepository.findByDepartment(dep));
		
		model.addAttribute("emps", empRepository.findByDepartment(depRepository.getOne(depId)));
		
		return "view";
	}
```
　findByDepartmentの引数でさらに検索を行うメソッドの戻り値を利用ています。
　getOneはJpaRepositoryからオーバーライドしたメソッドになります。

　しかし、こちらは２度DB接続を行なっています。
　システムの規模が大きくなった場合のパフォーマンスを考慮すると、DB接続を極力減らしたいところです。

　よりシンプルな書き方を求めると以下のようになります。

```java:EmpController.java
//検索処理
@GetMapping("/doSearchByDep")
//	public String doSearchByDep(Model model, Integer depId) {
	public String doSeerchByDep(Model model, Department dep) {
//		//フォームで選択された部署のidで初期化
//		Department dep = new Department();
//		dep.setId(depId);
//		model.addAttribute("emps", empRepository.findByDepartment(dep));
		
//		model.addAttribute("emps", empRepository.findByDepartment(depRepository.getOne(depId)));
		
		model.addAttribute("emps", empRepository.findByDepartment(dep));
		
		return "view";
	}
```

　こちらでは、フォームを受け取るdoSearchByDepの引数の型をDepartmentに変え、直接findByDepartmentの引数として渡しています。
　また、ビュー側のname属性も以下のように書き換えています。

```html:view.html
<!-- 部署名検索: <select  name="depId"> -->
		部署名検索: <select name="id">
					<option th:each="dep : ${session.deps}"
						th:value="${dep.id}"
						th:text="${dep.depName}"></option>
				</select> 
				<input type="submit" value="検索">
```
　こちらの記述でこれまでと同じ処理を実装することができます。
　しかし、name属性の値とdoSearchByDepの引数の名前が一致していないのは何故でしょうか？

　これにはSpringフレームワークの便利な機能が使われています。
　フォームから値を受け取る際に、フォームクラスを定義し自動代入されたフィールドから値を所得する方法があります。
　ここでもその機能が働いており、doSearchByDepの引数depには、そのフィールドidに値が自動代入されているということです。
　このような訳から、引数から直接findByDepartmentに値を渡すことができます。

　ということで、１行でかつDB接続を抑えた記述をすることができました。
　フレームワークの機能を理解することで様々な記述ができるようになるのは、可読性や効率性など様々な面で役に立つと思います。


# おわりに

長々と読んでいただきありがとうございました。

不正確な情報や意見があればコメントいただければと思います。