# BIRC後端專案開發流程 ## 開發前置 ### Gitlab 1. 通知負責人把你拉入該專案的GitLab 2. 會在Project看到一個項目 ![image](https://hackmd.io/_uploads/rkungT1ixe.png) 3. 進入該項目並點選分支(fork) ![image](https://hackmd.io/_uploads/Hyl0xpyjxx.png) 4. 選擇自己帳號 ![image](https://hackmd.io/_uploads/HyAbZ6yjll.png) 5. 回到Project會看到多一個項目(標籤為Master) ![image](https://hackmd.io/_uploads/SyjrZpksxl.png) 6. 進入該項目並複製HTTPS(要確認該網址中有包含你的帳號,附圖為"112...") ![image](https://hackmd.io/_uploads/BkV_-Tysel.png) 7. 開啟fork並點選File -> Clone ![image](https://hackmd.io/_uploads/BkxRbpkixg.png) 8. 如圖(需確認Url中包含自己GitLab帳號) ![image](https://hackmd.io/_uploads/r1LNQp1sex.png) 9. 在左側建立一個remote ![image](https://hackmd.io/_uploads/ryDt7Tkogl.png) 10. 通常命名為upstream,Url填入GitLab中最初的那個項目的HTTPS(該Url不得包含自己帳號!) ![image](https://hackmd.io/_uploads/Syn67ayixl.png) ![image](https://hackmd.io/_uploads/Sybk4aJsll.png) 11. 確保自己為最新進度 *** ### 資料庫 1. 填寫防火牆許可,[相關連結](https://docs.google.com/forms/d/e/1FAIpQLSdj7jBY3e8X1soCqxxw8FsgDAmYBjeq2UjP0BBlM8UqvpQPVA/viewform) 2. 系網群收到表單後會有負責人傳送資料庫連線資訊給你(Url、Account、Password) *** ### 專案配置檔(.yml) 1. 複製一份application-local-example.yml並命名為application-local.yml ![image](https://hackmd.io/_uploads/ryIYST1oxl.png) 2. 將資料庫連線資訊填入,並將其他參數設定好 3. 確認build.gradle中的processResources設定 ![image](https://hackmd.io/_uploads/S1cZ8pkole.png) 需要將裡面的程式碼註解掉才能開啟專案(包檔時需將註解刪除!) ![image](https://hackmd.io/_uploads/BktmIT1sxe.png) ## 開發過程 ### 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(); } } ```