Spring/JPA

[JPA] MyBatis와 JPA, 도대체 뭐가 다를까?

ummchicken 2023. 2. 6. 13:36

 

✔️ 들어가기 전... 왜 이 포스팅을 작성하나?

(긴 글 주의)

 

먼저 난, MyBatis와 JPA에 한이 맺힌 사람이다.

 

국비학원에서 배운 MyBatis... 

모든 걸 처음 배웠던 때이기도 했다.

 

그리고 국비학원 후기에도 올렸듯이, 

규모가 큰 국비학원 과정이었던지라 당연히 취업깡패 커리큘럼인지 알았다.

이때까지만 해도, 힘들었던 내 인생 드디어 보상받는 줄 알았다.

왜 힘들었는지는 '국비학원 후기'에 대략 적혀있음.

(아, 취업깡패 커리큘럼 맞긴 맞다. 다만 SI 깡패라서 그렇지.)

 

따라서 난, 

학원 과정을 성실히 밟아가면 취업이 원활하게 될 수 있을 줄 알았다.

(나름 학원에서 우수 학생 선정도 되고 그랬었음)

 

그리고 학원에서 누구보다 더 열심히 할 자신이 있었던 게

좀 거만한 얘기일수도 있지만...

그전까지 내 인생을 보면, 

알바(일)를 하든, 학원에서 공부를 하든, 

항상 윗사람?들이 날 예뻐해줬다. 

열심히 한다고... 

그리고 같이 일하거나 공부했던 동료들도 날 열심히 하는 사람 취급을 해줬다.

'취급'이라고 하면 좀 어감이 그렇긴 한데, 

한마디로 걍 열심히 하는 거 인정해 줬다고 보면 된다.

 

그래서 난 국비 학원을 등록했을 때도, 

당연히 강사님의 애제자가 될 거라고 예상도 했었다.ㅋ;;;

학원 다니면 항상 강사님들의 애제자였음.

잘하고 말고를 떠나서 열심히하면, 안 예뻐할 수가 없거든.

맨날 졸졸 쫓아 다니면서 질문해 보면, 안 예뻐하기도 힘들듯?

 

근데 지난 3년간, 내가 내가 아닌 것 같았다.

위에 첨부한 글들에 내 힘들었던 시절을 보면 알 수 있듯이 

약 3년간은 삶의 의지를 잃은채 좀비처럼 살았었다.

 

 

아무튼 학원 수료할 쯤 취업을 위해 포트폴리오를 만들다가... 

필수 사항에 모두 JPA가 적혀있는 것이었다?

(아 SI말고, 원티드나 로켓펀치 등에 나온 공고들이요.

한마디로 자체서비스.)

 

뭐지? 하고 알아봄.

 

그러다 엄청난 사실을 알게 되는데...

MyBatis는 SI에서 주로 사용하는 기술들이고, 

JPA는 자체서비스에서 주로 사용하는 기술이라더라.

 

진짜 창피하게도, SI랑 자체서비스 차이도 이때 처음 알았다.

알고 보니 개발자가 취업 깡패라는 것도 SI 한정인 것 같았다.

(SI는 인력이 없어서 문제라던데, 사실인가?)

자체서비스는 가기 넘 어렵다;;;

 

근데 내가 가고싶은 회사들은 다 자체서비스였다.

난 내가 직접 개발하고 사람들에게 서비스하고 싶다.

 

그래서 JPA로 프로젝트 하나 만들고, 그걸로 포트폴리오 만들어야겠다 생각했다.

 

근데 공부를 하다보니... JPA는 단순 구글링으로 해결할 문제가 아니었다;;;

이거 하려면 Java 객체지향도 잘 알아야 하고, 

암튼 한마디로 JONNA 어렵고 까다로운,

파면 팔수록 신세계지만 동시에 멘붕도 같이 오는 그런 기술이었다.

 

 

이거 때문에 너무 힘들었고, 지금도 힘들다.

단순 구현만 했다고 다가 아니라, 

이게 왜 이렇게 동작하는지 알아야 하잖아? 근데 그거 이해가 어렵다고;;;

더 엄밀히 따지면, JPA 하나 보다는 Java 객체지향 설계까지 진짜 너무 어렵다.

그거 이해하려면 Java도 제대로 알아야 하고, Spring MVC도 제대로 알아야 하고... 

