Spring JPA

Spring JPA

2018/12/28
2022/01/08 (新增連結)

基本概念

Spring Framework支援Java Persistence API(JPA)的相關元件,JPA簡化了java對資料庫的存取。不過,如果不是單純的新增、刪除、修改,使用JPA可能就會有一些效能上的問題。

設定

第一、要安裝mySQL server (或其他資料庫)

第二、要啟動mySQL server (或其他資料庫) (請參考投影片)

第三、要建立資料表 (或匯入資料表) (請參考投影片) (或詳參: https://dev.mysql.com/doc/workbench/en/wb-table-editor.html)

CREATE TABLE `practice`.`customer` (

`id` INT NOT NULL AUTO_INCREMENT,

`name` VARCHAR(15) NULL,

`address` VARCHAR(45) NULL,

`weight` INT NULL,

PRIMARY KEY (`id`));

以下的以下的兩個table是為了是為了資料表關聯,請先新增book_category。請記得要新增一到兩筆資料。

**注意** 在java裡,類別名稱或變數名稱有大小寫的話(如:BookCategory),在資料表裡要命名為Book_Category,因為資料庫的資料表及欄位是不分大小寫,JPA在對應時,會自動加上底線。

CREATE TABLE `practice`.`book_category` (

`id` int(11) NOT NULL AUTO_INCREMENT,

`name` varchar(15) COLLATE utf8_unicode_ci NOT NULL,

PRIMARY KEY (`id`)

);

再新增book。最下面的幾行在mySQL workbench要利用Foreign Keys去建立。Foreign Key name: book_category, referenced table: book_category, column:category (in book), referenced column: id (in book_category), on update: cascade, on delete: restrict。**注意:如果book裡已經有資料裡,book的category裡的值在book_category的id中必須存在,否則無法建立Foreign Key。(詳參: https://dev.mysql.com/doc/workbench/en/wb-table-editor-foreign-keys-tab.html)

CREATE TABLE `practice`.`book` (

`id` int(11) NOT NULL AUTO_INCREMENT,

`name` varchar(45) COLLATE utf8_unicode_ci DEFAULT NULL,

`price` int(11) DEFAULT NULL,

`category` int(11) DEFAULT NULL,

PRIMARY KEY (`id`),

KEY `book_category_idx` (`category`),

CONSTRAINT `book_category` FOREIGN KEY (`category`) REFERENCES `book_category` (`id`) ON UPDATE CASCADE

);

第四、更動pom.xml,引用JDBC及MySQL的相關packages

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-data-jpa</artifactId>

</dependency>


<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-jdbc</artifactId>

</dependency>


<!-- MySQL database driver -->

<dependency>

<groupId>mysql</groupId>

<artifactId>mysql-connector-java</artifactId>

<scope>runtime</scope>

</dependency>

第五、設定參數 (application.properties),以使用mySQL為例。

spring.datasource.url=jdbc:mysql://localhost/practice?verifyServerCertificate=false&useSSL=false&requireSSL=false

practice是資料庫的名稱 (在mySQL中稱為schema),verifyServerCertificate的部分是設定不要檢查SSL,同時設定不要useSSL及requireSSL。

#的部份是註解。

spring.datasource.url=jdbc:mysql://localhost/practice?verifyServerCertificate=false&useSSL=false&requireSSL=false

#spring.datasource.url=jdbc:mysql://localhost/practice

spring.datasource.username=root

spring.datasource.password=1234

spring.datasource.driver-class-name=com.mysql.jdbc.Driver

使用CrudRepository

要使用JPA,首先,產生一個CustomerDAO,並繼承CrudRepository,並且設定採用的Entity(如:Customer)及id的資料型態(如:Long、Integer)。

CrudRepository提供了很多方法,如: save、findAll、findOne、delete,可以不需要使用SQL就可以存取資料庫裡的資料 (詳參: http://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/repository/CrudRepository.html )。

package com.example.demo.dao;


import org.springframework.data.repository.CrudRepository;


import com.example.entity.Customer;


public interface CustomerDAO extends CrudRepository<Customer, Long>{


}

Customer.java裡利用@Entity告訴JPA這是個Entity類別,JPA會自動連接到類別名稱對應的資料表(customer)。另外,並以@Id指定一個欄位為資料表對應的主鍵(primary key),如果這個欄位的內容是自動產生,則必須加上@GeneratedValue(strategy=GenerationType.IDENTITY) (詳參: http://www.thoughts-on-java.org/jpa-generate-primary-keys/ )。**在Hibernate 5,設為AUTO會使用一個資料表來產生序號,所以,請改為IDENTITY或SEQUENCE (詳參: Migration from Hibernate 4 to 5)

package com.example.demo.entity;


import javax.persistence.Entity;

import javax.persistence.GeneratedValue;

import javax.persistence.GenerationType;

import javax.persistence.Id;


@Entity

public class Customer {

@Id

@GeneratedValue(strategy=GenerationType.IDENTITY)

private Long id;


private String name;

private String address;

private int weight;


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 String getAddress() {

return address;

}

public void setAddress(String address) {

this.address = address;

}


public int getWeight() {

return weight;

}

public void setWeight(int weight) {

this.weight = weight;

}

}

對應的Controller中利用@Autowired串連前面的CustomerDAO:

package com.example.demo.controller;


import java.sql.SQLException;


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

import org.springframework.stereotype.Controller;

import org.springframework.web.bind.annotation.ModelAttribute;

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;


import com.example.demo.dao.CustomerDAO;

import com.example.demo.entity.Customer;


@Controller

public class CustomerController {

@Autowired

CustomerDAO dao;

@RequestMapping(value = "/customerCreate", method = RequestMethod.GET)

public ModelAndView openFormCreate() {

ModelAndView model = new ModelAndView("customerCreate");

return model;

}


@RequestMapping(value = "/customerCreate", method = RequestMethod.POST)

public ModelAndView processFormCreate(@ModelAttribute Customer cus) throws SQLException {

ModelAndView model = new ModelAndView("redirect:/customerRetrieveAll");


dao.save(cus);

//model.addObject(cus);

return model;

}

@RequestMapping(value = {"/customerRetrieveAll","/"}, method = RequestMethod.GET)

public ModelAndView retrieveCustomers() throws SQLException{

Iterable<Customer> customers = dao.findAll();

ModelAndView model = new ModelAndView("customerList");

model.addObject("customers",customers);

return model;

}


@RequestMapping(value = "/customerUpdate", method = RequestMethod.GET)

public ModelAndView openFormUpdate(@RequestParam(value="id", required=false, defaultValue="1") Long id) {

ModelAndView model = new ModelAndView("customerUpdate");

Customer customer = dao.findOne(id);

model.addObject(customer);

return model;

}


@RequestMapping(value = "/customerUpdate", method = RequestMethod.POST)

public ModelAndView processFormUpdate(@ModelAttribute Customer cus) throws SQLException {

ModelAndView model = new ModelAndView("redirect:/customerRetrieveAll");

dao.save(cus);

return model;

}

@RequestMapping(value = "/customerDelete", method = RequestMethod.GET)

public ModelAndView deleteCustomer(@RequestParam(value="id", required=false, defaultValue="1") Long id) {

ModelAndView model = new ModelAndView("redirect:/customerRetrieveAll");

dao.delete(id);

return model;

}

}

新增資料

CrudRepository內建了一個save的方法,把物件儲存到資料庫,可以進行資料的新增或更新。

@RequestMapping(value = "/customerCreate", method = RequestMethod.POST)

public ModelAndView processFormCreate(@ModelAttribute Customer cus) throws SQLException {

ModelAndView model = new ModelAndView("redirect:/customerRetrieveAll");


dao.save(cus);

model.addObject(cus);

return model;

}

對應的view,customerCreate.html (詳細解釋請參考Spring View)。

<!DOCTYPE html>

<html lang="en" xmlns:th="http://www.thymeleaf.org">

<head>

<meta charset="UTF-8"/>

<title>Create New Customer</title>

</head>

<body>


<form action="customerCreate" method ="post">


姓名:<input type="text" name="name"/><br/>

地址:<input type="text" name = "address"/><br/>

重量:<input type="text" name ="weight"/><br/>

<input type="submit" value="Submit"/>


</form>


</body>

</html>

讀取資料

CrudRepository內建了一個findAll的方法,讀取所有物件。

@RequestMapping(value = {"/customerRetrieveAll","/"}, method = RequestMethod.GET)

public ModelAndView retrieveCustomers() throws SQLException{

Iterable<Customer> customers = dao.findAll();

ModelAndView model = new ModelAndView("customerList");

model.addObject("customers",customers);

return model;

}

對應的view,customerList.html (詳細解釋請參考Spring View)。

<!DOCTYPE html>

<html lang="en" xmlns:th="http://www.thymeleaf.org">

<head>

<meta charset="UTF-8"/>

<title>Create New Customer</title>

</head>

<body>

<a href="customerCreate">新增顧客</a>

<table>

<tr>

<th>編號</th>

<th>姓名</th>

<th>地址</th>

<th>重量</th>

<th></th>

</tr>

<tr th:each="customer : ${customers} " th:object="${customer}">

<td th:text="*{id}">1</td>

<td th:text="*{name}">Ben</td>

<td th:text="*{address}">Fu Jen</td>

<td th:text="*{weight}">56</td>

<td><a th:href="@{customerUpdate(id=${customer.id})}">修改</a> <a th:href="@{customerDelete(id=${customer.id})}">刪除</a></td>

</tr>

</table>



</body>

</html>

更新資料

通常會需要讀取該筆資料讓使用者可以更新該筆資料。利用CrudRepository內建的findOne的方法,讀取該物件的內容。findOne會使用在Entity類別(如:Customer)中@Id所指定的變數(如:id)作為搜尋的依據。

@RequestMapping(value = "/customerUpdate", method = RequestMethod.GET)

public ModelAndView openFormUpdate(@RequestParam(value="id", required=false, defaultValue="1") Long id) {

ModelAndView model = new ModelAndView("customerUpdate");

Customer customer = dao.findOne(id);

model.addObject(customer);

return model;

}

將資料送到對應的view,customerUpdate.html (詳細解釋請參考Spring View)。因為在update時,必須有id,但又不能讓使用者隨便更動id,所以,使用hidden來傳送資料給controller。

<!DOCTYPE html>

<html lang="en" xmlns:th="http://www.thymeleaf.org">

<head>

<meta charset="UTF-8"/>

<title>Update Customer</title>

</head>

<body>


<form action="customerUpdate" th:object="${customer}" method ="post">


name:<input type="text" name="name" th:field="*{name}"/><br/>

address:<input type="text" name = "address" th:field="*{address}"/><br/>

weight:<input type="text" name ="weight" th:field="*{weight}"/><br/>

<input type="hidden" name ="id" th:field="*{id}"/>

<input type="submit" value="Submit"/>


</form>


</body>

</html>

將更新的資料利用送回,利用CrudRepository內建的save的方法,儲存內容。

@RequestMapping(value = "/customerUpdate", method = RequestMethod.POST)

public ModelAndView processFormUpdate(@ModelAttribute Customer cus) throws SQLException {

ModelAndView model = new ModelAndView("redirect:/customerRetrieveAll");

dao.save(cus);

return model;

}

刪除資料

CrudRepository內建了一個delete的方法,刪除特定物件。delete會使用在Entity類別(如:Customer)中@Id所指定的變數(如:id)作為刪除的依據。

@RequestMapping(value = "/customerDelete", method = RequestMethod.GET)

public ModelAndView deleteCustomer(@RequestParam(value="id", required=false, defaultValue="1") Long id) {

ModelAndView model = new ModelAndView("redirect:/customerRetrieveAll");

dao.delete(id);

return model;

}

類別與資料庫的對應

如果類別名稱與資料表名稱不一致,就可以使用@Table來指定對應的資料表名稱(如:@Table(name = "customer_category"))。如果欄位名稱不一致,就可以使用來指定對應的欄位名稱(如:@Column(name = "description"))。有時候用到資料庫的關鍵字(如: order, describe),也可以利用@Table @Column來對應非關鍵字的資料表、欄位名稱。

package com.example.demo.entity;


import javax.persistence.Entity;

import javax.persistence.GeneratedValue;

import javax.persistence.GenerationType;

import javax.persistence.Id;

import javax.persistence.Table;

import javax.persistence.Column;


@Entity

@Table(name="customer_category")

public class CustomerCategory {

@Id

@GeneratedValue(strategy=GenerationType.AUTO)

private Long id;


@Column(name = "description")

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;

}

}

客製查詢

JPA提供一個利用方法名稱客製查詢的方法,例如:可以在DAO中寫findAllByOrderByWeightAsc,findAll就是回傳資料表所有資料,ByOrder,表示要以後面的變數排序,ByWeightAsc,就是要以"weight"變數由小到大(Ascending)排序,如果要由大到小(Descending)就是ByWeightDesc。也可以寫findByWeightGreaterThan,就可以找在某個重量以上的客戶,同樣的作法可以使用@Query來下Java Persistence Query Language(JPQL)查詢(語法類似SQL),好處是方法名稱就可以自訂。 (詳參: http://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.query-creation, http://docs.spring.io/spring-data/jpa/docs/1.11.1.RELEASE/reference/html/#repositories.query-methods.query-creation, http://openjpa.apache.org/builds/1.0.1/apache-openjpa-1.0.1/docs/manual/jpa_overview_query.html)。

package com.example.demo.dao;


import org.springframework.data.repository.CrudRepository;


import com.example.demo.entity.Customer;


public interface CustomerDAO extends CrudRepository<Customer, Long>{


public Iterable<Customer> findAllByOrderByWeightAsc();

public Iterable<Customer> findByWeightGreaterThan(Integer weight);

public Iterable<Customer> findByNameContaining(String name);


//in @Query, the entity name is case sensitive

@Query("select c from Customer c where c.weight > 40")

public Iterable<Customer> findOverWeight();


}

其中的"select c from Customer c where c.weight > 40" 對應的類似sql是"select * from customer c where c.weight > 40" ,不同的是sql的在from前面是欄位的名稱,但是JPQL在from前面是entity class的名稱(上面的範例是利用Customer的別名 c)。sql的在from後面是table的名稱,但是JPQL在from後面是entity class的名稱。

資料表關聯

當資料表之間有關聯時,JPA也提供簡單的方法來處理。一本書可以設定類別。與Customer相較,因為書籍類別會關聯到BookCategory,所以,多了@ManyToOne表示這個會有多本書使用同一個類別,@JoinColumn表示在資料表裡是以"category"這個欄位為Foreign Key。記得要先建好book及book_category兩個table。

package com.example.demo.entity;


import javax.persistence.Entity;

import javax.persistence.GeneratedValue;

import javax.persistence.GenerationType;

import javax.persistence.Id;

import javax.persistence.JoinColumn;

import javax.persistence.ManyToOne;


@Entity

public class Book {

@Id

@GeneratedValue(strategy = GenerationType.AUTO)

private Long id;

private String name;

private int price;


// JoinColumn refers to the column name in this table (book)

@ManyToOne

@JoinColumn(name = "category")

private BookCategory bookCategory;


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 int getPrice() {

return price;

}


public void setPrice(int price) {

this.price = price;

}


public BookCategory getBookCategory() {

return bookCategory;

}


public void setBookCategory(BookCategory bookCategory) {

this.bookCategory = bookCategory;

}


}

書籍類別 (BookCategory.java)可以利用@OneToMany去對應類別下的書籍,@mappedBy則是指Book中對應的變數名稱。最後,提供getBooks()來取得類別下的所有書籍。

package com.example.demo.entity;


import java.util.List;


import javax.persistence.Entity;

import javax.persistence.GeneratedValue;

import javax.persistence.GenerationType;

import javax.persistence.Id;

import javax.persistence.OneToMany;

import javax.persistence.Table;


@Entity

@Table(name = "book_category")

public class BookCategory {

@Id

@GeneratedValue(strategy = GenerationType.AUTO)

private Long id;

private String name;


// mappedBy refers to the variable in Book

@OneToMany(mappedBy = "bookCategory")

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 Iterable<Book> getBooks() {

return books;

}



}

BookDAO.java中可定義了一個列出特定類別書籍的方法。

package com.example.demo.dao;



import org.springframework.data.repository.CrudRepository;


import com.example.entity.Book;

import com.example.entity.BookCategory;


public interface BookDAO extends CrudRepository<Book, Long>{

public Iterable<Book> findByBookCategory(BookCategory category);


}

BookCategoryDAO.java

package com.example.demo.dao;



import org.springframework.data.jpa.repository.Query;

import org.springframework.data.repository.CrudRepository;


import com.example.entity.BookCategory;


public interface BookCategoryDAO extends CrudRepository<BookCategory, Long>{


}

跟CustomerController相較,BookController需要BookDAO及BookCategoryDAO。新增書籍及更新書籍前,先去資料庫取得所有的書籍類別。

package com.example.demo.controller;


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

import org.springframework.stereotype.Controller;

import org.springframework.web.bind.annotation.ModelAttribute;

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;


import com.example.demo.dao.BookCategoryDAO;

import com.example.demo.dao.BookDAO;

import com.example.demo.entity.Book;

import com.example.demo.entity.BookCategory;


@Controller

public class BookController {

@Autowired

BookDAO dao;


@Autowired

BookCategoryDAO categoryDao;


@RequestMapping(value = "/bookCreate", method = RequestMethod.GET)

public ModelAndView openFormCreate() {

ModelAndView model = new ModelAndView("bookCreate");

Iterable<BookCategory> categories = categoryDao.findAll();

model.addObject("allBookCategories", categories);

return model;

}


@RequestMapping(value = "/bookCreate", method = RequestMethod.POST)

public ModelAndView processFormCreate(@ModelAttribute Book book) {

ModelAndView model = new ModelAndView("redirect:/bookRetrieveAll");

dao.save(book);

model.addObject(book);

return model;

}


@RequestMapping(value = { "/bookRetrieveAll", "/book" }, method = RequestMethod.GET)

public ModelAndView retrieveBooks() {


ModelAndView model = new ModelAndView("bookList");

Iterable<BookCategory> categories = categoryDao.findAll();

model.addObject("allBookCategories", categories);

BookCategory category = categories.iterator().next();//get first category

model.addObject("bookCategory", category);


Iterable<Book> books = dao.findAll();

model.addObject("allBooks", books);

return model;

}


@RequestMapping(value = { "/bookRetrieveByCategory" }, method = RequestMethod.POST)

public ModelAndView retrieveBooksByCategory(

@RequestParam(value = "id", required = false, defaultValue = "1") Long id) {

ModelAndView model = new ModelAndView("bookList");

Iterable<BookCategory> categories = categoryDao.findAll();

model.addObject("allBookCategories", categories);

BookCategory category = categoryDao.findOne(id);

model.addObject("bookCategory", category);

model.addObject("allBooks", category.getBooks());

return model;

}


@RequestMapping(value = "/bookUpdate", method = RequestMethod.GET)

public ModelAndView openFormUpdate(

@RequestParam(value = "id", required = false, defaultValue = "1") Long id) {

ModelAndView model = new ModelAndView("bookUpdate");

Book book = dao.findOne(id);

model.addObject(book);

Iterable<BookCategory> categories = categoryDao.findAll();

model.addObject("allBookCategories", categories);

return model;

}


@RequestMapping(value = "/bookUpdate", method = RequestMethod.POST)

public ModelAndView processFormUpdate(@ModelAttribute Book book) {

ModelAndView model = new ModelAndView("redirect:/bookRetrieveAll");

dao.save(book);

return model;

}


@RequestMapping(value = "/bookDelete", method = RequestMethod.GET)

public ModelAndView deleteBook(

@RequestParam(value = "id", required = false, defaultValue = "1") Long id) {

ModelAndView model = new ModelAndView("redirect:/bookRetrieveAll");

dao.delete(id);

return model;

}


}

以上範例多了一個功能,就是可以只列出特定類別的書籍。首先先從view取得使用者選擇的類別,再利用findOne取得該書籍類別,取得之後將類別下的書及回傳到view。

@RequestMapping(value = { "/bookRetrieveByCategory" }, method = RequestMethod.POST)

public ModelAndView retrieveBooksByCategory(

@RequestParam(value = "id", required = false, defaultValue = "1") Long id) {

ModelAndView model = new ModelAndView("bookList");

Iterable<BookCategory> categories = categoryDao.findAll();

model.addObject("allBookCategories", categories);

BookCategory category = categoryDao.findOne(id);

model.addObject("bookCategory", category);

model.addObject("allBooks", category.getBooks());

return model;

}


bookCreate.html中將所有書籍類別放在select裡。

<!DOCTYPE html>

<html lang="en" xmlns:th="http://www.thymeleaf.org">

<head>

<meta charset="UTF-8"/>

<title>Create New Book</title>

</head>

<body>


<form action="bookCreate" method ="post">


名稱:<input type="text" name="name"/><br/>

類別:<select name = "bookCategory">

<option th:each="bookCategory : ${allBookCategories}"

th:value="${bookCategory.id}"

th:text="${bookCategory.name}">商業</option>

</select>


價格:<input type="number" name ="price"/><br/>

<input type="submit" value="Submit"/>


</form>


</body>

</html>

bookUpdate.html也將所有書籍類別放在select裡。

<!DOCTYPE html>

<html lang="en" xmlns:th="http://www.thymeleaf.org">

<head>

<meta charset="UTF-8"/>

<title>Update Book</title>

</head>

<body>


<form action="bookUpdate" th:object="${book}" method ="post">


名稱:<input type="text" name="name" th:field="*{name}"/><br/>


類別:<select name = "bookCategory" th:field="*{bookCategory}">

<option th:each="bookCategory : ${allBookCategories}"

th:value="${bookCategory.id}"

th:text="${bookCategory.name}">商業</option>

</select>

價格:<input type="number" name ="price" th:field="*{price}"/><br/>


<input type="hidden" name ="id" th:field="*{id}"/>

<input type="submit" value="Submit"/>


</form>


</body>

</html>

bookList.html第一個部份是將所有書籍類別列出,供使用者選擇,原理與上面兩個範例類似。比較不同的是,field必須是bookCategory.id而不能是bookCategory。

<!DOCTYPE html>

<html lang="en" xmlns:th="http://www.thymeleaf.org">

<head>

<meta charset="UTF-8"/>

<title>List ALl Books</title>

</head>

<body>

<form action="bookRetrieveByCategory" method ="post">

類別:<select name = "bookCategory" th:field="${bookCategory.id}">

<option th:each="bookCategory : ${allBookCategories}"

th:value="${bookCategory.id}"

th:text="${bookCategory.name}">商業</option>

</select>

<input type="submit" value="Submit"/>

<a href="bookRetrieveAll"><input type="button" value="Show All Books"/></a>

</form>

<a href="bookCreate"><input type="button" value="新增書籍"/></a>

<table>

<tr>

<th>編號</th>

<th>類別</th>

<th>名稱</th>

<th>價格</th>

<th></th>

</tr>

<tr th:each="book : ${allBooks} " th:object="${book}">

<td th:text="*{id}">1</td>

<td th:text="*{bookCategory.name}">business</td>

<td th:text="*{name}">Ben</td>

<td th:text="*{price}">300</td>

<td><a th:href="@{bookUpdate(id=*{id})}">修改</a> <a th:href="@{bookDelete(id=*{id})}">刪除</a></td>

</tr>

</table>



</body>

</html>

常見問題

如果我的table的主鍵是由多個欄位組合,那要如何寫呢?

要將這些欄位組合成一個類別 (詳參:

https://stackoverflow.com/questions/13032948/how-to-create-and-handle-composite-primary-key-in-jpa )。

為何資料庫不接受date?

java的date與資料庫l的date的儲存方式不同,所以,必須轉換:

http://www.developerscrappad.com/228/java/java-ee/ejb3-jpa-dealing-with-date-time-and-timestamp/

參考資料

04_DB_Environment.ppt