Spring Data JPA

# 目的
Spring Data JPAにて開発中、ハマったポイントと原因と対処を書き溜めていく

# 環境
Spring-boot
java8
STS
Spring-data-jpa
hivernate

# 問題と対処
### 事象1.0
BeanUtil.copyPropertiesにて、フォームから取得した値をDBへ登録するEntityへ転写する際、値が引き継がれずnullになるプロパティがある

import org.springframework.beans.BeanUtils

```
BeanUtils.copyProperties(フォーム, エンティティ);//左から右に値をコピー
```

画面からPOSTされた値をFormクラスを経由しコントローラで取得し、
javax.persistance.Entityクラスに引き継ぎ、
DBへ保存する。

フォームには値が入っているが、
DB側からNULLを許容しないエラーが発生する
画面から値の流れを調査したら、
copyPropatyにて値が引き継がれていなかった


### 原因と対処 1.0
Formクラスで定義されていたデータ型が、Entityと一致していない。
EntityでDoubleで定義されていた値に、FormクラスでIntegerで定義されたフィールドを割り当てていた。
springframeworkのcopyPropatyの場合、例外とはならず、nullが渡される。

### その他 1.0
ちなみにハマったわけではないが、
フォームでは文字列で入力されている値を、Entityへ移動する際に、データ型をDateへ変換する処理

```java:Util.java
// 明細部
	// 文字列の日付をdbのためdate型へ変換してFormからEntityへ
	public static MitsumoriDtl setMeisaiDateFormToEntity(MeisaiForm form, MitsumoriDtl entity) {

		SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");

		try {
			entity.setSys_create_ymdhm(sdf.parse(form.getSys_create_ymdhm()));
			entity.setSys_delete_ymdhm(sdf.parse(form.getSys_delete_ymdhm()));
			entity.setSys_update_ymdhm(sdf.parse(form.getSys_update_ymdhm()));

		} catch (Exception e) {
			e.printStackTrace();
		}
		return entity;

	}
```

### 事象2.0
JPQLにて検索処理に失敗する。

```
@Query("SELECT t FROM t_mitsumori_dtl t WHERE :hdrKey = t.mitsumori_hdr_no")
    List<MitsumoriDtl> findByHdrKey(@Param("hdrKey") String hdrKey);
```

t_mitsumori_dtlというテーブルから、JPQLを利用し、検索した結果をリストで返す。
検索はhdrKeyという変数の値で、mitsumori_hdr_no　を絞り込む

しかし、t_mitsumori_dtlがマッピングできないというHibernateのエラーとなる


### 原因と対処 2.0
@Queryの中に入るのは、Entityクラスで、テーブルではない

```java
package com.example.repository;

import java.util.Date;
import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import com.example.domain.MitsumoriDtl;

@Repository
@Transactional
public interface MitsumoriDtlRepository extends JpaRepository<MitsumoriDtl, Integer> {

	// hdrに紐づく明細を取得する
	@Query("SELECT t FROM MitsumoriDtl t WHERE :hdrKey = t.mitsumori_hdr_no")
	List<MitsumoriDtl> findByHdrKey(@Param("hdrKey") String hdrKey);
	

}
```

@Queryの中に入るのは、Entityクラスで、テーブルではない
上記に修正し解消することができた。

### 事象3.0
コントローラから、一部値を入れたエンティティを画面へ送り、
画面から残りの値を編集しデータの作成を行いたい

```
@RequestMapping(value = "editMeisai", method = RequestMethod.GET)
	String edit(Model model,@RequestParam Integer id,MeisaiForm form){
		// ここで取得しなければならないのは、紐づけるhdrのID番号だけで、そこへ新規の明細データをaddしていく
		
		// フォーム用
		// からの明細フォームを送る。hdridをあらかじめ入れておく
		MeisaiForm newMeisai = new MeisaiForm();
		newMeisai.setMitsumori_hdr_no(String.valueOf(id));
		model.addAttribute("newmeisai", newMeisai);
		return "views/meisai";
	}
```

この受け渡らせた値を受け取るフォーム側

```
		<form th:action="@{/testdb/editMeisai}" th:object="${newmeisai}"
				modelAttribute="meisaiForm" method="post">
				<fieldset>
					<legend>登録</legend>
```

しかし、以下のエラーが出る。