뭐하나 단독적인 게 없음. 

다 연결되어있다.

 

한마디로 파면 팔 수록 매번 새로운 게 튀어나옴.

 

나 웹서비스 하나 만들었어요! 짠! 이게 다가 아니라, 이게 왜 이렇게 동작하는지...

그거 공부하는 게 정말 매번 멘붕이고 파면 팔 수록 진짜 개어렵다.

 

나만 어렵나? 

 

멘토가 있거나 학원에 다니면 멘토 한테 질문하면 바로 해결되는 것들을 

학원 수료 후 나 혼자 하다 보니 진전 속도가 좀 더딘 느낌이고, 

무엇보다 궁금증이 속 시원히 해결되는 느낌이 아니다.

예를 들어, 난 이렇게 이해했는데 이게 맞는 건가? 하는 의문이 계속 든다.

몇 시간, 며칠 구글링하고 책 읽고 강의 듣는 것보다, 걍 한번 얼굴 마주 보고 질문하는 게 더 머릿속에 잘 들어온다.

효율적인 느낌이 아니다. 

마치 학부 때, 수업 외 시간에 교수님이나 조교님한테 이메일로 질문하고 답변받는 느낌;;;

수업 시간이나 실습 시간에 말로 질문하면 바로 해결되는 걸.

 

 

암튼... 그래서 결론은 

난 MyBatis와 JPA에 한이 맺힌 사람이다.

 

으 죽겠다 진짜.

 

암튼 서론이 길었고, 

JPA 너무 어렵고 힘들어서 때려치고 싶을 때 

왜 JPA를 공부해야 하나 다시 처음으로 돌아가는 마음으로... 이 포스팅을 작성한다.

 

 

어려울수록 기본에 충실해야 한다.

 

 

도대체 MyBatis와 JPA 차이가 뭐길래 날 이렇게 힘들게 하나?

 

 


 

✔️ JPA의 등장 배경

💡 객체와 관계형 데이터베이스 간의 차이를 중간에서 해결해주는 ORM(Object-Relational Mapping) 프레임워크.
JPA는 자바 진영의 ORM 기술 표준이다.

※ ORM 프레임워크 : 객체와 관계형 데이터베이스를 매핑한다.
객체와 테이블을 매핑해서 패러다임의 불일치 문제를 해결해 준다.

 

기존의 MyBatis나 JDBC 같은 SQL Mapper를 사용하면, 

CRUD용 SQL은 반복해서 작성해야 한다.

 

이렇게 되면, 애플리케이션의 요구 사항이 점점 추가될 때 단순 매핑에 끝나지 않는다.

김영한 개발자님에 따르면,

"객체 지향의 장점을 포기하고, 객체를 단순히 테이블에 맞추어 데이터 전달 역할만 하도록 개발할까?"라는 의문이 든다고 한다.

 

객체 모델링을 세밀하게 진행할수록 객체를 데이터베이스에 저장하거나 조회하기는 점점 더 어려워지고, 

객체와 관계형 데이터베이스의 차이를 메우기 위해 더 많은 SQL을 작성해야 한다.

 

 

하지만 JPA는 지루하고 반복적인 CRUD SQL을 알아서 처리해 줄 뿐만 아니라, 

객체 모델링과 관계형 데이터베이스 사이의 차이점도 해결해 준다.

 

ORM Class와 RDB의 테이블을 매핑한다.

 

그리고 JPA는 실행 시점에 자동으로 SQL을 만들어서 실행하는데, 

JPA를 사용하는 개발자는 SQL을 직접 작성하는 것이 아니라, 어떤 SQL이 실행될지 생각만 하면 된다.

(JPA가 실행하는 SQL은 쉽게 예측할 수 있다고 한다. ← 근데 난 어려움)

(개발자는 SQL 매퍼가 아니다.)

 

 


 

✔️ JPA와 MyBatis의 장단점

