Java SpringBoot

Git設定

設定

設定ファイル ダウンロード

ダウンロードしたファイルを展開する。

  1. Elipseの[ファイル]-[インポート]で「一般」配下の「設定」を選び、「次へ」を押す。
  2. 「参照」ボタンを押してダウンロードした SpringBoot.epfを指定する。
  3. 「再開」を押す
  4. 起動したら[ウィンドウ]-[パースペクティブ]-[パースペクティブを開く]-[その他]で「SpringBoot」を選択し、「開く」を押す。

プロジェクトの新規作成

[ファイル]-[新規]-[Springスタータープロジェクト]

「Spring Bootバージョン」を「2.7.11」

新規スターター・プロジェクト依存関係

※以下はデータベース使用時

完了を押し、インポートが終わるまでしばらく待つ。

インストール後のbuild.gralde dependenciesの例
dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
	implementation 'org.springframework.boot:spring-boot-starter-web'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

実行

プロジェクトを右クリックし[実行]-[Spring Bootアプリケーション]

ブラウザ http://localhost:8080 にアクセス("/"マッピング時)

2回目以降は停止して実行、あるいはRelaunchボタン

スニペット

コードの断片を素早く挿入できる機能。

SpringBoot用スニペット

スニペットの設定

  1. Eclipseでスニペットをクリック
  2. 下の空いている場所で右クリックし[カスタマイズ]を選択
  3. インポートボタンを押し、ダウンロードしたXMLファイルを指定する(1回1ファイル)

スニペットの利用

ダブルクリックするとカーソル位置に挿入されます。

パラメータがあるスニペットは値を設定して挿入します。

コントローラ

[src/main/java]内のパッケージ内に通常のクラスを新規に作成。
@Controller を付け、メソッドにURLを割り当てる。
@GetMapping でURLを設定する(POSTのときには @PostMapping)。

@Controller
public class HanbaiController {

	@GetMapping("/slist")
	public String slist() {

		 return "slist";
	}
}

この例ではURLが /slist 、メソッドはGET でアクセスすると slistメソッドが呼ばれる。
戻り値で表示したいhtmlファイルの名前を指定する( slist.html なら slist だけでよい )。
htmlファイルはresourcesのtemplate内に置く。

テンプレートにデータを渡す

htmlファイルにオブジェクトを送るには、メソッドの引数にModelクラスのインスタンスを指定する。これのaddAttributeメソッドで渡したいデータを名前と共に指定する。

@Controller
public class SampleController {

	@GetMapping("/index")
	public String index(Model model) {
		// num という名前で10を渡す。
		model.addAttribute("num", 10);
	
		return "index";
	}
}

このときに指定した名前でhtml内で以下のように指定する。

	<p th:text="${num}"></p>
これは以下のようになる。
	<p>10</p>

名前の省略

コントローラでデータ設定時、名前は省略も可能。省略時にはクラスの先頭文字を小文字にしたものになる。また、リストの場合、リストの要素型+Listになる(先頭は小文字)。

@Controller
public class SampleController {

	@GetMapping("/index")
	public String index(Model model) {
	
		Shouhin s = new Shouhin();
		model.addAttribute(s);	// 名前はshouhin

		List<Shouhin> list = new ArrayList<>();
		model.addAttribute(list);	// 名前はshouhinList
	
		return "index";
	}
}

URLパラメータ

URLにパラメータを付けてアクセスする(例:/uriage/3 )し、その値をコントローラで受け取る。

@GetMapping("/uriage/{sid}")
public String uriage(@PathVariable int sid,	Model model) {

	List<Uriage> list = repository.findBySid(sid);

	model.addAttribute(list);
	
	return "uriage";
}

@GetMapping のURLに{名前}を書く。URLのこの部分に入れた値をメソッドの引数「@PathVariable 変数型 変数名」で受け取る。

フォーム

フォームからの受け取りはメソッドの引数で行う。

//	HTML
<form method="post" action="slist">
商品名:<input type="text" name="sname" ><br>
単価:<input type="text" name="tanka" ><br>
<input type="submit" value="追加">
</form>