```
dtl ---- : MitsumoriDtl(t_mitsumori_dtl_seq=null, mitsumori_hdr_no=1, mitsumori_dtl_no=123, mitsumori_dtl_mei=123, mitsumori_dtl_suryo=123.0, mitsumori_dtl_suryo_tani=123, mitsumori_dtl_tanka=123, mitsumori_dtl_gaku=123, mitsumori_dtl_biko=123, t_mitsumori_hdr_seq=1, sys_create_user=123, sys_create_ymdhm=Thu Jan 01 00:00:00 JST 2015, sys_create_class=123, sys_update_user=123, sys_update_ymdhm=Thu Jan 01 00:00:00 JST 2015, sys_update_class=123, sys_delete_user=123, sys_delete_ymdhm=Thu Jan 01 00:00:00 JST 2015, sys_delete_class=123, sys_delete_no=123, sys_version=1, headerEntity=null)
2016-03-22 01:20:43.768 ERROR 5996 --- [nio-8080-exec-6] org.thymeleaf.TemplateEngine             : [THYMELEAF][http-nio-8080-exec-6] Exception processing template "views/meisai": Exception evaluating SpringEL expression: "#fields.hasErrors('mitsumori_hdr_no')" (views/meisai:29)
2016-03-22 01:20:43.800 ERROR 5996 --- [nio-8080-exec-6] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.thymeleaf.exceptions.TemplateProcessingException: Exception evaluating SpringEL expression: "#fields.hasErrors('mitsumori_hdr_no')" (views/meisai:29)] with root cause

java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'newmeisai' available as request attribute
```

画面からmodelのnewmeisaiに紐づく値で
Neither BindingResult nor plain target object for bean name 'newmeisai' available as request attribute
リクエストのnewmeisaiは、BuildingResultでもplainでもないというエラーが発生している


### 原因と対処 3.0
画面からの入力を行うフォーム項目は、完全な状態かからの状態でなければならない。
中途半端に入力してしまうと、バリデーションが走ってしまい、適切に表示できない
つまり、${newmeisai}、は、直接ここから受け取ることはできず、プレーンなフォームオブジェクトを与えなければならない

```
 <form th:action="@{/testdb/editMeisai}" th:object="${newmeisai}"
                modelAttribute="meisaiForm" method="post">
```

ではどうやって
コントローラから受け取った値を、Formにバインドされた新規のフォームクラスへ受け渡すか

方針として
1. Formの外にコントローラからkey（${newmeisai}に入っているキーとなる値を持っているフィールド）を受け取るだけの要素を作る
2. javascriptで、それぞれのdom要素を取得し、${newmeisai}コントローラから渡ってきた値を渡したい*{mitsumori_hdr_no}に引き渡す
3. この操作はHTML読み込み完了後に呼び出されるfooterでやる、また、値受け取り用のフォームはhiddenタイプにして隠蔽する

以下のように、値受け取り専用のformを作成し、値を取得する

```html
<!-- ここで値を受け取る -->
<form th:object="${newmeisai}" ><input type="text" id="pased_id"
								name="mitsumori_hdr_no"
								th:field="*{mitsumori_hdr_no}" class="form-control"
								th:value="*{mitsumori_hdr_no}" /></form>
<!----------------------->
		<h1>明細</h1>
		<!-- 登録を行う  -->
		<div class="col-sm-12">
			<!-- th:objectでは、フォームクラス名の頭小文字を指定すると、フォームオブジェクトとHTMLフィールドをバインドでき、
	このクラスのフィールド名で　*{フィールド名}で値を呼び出せる -->
			<form th:action="@{/testdb/editMeisai}" th:object="${meisaiForm}"
				modelAttribute="meisaiForm" method="post">
				<fieldset>
					<legend>登録</legend>
					<!-- 見積もり番号 -->
					<div class="form-group"
						th:classappend="${#fields.hasErrors('mitsumori_hdr_no')}? 'has-errorhas-feedback'">
						<label for="mitsumori_hdr_no"
							class="col-sm-3control-label">見積もり番号 </label>
						<div class="col-sm-11">
							<input type="text" id="mitsumori_hdr_no"
								name="mitsumori_hdr_no"
								th:field="*{mitsumori_hdr_no}" class="form-control"
								th:value="${mitsumori_hdr_no}" /> 

```

上記は、HTMLでは以下のようにレンダリングされる