💡 JPA의 장단점

  • 장점
    • SQL이 아닌, 객체 중심으로 개발할 수 있다.
    • RDB 종류와 관계없이 사용가능하기 때문에 DB 변경이 있어도 코드 재활용이 가능하다.
    • 기본적인 CRUD 제공과 페이징 처리 등 구현되어 있는 것이 많아 비즈니스 로직에 집중할 수 있다.
    • 테이블 생성, 변경 등 엔티티 관리가 간편하다.
    • 쿼리를 직접 작성할 필요 없이 Java 코드로 간편하게 사용할 수 있다.
    • 쿼리에 로직을 담지 않아도 된다.
    • 1차 캐시, 쓰기 지연, 변경감지, 지연로딩을 제공하여 성능상 이점을 얻을 수 있다.
    • JPA는 별도의 수정 메소드를 제공하지 않는다. 대신에 객체를 조회해서 값을 변경만 하면, 트랜잭션을 커밋할 때 데이터베이스에 적절한 UPDATE SQL이 전달된다. (변경 감지)

 

  • 단점
    • 배우기 위한 난이도가 있다.
    • JPA의 고급 기능을 공부해야 한다.
    • 단방향, 양방향, 임베디드 관계 등 공부할 내용이 많다.
    • 연관관계에 대한 이해가 없이 코딩을 하게 되면, 성능 문제를 일으킬 수 있고 원하지 않는 결과를 가져올 수 있다.
      • 성능 상 이슈 : N +1, FetchType, Proxy, 연관관계 등

 

 

💡 MyBatis의 장단점

  • 장점
    • JPA에 비해 쉽다.
    • SQL 쿼리를 그대로 사용하기 때문에, 복잡한 Join, 튜닝 등을 편하게 작성할 수 있다.
    • DB 조회 결과를 복잡한 객체 구조로 변환해야 할 때 MyBatis 기능이 좋다. (resultMap)
    • 동적 쿼리 사용 시 JPA보다 간편하게 구현 가능하다.
      • 동적 쿼리 : 상황에 따라 분기처리를 통해 SQL문을 동적으로 만드는 기법

 

  • 단점
    • SQL에 의존적인 개발을 피하기 어렵다.
    • DB 설정 변경 시 수정할 부분이 많다.
    • Mapper 작성부터, 인터페이스, 모델 설계까지 JPA보다 많은 설계와 파일, 로직이 필요하다.
    • 특정 DB에 종속적이다. DB가 바뀌면 DB 문법에 맞게 mapper를 전부 수정해야 한다.
    • 쿼리에 로직을 녹여야 하는데 유지보수 하기가 힘들고 테스트도 까다롭다.
    • 자주 쓰는 CRUD 메소드를 직접 다 작성해주어야 한다.

 

 

💡 JPA와 MyBatis의 차이 요약

  • MyBatis : SQL 매퍼
    • 객체와 SQL을 매핑한다.
    • 개발자가 SQL을 직접 작성해야 하므로, SQL에 의존하는 개발을 피할 수 없다.
  • ORM : 객체와 테이블 매핑
    • ORM 프레임워크가 SQL을 만들어서 데이터베이스와 관련된 처리를 함.
    • SQL에 의존하는 개발을 피할 수 있음.

 

 

💡 결론

JPA는 SQL을 개발자 대신 작성해서 실행해 주는 것 이상의 기능들을 제공한다.

 

하지만 JPA는 만능이 아니다.

애플리케이션이 고도화된다면, 오히려 손이 더 많이 가는 경우가 많다.

 

따라서 JPA와 함께 문제점을 보완해 줄 수 있는 다른 라이브러리가 필요하다.

그중 하나가 MyBatis가 될 수 있다.

이 둘을 적절히 혼용하여 사용한다면 안정적인 서비스를 제공할 수 있다.

 

 

 


 

✔️ JPA의 패러다임의 불일치 해결

 

1. 연관관계

객체는 참조를 사용해서 다른 객체와 연관관계를 가지고
참조에 접근해서 연관된 객체를 조회한다.

반면 테이블은 외래 키를 사용해서 다른 테이블과 연관관계를 가지고
조인을 사용해서 연관된 테이블을 조회한다.

 

참조를 사용하는 객체와 외래 키를 사용하는 관계형 데이터베이스 사이의 패러다임 불일치는 

객체지향 모델링을 거의 포기하게 만들 정도로 극복하기 어렵다.

 

연관관계

→ Member 객체는 Member.team 필드에 Team 객체의 참조를 보관해서 Team 객체와 관계를 맺는다.
따라서 이 참조 필드에 접근하면 Member와 연관된 Team을 조회할 수 있다.

