新規プロジェクトを作成する。
プロジェクトの種類は、Springスターター・プロジェクトを選択する。
プロジェクト名を「bookshelf」にして次へをクリック。
依存関係は、以下の項目を選択する。
- HyperSQL Database
- Spring Data JPA
- Spring Web
- Thymeleaf
「完了」でbookshelfプロジェクトができる。
pom.xml を開いて、8行目のバージョンを2.2.1に修正する。
<version>2.2.1.RELEASE</version>
[実行]-[Spring Boot アプリケーション]を選択するとサーバーが起動する。
http://localhost:8080 にアクセスすると、Whitelabel Error Page が表示される。
IndexControler.java を作成する。
package jp.kpc;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class IndexController {
@RequestMapping("/")
public String index() {
return "index";
}
}
example プロジェクトの index.html をコピーして、src/main/resources の templates に貼り付ける。
SpringBootアプリケーションを再起動すると、「最初のページ」が表示される。
application.properties にデータベース接続設定を追加する。
spring.datasource.url=jdbc:hsqldb:hsql://localhost/bookshelf spring.datasource.username=SA spring.datasource.password= spring.datasource.driver-class-name=org.hsqldb.jdbc.JDBCDriver spring.jpa.hibernate.ddl-auto=update
hsqldb.bat に bookshelf インスタンスを追加する。
cd data java -classpath ../lib/hsqldb.jar org.hsqldb.server.Server ^ --database.0 db/shindan --dbname.0 shindan ^ --database.1 db/bookshelf --dbname.1 bookshelf
index.html に、新しい本棚を追加するためのフォームを用意する。
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Bookshelf - 本棚</title> </head> <body> <h1>Bookshelf list</h1> <hr /> <div>新しい本棚</div> <form action="/addshelf" method="post"> <div>なまえ</div> <div><input type="text" name="name" /></div> <div><input id="submit" type="submit" value="+追加" /></div> </form> </body> </html>
IndexController.java に /addshelf に対する POST を受け取る用意をする。POSTを受け取ったあとは、URL=”/” にリダイレクトする。
package jp.kpc;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class IndexController {
@RequestMapping("/")
public String index() {
return "index";
}
@RequestMapping(value="/addshelf", method=RequestMethod.POST)
public ModelAndView formPost(ModelAndView mav,
@RequestParam("name") String name) {
return new ModelAndView("redirect:/");
}
}
本棚に対応する Bookshelf クラスを作成する。
package jp.kpc;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
@Entity
public class Bookshelf {
@Id
@GeneratedValue
@Column
@NotNull
private long id;
@Column
@NotEmpty
private String name;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
新規インターフェースを作成する。
BookshelfRepository.java
package jp.kpc;
import org.springframework.data.jpa.repository.JpaRepository;
public interface BookshelfRepository extends JpaRepository<Bookshelf, Long> {
}
IndexController.java で、リクエストパラメータで渡された名前の本棚を保存する処理を追加する。
package jp.kpc;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class IndexController {
@Autowired
private BookshelfRepository repository;
@RequestMapping("/")
public String index() {
return "index";
}
@RequestMapping(value="/addshelf", method=RequestMethod.POST)
public ModelAndView formPost(ModelAndView mav,
@RequestParam("name") String name) {
Bookshelf bookshelf = new Bookshelf();
bookshelf.setName(name);
repository.saveAndFlush(bookshelf);
return new ModelAndView("redirect:/");
}
}
トップページにアクセスがあったときに、本棚リストをテンプレートに渡す。
IndexController.java
@Controller
public class IndexController {
@Autowired
private BookshelfRepository repository;
@RequestMapping("/")
public ModelAndView index(ModelAndView mav) {
mav.setViewName("index");
List<Bookshelf> list = repository.findAll();
mav.addObject("list", list);
return mav;
}
テンプレートで本棚のリストを表示する。
index.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Bookshelf - 本棚</title>
</head>
<body>
<h1>Bookshelf list</h1>
<table>
<tr th:each="bs : ${list}">
<td th:text="${bs.name}"></td>
</tr>
</table>
<hr />
<div>新しい本棚</div>
<form action="/addshelf" method="post">
<div>なまえ</div>
<div><input type="text" name="name" /></div>
<div><input id="submit" type="submit" value="+追加" /></div>
</form>
</body>
</html>
Bookクラスを作成する。
package jp.kpc;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
@Entity
public class Book {
@Id
@GeneratedValue
@Column
@NotNull
private long id;
@Column
@NotEmpty
private String title;
@Column
@NotEmpty
private String author;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
}
BookController.java
package jp.kpc;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class BookController {
@RequestMapping("/books")
public ModelAndView index(ModelAndView mav) {
mav.setViewName("books");
return mav;
}
}
books.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Book - 本のリスト</title>
</head>
<body>
<h1>Book list</h1>
<table>
<tr th:each="book : ${list}">
<td th:text="${book.title}"></td>
<td th:text="${book.author}"></td>
</tr>
</table>
<hr />
<div>新しい本</div>
<form action="/addbook" method="post">
<div>タイトル: <input type="text" name="title" /></div>
<div>著者: <input type="text" name="author" /></div>
<div><input id="submit" type="submit" value="+追加" /></div>
</form>
</body>
</html>
BookRepository.java
package jp.kpc;
import org.springframework.data.jpa.repository.JpaRepository;
public interface BookRepository extends JpaRepository<Book, Long> {
}
リポジトリを用意したので、それをBookControllerで使う。
package jp.kpc;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class BookController {
@Autowired
private BookRepository repository;
@RequestMapping("/books")
public ModelAndView index(ModelAndView mav) {
mav.setViewName("books");
List<Book> list = repository.findAll();
mav.addObject("list", list);
return mav;
}
@RequestMapping(value="/addbook", method=RequestMethod.POST)
public ModelAndView formPost(ModelAndView mav,
@RequestParam("title") String title,
@RequestParam("author") String author) {
Book book = new Book();
book.setTitle(title);
book.setAuthor(author);
repository.saveAndFlush(book);
return new ModelAndView("redirect:/books");
}
}
本と本棚の関連付けを作る。
Bookshelf.java
package jp.kpc;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
@Entity
public class Bookshelf {
@Id
@GeneratedValue
@Column
@NotNull
private long id;
@Column
@NotEmpty
private String name;
@OneToMany
private List<Book> books;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Book> getBooks() {
return books;
}
public void setBooks(List<Book> books) {
this.books = books;
}
}
Book.java
package jp.kpc;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
@Entity
public class Book {
@Id
@GeneratedValue
@Column
@NotNull
private long id;
@Column
@NotEmpty
private String title;
@Column
@NotEmpty
private String author;
@ManyToOne
private Bookshelf bookshelf;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public Bookshelf getBookshelf() {
return bookshelf;
}
public void setBookshelf(Bookshelf bookshelf) {
this.bookshelf = bookshelf;
}
}
本を入れる本棚を指定するための画面を作る。
そのために、/book/{id} (idはBookのid)というURLを用意する。
package jp.kpc;
import java.util.List;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class BookController {
@Autowired
private BookRepository repository;
@RequestMapping("/books")
public ModelAndView index(ModelAndView mav) {
mav.setViewName("books");
List<Book> list = repository.findAll();
mav.addObject("list", list);
return mav;
}
@RequestMapping(value="/addbook", method=RequestMethod.POST)
public ModelAndView formPost(ModelAndView mav,
@RequestParam("title") String title,
@RequestParam("author") String author) {
Book book = new Book();
book.setTitle(title);
book.setAuthor(author);
repository.saveAndFlush(book);
return new ModelAndView("redirect:/books");
}
@RequestMapping(value = "/book/{id}", method = RequestMethod.GET)
public ModelAndView book(ModelAndView mav,
@PathVariable long id) {
mav.setViewName("book");
Optional<Book> data = repository.findById(id);
mav.addObject("book", data.get());
return mav;
}
}
book.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Book - 本を入れる本棚を指定する</title>
</head>
<body>
<h1>Book</h1>
<table>
<tr>
<td th:text="${book.title}"></td>
<td th:text="${book.author}"></td>
</tr>
</table>
<hr />
<div>どの本棚に入れますか?</div>
<form action="/book" method="post">
<input type="hidden" name="bookId" th:value="${book.id}" />
<div>本棚ID: <input type="text" name="bookshelfId" /></div>
<div><input id="submit" type="submit" value="本棚に入れる" /></div>
</form>
</body>
</html>
本と本棚の関連付けを保存する。
BookController.java
@Autowired private BookshelfRepository bookshelfRepository;
@RequestMapping(value="/book", method=RequestMethod.POST)
public ModelAndView save(ModelAndView mav,
@RequestParam("bookId") long bookId,
@RequestParam("bookshelfId") long bookshelfId) {
Optional<Book> data = repository.findById(bookId);
Book book = data.get();
Optional<Bookshelf> bsData = bookshelfRepository.findById(bookshelfId);
Bookshelf bookshelf = bsData.get();
book.setBookshelf(bookshelf);
repository.saveAndFlush(book);
return new ModelAndView("redirect:/books");
}
books.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Book - 本のリスト</title>
</head>
<body>
<h1>Book list</h1>
<table>
<tr>
<th>ID</th><th>タイトル</th><th>著者</th><th>本棚</th>
</tr>
<tr th:each="book : ${list}">
<td th:text="${book.id}"></td>
<td th:text="${book.title}"></td>
<td th:text="${book.author}"></td>
<td th:if="${book.bookshelf != null}" th:text="${book.bookshelf.name}"></td>
<td th:if="${book.bookshelf == null}" th:text="本棚に入れてません"></td>
</tr>
</table>
<hr />
<div>新しい本</div>
<form action="/addbook" method="post">
<div>タイトル: <input type="text" name="title" /></div>
<div>著者: <input type="text" name="author" /></div>
<div><input id="submit" type="submit" value="+追加" /></div>
</form>
</body>
</html>
本のタイトルをクリックしたら、/book/{id} に飛べるようにする。
<h1>Book list</h1>
<table>
<tr>
<th>ID</th><th>タイトル</th><th>著者</th><th>本棚</th>
</tr>
<tr th:each="book : ${list}">
<td th:text="${book.id}"></td>
<td><a th:href="@{'/book/' + ${book.id}}" th:text="${book.title}"></a></td>
<td th:text="${book.author}"></td>
<td th:if="${book.bookshelf != null}" th:text="${book.bookshelf.name}"></td>
<td th:if="${book.bookshelf == null}" th:text="本棚に入れてません"></td>
</tr>
</table>
本棚に入っている本のリストを表示する画面を作る。
/bookshelf/{id} で本棚内の本のリストを表示する。
bookshelf.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Bookshelf - 本棚に入っている本のリスト</title>
</head>
<body>
<h1>Bookshelf - 本棚に入っている本のリスト</h1>
<h3 th:text="${bookshelf.name}"></h3>
<ul th:each="book : ${bookshelf.books}">
<li th:text="${book.title + ' - ' + book.author}"></li>
</ul>
</body>
</html>
Bookshelf.java
@Entity
public class Bookshelf {
@Id
@GeneratedValue
@Column
@NotNull
private long id;
@Column
@NotEmpty
private String name;
@OneToMany(mappedBy = "bookshelf")
private List<Book> books;
IndexController.java
@RequestMapping(value = "/bookshelf/{id}", method = RequestMethod.GET)
public ModelAndView bookshelf(ModelAndView mav,
@PathVariable long id) {
mav.setViewName("bookshelf");
Optional<Bookshelf> data = repository.findById(id);
mav.addObject("bookshelf", data.get());
return mav;
}
index.htmlのテーブルに列を追加して、本棚のIDを表示する。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Bookshelf - 本棚</title>
</head>
<body>
<h1>Bookshelf list</h1>
<table>
<tr th:each="bs : ${list}">
<td th:text="${bs.id}"></td>
<td th:text="${bs.name}"></td>
</tr>
</table>
<hr />
<div>新しい本棚</div>
<form action="/addshelf" method="post">
<div>なまえ</div>
<div><input type="text" name="name" /></div>
<div><input id="submit" type="submit" value="+追加" /></div>
</form>
</body>
</html>
本棚の名前をクリックしたら、本棚の内容を表示するページに移動できるようにする。
本のリストページ(/books)へのリンクも追加する。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Bookshelf - 本棚</title>
</head>
<body>
<h1>Bookshelf list</h1>
<table>
<tr th:each="bs : ${list}">
<td th:text="${bs.id}"></td>
<td><a th:href="@{'/bookshelf/' + ${bs.id}}" th:text="${bs.name}"></a></td>
</tr>
</table>
<hr />
<div>新しい本棚</div>
<form action="/addshelf" method="post">
<div>なまえ</div>
<div><input type="text" name="name" /></div>
<div><input id="submit" type="submit" value="+追加" /></div>
</form>
<a href="/books">本のリスト</a>
</body>
</html>
books.html、book.html、bookshelf.html に、トップページへのリンクを追加する。
books.html
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Book - 本のリスト</title> </head> <body> <h1>Book list</h1> <a href="/">トップ</a> <table> :
book.html
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Book - 本を入れる本棚を指定する</title> </head> <body> <h1>Book</h1> <a href="/">トップ</a> <table> :
bookshelf.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Bookshelf - 本棚に入っている本のリスト</title>
</head>
<body>
<h1>Bookshelf - 本棚に入っている本のリスト</h1>
<a href="/">トップ</a>
<h3 th:text="${bookshelf.name}"></h3>
:
本棚ページ内に表示した本のリストで、タイトルをクリックすると本のページに移動できるようにする。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Bookshelf - 本棚に入っている本のリスト</title>
</head>
<body>
<h1>Bookshelf - 本棚に入っている本のリスト</h1>
<a href="/">トップ</a>
<h3 th:text="${bookshelf.name}"></h3>
<ul th:each="book : ${bookshelf.books}">
<li>
<a th:href="@{'/book/' + ${book.id}}" th:text="${book.title}"></a>
<span th:text="${' - ' + book.author}"></span>
</li>
</ul>
</body>
</html>
book.html で、本棚IDを入力するのではなく、select で選択できるようにする。
BookController.java で、本棚のリストをテンプレートに渡すようにする。
@RequestMapping(value = "/book/{id}", method = RequestMethod.GET)
public ModelAndView book(ModelAndView mav,
@PathVariable long id) {
mav.setViewName("book");
Optional<Book> data = repository.findById(id);
mav.addObject("book", data.get());
List<Bookshelf> list = bookshelfRepository.findAll();
mav.addObject("bookshelfList", list);
return mav;
}
book.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Book - 本を入れる本棚を指定する</title>
</head>
<body>
<h1>Book</h1>
<a href="/">トップ</a>
<table>
<tr>
<td th:text="${book.title}"></td>
<td th:text="${book.author}"></td>
</tr>
</table>
<hr />
<div>どの本棚に入れますか?</div>
<form action="/book" method="post">
<input type="hidden" name="bookId" th:value="${book.id}" />
<div>
<select name="bookshelfId">
<option th:each="bs : ${bookshelfList}" th:value="${bs.id}" th:selected="${bs.id == book.id}" th:text="${bs.name}"></option>
</select>
</div>
<div><input id="submit" type="submit" value="本棚に入れる" /></div>
</form>
</body>
</html>
本棚に入れない選択肢を追加する。
<div>どの本棚に入れますか?</div>
<form action="/book" method="post">
<input type="hidden" name="bookId" th:value="${book.id}" />
<div>
<select name="bookshelfId">
<option value="0">本棚に入れない</option>
<option th:each="bs : ${bookshelfList}" th:value="${bs.id}" th:selected="${bs.id == book.id}" th:text="${bs.name}"></option>
</select>
</div>
<div><input id="submit" type="submit" value="本棚に入れる" /></div>
</form>
コントローラでは、本棚検索して見つからないときは null を設定する。
@RequestMapping(value="/book", method=RequestMethod.POST)
public ModelAndView save(ModelAndView mav,
@RequestParam("bookId") long bookId,
@RequestParam("bookshelfId") long bookshelfId) {
Optional<Book> data = repository.findById(bookId);
Book book = data.get();
Optional<Bookshelf> bsData = bookshelfRepository.findById(bookshelfId);
if(bsData.isPresent()) {
Bookshelf bookshelf = bsData.get();
book.setBookshelf(bookshelf);
} else {
book.setBookshelf(null);
}
repository.saveAndFlush(book);
return new ModelAndView("redirect:/books");
}