iT邦幫忙

2023 iThome 鐵人賽

DAY 8
0

筆者前公司的產品,是使用 NoSQL 資料庫(MongoDB),並非關聯式資料庫。後來偶爾看看徵才訊息,發現要求 SQL 能力的職缺還不少。但我沒有這方面的工作經驗,於是趁著鐵人賽來補一些知識。

本文將在 Spring Boot 專案透過 JPA 的介面去存取 MySQL,這會是一個 hello world 性質的範例。後面幾天的文章,才會進一步說明如何設計各種欄位,以及一對多、多對多的關係。而文末將簡介 Spring Data JPA。


一、安裝 MySQL

筆者透過 Docker 安裝 MySQL,版本為 8.1.0。其他版本可參考它的 Docker Hub

docker pull mysql:8.1.0
docker run -d -p 3306:3306 --name DemoMySQL -e MYSQL_ROOT_PASSWORD=123456 99afc808f15b
  • -p 3306:3306:MySQL 的 port 號為 3306。
  • -name DemoMySQL:設定 container 名稱為「DemoMySQL」。
  • -e MYSQL_ROOT_PASSWORD=123456:設定 root 帳號的密碼為「123456」。
  • 99afc808f15b:Docker 的 Image Id

讀者還可另外安裝開發工具,本文使用的是 Workbench

接著請在 MySQL 中建立一個資料庫(schema),以便在裡面存放一至多個資料表(table)。筆者取名為「demo」。
https://ithelp.ithome.com.tw/upload/images/20230904/20131107sxoZoM0wHn.jpg

二、準備 Spring Boot 專案

(一)建立專案

透過 Spring Initializr 工具,可輕鬆產生一個 Spring Boot 專案。本文使用的版本如下。

  • 建置工具:Maven
  • 程式語言:Java 17(zulu-17)
  • Spring Boot:3.1.3

開啟專案後,請在 pom.xml 檔案添加串接 MySQL 時所需要的依賴。

<!-- MySQL -->
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <scope>runtime</scope>
</dependency>

<!-- Spring Data JPA -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

在後面文章的範例,我們會在測試程式中,透過呼叫 repository bean 的做法來存取資料庫。因此請把相關的依賴添加進來。

<!-- Spring Web -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- Spring Boot 測試 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

<!-- 單元測試 -->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
</dependency>

(二)連線設定

請在 application.properties 檔案,添加以下參數。

# 資料庫地址
spring.datasource.url=jdbc:mysql://localhost:3306/demo

# 資料庫帳密
spring.datasource.username=root
spring.datasource.password=123456

# Driver 的類別路徑
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# 轉成 SQL 指令的翻譯器
spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect

# 啟動 server 時,要如何配置資料表;create 是基於測試用途,正式環境別誤用
spring.jpa.hibernate.ddl-auto=create

# MySQL 的儲存引擎
spring.jpa.properties.hibernate.dialect.storage_engine=innodb

# 是否在 log 印出 SQL 指令並對其格式化;適合開發和測試期間開啟做確認
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.format_sql=true

值得一提的是 spring.jpa.hibernate.ddl-auto 參數。它有五種在啟動 server 時配置 table 的選項。

  • create:啟動時,重新建立 table(現有資料會遺失)。
  • create-drop:同 create,且程式關閉後,會刪除 table。
  • update:啟動時,會依據 model 類別的定義更新 table。
  • validate:啟動時,檢查 table 是否被改變,有的話會拋出例外,具有「提醒」的效果。
  • none:不做動作。

若基於開發過程中的測試目的,可使用 create,畢竟 model 類別的欄位定義可能會修修改改的。而在正式環境,會選擇 updatevalidate

Ref:
(19) 導入Spring-Data-JPA,將資料庫與物件進行綁定與 Sequence 的設定
MySQL資料庫引擎InnoDB與MyISAM有何差異

三、定義資料 Model

在資料庫中,每個 table 在程式中都有一個對應的 model 類別,也就是 PO(persistent object)。比方說想儲存學生資料,那就設計一個對應的類別。

import jakarta.persistence.*;

@Entity
@Table(name = "student")
public class StudentPO {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;
    
    private String name;
    private int age;

    // getter, setter...
}