```html

	<form><input type="text" id="pased_id" name="mitsumori_hdr_no" class="form-control" value="1" /></form>
		<h1>明細</h1>
		<!-- 登録を行う  -->
		<div class="col-sm-12">
			<!-- th:objectでは、フォームクラス名の頭小文字を指定すると、フォームオブジェクトとHTMLフィールドをバインドでき、
	このクラスのフィールド名で　*{フィールド名}で値を呼び出せる -->
			<form modelAttribute="meisaiForm" method="post" action="/testdb/editMeisai">
				<fieldset>
					<legend>登録</legend>
					<!-- 見積もり番号 -->
					<div class="form-group">
						<label for="mitsumori_hdr_no" class="col-sm-3control-label">見積もり番号 </label>
						<div class="col-sm-11">
							<input type="text" id="mitsumori_hdr_no" name="mitsumori_hdr_no" class="form-control" value="" /> 
						</div>

```

ボディ要素のしたに以下のjavascriptを追加し、
上のdom要素を取得し、コントローラから値を受けとているpassed_idの要素のvalueを
今回値を登録したいmitsumori_hdr_noのvalueへ移し替える


```js
<footer>
<script type="text/javascript">
var passed_key = document.getElementById('pased_id');
var post_key = document.getElementById('mitsumori_hdr_no');
post_key.value = passed_key.value
</script>
</footer>
```

値が確かに引き渡されている。受け取り側のtypeをhidenにすれば自然に渡せる
![スクリーンショット 2016-03-22 2.18.08.png](https://qiita-image-store.s3.amazonaws.com/0/57455/ef65ed71-89e9-f2fd-1d6a-bafda17d154c.png)

### 事象4.0
関連テーブルのデータを削除できない

- １対多の関連を持つテーブルがあるこれに対応した以下のドメインクラスがある    
hdrEntity　（単数）    
meisaiDtl　（多：関連テーブル）

**このクラスhttp://qiita.com/yukihigasi/items/54e224ce346dbd89e981**
- 関連テーブル側をrepositoryのdeleteメソッドで直接削除しようとした場合、削除されない

### 原因と対処 4.0
- 原因   
元テーブル側を経由して関連テーブルの削除を行わなければならない    
関連テーブルだけを単純に

```
 mitsumoriDtlService.delete(id);
```
関連テーブル用のリポジトリ.delete(関連テーブルの主キー)　　　
で削除できない
元ドメインクラスが生きている限り、マネージャがまだメモリ上に保持している

- 対処   
元テーブル側から削除する   
元テーブルにコレクションクラスで保持されている関連テーブルの中から、    
今回削除したい関連テーブルオブジェクトを除去し、    
元テーブルのドメインクラスをアップデートする

以下で例示する    
1. dtl(多)を検索してhdr(単一)の主キーを検索し、
2. hdrが保持するすべてのdtlを洗い、今回のdtlのidを見つけ、リストから削除する
3. 編集されたdtlのリストを持つhdrをアップデートする

```java:コントローラクラス
@RequestMapping(value = "deleteMeisai", method = RequestMethod.POST)
	String deleteMeisai(@RequestParam Integer id) {
		// TODO 元エンティティから削除をしないと、マネージャ持されてしまうため、関連エンティティは直接けせない
		// dtlを検索してhdrの主キーを検索し、
		MitsumoriDtl dtl = mitsumoriDtlService.findOne(id);
		// hdrのdtlを繰り返し今回のdtlのidを見つけ、削除する
		HeaderEntity hdr = mitsumoriHdrService.findOne(dtl.getT_mitsumori_hdr_seq());
		List<MitsumoriDtl> serchDeleteLst = hdr.getMitsumoriDtl();
		int i=0;
		int index=0;
		for (MitsumoriDtl adtl:serchDeleteLst){
			if(adtl.getT_mitsumori_dtl_seq() == id){
				index=i;
			}
			i++;
		}
		serchDeleteLst.remove(index);
		hdr.setMitsumoriDtl(serchDeleteLst);
		// hdrをアップデートする
		mitsumoriHdrService.update(hdr);
		
		// mitsumoriDtlService.delete(id);
		return "redirect:/testdb";
	}
```
以上で、関連テーブルのキーを元に、関連テーブルのレコードを削除することができる。


-----

以後、何かあり次第追記いたします。