前面已經介紹過各類資料操作框架的特色之後,這邊來介紹其中我比較熟悉也蠻多人使用的 Spring Data Jpa,雖然比較不用寫 SQL 所以可能會對於 SQL 掌握比較少,但我覺得透過物件的方式來操作資料也是本身 Java 的特色之一,搭配 Spring Boot 能夠整合在框架中使用是很方便的

使用前概念

根據常用開發架構的 MVC 架構,資料庫的部分主要是由 Model 層部分進行處理,這邊對應在 Spring Boot 的開發上會放在 Dao 和 Service,Dao 主要是定義我們與資料庫的溝通連結,在 Spring Data Jpa 主要就是繼承 JpaRepository 去處理資料的存取,最後將拿到得資料返回給 Service 進行業務邏輯的操作或是資料的組裝,最後回給 Controller。

引入套件

pom.xml 加入下面套件,spring-boot-starter-data-jpa 就是我們要使用的 JPA 套件,另外一個則是 mysql 連線用的套件,可以根據使用不同資料庫做更換

1
2
3
4
5
6
7
8
9
10
    <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

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

配置 application properties

這部分根據你使用的資料庫不同會有一些差異,以下拿 Mysql 為例,主要需要配置概念都差不多,就是配置你的資料庫 Driver, 資料來源, 使用者名稱, 密碼等等

1
2
3
4
5
6
7
8
9
10
11
12
13
# 基本來源配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/{databaseName}
spring.datasource.username={username}
spring.datasource.password={userPassword}

# 額外配置
# 資料表初始化及自動更新
spring.jpa.hibernate.ddl-auto=update

# 是否在 console 印出 SQL 指令並對其格式化
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.format_sql=true

假設我們想要設計產品 Product 的資料操作,會定義他的實體類別 (Entity),來對應到資料庫的 products 表,當我們進行資料都是用 Product 這個物件來操作資料的存取,查詢就將資料轉成我們定義好的物件回給我們,儲存、修改、刪除等等的操作也是由此物件去進行。

資料庫建立及資料表建立

資料庫建立部分大家可以自行找自己所使用的用法,這邊就不說明,提供一個簡單通用的 SQL 例子方便沒使用過的可以有環境進行操作

1
2
3
4
5
6
7
8
CREATE TABLE products (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
price DECIMAL(10, 2) NOT NULL,
description TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

Entity

新增一個 class,定義我們要作為資料庫操作物件,會對應到資料表的個別欄位

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

@Entity
@Table(name = "products")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String name;
private Double price;
private String description;

// Getter and Setter...
}

@Entity 告訴 Jpa 它是要儲存到資料庫的實體類別

@Table 則設定它在資料庫中,要對應到叫做「products」的 table 。

@Id 表示 table 中的主鍵(Primary Key,PK)

@GernatedValue() 定義了主鍵值的產生方式。設定的方式 strategy = GenerationType.IDENTITY 就是表示主鍵會自動增長。

後面章節會講到一些 Lombok 註解的應用,可以讓 Spring Data Jpa 使用上更加簡潔方便,但希望各位使用時還是要清楚知道每個註解的由來跟原始作法

Dao 層繼承 JpaRepository

新增 Repository 來做為資料庫溝通介面

1
2
3
4
5
6
7
8
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
Product findByName(String name);
}

Respository 就是屬於 Dao 的部分,這邊會新增一個介面( interface) 來繼承 JpaRepository,就可以將 JPA 對於物件實體的映射包含資料庫的連線等等實現,後面<> 內需要放入你要操作的物件實體及他內部主鍵型別 (id 的型別) 最後會是像這樣 JpaRepository<Entity, 主鍵型別>,下面可以加入任何你想要使用的方法來操作資料庫,通常預設已經有基本可以針對 id 搜尋單筆,或是直接查詢整筆,就不用宣告出來,透過繼承就可以後續直接再 Service 層中使用。

Service 層中用慣例優於配置來進行 CRUD 操作

新增 Service 層應用 JPA Repository 定義好的或是預設的方法操作資料

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;

@Service
public class ProductService {

@Autowired
private ProductRepository productRepository;

// 新增產品
public Product createProduct(Product product) {
return productRepository.save(product);
}

// 根據 id 查詢單一產品
public Optional<Product> getProductById(Long id) {
return productRepository.findById(id);
}

// 根據名稱查詢單一產品
public Product getProductByName(String name) {
return productRepository.findByName(name);
}

// 查詢所有產品
public List<Product> getAllProducts() {
return productRepository.findAll();
}

// 更新產品
public Product updateProduct(Long id, Product productDetails) {
Product product = productRepository.findById(id)
.orElseThrow(() -> new RuntimeException("Product not found with id: " + id));

product.setName(productDetails.getName());
product.setPrice(productDetails.getPrice());
product.setDescription(productDetails.getDescription());

return productRepository.save(product);
}

// 刪除產品
public void deleteProduct(Long id) {
Product product = productRepository.findById(id)
.orElseThrow(() -> new RuntimeException("Product not found with id: " + id));

productRepository.delete(product);
}
}

這邊就可以看到我們使用 JPA 所帶來的好處,已經直接提供給我們很多 ORM 包裝好的方法,像是 findById(), findAll(), save(), delete(),這些本該是自己要撰寫 Sql 才能和資料庫溝通的,但我們在 Repository 沒有定義任何方法就可以使用,除了一個 findByName 這個是我們自己定義的,但你也會發現這方法也沒進行任何處理,只是一個簡單明瞭的名稱就可以做到查詢,其實都是 JPA 幫我們處理好,他會自行匹配你定義好在 Entity 的個別欄位,所以當你寫 findBy{欄位名} 他就會自行匹配對應的 sql ( SELECT * FROM prodcuts WHERE {欄位名} = ‘{值}’; ),確實都不用寫到任何 SQL 就可以完成基本的操作。

這些慣例優於配置的用法其實還有很多,是你想要進行複雜查詢要自己寫的話要怎麼使用,後面會有進一步介紹。

Controller 建立接口接收請求及回傳結果

最後就是開端口接收請求及回傳 Service 的結果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Optional;

@RestController
@RequestMapping("/api/products")
public class ProductController {

@Autowired
private ProductService productService;

// 新增產品
@PostMapping
public Product createProduct(@RequestBody Product product) {
return productService.createProduct(product);
}

// 查詢單一產品
@GetMapping("/{id}")
public ResponseEntity<Product> getProductById(@PathVariable Long id) {
Optional<Product> product = productService.getProductById(id);
return product.map(ResponseEntity::ok)
.orElseGet(() -> ResponseEntity.notFound().build());
}

// 查詢所有產品
@GetMapping
public List<Product> getAllProducts() {
return productService.getAllProducts();
}

// 更新產品
@PutMapping("/{id}")
public ResponseEntity<Product> updateProduct(@PathVariable Long id, @RequestBody Product productDetails) {
try {
Product updatedProduct = productService.updateProduct(id, productDetails);
return ResponseEntity.ok(updatedProduct);
} catch (RuntimeException e) {
return ResponseEntity.notFound().build();
}
}

// 刪除產品
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteProduct(@PathVariable Long id) {
try {
productService.deleteProduct(id);
return ResponseEntity.noContent().build();
} catch (RuntimeException e) {
return ResponseEntity.notFound().build();
}
}
}

看完你會對於 Spring Data JPA 有進一步的了解了,也大概知道其中的特色在哪,有太多太多細節再透過後面的章節慢慢介紹。


參考資料 :