此類別就代表一個 table,除了定義欄位,也使用了一些標記(annotation)。

  • @Entity:將類別標示成一個 table。
  • @Table:透過 name 參數定義 table 名稱。
  • @Id:將欄位標示成主鍵(primary key,PK)。
  • @GeneratedValue:定義 PK 值的產生方式。

四、定義 Repository 層

Repository 屬於三層式架構中,負責存取資料庫的「資料存取層」,接下來讓我們定義它。

@Repository
public interface StudentRepository extends JpaRepository<StudentPO, Long>, JpaSpecificationExecutor<StudentPO> {
    List<StudentPO> findByAge(int age);
}

這個介面繼承了 JpaRepository,並於泛型傳入 model 與主鍵的類別。也繼承 JpaSpecificationExecutor,於泛型傳入 model 類別。若點進去查看這兩個介面,可看到它們以及父介面中已經有定義一些方法了,例如 findAllsaveAlldeleteById 等。

我們甚至能在上面的 repository 中,定義自己想要的方法。只是方法名稱、參數順序等要遵循一定的規則。這部分讀者可自行 google,或參考官方文件

此外,不需要另外準備 repository 介面的實作類別,因為 Spring Data JPA 會根據這些定義好的方法自動生成,並建立為 Spring 元件。

五、測試

定義好 repository 層,也在 Docker 運行 MySQL 後,就能實際使用了。以下是透過撰寫 Spring Boot 測試的方式來進行 CRUD。

@RunWith(SpringRunner.class)
@SpringBootTest
public class ApplicationTests {

	@Autowired
	private StudentRepository studentRepository;

	@Test
	public void testCRUD() {
		// create
		StudentPO student = new StudentPO();
		student.setName("Vincent");
		student.setAge(27);
		studentRepository.save(student);

		// read
		StudentPO result = studentRepository.findById(student.getId()).orElseThrow();
		assertEquals(student.getName(), result.getName());
		assertEquals(student.getAge(), result.getAge());

		// update
		student.setAge(26);
		studentRepository.save(student);

		result = studentRepository.findByAge(student.getAge()).get(0);
		assertEquals(student.getAge(), result.getAge());

		// delete
		studentRepository.deleteById(student.getId());
		assertFalse(studentRepository.existsById(student.getId()));
	}
}

提醒一下,由於上面的測試程式,在最後有執行刪除操作,因此當測試跑完,是不會在 DB 中看到該筆資料的。讀者可利用下中斷點(break point)的方式,每做完一個操作,就去 DB 確認狀況。

六、Spring Data JPA 介紹

最後簡介本文所接觸的 Spring Data JPA。

(一)ORM(Object-Relational Mapping)

如果讀者有用過 JDBC,應該對存取 DB 時的程式流程還留有一點印象。包含建立資料庫連線、組合出 SQL 語句後執行、對查詢結果(Result Set)的 raw data 做迴圈,轉換成業務邏輯物件。若更換資料庫種類,SQL 語句可能也要修改。

ORM 的概念,是為了解決上述的繁瑣操作。於是一些 ORM 框架被開發出來,如 Hibernate、MyBatis。它們不但封裝了邏輯,還讓我們在下指令時能以程式語言為導向,而不必寫出 SELECT * from table WHERE ... 這種會依賴 DB 種類的原生語法。

(二)JPA(Java Persistence API)

JPA 是一個規範,它是 ORM 框架在提供 API 時,可以遵循的標準。

(三)Spring Data JPA

Spring Data JPA 也是一個框架,而它所封裝的對象為 Hibernate。它讓我們能夠像本文第三、四節那樣,透過 annotation 和定義 repository 方法名稱,更進一步地簡化存取資料庫的程式邏輯。

Ref:JDBC、ORM、JPA、Spring Data JPA,傻傻分不清楚?一文帶你釐清箇中曲直,給你個選擇SpringDataJPA的理由!


今日文章到此結束!
最後推廣一下自己的部落格,我是「新手工程師的程式教室」的作者,請多指教/images/emoticon/emoticon41.gif


上一篇
【Java】HashMap 的工作原理(下)
下一篇
【Spring Boot】使用 JPA 設計資料表欄位
系列文
救救我啊我救我!CRUD 工程師的惡補日記50
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言