저번 과제는 JDBC를 이용한 일정관리 앱을 만드는거였는데 이번 과제는 JPA를 이용하여
일정관리 API를 만드는 것이다. 이번에는 한 단계 한 단계 차근차근 나가보려고한다.
일단 1단계인 일정 CRUD의 요구사항은 아래와 같다.
- 키워드
- @Entity: 데이터베이스 테이블과 매핑되는 클래스에 사용합니다.
- @Id: 해당 필드를 기본 키로 지정합니다.
- @GeneratedValue: 기본 키 생성 전략을 설정합니다.
- @Repository: DAO(Data Access Object) 클래스에 사용하여 데이터 접근을 명시합니다.
- 어노테이션
- 일정을 저장, 조회, 수정, 삭제할 수 있습니다.
- 일정은 아래 필드를 가집니다.
- 작성 유저명, 할일 제목, 할일 내용, 작성일, 수정일 필드
- 삭제의 경우
- 일정을 삭제할 때 일정의 댓글도 함께 삭제
- 이 때, JPA의 영속성 전이 기능을 활용
Todo (일정)패키지의 구조는 아래처럼 만들었다.
TodoController
@Valid는RequestDto에 들어오는 객체들을 검증하기 위하여 붙여주었다.
@RestController
@RequestMapping("/api/todo")
@RequiredArgsConstructor
public class TodoController {
private final TodoService service;
@GetMapping()
public ResponseEntity<List<TodoResponseDto>> todoFindAll(){
List<TodoResponseDto> todo = service.todoFindAll();
return ResponseEntity.ok(todo);
}
@GetMapping("/{id}")
public ResponseEntity<TodoResponseDto> todoFindById(@PathVariable("id") Long id){
Todo todo = service.todoOne(id);
return ResponseEntity.ok(new TodoResponseDto(todo));
}
@PostMapping()
public ResponseEntity<TodoResponseDto> todoCreate(@RequestBody @Valid TodoRequestDto reqDto){
TodoResponseDto createTodo = service.todoCreate(reqDto);
return ResponseEntity.status(HttpStatus.CREATED).body(createTodo);
}
@PutMapping("/{id}")
public ResponseEntity<Objects> todoModify(@PathVariable("id")Long id, @RequestBody ModifyDto modifyDto){
service.todoModify(id,modifyDto);
return ResponseEntity.status(HttpStatus.OK).build();
}
@DeleteMapping("/{id}")
public void todoDelete(@PathVariable("id") Long id){
service.todoDelete(id);
}
}
DTO
@Getter
public class ModifyDto {
private String title;
private String userName;
private String content;
}
@Getter
public class TodoRequestDto {
@NotBlank
private String title;
@NotBlank
private String userName;
@NotBlank
private String content;
}
@Getter
public class TodoResponseDto {
private final Long id;
private final String title;
private final String userName;
private final String content;
private final LocalDateTime createdAt;
private final LocalDateTime modifiedAt;
public TodoResponseDto(Todo todo){
this.id = todo.getId();
this.title = todo.getTitle();
this.userName = todo.getUserName();
this.content = todo.getContent();
this.createdAt = todo.getCreatedAt();
this.modifiedAt = todo.getModifiedAt();
}
}
Todo
댓글과 1:N 관계를 형성하기 위하여 미리 OneToMany 어노테이션을 이용하였다.
@Entity
@Table(name = "todo")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class Todo extends AuditingDate {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String title;
@Colum(nullable = false)
private String userName;
@Column(nullable = false)
private String content;
@OneToMany(mappedBy = "todo", cascade = {CascadeType.PERSIST,CascadeType.REMOVE})
private List <Comment> commentList = new ArrayList<>();
public static Todo from(String title, String content, String userName) {
Todo todo = new Todo();
todo.init(title,content, userName);
return todo;
}
private void init(String title, String content, String userName){
this.title = title;
this.userName = userName;
this.content = content;
}
public void modify(String title, String content, String userName) {
this.title = title;
this.userName = userName;
this.content = content;
}
}
TodoService
@Service
@RequiredArgsConstructor
public class TodoService{
private final TodoRepository todoRepository;
public List<TodoResponseDto> todoFindAll() {
return todoRepository.findAll().stream().map(TodoResponseDto :: new).toList();
}
@Transactional
public TodoResponseDto todoCreate(TodoRequestDto reqDto) {
Todo todo = Todo.from(reqDto.getTitle(),reqDto.getContent());
return new TodoResponseDto(todoRepository.save(todo));
}
@Transactional
public void todoModify(Long id, ModifyDto modifyDto) {
Todo findTodo = isValidId(id);
findTodo.modify(modifyDto.getTitle(),modifyDto.getContent());
}
@Transactional
public void todoDelete(Long id) {
isValidId(id);
todoRepository.deleteById(id);
}
public Todo todoOne(Long id) {
return isValidId(id);
}
private Todo isValidId(Long id) {
return todoRepository.findById(id).orElseThrow(()-> new IllegalArgumentException("존재하지 않는 Todo 번호"));
}
}
이렇게 1단계인 일정 CRUD를 해봤는데 과제를 하기 위해 JPA 강의 한번 스윽 보고 한거라 개념적으로 많이
부족하다는것을 느꼈다. 이번에는 기본생성자의 access 레벨을 Private이 아닌 Protected로 하였는데
@NoArgsConstructor에서 private 접근 제어자를 사용하면 JPA가 제대로 작동하지 않을 수 있다는 이슈가 있다.
그렇기에 JPA에서는 Public 또는 Protected 접근 제어자를 가져야하는데 캡슐화와 객체의 올바를 사용을 위해
Public 사용보다는 Protected 사용을 지향하는게 좋을 것 같아서 Protected로 하였다.
아직 주어진 과제의 내용이 많지만 차근차근 다 해보려고한다. (목요일까지 되겠지...?)
또한 그냥 대충 쓸줄 안다고해서 그냥 넘어가버리면 안될 것 같다. 오래오래 기억하도록 복습 또 복습을 해야겠다
'TIL' 카테고리의 다른 글
JPA - Entity (1) | 2024.10.15 |
---|---|
JPA - 댓글 CRUD (2) | 2024.10.14 |
JPA (1) | 2024.10.10 |
IoC & DI (1) | 2024.10.07 |
ResponseEntity (0) | 2024.10.04 |