今天我們大部分的內容會專注在整個註冊流程的資料層 (Data Layer),也就是負責與資料庫溝通的元件們。
一個完整的功能需要由許多不同的元件協力完成,因此在這之前,會先說明檔案架構,概覽這個過程需要建立的各元件,再接著建置Entity
與 Repository
。
本專案採用常見的三層式架構,每層職責說明如下:
除此之外,在這個階段還會手動建立幾個重要的package:
因為開發階段使用容器啟動服務,因此還會有dockerfile與docker-compose的檔案出現在下述的樹狀架構中:
food-print-project
├─.idea
├─.env
├─docker-compose
├─pom
└─auth-service
├─pom
├─dockerfile
├─.mvn
├─src
│ ├─main
│ │ ├─java
│ │ │ └─com
│ │ │ └─twohands
│ │ │ ├─config
│ │ │ ├─controller
│ │ │ ├─dto
│ │ │ ├─entity
│ │ │ ├─repository
│ │ │ └─service
│ │ │ └─Impl
│ │ └─resources
│ └─test
│ └─java
└─target
├─classes
│ └─com
│ └─twohands
└─generated-sources
└─annotations
Entity 用於定義物件屬性並產生對應的資料表。如同前幾天對 Spring Security 的介紹,我們會在這一步建立 UserEntity 的類別,其實作了 UserDetail 這個介面,讓使用者模型基於框架所定的屬性與方法延伸開發,細節如下:
...
@Entity
@Table(name = "users")
public class UserEntity implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String email;
@Column(nullable = false)
private String password;
@CreatedDate
@Column(nullable = false, updatable = false)
private LocalDateTime createdAt;
@LastModifiedDate
@Column(nullable = false)
private LocalDateTime updatedAt;
// --- 以下為 UserDetails 介面的實作 ---
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// 暫時先給所有使用者相同的權限
return List.of(new SimpleGrantedAuthority("ROLE_USER"));
}
@Override
public String getPassword() {
return this.password;
}
@Override
public String getUsername() {
return this.email;
}
@Override
public boolean isAccountNonExpired() { return true; }
@Override
public boolean isAccountNonLocked() { return true; }
@Override
public boolean isCredentialsNonExpired() { return true; }
@Override
public boolean isEnabled() { return true; }
public Long getId() {
return id;
}
public void setPassword(String password) {
this.password = password;
}
public void setEmail(String email) {
this.email = email;
}
}
因為沒有特別的需求,基本上就是定義帳號 (Email) 與密碼,另外自動給予每個新使用者一組唯一的序號,作為primary key,避免email未來有更動的風險。以下針對 Annotation 用途進行說明:
@Entity
UserEntity
Class 視為一個資料庫實體來管理。JPA 會為它建立資料表、進行 CRUD 操作。@Table(name = "users")
UserEntity
對應到資料庫中的資料表名稱為 users
。(沒指定的話預設會使用 Class 的小寫名稱 userentity
作為資料表名。)@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
IDENTITY
策略依賴於特定資料庫自動賦予primary key的功能,如PostgreSQL的SERIAL
,或MySQL的AUTO_INCREMENT
@Column(nullable = false, unique = true)
nullable = false
: 代表 email
這個欄位在資料庫中不允許是空值 (NULL)。unique = true
: 代表 email
欄位的值必須是唯一的,不能重複。@CreatedDate
與 @LastModifiedDate
這是 JPA Auditing (稽核) 功能的一環,可以自動添加時間相關欄位內容。要啟用此功能,需要在 Config 中加上 @EnableJpaAuditing 的設定:
...
@SpringBootApplication
@EnableJpaAuditing
public class AuthServiceApplication {
...
}
@CreatedDate
:在資料首次被新增至資料庫時,自動填入當前的時間。通常會搭配 @Column(updatable = false),確保這筆紀錄的創建時間永遠不會被更動。@LastModifiedDate
:在資料每次被更新時,自動填入當前的時間。UserDetail 提供的方法:
在 Spring Security 的 UserDetails
介面中,除了 getUsername()
、getPassword()
和 getAuthorities()
這三個核心方法外,還定義了四個用來檢查帳號狀態的方法。由於我們尚未規劃到那麼細緻的功能,先簡單回傳true,不檢核相關狀態:
方法 | 說明 | 使用場景 |
---|---|---|
isAccountNonExpired() |
帳號是否未過期。 | 用於有時效性的帳號,例如試用會員或僅可使用 30 天的臨時帳號。 |
isAccountNonLocked() |
帳號是否未被鎖定。 | 可用於「多次登入失敗鎖定帳號」的功能。當偵測到使用者連續輸入錯誤密碼達到一定次數時,回傳 false ,暫時鎖定帳號。 |
isCredentialsNonExpired() |
憑證 (通常指密碼) 是否未過期。 | 用於需要定期更換密碼的安全性要求。例如,系統強制使用者每 90 天更換一次密碼,若超過時間,此方法回傳 false ,強制使用者進入重設密碼流程。 |
isEnabled() |
帳號是否啟用。 | 可用於 Email 驗證場景。使用者建立帳號後,此方法先回傳 false,待使用者點擊驗證信中的連結後,才將狀態改為 true,視為成功開通帳號。 |
以上介紹了可以用來產生對應資料表的Entity
,讓我們建立一個跟資料庫溝通的管道:Repository
。
Repository 可以被視為程式與資料庫溝通的管道,如果對於資料庫的查詢是常見、簡單的 CRUD,大部分會以JPA的概念進行實作。
本次先以email查詢為例,在Repository資料夾下建立了一個 AuthRepository
的介面,該介面繼承自 JpaRepository
,實作細節如下:
...
public interface AuthRepository extends JpaRepository<UserEntity, Long> {
Optional<UserEntity> findByEmail(String email);
}
...
一直對於 Spring Data Jpa 能以這麼簡單的方式讓我們與資料庫進行溝通感到很好奇,看了一些資料後我的理解是:
Hibernate 是按照JPA 規範實作的 ORM 框架,但若直接使用 Hibernate,開發人員發現仍需自行處理許多資料庫互動的重複邏輯。
因此, Spring Data JPA 在 Hibernate 之上又加了一層抽象層,讓我們可以僅透過一套命名規則,來實現常見的資料庫操作。
使用上,只要繼承 Spring Data JPA 提供的 JpaRepository
,Spring Boot 啟動時,會掃描繼承 JpaRepository
的介面,為其生成代理物件。
當我們在程式中呼叫 Repository 介面上定義的方法(如 authRepository .findByEmail()
),該代理物件會攔截這次呼叫,根據命名規則生成並執行資料庫語法。
今天完成了註冊功能相關的資料層 (Entity 與 Repository)設定,明天,會以此基礎上繼續說明如何完成 DTO、Service 、 Controller 與 Config 設定的部分。