上のフォームからsnameとtankaが渡される。
これを下のメソッドの引数、snameとtankaで受け取る。

// Java
 @PostMapping("/insert")
 public String insert(String sname ,Integer tanka) {

	Shouhin s  = new Shouhin();
	s.setSname(sname);
	s.setTanka(tanka);
	 
	repository.save(s);	// データベース保存
	 
	return "redirect:/slist";
 }

なお、同nameが複数ある場合、配列で受け取ることが出来る。

		 String[] sname ,	//	snameが複数ある

オブジェクトでの受け取り

フォームから入力された内容を自動的にオブジェクトに設定できる。

※フォームのnameとオブジェクトのフィールド名が一致していること。nameが存在しない場合、フィールドに値が設定されない(nullなど)。

 @PostMapping("/insert")
 public String insert( Shouhin s) {

	repository.save(s);	// データベース保存
	 
	return "redirect:/slist";
 }

注:日付をこれで受け取るためには属性に@DateTimeFormatを付けて形式を指定する。

	// formがtype="date"時の設定
	@DateTimeFormat(pattern="yyyy-MM-dd")
	private Date hi;

リダイレクト

別ページにリダイレクトする場合、redirect:を付ける。

	 return "redirect:/slist";

フォームのボタンによる処理の振り分け

フォーム内に複数ボタンを配置し、どのボタンを押したかにより処理を振り分けます。まず、フォームのボタンにname属性を付けます。

<form method="post" action="insert">
商品名:<input type="text" name="sname"><br>
単価 :<input type="text" name="tanka"><br>
<input type="submit" value="追加" name="ok">
<input type="submit" value="キャンセル" name="cancel">
</form>

コントローラ内のメソッドではルーティングのアノテーションで、paramasを設定し、ボタンに付けたnameで呼ばれるメソッドを分けます。

	@PostMapping(value="/insert", params="ok")
	public String insert(Shouhin s) {
		repository.save(u);
		return "redirect:/slist";
	}
	
	@PostMapping(value="/insert", params="cancel")
	public String insert_cancel() {
		return "redirect:/";
	}

ビュー(Thymleaf)

動的なhtmlファイルは、resource/templates に、静的なhtmlはresource/staticに作る。

オブジェクトを渡す

htmlファイルにオブジェクトを送るには、Modelに addAttributeでオブジェクトを渡す。その際に名前を付けて渡す。

String mes = "こんにちは";

// 名前 mes で、オブジェクトmesを渡した
model.addAttribute("mes",mes);

return "slist";

Thymleaf

htmlファイルではthymeleafの書式を使って渡されたオブジェクトを表示する。
具体的には ${オブジェクト名} で取得できる。これをth:で始まる属性にセットする。

Eclipseでの候補表示

Eclipseではプロジェクトを右クリックし[Thymeleaf]-[Thymeleaf ネーチャーの追加]でHTML編集画面でth:の後にCtrl+Spaceを押すと選択肢がでるようになる。

テキスト

タグに囲まれた部分に出したい場合、タグのth:text属性にセットする。

	<p th:text="${mes}"></p>

上の例の場合、以下のようにHTMLが生成される(mesに「こんにちは」が入っている場合)。

	<p>こんにちは</p>

インライン表示

[[${名前}]] と書くことで直接そこに値を埋め込むことができる。
	<p>氏名:[[${shimei}]]</p>

オブジェクトの表示

オブジェクトのメソッドに getXxx() というものがある場合、"オブジェクト名.xxx" だけで表示できる。

例:shouhinにgetSname()がある場合

	<p th:text="${shouhin.sname}"></p>

属性

th:属性名="${名前}"

href属性

<a th:href="|del/${shouhin.sid}|">削除</a>

value属性

<input type="hidden" name="sid" th:value="${shouhin.sid}">

selected/checked属性

th:selectedに指定した条件がTrueならseleceted

<option value="0" th:selected="${shouhin.cid==0}">

th:checkedに指定した条件がTrueならchecked

<input type="radio" value="0" th:selected="${shouhin.cid==0}">

