Spring Bootで簡易検索アプリ作成

標題の通り、検索アプリを作成しました。

##使用環境
・Windows10 (64bit)
・spring-boot：2.2.6
・Eclipse：4.9.0
・H2

#完成画面
ジャンル、著者、タイトルのいずれかまたは、すべて入力した際に
画面の下部にある内容を引っ張ってきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631044/97aab4a7-525b-eb09-b155-5b8c51140ba9.png)


#Entity

```java:BookData.java
import javax.persistence.Entity;
import javax.persistence.Id;

@Entity
public class BookData {

	@Id
	private long isbn;

	private String genre;
	private String author;
	private String title;
	private int stock;
	private Boolean status;

	public long getIsbn() {
		return isbn;
	}
	public void setIsbn(long isbn) {
		this.isbn = isbn;
	}
	public String getGenre() {
		return genre;
	}
	public void setGenre(String genre) {
		this.genre = genre;
	}
	public String getAuthor() {
		return author;
	}
	public void setAuthor(String author) {
		this.author = author;
	}
	public String getTitle() {
		return title;
	}
	public void setTitle(String title) {
		this.title = title;
	}
	public int getStock() {
		return stock;
	}
	public void setStock(int stock) {
		this.stock = stock;
	}
	public Boolean getStatus() {
		return status;
	}
	public void setStatus(Boolean status) {
		this.status = status;
	}
}

```

#Dao
ジャンル、著者、タイトルで検索するためDaoクラスを用意します。

```java:BookDataDao.java
import java.io.Serializable;
import java.util.List;

public interface BookDataDao extends Serializable {

	public List<BookData> search(String genre, String author, String title);
}
```

#Repository
Daoクラスを実装したBookDataDaoImplを作成します。
各記述の内容はコメントを参照ください。

```java:BookDataDaoImpl.java
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.Query;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

@Repository
public class BookDataDaoImpl implements BookDataDao {

	//Entityを利用するために必要な機能を提供する
	@Autowired
	private EntityManager entityManager;

	public BookDataDaoImpl() {
		super();
	}

	public BookDataDaoImpl(EntityManager manager) {
		this();
		entityManager = manager;
	}

	//Daoクラスで用意したsearchメソッドをオーバーライドする
	@SuppressWarnings("unchecked")
	@Override
	public List<BookData> search(String genre, String author, String title) {

		//StringBuilderでSQL文を連結する
		StringBuilder sql = new StringBuilder();
		sql.append("SELECT b From BookData b WHERE ");

		boolean genreFlg  = false;
		boolean authorFlg = false;
		boolean titleFlg  = false;
		boolean andFlg    = false;

		//genreがブランクではなかった場合、sql変数にappendする
		//フラグをtrueにしとく
		if(!"".equals(genre)) {
			sql.append("b.genre LIKE :genre");
			genreFlg = true;
			andFlg   = true;
		}

		//authorがブランクではなかった場合、sql変数にappendする
		//フラグをtrueにしとく
		if(!"".equals(author)) {
			if (andFlg) sql.append(" AND ");
			sql.append("b.author LIKE :author");
			authorFlg = true;
			andFlg    = true;
		}

		//titleがブランクではなかった場合、sql変数にappendする
		//フラグをtrueにしとく
		if(!"".equals(title)) {
			if (andFlg) sql.append(" AND ");
			sql.append("b.title LIKE :title");
			titleFlg = true;
			andFlg   = true;
		}

		/*
		QueryはSQLでデータを問い合わせるためのクエリ文に相当する機能を持つ
		entityManagerのcreateQueryメソッドを使用する
		sql変数を引数に渡す
		*/
	    Query query = entityManager.createQuery(sql.toString());

	    //上記のif文でtrueになった場合は、各変数に値をセットする
	    //今回、あいまい検索したいのでlike句を使用する
	    if (genreFlg) query.setParameter("genre", "%" + genre + "%");
	    if (authorFlg) query.setParameter("author", "%" + author + "%");
	    if (titleFlg) query.setParameter("title", "%" + title + "%");
	    return query.getResultList();
	}
}

```

#BookDataRepository

