﻿# 快速上手 Spring Data JPA 與 SQL Server 資料庫開發

我們在開發任何 Web 應用程式時，幾乎可以說 99% 的情境都需要存取資料庫。而在 Java 世界裡有個 JPA 標準規範，幫助你大幅簡化資料存取所需的程式碼。而 Spring 框架整合了 JPA 標準，幫助你更好的在應用程式中使用 JPA 來開發資料存取層的邏輯。今天這篇文章我就來好好釐清快速上手的過程。

![Spring Data JPA](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/ea46e06fbcca82e635e7-194109102-438720d6-b6be-45fb-a88c-c3fef27778ab.png)

### 簡介 JPA 標準

JPA 全名為 [Jakarta Persistence API](https://jcp.org/en/jsr/detail?id=338)，早期名稱叫 [Java Persistence API](https://www.oracle.com/java/technologies/persistence-jsp.html)，主要用來實現一致的 ORM 的開發介面 (API Interface)，讓你通過簡單的**標注**(Annotation)來對應出**物件**與**資料**之間的關聯關係。

Spring Boot 內建一個 `spring-boot-starter-data-jpa` 相依套件，裡面定義了更多種相依套件，主要有：

1.  `Hibernate`: 這是 JPA 標準中最知名的實作
2.  `Spring Data JPA`: 幫助你實現以 JPA 為主的 Repositories
3.  `Spring ORM`: 由 Spring 框架核心支援的 ORM 架構

### 準備 SQL Server 容器

開發資料庫應用之前，當然要先準備好資料庫，這裡我就拿我擅長的 SQL Server 來做示範。在 Docker 的輔助之下，一個命令就可以在 10 秒內啟動一個 SQL Server 資料庫伺服器，以下是啟動的命令與匯入範例資料的步驟：

1.  啟動 SQL Server 容器
    
    ```bash
    docker run --rm -e "ACCEPT_EULA=Y" -e "SA_PASSWORD=Ver7CompleXPW" -p 1433:1433 --name sql1 -d mcr.microsoft.com/mssql/server:2019-latest
    ```
    
    > 上述命令能讓你從本機連接 `localhost:1433` 伺服器，並使用 `sa` 帳號以 `Ver7CompleXPW` 密碼登入。
    
2.  下載 [ContosoUniversity](https://gist.github.com/doggy8088/2a2f7075d49b3814d19513426ede3549) 範例資料庫
    
    ```bash
    curl -SLO https://gist.github.com/doggy8088/2a2f7075d49b3814d19513426ede3549/raw/ba8c4e7d46597188a17a39de7906d358f18d834d/ContosoUniversity.sql
    ```
    
3.  建立範例資料庫 (含資料匯入)
    
    ```bash
    docker cp ContosoUniversity.sql sql1:/
    
    docker exec -it sql1 /opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P "Ver7CompleXPW" -i /ContosoUniversity.sql
    ```
    
4.  測試連接 ContosoUniversity 資料庫 (列出所有表格清單)
    
    ```bash
    docker exec sql1 /opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P "Ver7CompleXPW" -d "ContosoUniversity" -W -Q "SELECT name FROM sys.tables"
    ```
    
    > 你也可以透過 [SSMS](https://learn.microsoft.com/zh-tw/sql/ssms/download-sql-server-management-studio-ssms)、[osql](https://learn.microsoft.com/zh-tw/sql/tools/osql-utility?view=sql-server-ver15&WT.mc_id=DT-MVP-4015686)、[sqlcmd](https://learn.microsoft.com/zh-tw/sql/tools/sqlcmd-utility?view=sql-server-ver15&WT.mc_id=DT-MVP-4015686) 或 [mssql-cli](https://learn.microsoft.com/zh-tw/sql/tools/mssql-cli?view=sql-server-ver15&WT.mc_id=DT-MVP-4015686) 連接資料庫。
    

### 建立 Spring Boot 應用程式

你可以透過 [Spring Boot CLI](https://docs.spring.io/spring-boot/docs/current/reference/html/cli.html) 建立專案，也可以使用 IDE 內建的 [Spring Initializr](https://start.spring.io/) 工具建立專案。

以下我以 Spring Boot CLI 為例進行解說：

1.  安裝 Spring Boot CLI 命令列工具
    
    若在 Windows 可用 [Chocolatey](https://community.chocolatey.org/packages/spring-boot-cli/) 進行安裝：
    
    ```bash
    choco install spring-boot-cli -y
    ```
    
2.  查詢 Spring Boot CLI 可用的參數與清單
    
    ```bash
    spring init --list
    ```
    
3.  建立 Spring Boot 專案
    
    這裡我們選擇四個相依套件，分別是 Spring Web (`web`), Lombok (`lombok`), Spring Data JPA (`data-jpa`), MS SQL Server Driver (`sqlserver`) 這四個：
    
    ```bash
    spring init '-dweb,lombok,data-jpa,sqlserver' demojpa
    ```
    
4.  使用任何 IDE 開啟專案 (以下使用 VSCode 為例)
    
    ```bash
    code demojpa
    ```
    

### 開發 Spring Data JPA 的 Spring Boot 應用程式

因為我們在建立專案時，已經加入了 `data-jpa` 與 `sqlserver` 相依套件，

1.  設定 JPA 的 SQL Server Driver
    
    編輯 `src/main/resources/application.properties` 屬性檔，加入連接字串與相關參數
    
    ```init
    spring.datasource.url=jdbc:sqlserver://127.0.0.1:1433;databaseName=ContosoUniversity;sslProtocol=TLS;Encrypt=false
    spring.datasource.username=sa
    spring.datasource.password=Ver7CompleXPW
    spring.datasource.driver-class-name=com.microsoft.sqlserver.jdbc.SQLServerDriver
    spring.jpa.database=sql-server
    spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
    ```
    
2.  建立 Entity 實體類別
    
    傳統的 JPA 實作都需要定義一個 `persistence.xml` 檔案，但在 Spring 框架中不需要定義這個檔，而是只要你的類別有套用 `@Entity` 標注(Annotation)，就可以透過 "Entity Scanning" (實體掃描) 機制自動註冊到 Spring 的 DI 容器中。預設只要是在有**標注** `@EnableAutoConfiguration` 或 `@SpringBootApplication` 的類別，Spring 框架都會自動找出所有啟動類別的 sub-package 下的所有**實體類別**(Entity Class)。
    
    定義實體類別應該是使用 JPA 最繁瑣的部分，因為要定義物件與資料的對應關係，所以可以藉由一些 IDE 提供的工具自動產生，或透過 [Lombok](https://projectlombok.org/) 簡化程式碼。以下是實體類別的範例：
    
    ```java
    import javax.persistence.*;
    import lombok.Data;
    
    @Data
    @Entity
    @Table(name = "Course", schema = "dbo", catalog = "ContosoUniversity")
    public class CourseEntity {
        @Id
        @Column(name = "CourseID", nullable = false)
        private int courseId;
    
        @Basic
        @Column(name = "Title", nullable = true, length = 50)
        private String title;
    
        @Basic
        @Column(name = "Credits", nullable = false)
        private int credits;
    }
    ```
    
3.  建立 Spring Data JPA Repositories 類別
    
    所謂的 Spring Data JPA Repositories 只是一個**介面**(interface)而已，有趣的地方來了，Spring 設計成只要在這些 Repository 介面中撰寫**特定命名規則**的**方法名稱**(Method Name)，就可以自動查詢到結果，非常酷！
    
    以下是一份最精簡的 Repository 範例程式，在 [CrudRepository<T,ID>](https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/repository/CrudRepository.html) 介面的幫助下，預設已經提供許多常用的資料存取操作：
    
    ```java
    import org.springframework.data.repository.CrudRepository;
    import org.springframework.stereotype.Repository;
    
    import com.example.demojpa.entity.CourseEntity;
    
    @Repository
    public interface CourseEntityRepository extends CrudRepository<CourseEntity, Integer> {
    }
    ```
    
4.  撰寫 API Controller 控制器
    
    以下僅提供簡易使用範例，實務上我們的 Controller 應該盡可能的結構清晰、語意清楚，所以可能會多包一層 Service 回應 DTO 結果，例外處理也可能透過 `@ControllerAdvice` 來進行集中處理，請自行斟酌調整程式碼結構，本文主要介紹 JPA 的存取方法。
    
    ```java
    @RestController
    @RequestMapping("/")
    public class HomeController {
        private CourseEntityRepository repository;
    
        public HomeController(CourseEntityRepository repository) {
            this.repository = repository;
        }
    
        @GetMapping
        public ResponseEntity<List<CourseEntity>> getAllCourseEntity() {
            try {
                List<CourseEntity> items = new ArrayList<CourseEntity>();
    
                repository.findAll().forEach(items::add);
    
                return ResponseEntity.ok(items);
            } catch (Exception e) {
                return ResponseEntity.internalServerError().build();
            }
        }
    }
    ```
    

### 總結

其實 [Hibernate ORM](https://hibernate.org/) 與 [Spring Data JPA](https://spring.io/projects/spring-data-jpa) 都不是個很小的主題，還有許多內涵等著大家去發掘，建議多閱讀官網，學習更進階的各種用法。

### 相關連結

-   [Getting Started | Accessing Data with JPA](https://spring.io/guides/gs/accessing-data-jpa/)
-   [Spring Data JPA](https://spring.io/projects/spring-data-jpa)
-   [9.1. SQL Databases | Spring Boot Reference Documentation](https://docs.spring.io/spring-boot/docs/2.7.4/reference/htmlsingle/#data.sql)
-   [Hibernate. Everything data.](https://hibernate.org/)
-   [Pagination and Sorting using Spring Data JPA | Baeldung](https://www.baeldung.com/spring-data-jpa-pagination-sorting)
-   [下載 - JDBC Driver for SQL Server | Microsoft Learn](https://learn.microsoft.com/zh-tw/sql/connect/jdbc/download-microsoft-jdbc-driver-for-sql-server?view=sql-server-ver16&WT.mc_id=DT-MVP-4015686)
-   [Yaml to properties / Properties to Yaml converter](http://mageddo.com/tools/yaml-converter)