th:field

inputタグの場合、th:fieldを指定すると自動的にname属性とvaue属性(とid属性)を設定できる。

<input type="hidden" th:field="${shouhin.sid}">

これは以下と同じ。

<input type="hidden" name="sid" th:value="${shouhin.sid}">

selectタグに指定した場合、nameを自動的に設定し、optionタグの対応するものをselectedしてくれる。

shouhin.sidが2の場合、value="2" のあとに自動的に selected が入る。

<select th:field="${shouhin.sid}">
	<option value="1">りんご</option>
	<option value="2">みかん</option>
	<option value="3">いちご</option>
</select>

リスト

	List<Shouhin> list = repository.findAll();

	model.addAttribute("list",list);

	return "slist";

上のコードでlistをhtmlファイルに送った場合、以下のようにして表示する。

<table>
	<tr th:each="shouhin : ${list}">
		<td th:text="${shouhin.sid}"></td>
		<td th:text="${shouhin.sname}"></td>
		<td th:text="${shouhin.tanka}">円</td>
	</tr>
</table>

th:eachを書いたタグとそれに囲まれた部分がリストの要素数だけ繰り返される。
リストの一個の要素はshouhinに入るので ${shouhin.sid}のようにして表示する。

リストの数により処理を分けたい場合、th:ifを使って以下のように行う。

	
	<div th:if="${#lists.isEmpty(list)}">
		リストに一個も無い時
	</div>
	
	<div th:if="!${#lists.isEmpty(list)}">
		リストに一個以上ある時
	</div>

	<div th:if="${#lists.size(list)}>3" >
		リストに3個より多い要素がある時
	</div>

繰り返しのインデックスなどを知りたい場合、以下のようにして表示する。

	<tr th:each="shouhin , stat : ${list}">
		<td th:text="${stat.count}"></td>	<!-- 1始まりのカウント -->
		<td th:text="${stat.index}"></td>	<!-- 0始まりのカウント -->
		<td th:text="${stat.size}"></td>	<!-- 全要素数 -->
		<td th:text="${shouhin.sname}"></td>
	</tr>

リストの0番目などと指定して表示したい場合、配列のように[番号]で表示する。

	<td th:text="${list[0].sname}"></td>

文字の埋め込み

||で囲むと、その中の文字列中に ${オブジェクト} の値が埋め込まれる。
例:|del/${shouhin.sid}|
shouhin.sidが1なら
del/1
になる。

// リンクの例
<a th:href="|del/${shouhin.sid}|">削除</a>

日付の整形

<td th:text="${#dates.format(uriage.hi,'yyyy/MM/dd')}"></td>

条件

条件によって出す文字列を変えたいときには三項演算子を使う

条件 ? Trueのときの値 : Falseのときの値

<p th:text="${shouhin.tanka>100 ? '高い' : '安い'}"></p>

th:ifに指定した条件がTrueならそのタグが表示、そうでないなら非表示

<p th:if="${shouhin.tanka>100}">高い</p>
<p th:if="${shouhin.tanka<100}">安い</p>

テンプレート

別ファイルの一部を読み込むことが出来る

例:head.htmlの一部
<header th:fragment="header">
<h1>ヘッダ</h1>
<p>ヘッダ部分</p>
</header>
これを読み込む
<header th:include="head::header"></header>

データベース

準備

プロジェクト作成時、Spring Data JPAとMySQLをチェックしていなかったとき、 build.gradleのdependencies内に以下を追加

implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'com.mysql:mysql-connector-j'

application.propertiesファイルに設定を書く。

spring.datasource.url=jdbc:mysql://localhost:3306/データベース名
spring.datasource.username=ユーザ名
spring.datasource.password=パスワード
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
spring.jpa.database=MYSQL
spring.jpa.hibernate.ddl-auto=none

spring.datasource.url=jdbc:mysql://localhost:3306/hanbai
spring.datasource.username=root
spring.datasource.password=
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
spring.jpa.database=MYSQL
spring.jpa.hibernate.ddl-auto=none

エンティティ