→ Member 테이블은 MEMBER.TEAM_ID 외래 키 컬럼을 사용해서 TEAM 테이블과 관계를 맺는다.
이 외래 키를 사용해서 MEMBER 테이블과 TEAM 테이블을 조인하면, MEMBER 테이블과 연관된 TEAM 테이블을 조회할 수 있다.

🚨 객체는 참조가 있는 방향으로만 조회할 수 있다.
예를 들어, 위의 예에서 member.getTeam()은 가능하지만, 
반대방향인 team.getMember()는 참조가 없으므로 불가능하다.

반면, 테이블은 외래 키 하나로 MEMBER JOIN TEAM도 가능하지만, 
TEAM JOIN MEMBER도 가능하다.

 

 

JPA는 연관관계와 관련된 패러다임의 불일치를 해결해준다.

 

member.setTeam(team); // 회원과 팀 연관관계 설정
jpa.persist(member); // 회원과 연관관계 함께 저장
// persist() 메소드 : 객체를 데이터베이스에 저장한다.
// 이 메소드를 호출하면 JPA가 객체와 매핑 정보를 보고 적절한 INSERT SQL을 생성해서 데이터베이스에 전달한다.
→ 개발자는 회원과 팀의 관계를 설정하고 회원 객체를 저장하면 된다.
→ JPA는 team의 참조를 외래 키로 변환해서 적절한 INSERT SQL을 데이터베이스에 전달한다.
→ 객체를 조회할 때 외래 키를 참조로 변환하는 일도 JPA가 처리해준다.

 

 

 

2. JPA와 객체 그래프 탐색 & 지연로딩에 관하여

객체에서 회원이 소속된 팀을 조회할 때는 아래 코드처럼 참조를 사용해서 연관된 팀을 찾으면 되는데, 
이것을 객체 그래프 탐색이라고 한다.
Team team = member.getTeam();

 

 

만약 객체 연관관계가 다음과 같이 설계되어 있다고 하자.

객체 연관관계

 

 

다음은 객체 그래프를 탐색하는 코드다.

member.getOrder().getOrderItem() ... // 자유로운 객체 그래프 탐색

→ 객체는 마음껏 객체 그래프를 탐색할 수 있어야 한다. 그런데 마음껏 객체 그래프를 탐색할 수 있을까?

 

 

예를 들어보자.

SELECT M.*, T.*
	FROM MEMBER M
    JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID

→ MemberDAO에서 member 객체를 조회할 때 이런 SQL을 실행해서 회원과 팀에 대한 데이터만 조회했다면, member.getTeam()은 성공하지만 

다음처럼 다른 객체 그래프는 데이터가 없으므로 탐색할 수 없다.

member.getOrder(); // null

 

🚨 SQL을 직접 다루면, 처음 실행하는 SQL에 따라 객체 그래프를 어디까지 탐색할 수 있을지 정해진다.

이것은 객체지향 개발자에겐 너무 큰 제약이다.

왜냐? 

비즈니즈 로직에 따라 사용하는 객체 그래프가 다른데, 언제 끊어질지도 모르는 객체 그래프를 함부로 탐색할 수는 없기 때문이다... 

 

예를 들어보자.

<회원 조회 비즈니스 로직>이다.

class MemberService {
	...
    public void process() {
    	
        Member member = memberDAO.find(memberId);
        member.getTeam(); // member -> team 객체 그래프 탐색이 가능한가?
        member.getOrder(); // ???
    }
}
→ MemberService는 memberDAO를 통해서 member 객체를 조회했지만, 
이 객체와 연관된 Team, Order, Delivery 방향으로 객체 그래프를 탐색할 수 있을지 없을지는 
이 코드만 보고는 전혀 예측할 수 없다.

→ 결국, 어디까지 객체 그래프 탐색이 가능한지 알아보려면 
데이터 접근 계층인 DAO를 열어서 SQL을 직접 확인해야 한다.

🚨 이것은 SQL에 의존적인 개발에서도 나왔듯이, 엔티티가 SQL에 논리적으로 종속되어서 발생하는 문제다.

결국 이 경우에는 MemberDAO에 회원을 조회하는 메소드를 상황에 따라 여러 벌 만들어서 사용해야 한다.

 

 

객체 그래프를 신뢰하고 사용할 수 있으면 이런 문제를 어느정도 해결할 수 있다.

JPA는 이 문제를 어떻게 해결할까?

 

 

