JPA에 대해 심화 내용을 학습하던 중, RDBMS를 사용하면서도
FK를 가지지 않는 회사들이 많다는 내용을 확인했다.
나도 겪었던 문제지만, post_id를 fk로 가지는 comment 를 작성할 때
post가 없을 때에는 comment가 작성되지 않는다.
이게 단순히 1차에서 끝나는 것이 아니라, post_id에는 user_id가 필요하고
또 첨부파일이 있으면 file_id 도 필요하고 등등등등..
이게 길어지다 보면 테스트하는데 빠른 개발이 불가하다는 것이다.
현재 내가 작성 중인 프로젝트에서 어떻게 FK를 뺄까? 를 먼저 고민했다.
override fun searchByNickname(nickname: String, pageable : org.springframework.data.domain.Pageable): Page<PostEntity> {
// 이거도 총 개수부터
val totalCount = queryFactory
.select(post.count())
.from(post)
.join(post.user, user)
.where (user.nickname.containsIgnoreCase(nickname))
.fetchOne() ?: 0L
// 페이지에 몇번째?
val pageCount = queryFactory
.select(post.count())
.from(post)
.join(post.user, user)
.where (user.nickname.containsIgnoreCase(nickname))
.offset(pageable.offset)
.limit(pageable.pageSize.toLong())
.fetch()
return PageImpl(pageCount, pageable, totalCount)
}
내가 바꿔보기로 한 코드는 searchByNickname으로,
닉네임으로 글을 검색하는 기능이다.
현재는 post에 user가 연관관계로 설정되어 있기 때문에
join을 통해 user data를 가져올 수 있는 상태이다.
먼저, 연관 관계를 해제해야 하기 때문에 아래와 같이 createPost를 변경했다.
override fun createPost(request: CreatePostRequest, userPrincipal: UserPrincipal): PostResponse {
val user = userRepository.findByIdOrNull(userPrincipal.id)
?: throw UserNotFoundException (userPrincipal.id)
return postRepository.save(PostEntity(
title = request.title,
content = request.content,
condition = request.condition,
user_id = user.id
user_nickname = user.nickname // jwtconfig도 수정해야 함
)
).toResponse()
}
user_id = user.id
를 통해 로그인한 유저의 토큰 인증 정보를 통해 id를 가져와서,
post 테이블에 저장할 때 user_id도 함께 저장하도록 설정했다.
원래는 user = user으로 끝났다.
val user = userRepository.findByNickname(nickname)
if (user != null) {
val totalCount = queryFactory
.select(post.count())
.from(post)
.where(post.nickname.eq(nickname))
.fetchOne() ?: 0L
1) 간단하다.
1) post는 post대로, user는 user대로 가져온다.
연관 관계를 맺지 않으므로 어떤 user가 작성한 post인지 확인할 수 없다.
2) 두 번의 데이터베이스 접근이 필요하다.
user에서 user찾고, post에서 post찾고.
쿼리가 두 개씩 발생한다.
val subQuery = JPAExpressions
.select(user.nickname)
.from(user)
.where(user.nickname.eq(nickname))
val totalCount = queryFactory
.select(post.count())
.from(post)
.where(post.nickname.in(subQuery))
.fetchOne() ?: 0L
1) 한 쿼리 내에서 두 테이블을 조회할 수 있다.
1) 복잡하다. (구현하지 못했다)
2) 가독성이 좋지 않다.
val userAndPost = QUserAndPostEntity.userAndPostEntity
val totalCount = queryFactory
.select(userAndPost.count())
.from(userAndPost)
.where(userAndPost.nickname.eq(nickname))
.fetchOne() ?: 0L
1) 없는 것 같다..
1) 지금 걸 바꾸는게 아니라 UserEntity, PostEntity 모든걸 바꿔야 한다.
2) 사실상 의미가 없는 것이 아닐까 ?
만약 FK를 사용하지 못할 경우에는 1번이 가장 좋은 방법일 것 같다.
하지만 관계형 데이터베이스를 사용하는데 FK를 안 쓸 이유가 대체 무엇일까? 라는 생각이 들었다.
개발할 때 FK로 인해서 테스트에 제약이 걸린다면 FK를 걸지 않은 상태에서 개발하고
마지막 론칭 시점 등에 FK를 걸어주면 되는 거 아닐까?
서비스 확장에 다소 불편함이 있을 수 있다는 것은 이해하지만
개발을 조금 더 쉽게 하자고 더 큰 불편함을 감수하는 것은 아닐까..
추가
FK를 쓰지 않는다고 객체지향이 아닌 것이 아니다 !!!!
db에서만 fk를 안쓰는거지 join은 문제 없다