```java:BookDataRepository.java
import org.springframework.data.jpa.repository.JpaRepository;

public interface BookDataRepository extends JpaRepository<BookData, Long> {

}
```

#Service

```java:BookService.java
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class BookService {

	@Autowired
	private BookDataRepository bookDataRepository;

	@Autowired
	private BookDataDaoImpl bookDataDaoImpl;


	//全件検索
	public List<BookData> findAll(){
		return bookDataRepository.findAll();
	}

	//該当のID見つける
	public Optional<BookData> findById(long isbn) {
        return bookDataRepository.findById(isbn);
    }

	//保存
	public BookData save(BookData bookData) {
		return bookDataRepository.saveAndFlush(bookData);
	}

	//検索
	public List<BookData> search(String genre, String author, String title){

		List<BookData> result = new ArrayList<BookData>();

		//すべてブランクだった場合は全件検索する
		if ("".equals(genre) && "".equals(author) && "".equals(title)){
			result = bookDataRepository.findAll();
		}
		else {
			//上記以外の場合、BookDataDaoImplのメソッドを呼び出す
			result = bookDataDaoImpl.search(genre, author, title);
		}
		return result;
	}
}
```

#Controller

```java:StockController.java
import java.util.List;
import java.util.Optional;

import javax.annotation.PostConstruct;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/book")
public class StockController {

	@Autowired
	private BookService bookService;

	@PersistenceContext
	EntityManager entityManager;

	//一覧表示処理
	@GetMapping
	public String index(Model model,@ModelAttribute("formModel") BookData bookdata) {

		model.addAttribute("msg", "在庫管理");
		model.addAttribute("msg2", "検索条件を入力してください");
		List<BookData> books = bookService.findAll();
		model.addAttribute("books", books);

		return "index";
	}

	//検索結果の受け取り処理
	//@ModelAttributeでformからformModelを受け取り、
	//その型(BookData)と変数(bookdata)を指定する
	@PostMapping
	public String select(@ModelAttribute("formModel") BookData bookdata, Model model) {

		model.addAttribute("msg", "検索結果");
		//bookdataのゲッターで各値を取得する
		List<BookData> result = bookService.search(bookdata.getGenre(),bookdata.getAuthor(), bookdata.getTitle());
		model.addAttribute("books", result);

		return "index";
	}

	//詳細画面処理
	//@PathVariableでURLから受け取った値を取得する
	@GetMapping("detail/{isbn}")
	public String detail(@PathVariable long isbn, Model model) {

		model.addAttribute("msg", "参照画面");
		Optional<BookData> data = bookService.findById(isbn);
		//Optionalを使用する際、値はget()で取得する
		model.addAttribute("form", data.get());

		return "detail";
	}


	//初期化処理
	@PostConstruct
	public void init() {

		BookData d1 = new BookData();
		d1.setAuthor("夏目漱石");
		d1.setTitle("こころ");
		d1.setGenre("文学");
		d1.setIsbn(11111);
		d1.setStock(100);
		d1.setStatus(false);
		bookService.save(d1);

		BookData d2 = new BookData();
		d2.setAuthor("大野次郎");
		d2.setTitle("Spring入門");
		d2.setGenre("技術");
		d2.setIsbn(22222);
		d2.setStock(1);
		d2.setStatus(false);
		bookService.save(d2);

		BookData d3 = new BookData();
		d3.setAuthor("田中太郎");
		d3.setTitle("ネットワークのしくみ");
		d3.setGenre("技術");
		d3.setIsbn(33333);
		d3.setStock(20);
		d3.setStatus(false);
		bookService.save(d3);

		BookData d4 = new BookData();
		d4.setAuthor("吉川量");
		d4.setTitle("泥の銃弾");
		d4.setGenre("ミステリー");
		d4.setIsbn(44444);
		d4.setStock(99);
		d4.setStatus(false);
		bookService.save(d4);

		BookData d5 = new BookData();
		d5.setAuthor("夏目漱石");
		d5.setTitle("草枕");
		d5.setGenre("文学");
		d5.setIsbn(55555);
		d5.setStock(40);
		d5.setStatus(false);
		bookService.save(d5);

		BookData d6 = new BookData();
		d6.setAuthor("テスト敏郎");
		d6.setTitle("連続殺人事件簿");
		d6.setGenre("ミステリー");
		d6.setIsbn(66666);
		d6.setStock(40);
		d6.setStatus(false);
		bookService.save(d6);

		BookData d7 = new BookData();
		d7.setAuthor("蛇場太郎");
		d7.setTitle("Java入門");
		d7.setGenre("技術");
		d7.setIsbn(77777);
		d7.setStock(40);
		d7.setStatus(false);
		bookService.save(d7);
	}
}
```

