第2章 テーブル同士を連携しよう
2.3 複数のテーブルを元にデータを取得しよう
では実際にサンプルコードで動きを確認してみましょう。事前準備として、DBに以下の内容でbookinfoテーブルとorderinfoテーブルを作成してください。
CREATE TABLE bookinfo ( isbn VARCHAR(20) PRIMARY KEY, title VARCHAR(100), price INTEGER )ENGINE=InnoDB; INSERT INTO bookinfo VALUES('0001','Java',1001); INSERT INTO bookinfo VALUES('0002','PHP',1002); INSERT INTO bookinfo VALUES('0003','Linux',1003);
CREATE TABLE orderinfo ( orderno INTEGER AUTO_INCREMENT PRIMARY KEY, order_isbn VARCHAR(20), date date, FOREIGN KEY (isbn) REFERENCES bookinfo(isbn) on update cascade on delete cascade )ENGINE=InnoDB; INSERT INTO orderinfo VALUES(null,'0001', CURDATE()); INSERT INTO orderinfo VALUES(null,'0002', CURDATE()); INSERT INTO orderinfo VALUES(null,'0001', CURDATE());
この2つのテーブル同士の関係性を図で表すと、以下のようになります。
また、ER図で表すとこのようになります。1件の書籍情報は、複数の売上情報に存在するため、orderinfoテーブルからみると、多対1の関係になります。
※ER図について
「テーブル同士の関連性を図式化したもの」です。 システムに必要なデータの全体像を把握しやすくすることができます。
では、売上情報画面上に、売上番号、ISBN番号、書籍タイトル、購入日付が表示されるようなプログラムを作成してみましょう。
実行結果
フォルダ構造
DB接続設定
「src/main/resources」フォルダ内の「application.properties」に以下に示すソースコードを記述する
【ファイル名:application.properties】
spring.datasource.driverClassName=org.mariadb.jdbc.Driver spring.datasource.url=jdbc:mariadb://localhost:3306/mybookdb spring.datasource.username=root spring.datasource.password=root123
また、プロジェクトフォルダ直下の「pom.xml」に以下に示すソースコードを記述する
【ファイル名:pom.xml】
<dependency> <groupid>org.mariadb.jdbc</groupid> <artifactid>mariadb-java-client</artifactid> <scope>runtime</scope> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-data-jpa</artifactid> </dependency> </dependency>
【ファイル名:Book.java】
package jp.co.f1.spring.entity; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Id; import jakarta.persistence.Table; @Entity @Table(name = "bookinfo") public class Book { // ISBN @Id @Column(length = 20) private String isbn; public String getIsbn() { return isbn; } public void setIsbn(String isbn) { this.isbn = isbn; } // タイトル @Column(length = 100, nullable = true) private String title; public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } // 価格 @Column(length = 11, nullable = true) private String price; public String getPrice() { return price; } public void setPrice(String price) { this.price = price; } }
【ファイル名:Order.java】
package jp.co.f1.spring.bms.entity; import java.time.LocalDate; import com.fasterxml.jackson.annotation.JsonFormat; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; import lombok.Data; @Entity @Table(name = "orderinfo") public class Order { // 注文No @Id @Column(length = 20, nullable = true) @GeneratedValue(strategy = GenerationType.IDENTITY) private int orderno; public int getOrderno() { return orderno; } public void setOrderno(int orderno) { this.orderno = orderno; } @Column(name = "order_isbn", length = 20) private String isbn; public String getIsbn() { return isbn; } public void setIsbn(String isbn) { this.isbn = isbn; } // 購入日付 @Column(length = 20, nullable = true) @JsonFormat(pattern = "yyyy/MM/dd") private LocalDate date; public LocalDate getDate() { return date; } public void setDate(LocalDate date) { this.date = date; } // Web版のOrderedItemにあたるリレーション @ManyToOne(optional = false) @JoinColumn(name = "order_isbn", referencedColumnName = "isbn", insertable = false, updatable = false) private Book book; public Book getBook() { return book; } public void setBook(Book book) { this.book = book; } }
【ファイル名:OrderRepository.java】
package jp.co.f1.spring.repository; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import jp.co.f1.spring.entity.Order; @Repository public interface OrderRepository extends JpaRepository<Order, Integer> { }
【ファイル名:BookRepository.java】
package jp.co.f1.spring.repository; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import jp.co.f1.spring.entity.Book; @Repository public interface BookRepository extends JpaRepository<Book, String> { }
【ファイル名:TableController.java】
package jp.co.f1.spring.table; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.servlet.ModelAndView; import jp.co.f1.spring.entity.Order; import jp.co.f1.spring.repository.OrderRepository; @Controller public class TableController { // Repositoryインターフェースを自動インスタンス化 @Autowired private OrderRepository orderinfo; /** * 「/showOrderedItem」へアクセスがあった場合 */ @GetMapping("/showOrderedItem") public ModelAndView showOrderedItem(ModelAndView mav) { // 購入履歴情報の全件を取得(書籍情報を含む) Iterable < Order > orderedList = orderinfo.findAll(); mav.addObject("orderedList", orderedList); mav.setViewName("orderList"); return mav; } }
【ファイル名:orderList.html】
<!DOCTYPE html> <html> <head> <title>複数のテーブルからデータを取得しよう</title> </head> <body> <table border="1" cellpadding="10" cellspacing="0"> <thead> <tr> <th>注文番号</th> <th>ISBN</th> <th>タイトル</th> <th>時間</th> </tr> </thead> <tbody th:if="${orderedList.size() >= 1}"> <tr th:each="Order : ${orderedList}" th:object="${Order}"> <td> <a href="#" th:text="*{orderno}"></a> </td> <td th:text="*{isbn}"> </td> <td th:text="*{book.title}"> </td> <td th:text="*{date}"> </td> </tr> </tbody> </table> </body> </html>
アプリケーションにアクセス
以下のアドレスからアプリケーションにアクセスします。
URL:http://localhost:8080/showOrderedItem
解説
・@ManyToOne
多対1の関係を表す場合、@ManyToOneアノテーションを付けます。先ほどER図で確認したように、orderinfoが多(Many)で、bookinfoが1(One)です。
・optional属性
optional属性を指定すると、結合方法を指定することができます。
optional属性のデフォルト値はfalse、すなわち内部結合です。
内部結合や外部結合については、SQL基礎6章の単元で学習します。
・@JoinColumn(name = “order_isbn”, referencedColumnName = “isbn”)
結合するカラムを指定する場合に、@JoinColumnアノテーションを使用します。
name属性には、結合元テーブルの外部キー列名を指定します。referencedColumnName属性には、結合先のカラム名を指定します。今回の場合はorderinfoテーブルの” order_isbn”に、bookinfoテーブルの”isbn”を結合してね、という指定をしています。
・insertable = false, updatable = false
insertable属性とupdatable属性は、フィールドをinsertとupdate対象に含めるかどうかを指定します。サンプルコードではorder_isbnをinsert、updateに含めないという意味になります。今回のテーブルではbookinfoが親(参照される側)テーブル、orderinfoが子(参照する側)のテーブルとして作成しています。bookinfoが親テーブルの為、bookinfoにない書籍情報がorderinfoに存在することはできません。システムでDB情報の挿入や更新を行う時に、JPAが意図しない操作をしないためにinsertable属性とupdatable属性を『orderinfoのisbn情報を無理に挿入/更新しないでね』という風に、falseで指定してあげる必要があるのです。デフォルトはtrueになっているので、参照のみで扱うカラムは意図しない操作を防ぐためにも、記述を忘れないようにしましょう。