テーブルの一行を表すエンティティクラスを作る。@Entityを指定する。
主キーには@Idを指定する。
※インポートは javax.persistence.*

@Entity
public class Shouhin {
	@Id
	private Integer sid;
	private String sname;
	private Integer tanka;

	// あとはgetter setter
}

なお、テーブル名とクラス名が異なる場合、@Tableでテーブル名を指定する
列名とフィールド名が異なる場合、@Columnで列名を指定する。
auto_incrementの列には@GeneratedValue(strategy=GenerationType.IDENTITY)のように連番方法を指定する。

テーブル名、列名を指定した例
@Entity
@Table(name="Shouhin")
public class Shouhin {
	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	@Column(name="sid")
	private int sid;

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

	@Column(name="tanka")
	private int tanka;

	// あとはgetter setter
}

※java.util.Date型は以下のように日付の書式を指定しておくことで input type="datetime-local" の部品とやりとりを行える。

@DateTimeFormat(pattern="yyyy-MM-dd'T'HH:mm")
private Date nichiji;

日付のみの場合、以下の指定で input type="date"の部品とやりとりを行える。

@DateTimeFormat(pattern="yyyy-MM-dd")
private Date hi;

Lombok

Lombokを使うとGetter、Setterやコンストラクタは自動で生成される。

準備:build.gradleのdependenciesに以下を追加(新規プロジェクト作成時には「開発ツール」の「Lombok」にチェック)

compileOnly 'org.projectlombok:lombok'

エンティティに@Dataを付けると、getter、setterなどが自動生成される

@Data	// getter, setter, toString, equals, hashCode
@NoArgsConstructor	// 引数無しコンストラクタ
@AllArgsConstructor // 全てのフィールドを引数に持つコンストラクタ
@Entity
public class Shouhin {
	@Id
	private int sid;
	private String sname;
	private int tanka;
}

リポジトリ

データベースを操作できるrepositoryインタフェースを作る。
これはJpaRepositoryを継承する(エンティティクラス名と主キーのクラス名を指定)。これだけである程度の操作は可能。

@Repository
public interface ShouhinRepository extends JpaRepository <Shouhin, Integer> {
}

Controller内でrepositoryを実体化し、操作する。実体化は @Autowired で自動的に出来る。

@Controller
public class HanbaiController {

	@Autowired
	private ShouhinRepository repository;


	@GetMapping("/slist")
	public String slist(Model model) {
		List<Shouhin> list = repository.findAll();

		model.addAttribute("list",list);

		return "slist";
	}

}

リポジトリのメソッド

基本メソッド

	//	全行取得
	List<Shouhin> list = repository.findAll();

	//	主キーから一件取得
	Shouhin s = repository.findById(sid).get();

	//	追加・更新
	repository.save(s);

	//	主キーによる削除
	repository.deleteById(sid);
	
	// 全件数取得
	long count = repository.count();

	//	主キーによる存在確認
	repository.existsById(sid);

※findByIdはOptional<Shouhin>を返す。
get()でShouhinを取得できる。存在しない場合、例外 NoSuchElementException が起きる。
isPresent()でnullだったかどうかが分かる(falseならnull)

Optionalの処理例
	// 例外でエラー処理する例
	try {
		Shouhin s = repository.findById(sid).get();
		model.addAttribute("shouhin",s);
	}catch(NoSuchElementException e) {
		return "err";
	}
	// isPresentで判定する例
	Optional<Shouhin> s = repository.findById(sid);
	if( s.isPresent()) {
		model.addAttribute("shouhin",s.get());
	}else {
		return "err";
	}
	// orElseで存在しないときにnullを入れて判定する例
	Shouhin s = repository.findById(sid).orElse(null);
	if( s == null) {
		return "err";
	}
	model.addAttribute("shouhin",s.get());

他にエンティティ数を返す long count() や、IDのエンティティが存在するかを調べるboolean existsById(id) などがある。

※saveで追加後、オブジェクトには自動連番など追加後の値がセットされる。