💡 JPA를 사용하면 객체 그래프를 마음껏 탐색할 수 있다.

member.getOrder().getOrderItem() ... // 자유로운 객체 그래프 탐색
→ JPA는 연관된 객체를 사용하는 시점에 적절한 SELECT SQL을 실행한다.
→ 따라서 JPA를 사용하면, 연관된 객체를 신뢰하고 마음껏 조회할 수 있다.
→ 💡 이 기능은 실제 객체를 사용하는 시점까지 데이터베이스 조회를 미룬다고 해서 지연 로딩이라 한다.

 

 

지연 로딩을 사용하는 예를 들어보자.

// 처음 '조회' 시점에 SELECT MEMBER SQL
Member member = jpa.find(Member.class, memberId);

Order order = member.getOrder();
order.getOrderDate(); // 'Order를 사용하는 시점'에 SELECT ORDER SQL
→ 마지막 줄의 order.getOrderDate() 같이 실제 Order 객체를 사용하는 시점에 
JPA는 데이터베이스에서 ORDER 테이블을 조회한다.

 

 


 

✔️ JPA를 사용해서 객체를 조회

JPA를 사용해서 객체를 저장하는 코드는 다음과 같다.

jpa.persist(member); // 저장

 

조회할 때도 JPA를 통해 객체를 직접 조회하면 된다.

JPA 조회

 

 


 

✔️ 그래서 결국, 왜 JPA를 사용해야 하는 건가?

참 멀리도 왔다... 

글이 이렇게 길어질 줄 몰랐는데;;;

(이것도 줄이고 줄인 게 함정)

 

 

1. 생산성

  • JPA를 사용하면, 자바 컬렉션에 객체를 저장하듯이 JPA에 저장할 객체를 전달하면 된다.
    • INSERT SQL을 작성하고, JDBC API를 사용하는 지루하고 반복적인 일은 JPA가 대신 처리해준다.
    • jpa.persist(member); // 저장
    • Member member = jpa.find(memberId); // 조회
  • 반복적인 CRUD용 SQL을 개발자가 직접 작성하지 않아도 된다.
  • CREATE TABLE 같은 DDL 문을 자동으로 생성해주는 기능도 있따.
  • 객체 설계 중심을 할 수 있다.

 

 

2. 유지보수

  • 필드를 추가하거나 삭제해도 수정해야 할 코드가 줄어든다.
  • 유연하고 유지보수하기 좋은 도메인 모델을 편리하게 설계할 수 있다.

 

 

3. 패러다임 불일치 해결

  • JPA는 상속, 연관관계, 객체 그래프 탐색, 비교하기와 같은 패러다임의 불일치를 해결해준다.

 

 

4. 성능

  • JPA는 애플리케이션과 데이터베이스 사이에서 다양한 성능 최적화 기회를 제공한다.
  • JPA는 애플리케이션과 데이터베이스 사이에서 동작한다.
    • 이렇게 애플리케이션과 데이터베이스 사이에 계층이 하나 더 있으면, 최적화 관점에서 시도할 것이 많다.
    • Ex) 같은 트랜잭션 안에서 같은 회원을 두 번 조회할 때, 회원 조회 SELECT SQL을 한 번만 데이터베이스에 전달하고, 두 번째는 조회한 회원 객체를 재사용한다.

 

5. 벤더 독립성

벤더 독립성

  • 애플리케이션과 데이터베이스 사이에 추상화된 데이터 접근 계층을 제공한다.
    • 애플리케이션이 특정 데이터베이스 기술에 종속되지 않도록 한다.
    • 만약 데이터베이스를 변경하면, JPA에게 다른 데이터베이스를 사용한다고 알려주기만 하면 된다.
    • Ex) 로컬 : H2, 개발 : MySQL

 

 

 


 

 

이게 다가 아니다. 

이건 시작의 시작의 시작일 뿐이다.

 

 

 

 


출처

 

 

 

 

 

'Spring > JPA' 카테고리의 다른 글

[JPA] OSIV란?  (6) 2023.02.07
[JPA] Querydsl 찍먹해보기  (0) 2023.02.06
스프링 데이터 JPA - (2)  (0) 2022.12.20
스프링 데이터 JPA - (1)  (0) 2022.12.19
JPA 활용 2 - API 개발과 성능 최적화  (0) 2022.12.19