https://github.com/99duuk/woosan-JPA-to-MyBatis
| 도입
우산 프로젝트에는 JPA를 사용했다.
프로젝트 설계 단계에서부터 최대한 간단하게 ERD를 그리고, QueryDSL과 MyBatis의 사용은 배제했고, 쿼리 작성 없이 Spring data JPA로 기능을 구현했다.
굳이 전환할 필요는 없었지만 프로젝트를 되돌아보니, 코드를 작성하며 "이거 그냥 쿼리 쓰면 금방인데..." 라고 떠올렸던 순간이 퍽 많이 떠올랐다.
프로젝트를 마무리한 뒤, 내가 작성했던 기능을 JPA에서 MyBatis로 전환해보았고, 전환하면서 겪은 차이점과 느낀 점을 작성해본다.
| 구조적 차이
ORM vs SQL 매퍼
JPA는 객체-관계 매핑(ORM)을 사용하여 자바 엔티티 클래스를 데이터베이스 테이블에 매핑한다. 이를 통해 데이터베이스 작업을 자바 객체를 통해 추상화할 수 있다.
반면, MyBatis는 SQL 매퍼로, SQL 쿼리를 직접 작성하고 DTO(Data Transfer Object)를 통해 결과를 매핑한다. JPA에서는 엔티티 클래스를 정의하는 반면, MyBatis에서는 DTO를 사용하여 데이터베이스와의 상호작용을 처리한다.
| 쿼리 작성 방식
JPA의 메서드 이름 기반 쿼리 생성
인터페이스 메서드 선언을 통해 쿼리를 정의한다. 예를 들어, findById 같은 메서드 이름을 사용하면 자동으로 "SELECT * FROM member WHERE id = ?"와 같은 쿼리가 생성된다.
MyBatis의 XML 기반 SQL 작성
MyBatis에서는 XML 파일에 SQL 쿼리를 직접 작성한다. SQL의 세부 사항을 직접 제어할 수 있다는 점에서 유리하며, 동적 SQL을 작성할 때도 XML에서 if, choose 등의 태그를 사용하여 조건에 따른 SQL을 생성할 수 있다.
| 성능과 최적화
복잡한 쿼리에서의 제어
복잡한 쿼리의 경우, MyBatis는 SQL을 직접 작성하기 때문에 더 세밀한 제어가 가능하다. 반면, JPA는 SQL을 자동으로 생성하기 때문에 예상치 못한 쿼리가 실행될 수 있으며, 성능 최적화가 어려울 수 있다.
지연 로딩 vs 명시적 조인
JPA는 지연 로딩(Lazy Loading)이라는 개념을 통해 필요한 시점에 데이터를 로드한다. 이는 성능을 최적화하는 데 도움이 되지만, 때로는 N+1 문제를 야기할 수 있다. MyBatis에서는 명시적으로 조인을 정의하여 이러한 문제를 피할 수 있다.
| 프로젝트 적합성
JPA가 적합한 경우
JPA는 데이터베이스 작업이 비교적 단순하고, 객체 지향적 설계를 유지하고자 할 때 매우 적합하다. 또한, 데이터베이스 스키마 변경이 빈번하지 않으며, 쿼리 성능이 큰 문제가 되지 않을 때 JPA를 사용하는 것이 유리하다
MyBatis가 유리한 경우
MyBatis는 복잡한 쿼리와 성능 최적화가 중요한 프로젝트에서 더 유리하다. SQL 작성이 곧바로 코드와 연결되기 때문에, SQL이 익숙한 개발자에게 더 직관적이다.
| 개인적 소감
MyBatis를 처음 사용하면서, SQL을 직접 다루는 것의 장점과 단점을 동시에 경험할 수 있었다.
| 전환하며 느꼈던 장단점
MyBatis
느꼈던 장점
- 쉽다.. 원하는 값에 따라 쿼리만 작성하면 되니 간단했다.
느꼈던 단점
- findById 같은 단순한 CRUD를 일일이 작성하다보니 귀찮았다.
JPA
느꼈던 장점
- 기본 CRUD 작업의 자동화: findById 같은 기본적인 CRUD 쿼리를 반복적으로 작성하지 않아도 된다는 것의 편리함을 체감했다.
- 컴파일 시점 오류 검출: 빌드 과정에서 에러 발생하는게 에러를 잡는데 매우 편리하다는 걸 체감했다.
느꼈던 단점
- 복잡성 증가: 기존 JPA 사용에서는 쿼리 하나만 쓰면 될 걸 객체를 조회하고, 연관관계 신경 쓰고, 모듈화하고... 했던 내 코드가 MyBatis 사용하고 나니 훨씬 복잡해 보였다.
| 몇 가지 사례
1. Entity to DTO
이렇게 매번 엔티티를 DTO로 변환했었는데,
처음부터 쿼리 결과를 resultMap으로 dto와 매핑하면 된다. 굳이 서비스 코드에서 엔티티를 매핑할 필요가 없다.
2. 컴파일 시점 에러 검출
장점에서 언급했듯, 컴파일 시점에 에러를 확인하는 게 참 편리하구나.. 느꼈다.
내가 작성한 댓글을 조회하는 기능을 전환하면서.. 분명 findByReplies를 제대로 작성했는데
에러 로그에는 "is_deleted" 컬럼이 없다며 계속해서 에러가 발생했다. 쿼리엔 오류가 없는데, is_deleted 컬럼도 분명 존재하고? 도대체 뭐가 문제지.. 한참을 쳐다봤다. 문제는 findByReplies가 아니라 페이징을 위한 totalCount를 계산하는 findMyRepliesTotalCount에 있었다.
WHERE 절에서 is_deleted에 "r."을 명시하지 않았다.
빌드 과정에서 오류 발생하지 않으니.. 몇 번 마주한 이런 간단한 오타 같은 에러 잡기가 조금 더 귀찮고 어려웠다.
3. 동적 쿼리
네이버 클라우드 훈련 과정 동안
MyBatis 자주 사용해본 학우가 jpa를 사용하며 "이거 동적 쿼리 쓰면 딸깍! 하고 끝나는데.." 라고 말하는 걸 자주 들었지만 사용해본 적이 없어서 전혀 와닿지 않았는데,
처음엔 생각없이 각각에 해당하는 쿼리 두 개를 썼었는데 갑자기 동적 쿼리가 떠올랐고,
가
로 간단하게 변했다.
| 소감
- JPA 사용하느라 느슨해졌던 SQL의 재미가 돌아왔다.
기능에 필요한 값을 확인하고, 테이블과 필요한 값을 머릿속으로 그린 뒤, 뚝딱뚝딱 쿼리를 쓰고 짜잔~하고 값이 나오는게 참 재밌었다.
MyBatis를 처음 사용했지만, 전환에 사용한 쿼리는 복잡한 쿼리랄게 없었기 때문에 쉽게 전환했다.
기능이 이전과 같이 잘 작동하는 것은 확인했지만, 여전히 이것보다 더 깨끗한 코드로, 더 좋은 성능으로 작성할 방법이 있을텐데..
하는 의구심과 미련이 올라온다. 그 부분이 조금 아쉽지만, 다른 밀린 할 일이 많아서 일단은 여기서 마무리한다..
- ORM과 SQL 매퍼의 각 장단점에 대한 실질적인 체감이 굉장히 흥미로웠다.
앞에서 들었던 예와 같은 경험에서 ORM은 ORM대로 기존 사용하면서 느끼지 못했던 장점과 단점을 체감했고, SQL 매퍼는 SQL 매퍼대로 JPA를 사용하면서 아쉬웠던 점을 해소하는 통쾌한 경험이었다..
- 그럼에도 불구하고 JPA vs Mybatis, 어떠한 조건에서 무엇이 더 적절한지 선택할 판단 기준은 여전히 찾지 못했다.
DDL이 익숙하지 않은 탓도 있겠지만, 테이블을 만들고 수정하는 건 jpa가 훨씬 편리하고 쉽다고 느꼈다.
그렇다면,
JPA로 테이블을 구성한 뒤, MyBatis로 나머지 기능을 구현하는 것이 좋은가?
JPA로 테이블을 구성한 뒤, MyBatis로 나머지 기능을 구현하되, 간단한 쿼리는 Spring data JPA를 사용하는 것이 좋은가?
처음부터 DDL로 테이블을 만들고, MyBatis로 기능 구현하는 것이 좋은가?
JPA와 QueryDSL을 적절히 활용해 구현하는 것이 좋은가?
셋 모두를 혼용하는 것이 좋은가 ?
각 프로젝트마다 조건과 요구사항은 다를테고, 100% 정답이 존재할리는 만무하다. 각 선택지에 트레이드오프가 분명 존재할테지만..
이렇게 한 번 전환을 해보면, "그럼에도 불구하고 이게 좀 더.." 같은 차선택을 적절히 선택할 판단 기준이 조금은 와닿지 않을까.. 생각했다.
하지만 여전히 잘 모르겠다.
나중에 JPA에서 MyBatis로 전환한 해당 프로젝트를 다시 MyBatis에서 QueryDSL로 전환해보면 그것도 재밌을 것 같다..
'네이버 클라우드 캠프' 카테고리의 다른 글
elk 스택을 활용한 연관 검색, 연관 게시글 제안 (0) | 2024.08.09 |
---|---|
elk 스택을 활용한 데이터베이스 연동 (0) | 2024.08.09 |
Projection을 사용한 명명 메서드: 성능 비교와 느낀 점 (0) | 2024.07.29 |
Spring data JPA vs ElasticSearch (0) | 2024.07.24 |
인터페이스 프로젝션 (0) | 2024.06.24 |