※find系で取得した後、オブジェクトを変更したら、flushで保存できる。

	//	主キーから一件取得
	Shouhin s = repository.findById(sid).get();
	
	s.setTanka(30);	//	単価変更
	
	repository.flush();	// 保存

整列

	//	tankaの昇順
	List<Shouhin> list = repository.findAll(Sort.by("tanka"));

	//	tankaの降順
	List<Shouhin> list = repository.findAll(Sort.by(Sort.Order.desc("tanka")));

	//	tankaの昇順、同じならsidの昇順
	List list = repository.findAll(Sort.by("tanka","sid"));

	//	tankaの昇順、同じならsidの降順
	List list = repository.findAll(Sort.by(Sort.Order.asc("tanka"),Sort.Order.desc("sid")));

クエリメソッドの追加

ルールに合わせた名前を宣言するだけで自動的に実装される。

	public interface UriageRepository extends JpaRepository <Uriage, Integer> {
	//	指定されたsid
		List<Uriage> findBySid(int sid);

	//	指定された日付
		List<Uriage> findByHi(Date hi);

	//	指定されたsidと日付
		List<Uriage> findBySidAndHi(int sid,Date hi);

	//	指定されたsidまたは日付
		List<Uriage> findBySidOrHi(int sid,Date hi);

	//	指定された個数より小さい
		List<Uriage> findByKosuLessThan(int kosu);

	//	指定された個数より大きい
		List<Uriage> findByKosuGreaterThan(int kosu);

	//	日付がNull
		List<Uriage> findByHiIsNull();

	//	日付がNullではない
		List<Uriage> findByHiIsNotNull();

	//	指定された日付をLikeで
		List<Uriage> findByHiLike(Date hi);

	//	指定されたsidを日付の降順
		List<Uriage> findBySidOrderByHiDesc(int sid);

	}
public interface ShouhinRepository extends JpaRepository <Shouhin, Integer>{
	// snameを含むもの
	List<Shouhin> findBySnameContains(String sname);
	
	// snameを含むものを指定ソート順で
	List<Shouhin> findBySnameContains(String sname,Sort sort);
	
	// snameで存在確認
	boolean existsBySname(String sname);
}
※Sortは以下のように使う。
	List<Shouhin> list = repository.findBySnameContains("ん",Sort.by(Direction.DESC,"tanka"));

ネイティブクエリー

@QueryでSQLを指定し実行することも出来る。

// 個数2個以上
@Query(value="SELECT * FROM uriage WHERE kosu>=2", nativeQuery = true)
List<Uriage> findKosu();

引数を使う場合、SQL内に?番号で引数の順番を指定する。

@Query(value="SELECT * FROM uriage WHERE kosu >= ?1", nativeQuery = true)
List<Uriage> findKosu(int kosu);

もしくは、@Param(名前)を指定し、SQL内に :名前 で指定する。

@Query(value="SELECT * FROM uriage WHERE kosu > =:kosu", nativeQuery = true)
List<Uriage> findKosu(@Param("kosu") int k);

集計関数を使う場合など単一値を返す場合、Integerなどを戻り値の型に指定する。

@Query(value="SELECT sum(kosu) as cnt FROM uriage", nativeQuery = true)
Integer sumKosuAll();

独自の列がある場合、それを格納するためにリポジトリ内にinterfaceを作り戻り値の型にする。

public interface UriageRepository extends JpaRepository <Uriage, Integer>{

	@Query(value="SELECT sid,sum(kosu) as cnt FROM uriage GROUP BY sid", nativeQuery = true)
	List<Result> sumKosu();

	public interface Result {
	    public int getSid();
	    public int getCnt();
	}
}
// 呼び出し側の例
List<UriageRepository.Result> results = repository.sumKosu();

更新系の場合、@Modifyingを付ける。

@Modifying
@Query(value="DELETE uriage WHERE sid=:sid", nativeQuery = true)
Integer deleteBySid(@Param("sid") int sid);

例示での検索

findAllや、count、existsメソッドの引数に Example.of(エンティティ) を指定することで、エンティティの内容と完全一致するもののみを検索対象に出来る。エンティティ内のnullの項目は無視される。

	Shouhin s = new Shouhin();
	s.setSname("りんご");
	s.setTanka(100);
	List<Shouhin> list = repository.findAll(Example.of(s)); // 「りんご」かつ100円のみ検索

