개발 낙서장

[TIL] 내일배움캠프 48일차 - FK 제약 조건 본문

Java/Sparta

[TIL] 내일배움캠프 48일차 - FK 제약 조건

권승준 2024. 3. 5. 22:01

 

 

오늘의 학습 키워드📚

FK 제약 조건

Entity에서 ManyToOne, JoinColumn 어노테이션을 통해 참조 테이블을 설정하면 자동으로 해당 테이블의 PK가 FK로 등록이 되고 제약 조건까지 생긴다.

FK의 제약 조건이 걸려있으면 장점들도 있다. 데이터 무결성, 참조 무결성을 크게 신경쓰지 않아도 컴파일러 단계에서 걸러내어 지킬 수 있고 데이터를 일관되게 저장할 수 있고 테이블 간 분석이 직관적일 수 있다.

하지만 많은 부분에서 단점으로 작용한다고 생각한다.

먼저 DB의 유연성이 매우 떨어지게 된다. 제약 조건이라는 것은 결국 뭔가 행동할 때 신경 써야 할 부분이 생긴다는 것이고 이는 확장성, 유연성이 중요한 개발에서 큰 단점으로 작용한다.
예를 들어 User를 참조하는 Comment 테이블이 있다고 가정하자. 1번 유저를 참조하는 Comment가 100개가 있다고 했을 때 1번 유저가 회원 탈퇴를 하면? 먼저 해당 유저의 댓글을 전부 삭제할지 말지도 신경써야 하는데 삭제한다고 하면 회원 탈퇴를 하면서 댓글들을 전부 삭제해줘야 해서 성능적으로 문제가 생길 수 있고, 삭제하지 않는다고 한다면 참조 무결성을 위배하게 된다.

두 번째로 테스트 코드 작성이 매우매우 불편해진다. 참조 무결성이라는 원칙 때문에 존재하지 않는 칼럼을 참조하려 한다면 컴파일러에서 걸러내기에 테스트할 때는 모든 필요한 데이터를 전부 넣어주고 테스트해야 한다.

    @Test
    @DisplayName("Create Comment")
    void createComment() {
        // given
        User user = new User("abc123@naver.com", "abc123", "abc12345", UserRoleEnum.USER);

        Long todoId = 100L;
        Todo todo = new Todo(TodoRequestDto.builder().todoName("Todo 이름")
            .content("Todo 내용").build(), user);
        todo.setTodoId(todoId);
        given(todoRepository.findById(todoId)).willReturn(Optional.of(todo));

        String content = "댓글 내용";

        // when
        CommentResponseDto responseDto = commentService.createComment(todoId,
            CommentRequestDto.builder().content(content).build(),
            user);

        // then
        assertNotNull(responseDto);
        assertEquals(content, responseDto.getContent());
        assertEquals(user.getUserName(), responseDto.getUserName());
        assertEquals(todo.getTodoName(), responseDto.getTodoName());
    }

User 테이블, User를 참조하는 Todo 테이블, User와 Todo를 참조하는 Comment 테이블이 있는데 CreateComment를 테스트하는 코드이다.
위처럼 Comment 하나를 추가하려는 것 뿐인데 참조 무결성 때문에 User와 Todo를 만들어 테스트를 해줘야 하는 번거로움이 발생한다.

그럼 어떻게 없애는데?

생각보다 아주아주 간단하다.

@Entity
@Getter
@Setter
@NoArgsConstructor
@Table(name = "comment")
public class Comment extends Timestamped {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long commentId;

    @Column(nullable = false)
    private String content;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id", nullable = false, foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
    private User user;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "card_id", nullable = false, foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
    private Todo todo;

    public Comment(CommentRequestDto requestDto, User user, Todo todo) {
        this.content = requestDto.getContent();
        this.user = user;
        this.todo = todo;
    }
}

JoinColumn을 해주면서 foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT) 옵션을 추가해주면 된다.
그러면 해당 칼럼은 해당 테이블을 참조하지만 실제로 제약 조건은 걸려있지 않은 것이다.

실제 DB에도 FK가 아닌 일반 칼럼으로 저장이 된다.

    @Test
    @DisplayName("Comment 생성")
    void createComment() {
        Comment comment = new Comment();
        String content = "댓글 내용";
        comment.setContent(content);
        comment.setUser(User.builder().userId(100L).build());
        comment.setTodo(Todo.builder().todoId(100L).build());
//        comment.setUser(user);
//        comment.setTodo(todo);

        Comment createdComment = commentRepository.save(comment);

        assertNotNull(createdComment);
        assertEquals(content, createdComment.getContent());
//        assertEquals(user, createdComment.getUser());
//        assertEquals(todo, createdComment.getTodo());
    }

해당 코드를 테스트하려면 원래는 참조 제약 조건이 있었으므로 테스트용 User 객체와 Todo 객체를 Repository에 저장한 다음에 해당 객체를 참조 객체로 사용해야 했었지만 이젠 그러지 않아도 된다.


오늘의 회고💬

JPA 심화 강의가 있는데 강의가 잘 안 잡힌다. 강의 스타일도 마음에 안 들고 내용도 별로 재미가 없어서 그냥 다른 추가적인 공부만 하고 있는 것 같다.

 

내일의 계획📜

강의를 듣긴 들어야 하니까 듣겠지만... 아마 내일도 개인적인 공부를 하지 않을까 싶다.

Comments