이전에 https://imbay.tistory.com/331, https://imbay.tistory.com/97 에서 BaseEntity와 Repository - Service - Controller 개념은 작성했었지만, 시간이 좀 지나서 다시 순서를 되짚어보려고 한다.
(이전에 면접에서 API 개발시 첨부터 끝까지 말로 코딩을 해보라 하셔서,, 갑자기 머리가 하얘져서 잘 말하지 못한 것 같아 다시 순서 되새겨봅니다,,)
1. BaseEntity 클래스 생성
1-1. 각 도메인마다 엔티티 클래스를 생성하기 전에, 중복되는 컬럼은 BaseEntity에 미리 만들어 놓는다.
예로 create나 update된 날짜는 각 테이블마다 존재하기 때문에 BaseEntity에 createDate과 updateDate 컬럼을 만들어준다.
예시)
@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity {
@Column(updatable = false)
@CreatedDate
private LocalDateTime createdDate;
@LastModifiedDate
private LocalDateTime updatedDate;
@PrePersist
protected void onCreate() {
if (createdDate == null) {
createdDate = LocalDateTime.now();
}
updatedDate = LocalDateTime.now();
}
@PreUpdate
protected void onUpdate() {
updatedDate = LocalDateTime.now();
}
}
- @MappedSuperclass
- BaseEntity 상속받는 엔티티가 아래 필드를 자신의 컬럼으로 포함하도록 설정 (이 클래스 자체는 테이블로 매핑되지 않음)
- @Column(updatable = false)
- 한번 필드 저장되면 업데이트 되지 않도록
- @CreatedDate
- AuditingEntityListener 활성화되어야 동작 (@EntityListeners(AuditingEntityListener.class))
- @LastModifiedDate
- 엔티티 수정시 현재 시간 자동 설정
2. 도메인별 엔티티 클래스 생성
2-1. 각 도메인마다 BaseEntity를 상속받은 엔티티 클래스를 생성해준다.
예시) public class LessonEntity extends BaseEntity {
2-2. 클래스 위에 어노테이션을 주입한다.
예시)
@Entity
@Table(name = "lesson")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Builder
@AllArgsConstructor
@DynamicInsert
public class LessonEntity extends BaseEntity {
- @Entity
- 해당 클래스가 DB 테이블과 매핑되는 엔티티임을 선언
- JPA를 통해 관리
- @Table(name = "테이블이름")
- 해당 엔티티가 매핑될 DB 테이블 이름 지정
- 테이블 이름을 클래스 이름과 다르게 지정할 때 사용
- @Getter
- Lombok 어노테이션으로, 모든 필드에 대한 getter 메소드를 자동 생성
- @NoArgsConstructor(access = AccessLevel.PROTECTED)
- Lombok 어노테이션으로 매개변수가 없는 기본 생성자 생성
- @Builder
- Lombok 어노테이션으로, 빌더 패턴을 사용하여 객체 생성 가능
- @AllArgsConstructor
- Lombok 어노테이션으로, 클래스의 모든 필드를 매개변수로 받는 생성자 자동 생성
- @DynamicInsert
- Hibernate 어노테이션으로, INSERT SQL 생성시 값이 할당된 필드만 포함되도록 설정
- 값이 없는 필드에 대해 기본값을 테이블에서 설정 가능
- 예로 필드에 @ColumnDefault(”0”)과 @Column(nullable = false) 사용할 때 선언
2-3. 각 테이블마다 컬럼을 엔티티의 필드로 작성해준다.
예시)
public class LessonEntity extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long lessonId;
@ManyToOne
@JoinColumn(name = "host_id", nullable = false)
private HostEntity hostEntity;
@OneToOne
@JoinColumn(name = "category_id", nullable = false)
private CategoryEntity categoryEntity;
@Column(nullable = false, length = 30)
private String title;
@Column(nullable = false)
private String location;
...생략...
@ColumnDefault("0")
@Column(nullable = false)
private int applicantSum;
@ColumnDefault("0")
@Column(nullable = false)
private boolean isDeleted;
// 강좌신청 누적인원 업데이트
public void updateApplicantSum(int applicantSum) {
this.applicantSum = applicantSum;
}
- @Id
- 해당 필드가 DB 테이블의 기본키
- @GeneratedValue(strategy = GenerationType.IDENTITY)
- MySQL의 AUTO_INCREMENT 같이 기본키 자동 생성
- @ManyToOne, @OneToOne
- 테이블 다대일, 일대일 관계시 매핑
- @JoinColumn(name = "외래키 컬럼 이름", nullable = false)
- name = 은 헤당 테이블에 생성될 외래키 컬럼 이름 지정
- nullable = false 은 외래키 컬럼 NULL 허용 안함
- @Column(nullable = false, length = 30)
- 해당 필드는 컬럼으로 매핑됨을 지정
- @ColumnDefault("기본값지정")
- Hibernate 어노테이션으로, 해당 필드의 기본값 지정
- 필드의 값이 지정되지 않으면 해당 값으로 적용
3. 도메인별 Repository 인터페이스 생성
3-1. 도메인별로 JpaRepository를 상속한 Repository 인터페이스를 생성한다.
상속할 때 해당 엔티티클래스와 Long을 제네릭 타입으로 지정한다.
예시) public interface LessonRepository extends JpaRepository<LessonEntity, Long> {
3-2. 인터페이스에 @Repository 어노테이션을 주입한다.
예시)
@Repository
public interface LessonRepository extends JpaRepository<LessonEntity, Long> {
3-3. JPA, JPQL, Native SQL 중 필요에 따라 사용하여 메소드를 작성한다. (https://imbay.tistory.com/312 참고)
- Optional(Wrapper 클래스)를 사용하면 NullPointerException을 방지할 수 있다.
- 예시) Optional<LessonDateEntity> findLessonDateEntityByLessondateId(Long lessondateId);
- JPA 사용자 함수
- findBy + 엔티티 + 필드
- find + 엔티티 + By + 엔티티
- And, Or을 메소드 이름에 추가 가능
- Desc, Asc 추가 가능
- First5, Last5 같이 n개로 개수 제한 가능
- JPQL
- DB에 직접적인 쿼리를 실행하지 않으며 엔티티 테이블에 매핑하여 사용
- @Query(value=”SQL 문 작성”) 어노테이션 추가
- FROM 절 뒤에는 엔티티 클래스 이름을 넣어준다. (테이블명이 아닌 클래스이름!)
- 예시)
- @Query(value = "SELECT m FROM MemberEntity m WHERE m.userId = :userid")
- List<MemberEntity> findByUserId_JPQL_Query(String userid);
- Native SQL
- DB에 직접적으로 쿼리를 실행시켜 속도가 빠르다. 여러 조인이나 복잡한 쿼리 작성시 적합
- @Query(value=”SQL 문 작성”, nativeQuery = true) 어노테이션을 추가
- 추가로 UPDATE, INSERT, DELETE 문은 @Modifying, @Transactional 어노테이션을 추가해야 한다.
- 예시)
-
@Query(value = "SELECT * FROM member WHERE user_id = :userid", nativeQuery = true) List<MemberEntity> findByUserId_nativeQuery(String userid); @Modifying @Transactional @Query(value = "UPDATE member SET user_id = :userid where id = :id", nativeQuery = true) int updateById_nativeQuery(Long id, String userid);
4. 도메인별 Service 인터페이스 생성
나는 구현은 따로 Impl 클래스에서 하고, API 목록을 한눈에 볼 수 있도록 인터페이스 생성을 먼저 해주었다.
예시)
public interface LessonService {
// 개설 클래스 관리- 개설 클래스 목록 출력
List<OpenedLessonsResDto> openedLessons(Long userId);
// 클래스 상세보기
LessonInfoResDto lessonInfo(Long userId, Long lessonId);
// 클래스 등록하기
void createLesson(Long userId, CreateLessonReqDto createLessonReqDto);
// 클래스 전체 조회 (클래스 탐색)
List<FullLessonResDto> fullLesson();
5. 도메인별 dto 패키지 생성
나는 값 전달은 대부분 DTO 클래스로 전달해주었다.
Map, List 등등 전달할 수 있는 방법은 다양하지만 수정할 때 클래스로 찾는게 더 편해서 이렇게 사용했다.
도메인별로 dto 패키지 생성해주고, 그 안에 request와 response 패키지를 생성했다.
예시)