Example.ofの第二引数に、ExampleMatcherを指定することで、検索方法のカスタマイズが出来る。

項目のどれかと一致

	Shouhin s = new Shouhin();
	s.setSname("りんご");
	s.setTanka(100);

	// 「りんご」または100円のみ検索
	ExampleMatcher em = ExampleMatcher.matchingAny();

	List<Shouhin> list = repository.findAll(Example.of(s, e)); 

文字列の部分検索

	Shouhin s = new Shouhin();
	s.setSname("ん");

	ExampleMatcher em = ExampleMatcher.matching()
			.withStringMatcher(StringMatcher.CONTAINING);

	List<Shouhin> list = repository.findAll(Example.of(s, e)); // 「ん」を含むものごのみ検索

CONTAINING以外に STARTING(前方一致)、ENDING(後方一致)がある。

そのほかの matching() 後に使えるメソッド

.withIgnorePaths(列名,,,)列名を無視
.withIncludeNullValues(列名,,,)nullの列も比較
.withMatcher(列名, 変数名 -> 変数名.メソッド名())その列のみを指定メソッドで比較

結合

エンティティ内に結合するクラスの変数を宣言し、以下のアノテーションを付ける。

1対1@OneToOne結合先のオブジェクト1つ
1対他@OneToMany結合先のオブジェクトのリスト
多対1@ManyToOne結合先のオブジェクト1つ
多対多@ManyToMany結合先のオブジェクトのリスト

さらに@JoinColumnも付ける。

name参照元テーブルの外部キー列名
referencedColumnName主キーの列名
insertable挿入時にこのオブジェクトのキーを使う。別にキー列のフィールドがある場合、false
updatable更新時にこのオブジェクトのキーを使う。別にキー列のフィールドがある場合、false

これを付けることにより、リポジトリからそのエンティティを読み込んだとき、自動的に結合先のエンティティも読み込む。
なお、リポジトリを通していないとき(自分でnewしたときやフォームから得たとき)には、自動では結合してくれないので、手動で結合先のエンティティを得る。

ManyToOneの例

Uriageクラスに以下を宣言すると、リポジトリからの読み込み時に自動的にsidで結合したShouhinを取得。

@ManyToOne
@JoinColumn(name = "sid",referencedColumnName = "sid",
        insertable = false,updatable = false)
private Shouhin shouhin;

OneToManyの例

Uriageクラスに以下を宣言すると、リポジトリからの読み込み時に自動的にsidで結合したShouhinを取得。

@OneToMany
@JoinColumn(name = "sid",referencedColumnName = "sid",
	insertable = false,updatable = false)
private List<Uriage> uriageList;

public List<Uriage> getUriageList() {
	return uriageList;
}

OneToManyで条件を付ける例

@OneToMany
@Where(clause = "kosu > 1")
@JoinColumn(name = "sid",referencedColumnName = "sid",
	insertable = false,updatable = false)
private List<Uriage> uriageList;

public List<Uriage> getUriageList() {
	return uriageList;
}

@OrderBy("hi desc") のようにして整列順序も指定できる。

※importは javax.persistence.OrderBy

例外処理

例外をキャッチするハンドラとなるメソッドを定義できる。例えば、フォームからの送信データをオブジェクトにバインドできなかったときはBindExceptionが発生するので以下のように書ける。

	@ExceptionHandler(BindException.class)
	public String handleBindException(BindException e, Model model) {

		model.addAttribute("msg", "正しく入力を行ってください。");
		    
	    return "err";

	}	

err.htmlには以下のように書く

<h1>エラー</h1>

<p th:text="${msg}"></p>

<button type="button" onclick="history.back()">戻る</button>

どのような例外かは以下で分かる

	for(ObjectError oe : e.getAllErrors()) {
		System.out.println(oe.getCode());	// エラーの種類
		System.out.println(oe.getObjectName()); // エラーを起こしたオブジェクト
	}