#index.html

```html:index.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>初期画面</title>
<style>
h1 {font-size:18pt; font-weight:bold; color:gray;}
body {font-size:13pt; font-weight:bold; color:gray; margin:5px 25px;}
tr {margin:5px;}
th {padding:5px;color:white; background:darkgray;}
td {padding:5px;color:black; background:#f0f0f0;}
</style>
</head>
<body>
	<h1 th:text="${msg}"></h1>
		<p th:text="${msg2}"></p>
		<!--入力した値をth:object="${formModel}"に詰める  -->
		<!--th:valueで初期値が入るようにする  -->
		<form method="post" th:action="@{/book}" th:object="${formModel}">
			<label>ジャンル:</label>
			<input type="text" name="genre" th:value="*{genre}"><p>
			<label>著者　　:</label>
			<input type="text" name="author" th:value="*{author}"><p>
			<label>タイトル:</label>
			<input type="text" name="title" th:value="*{title}"><p>
			<input type="submit" value="検索">
		</form>
		<table>
			<tr>
				<th>ISBN</th>
				<th>タイトル</th>
				<th>著者名</th>
				<th>在庫数</th>
				<th>ジャンル</th>
			</tr>
			<tr th:each="obj:${books}" th:object="${obj}">
				<!--th:hrefでリンク設定  -->
				<td><a th:href="@{/book/detail/{isbn}(isbn=*{isbn})}" th:text="*{isbn}"></a></td>
				<td th:text="*{title}"></td>
				<td th:text="*{author}"></td>
				<td th:text="*{stock}"></td>
				<td th:text="*{genre}"></td>
			</tr>
		</table>
</body>
</html>
```

#処理パターン1
ジャンルに「文学」と入力して、検索ボタン押下。
検索結果画面に遷移すると、前のページで入力した値を保持して、画面に表示。
検索結果のISBNリンクをクリックすると、参照画面に遷移。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631044/35f6fce7-77e4-83b4-fb0f-986e6ff391a0.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631044/f1f29d39-07c4-a47f-b654-ff62ce704c79.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631044/6163d14f-e70b-509e-9a10-b77644de8b83.png)

#処理パターン2
著者に「太」を入力して、検索ボタン押下。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631044/9c69006e-1819-c08a-fd9a-003ca9a1ea51.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/631044/472f5883-b911-b02b-28ab-6d09c29a3f89.png)



#最後に
簡易的なアプリを作成してみました。
「もっとこうした方が良い！」などのご意見があればご教示頂ければ幸いです。


#参考
[Spring Boot 2 プログラミング入門](https://www.amazon.co.jp/Spring-Boot-2-%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%E5%85%A5%E9%96%80-%E6%8E%8C%E7%94%B0%E6%B4%A5%E8%80%B6%E4%B9%83-ebook/dp/B07KF4R1HT/ref=sr_1_3?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&dchild=1&keywords=Spring+boot&qid=1590664580&sr=8-3)

[Spring徹底入門](https://www.amazon.co.jp/Spring%E5%BE%B9%E5%BA%95%E5%85%A5%E9%96%80-Spring-Framework%E3%81%AB%E3%82%88%E3%82%8BJava%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E9%96%8B%E7%99%BA-%E6%A0%AA%E5%BC%8F%E4%BC%9A%E7%A4%BENTT%E3%83%87%E3%83%BC%E3%82%BF-ebook/dp/B01IEWNLBU/ref=sr_1_2?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&dchild=1&keywords=Spring+boot&qid=1590664580&sr=8-2)