6. 도메인별 exception 클래스 생성
6-1. CustomException을 상속받아 exception 클래스를 생성한다.
메시지나 상태코드가 부여도록 메소드를 작성한다.
예시)
public class LessonNotFoundException extends CustomException {
static String MESSAGE = "LESSON_NOT_FOUND";
public LessonNotFoundException() {
super(MESSAGE);
}
@Override
public HttpStatus getHttpStatus() {
return HttpStatus.NOT_FOUND;
}
}
7. 도메인별 Service 클래스 생성
7-1. Service 인터페이스를 상속한 ServiceImpl 클래스를 생성한다.
예시) public class LessonServiceImpl implements LessonService {
7-2. 클래스에 @Service, @ResuiredArgsConstructor 어노테이션을 주입한다.
7-3. 필요한 Repository를 private final로 의존성 필드를 작성한다.
예시) private final LessonDateRepository lessonDateRepository;
7-4. Service 인터페이스에서 작성한 메소드를 @Override하여 메소드를 구현한다.
각 메소드마다 @Transactional 어노테이션을 줘야 한다. (트랜잭션 단위로 실행)
메소드 구현은 JPA 함수, Repository에서 만든 메소드, custom한 exception 등을 사용하고,
필요한 dto 클래스 생성, stream() 함수, 빌더 등등 사용해서 메소드를 구현한다.
- JPA 기본함수 종류
- findAll()
- 테이블 전체조회
- findBy열이름()
- 테이블 조건맞춰 조회
- save()
- 테이블 값 추가, 업데이트
- delete()
- count()
- findAll()
8. 도메인별 Controller 클래스 생성
8-1. @RestController, @RequiredArgsConstructor 어노테이션을 주입한 Controller 클래스를 생성한다.
도메인별 기본 URI가 있다면, @RequestMapping(”경로”) 어노테이션을 주입한다.
8-2. Service 를 private final로 의존성 필드를 작성한다.
예시) private final LessonService lessonService;
8-3. POST/GET/DELETE/PUT에 맞춰 경로를 설정하여 메소드를 작성한다. (https://imbay.tistory.com/317 참고)
메소드에 @POST/GET/DELETE/PUTMapping(”경로”) 어노테이션을 주입한다.
나는 Reponse 클래스를 따로 만들어서 해당 값에 맞춰 return 하였다.
예시)
// 개설 클래스 관리- 개설 클래스 목록 출력
@GetMapping("/reservation/my/opened")
public ResponseEntity<ApiResponse> openedLessons(@AuthenticationPrincipal Long userId) {
List<OpenedLessonsResDto> openedLessons = lessonService.openedLessons(userId);
return ResponseEntity.ok(new ApiResponse<>(true, "ok", openedLessons));
}
// 개설 클래스 상세
@GetMapping("/reservation/my/opened/{lessonId}")
public ResponseEntity<ApiResponse> lessonDetail(@PathVariable Long lessonId) {
List<LessonDetailResDto> lessonDetails = lessonDateService.lessonDetail(lessonId);
return ResponseEntity.ok(new ApiResponse<>(true, "ok", lessonDetails));
}
// 클래스 이미지 업로드
@PostMapping("/lesson/image-upload")
public ResponseEntity<String> uploadImage(@RequestParam("file") MultipartFile file) {
try {
String imageUrl = s3UploadService.saveFile(file);
return ResponseEntity.ok(imageUrl);
} catch (IOException e) {
return ResponseEntity.status(500).body("파일 업로드 중 오류가 발생했습니다.");
}
}
// 클래스 등록하기
@PostMapping("/lesson/create")
public void createLesson(@AuthenticationPrincipal Long userId, @RequestBody CreateLessonReqDto createLessonReqDto) {
lessonService.createLesson(userId, createLessonReqDto);
}
- @PathVariable
- URL 경로에서 데이터를 추출하는데 사용
- 예시) /reservation/{lessonId} ⇒ /reservation/123
- @RequestParam
- URL의 쿼리 문자열에 데이터 포함시킬 때 사용
- 필터링, 검색, 페이징 처리 등 파라미터 전달 시 사용
- URL 뒤에 ?key=value 형식으로 추가
- 예시) /reservation?userId=1&status=active
- @RequestBody
- DTO 클래스를 사용하여 JSON 형식으로 전송
- @AuthenticationPrincipal
- 현재 인증된 사용자 정보 전달
- @POST/GET/DELETE/PUTMapping(”경로”)
- HTTP POST/GET/DELETE/PUT 요청 처리
다음은 React-TypeScript로 REST API 연동하는 순서를 작성해보겠습니다.
'Back-end > Spring Boot' 카테고리의 다른 글
[Spring Boot] AWS S3를 사용한 이미지 업로드 방법 (0) | 2024.06.27 |
---|---|
[Spring Boot] Repository - Service - Controller (0) | 2024.04.21 |
[Spring Boot] Scheduler (0) | 2024.04.13 |
[Spring Boot] TDD (0) | 2024.04.13 |
[Spring Boot] MyBatis (0) | 2024.04.13 |