バリデーション

build.gradleのdependenciesに以下を追加(新規プロジェクト作成時には「I/O」の「検証」にチェック)

implementation 'org.springframework.boot:spring-boot-starter-validation'

エンティティにアノテーション設定

@Entity
public class Shouhin {
	@Id
	private int sid;

	@NotEmpty(message="商品名を入力してください")
	private String sname;

	@Min(0)
	private int tanka;

コントローラにはエンティティ前に@Validatedを指定し、その直後にBindingResultを受け取るようにする。
エラーがあるときには元の画面にリダイレクト。

@PostMapping("/insert")
public String insert(@Validated Shouhin s, BindingResult result,  RedirectAttributes attr)  {

	if( result.hasErrors() ) {
		attr.addFlashAttribute(BindingResult.MODEL_KEY_PREFIX +"shouhin", result);
		attr.addFlashAttribute(s);
		return "redirect:/shouhin"
	}
	repository.save(s);
	return "redirect:/shouhin";
}

テンプレートでのエラー表示。

<div th:if="${shouhin}" th:object="${shouhin}" >
<form action="/insert" method="post">

商品名:<input type="text" name="sname" value="">
<span th:if="${#fields.hasErrors('sname')}" th:errors="*{sname}"></span>
<br>

単価:<input type="text" name="tanka" value="">
<span th:if="${#fields.hasErrors('tanka')}" th:errors="*{tanka}"></span>
<br>
<input type="submit" value="追加">
</form>
</div>

まとめてエラー表示する場合(formタグの中に書く)

<div th:if="${shouhin}" th:object="${shouhin}" >
<ul th:if="${#fields.hasErrors('*')}">
	<li th:each="err : ${#fields.errors('*')}" th:text="${err}"></li>
</ul>
</div>

エラーメッセージを変更する場合、resource配下に messages.properties を作成し、以下のように書く(反映されない場合、Eclipse再起動)。

typeMismatch.tanka=単価は整数で入力してください。
typeMismatch.int=整数で入力してください。

コントローラでエラーを追加したい場合、以下のように書く(snameでエラー時)。

result.addError(new FieldError("shouhin","sname","商品名が不正です"));

コントローラで個別にエラーを処理したい場合、以下のように書く(tankaで型変換エラー時)。

	FieldError er = result.getFieldError("tanka");
	if( er != null && er.getCode().equals("typeMismatch")) {
		// tankaのエラー時の処理
	}

セッション

実体化

Autowiredでセッションも自動的にインスタンス化できる。


@Controller
public class HanbaiController {

	@Autowired
	private HttpSession session;


	@GetMapping("/del/{sid}")
	public String del(@PathVariable int sid) {

		Shouhin s = repository.findById(sid).get();

		//	セッションに入れる。
		session.setAttribute("del",s);

		return "del";
	 }

	@PostMapping("/del")
	public String shouhinDel() {

		//	セッションから取り出す
		Shouhin s = (Shouhin)session.getAttribute("del");

		// 削除処理
		repository.delete(s);
		
		// セッションから削除
		session.removeAttribute("del");

		return "redirect:/slist";

	}
}

Thymleafでのセッション

暗黙オブジェクト session を使用する

// 表示
<div th:text="${session.shouhin.sname}"></div>

// セッションによる判定 キーがあるとき
<div th:if="${session.shouhin}"></div>

// セッションによる判定 キーが無いとき
<div th:if="${session.shouhin==null}"></div>

フラッシュメッセージ

リダイレクト先で一度のみ表示するメッセージ。セッションを利用して行う。

コントローラの最後の引数にRedirectAttributesを追加。そのaddFlashAttributeメソッドでメッセージ追加

@PostMapping("/insert")
public String insert(Shouhin s, RedirectAttributes attr)  {

	repository.save(s);

	// フラッシュメッセージの追加
	attr.addFlashAttribute("message", "商品を追加しました");

	return "redirect:/shouhin";
}

表示はmodelにaddAttributeしたときと同じ

<p th:if="${message}" th:text="${message}"></p>