# BIRC後端專案開發流程
## 開發前置
### Gitlab
1. 通知負責人把你拉入該專案的GitLab
2. 會在Project看到一個項目

3. 進入該項目並點選分支(fork)

4. 選擇自己帳號

5. 回到Project會看到多一個項目(標籤為Master)

6. 進入該項目並複製HTTPS(要確認該網址中有包含你的帳號,附圖為"112...")

7. 開啟fork並點選File -> Clone

8. 如圖(需確認Url中包含自己GitLab帳號)

9. 在左側建立一個remote

10. 通常命名為upstream,Url填入GitLab中最初的那個項目的HTTPS(該Url不得包含自己帳號!)


11. 確保自己為最新進度
***
### 資料庫
1. 填寫防火牆許可,[相關連結](https://docs.google.com/forms/d/e/1FAIpQLSdj7jBY3e8X1soCqxxw8FsgDAmYBjeq2UjP0BBlM8UqvpQPVA/viewform)
2. 系網群收到表單後會有負責人傳送資料庫連線資訊給你(Url、Account、Password)
***
### 專案配置檔(.yml)
1. 複製一份application-local-example.yml並命名為application-local.yml

2. 將資料庫連線資訊填入,並將其他參數設定好
3. 確認build.gradle中的processResources設定

需要將裡面的程式碼註解掉才能開啟專案(包檔時需將註解刪除!)

## 開發過程
### Entity範例
| 名詞 | 解釋 |
| ------------- | ------------------------------------------------------------------------------------------------- |
| `@Entity` | 標記該類別為 **JPA 實體**,會對應到資料庫中的一張表。 |
| `@Table` | 指定 **實體對應的資料表名稱**,可設定 `name`(表名)、`schema`(資料庫名)。 |
| `@Data` | Lombok 提供的註解,自動產生 **getter/setter** 等方法,省去手寫程式碼。 |
| `@Id` | 指定該欄位為 **主鍵 (Primary Key)**。 |
| `@Column` | 設定欄位對應的資料表欄位名稱及屬性,例如 `name`(欄位名)、`nullable`(是否可為 NULL)、`length`(字串長度)。 |
| `@ManyToOne` | 表示 **多對一關聯**。 |
| `@JoinColumn` | 指定關聯的外鍵欄位,`name` 是本表的外鍵欄位,`referencedColumnName` 是關聯表的主鍵欄位,`insertable` 和 `updatable` 控制是否允許寫入更新。 |
```java
/**
* 成員表
*
* @since 1.0.0
*/
@Entity
@Table(name = "member", schema = Config.DATABASE_NAME)
@Data
public class Member {
/**
* 學號
*
* @since 1.0.0
*/
@Id
@Column(name = "member_id", nullable = false, length = 10)
private String memberId;
/**
* 年級編號
*
* @since 1.0.0
*/
@Column(name = "grade_id", nullable = false)
private Integer gradeId;
/**
* 姓名
*
* @since 1.0.0
*/
@Column(name = "name", nullable = false, length = 10)
private String name;
/**
* 電子信箱
*
* @since 1.0.0
*/
@Column(name = "email", nullable = false, length = 50)
private String email;
/**
* 性別(0:男/1:女)
*
* @since 1.0.0
*/
@Column(name = "gender", nullable = false, length = 1)
private String gender;
/**
* 年級表
*
* @see Grade
* @since 1.0.0
*/
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "grade_id", referencedColumnName = "grade_id", insertable = false, updatable = false, nullable = false)
private Grade GradeByGradeId;
}
```
### DAO
| 名詞 | 解釋 |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
|`@Repository` | **資料存取層 (DAO)** 的註解,通常放在操作資料庫的類別(如 JPA Repository)。|
DAO為interface需繼承BaseDAO
BaseDAO接口裡面裝的是entity跟id的型別
可以按著ctrl點進BaseDAO查看
若要實作條件查詢則額外繼承JpaSpecificationExecutor接口
```java
@Repository
public interface MemberDAO extends BaseDAO<Member, String>, JpaSpecificationExecutor<Member> {
}
```
### Bean
不會用Entity做與前端傳遞參數的物件(因為前端需要的資料不一定完全等於資料庫的欄位)
因此使用Bean當作傳遞的物件
表單驗證常用註解,可透過 message 參數來自訂錯誤訊息
| 名詞 | 解釋 |
| ----------- | ------------------------------------------- |
| `@Null` | 限制該欄位 **必須為 `null`**。 |
| `@NotNull` | 限制該欄位 **不能為 `null`**(但可以是空字串或空集合)。 |
| `@NotBlank` | 限制字串 **不能為 `null`,也不能是空字串或只有空白**。 |
| `@Size` | 限制字串、集合、陣列的長度/大小,例如 `@Size(min=1, max=10)`。 |
| `@Max` | 限制數值型欄位的 **最大值**,例如 `@Max(100)`。 |
| `@Min` | 限制數值型欄位的 **最小值**,例如 `@Min(18)`。 |
| `@Email` | 驗證字串是否符合 **電子郵件格式**。 |
```java
@Data
public class MemberBean {
@NotBlank(message = "學號 - 未填寫")
@Size(max = 10, message = "學號 - 長度不可超過{max}")
private String memberId;
@NotNull(message = "年級 - 未填寫")
private Integer gradeId;
@NotBlank(message = "姓名 - 未填寫")
@Size(max = 10, message = "姓名 - 長度不可超過{max}")
private String name;
@NotBlank(message = "信箱 - 未填寫")
@Size(max = 50, message = "信箱 - 長度不可超過{max}")
@Email
private String email;
@NotNull(message = "性別 - 未填寫")
private String gender;
@Null(message = "性別名稱 - 不得填寫")
private String gradeName;
private MultipartFile file;
private String filePath;
}
```
### Transformer(Impl)
前面提到Bean是用於前端與後端交換資料
那存入資料庫要使用Entity型別
Bean轉換成Entity自然就需要一個transform(轉換),因此要做轉換器(transformer)
繼承BeanEntityTransformer接口,裡面裝的是Bean與Entity
```java
public interface MemberTransformer extends BeanEntityTransformer<MemberBean, Member> {
}
```
並完成Impl,需加入@Component註解
| 名詞 | 解釋 |
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------
| `@Component` | **通用元件註解**,任何想交給 Spring 容器管理的類別都可以加上 `@Component`。`@Repository`、`@Service`、`@Controller` 其實都是 `@Component` 的特殊化。 |
```java
@Component
public class MemberTransformerImpl implements MemberTransformer {
@Nonnull
@Override
public Member transferToEntity(@Nonnull MemberBean memberBean) {
return JavaBeanUtils.copy(memberBean, new Member());
}
@Nonnull
@Override
public MemberBean transferToBean(@Nonnull Member member) {
return JavaBeanUtils.copy(member, new MemberBean());
}
}
```
### Service(Impl)
繼承BaseService接口並傳入Bean與PK型別
```java
public interface MemberService extends BaseService<MemberBean, String> {
}
```
並完成Impl
繼承BaseServiceImpl傳入Bean, Entity, PK型別,並實作(implements)前面的Service
| 名詞 | 解釋 |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `@Service` | **業務邏輯層 (Service Layer)** 的註解,通常放在處理商業邏輯的類別。語意上代表該類別負責 **服務/業務邏輯**。 |
```java
@Service
public class MemberServiceImpl extends BaseServiceImpl<MemberBean, Member, String> implements MemberService {
private final MemberDAO memberDAO;
private final MemberTransformer transformer;
public MemberServiceImpl(MemberDAO memberDAO, MemberTransformer transformer) {
super(memberDAO, transformer);
this.memberDAO = memberDAO;
this.transformer = transformer;
}
// 將MemberBean轉換成Member並透過DAO儲存,使用Member型別接收儲存後的Member並回傳MemberBean型別
@Override
public MemberBean save(MemberBean memberBean) {
Member member = memberDAO.save(transformer.transferToEntity(memberBean));
return transformer.transferToBean(member);
}
}
```
### Controller
| 名稱 | 解釋 |
| ----------------------------------- | --------------------------------------------------------------------------------------------------------- |
| `@AllArgsConstructor` | **Lombok 註解**,自動產生包含所有欄位的建構子(Constructor)。通常用來讓 Spring **建構子注入依賴 (Constructor Injection)** 更方便。 |
| `@RestController` | **Spring MVC 註解**,相當於 `@Controller + @ResponseBody`,表示這個類別會提供 RESTful API,回傳的物件會自動序列化成 JSON。 |
| `@RequestMapping(path = "/member")` | **Spring MVC 註解**,用來定義控制器的基礎路徑。這樣該 Controller 裡的所有 API 都會以 `/member` 開頭,例如 `/member/list`、`/member/{id}`。 |
```java
@AllArgsConstructor
@RestController
@RequestMapping(path = "/member")
public class MemberController {
private final MemberService memberService;
/**
* 實作全查
* ArrayData 表示 JSON 中的陣列 []
* ObjectData 表示 JSON 中的物件 {}
* 因此這裡的回傳會類似
*
* @return
* [
* {
* "memberId": "001",
* "name": "小明",
* "email": "001@gmail.com"
* },
* {
* "memberId": "002",
* "name": "蔡先生",
* "email": "002@gmail.com"
* }
* ]
*/
@GetMapping("/")
public ResponseEntity<String> searchAllMember() {
ArrayData data = new ArrayData();
for (MemberBean memberBean : memberService.searchAll()) {
ObjectData objectData = data.addObject();
objectData.add("memberId", memberBean.getMemberId());
objectData.add("name", memberBean.getName());
objectData.add("email", memberBean.getEmail());
}
return ResponseEntityBuilder.success()
.data(data)
.message("查詢成功")
.build();
}
}
```