JPA vs Mybatis

4 minute read

JPA (Java Persistence API)

1. JPA 란?

  • JPA는 DB 작업을 객체 기반으로 다루는 인터페이스 표준 으로 관계형 데이터 베이스를 어떻게 사용해야하는지를 정의
    - Hibernate : JPA를 준수하여 구현한 JPA 구현체 라이브러리 (by Hibernate 개발자)

    - Spring Data JPA : Hibernate, JPA와 달리 Spring에서 제공하는 JPA 모듈이다.

    API : https://docs.spring.io/spring-data/jpa/docs/current/api/org/springframework/data/jpa/repository/JpaRepository.html

    구현체 : https://docs.spring.io/spring-data/jpa/docs/current/api/org/springframework/data/jpa/repository/support/SimpleJpaRepository.html

  • 개념적으로 JPA는 JDBC와 Application 사이에 위치한다.
    JDBC - ORM - JPA - Application

2. JPA의 장점

Hibernate가 JPA의 구현체
1) 객체 지향적 DB 접근
2) 성능 최적화 기능
- 조회 시 캐싱 지원 (같은 트랙잭션 안에서)
- INSERT 버퍼링 기능 (같은 트랜잭션 안에서)
- 지연로딩, 즉시로딩 옵션화

    Member member = memberDAO.find(memberId);
    Team team = member.getTeam();

    team.getName(); //값이 필요한 시점에 한번만 SELECT 수행

3. JPA의 단점 - Mybatis와 비교

  • SQL에 대한 모든 컨트롤을 하고자 할때 적절하지 않음.
  • SQL을 최적화해야할 때 적절하지 않음.

4. 결론

  • JPA는 Mybatis에 비해 어렵지만 객체 지향적인 DB 작업가능.
  • Hibernate 구현체가 아니더라도 Spring Data JPA 와 같은 인터페이스와 함께 별도 구현체를 더해서 사용도 가능.
  • 대규모 시스템에서 데이터 엔지니어(또는 DA)와 협업이 필요할 경우 적합하지 않음
    ex) Native 쿼리를 자주 사용해야하는 경우
    ex) Hint index를 넣어야 하는 경우
  • SQL 의존도가 낮은 시스템에서 비교적 유용.

생각해볼 문제 (Study Item)

  • JPA를 사용할 경우 복잡한 쿼리는(outer, multiple join) 어떻게 해결할지?
    ☞ Join 할 Object를 지정해주는 방식. (N+1 문제 발생)
      @ManyToOne(fetch = FetchType.LAZY)
      @JoinColumn(name = "wedul_classes_id")
      @JsonBackReference
      private WedulClasses wedulClasses;
    

    ☞ native left fetch join, EntityGraph 이용 (비효율…)
    ☞ QueryDSL이 SQL과 같은 쿼리를 인터페이스를 통해 사용할 수 있게 해준다.

      public List<WedulClasses> findAllWithStudent() {
          QWedulClasses wedulClasses = QWedulClasses.wedulClasses;
          QWedulStudent wedulStudent = QWedulStudent.wedulStudent;
          return from(wedulClasses).leftJoin(wedulClasses.wedulStudentList, wedulStudent) .fetchJoin() .distinct() .fetch();
      }
    

    참고 블로그 : https://wedul.site/638

  • 프로시저, Hint, Index 등을 쿼리에 강제로 넣어야하는 경우는 어떻게 해야하는가?
    ☞ Native SQL query 필요
    return JPA.em().createNativeQuery(
      "select * from transaction with (INDEX(_dta_index_k1_1)) ...",
      Transaction.class).getResultList();
    
  • JPA 라이브러리에서 Transaction 내부 실행 순서를 관리하는데, 의도하지 않은 문제나 성능 튜닝이 필요하지는 않을지?

  • Spring Data JPA와 Hibernate 둘다 동일하게 where 조건을 함수명으로하여 자동으로 쿼리 생성하는지?

5. Query DSL & Fetch Join

  • 복잡한 쿼리(Multiple Join, 조건) 및 N+1 문제 등의 JPA의 한계를 극복하기 위한 라이브러리
  • 가장 큰 장점으로 개발자는 비지니스 로직에만 집중할 수 있어 복잡한 Join 및 조건 처리도 Code로 빠르게 쿼리를 작성할 수 있다.

생각해볼 내용 : 1) 복잡한 Query나 DBA와 작성한 Query의 경우 Query를 코드로 Migration이 필요하다. (쿼리와 불일치 가능성 존재) 2) DB Scheme 나 기존 쿼리가 변경될 경우 관련 Java 코드를 수정해야한다. 3) 컴파일 시점에 에러가 확인되므로 변경사항에 대한 코드 개발이 어렵다. 4) 쿼리 계획을 JPA가 하므로 DBMS 성능에 대한 이슈가 생길 수 있다.

※ Query DSL 예시 1

JPAQueryFactoryquery = new JPAQueryFactory(em);
QMemberm=QMember.member;
QTeamt=QTeam.team;
​
List<Member>list=
    query.selectFrom(m)
         .join(m.team, t)
         .where(t.name.eq("teamA"))
         .fetch();

※ Query DSL 예시 2

※ Fetch Join (N+1 문제 해결) 예시

  • SQL 에서 사용하는 개념이아닌 JPQL에서 사용한다. JPQL의 한계를 극복하기 위한 Join 기법이다.
    Select m from Member m join fetch m.team
    

    얼핏 보면 SQL과 유사해보이는데 차이점이 있다. 우선 첫 번째는, join 뒤에 fetch가 붙는다. 그리고 프로젝션에 m(Member)만 존재한다. (이 부분이 매우 중요)

위 JQPL은 아래의 SQL로 변역된다.

SELECT M.*, T.* FROM MEMBER M INNER JOIN TEAMT ON M.TEAM_ID = T.ID

즉, Fetch Join의 목적은 관계 있는 Entity를 한 방에 가져오는데 그 목적이 있다. 즉, Fetch Join은 지연로딩을 하지 않고 즉시 로딩(EAGER)로 가져오는 쿼리이다. 가령 N명의 멤버는 1개의 팀을 가질 수 있다는 다대일 관계를 생각해보자. 여기서 N명의 멤버를 가져오기위한 쿼리가 발생할 것이고, 이후 각 멤버는 팀을 프록시 객체로 받아온 뒤 프록시 객체의 값에 접근하려고 할 때 실제 쿼리를 날려서 팀의 정보를 가져오게 된다. 문제는 이 때, N명의 멤버가 모두 각기 다른 팀을 가진다고 가정하면 1명의 멤버마다 그 멤버가 소속된 팀의 쿼리가 나아갈 것이고 (1차캐시가 안될거니까) 이것이 우리가 흔히 말하는 N+1 문제가 발생하게 되는 원인이다. 사실 이러한 N+1 문제는 지연로딩이든 즉시로딩이든 발생하는 문제인데, 이를 해결하는 방법 중 하나가 바로 JPQL의 Fetch Join이다.

참조 : https://sloth.tistory.com/m/13

6. 서비스 특성 별 쿼리 특징

JPA, Query DSL이 안좋다고 생각하는 것은 아니다.

  • 반복적인 CRUD에서 자유로워 진다.
  • 실시간 처리가 많거나 스키마가 비즈니스 로직과 연결되는 서비스에서는 코드 기반으로 빠른 개발이 가능하다.
  • 객체 지향과 RDBMS를 연결할 때 서로의 패러다임의 차이를 줄이려는 시도.

JPA, Query DSL은 분명 장점이 많고 효율적인 코드 작성이 가능한 훌륭한 도구이다. JPA가 유리한 서비스와 유리한 팀이 있다.

그렇기 때문에 JPA, Query DSL에 대해서 잠깐 공부해보자.

  • Query DSL 공식 레퍼런스 : https://querydsl.com/static/querydsl/4.0.1/reference/ko-KR/html_single/#d0e1450

느낌은 너무 방대한 양이다. 패러다임 불일치를 극복하고 복잡한 쿼리까지 해결하려고 대단한 정성을 들여 만든 라이브러리 느낌이다. 이를 공부해야하는 개발자도 대단한 정성이 필요하다.

☞ 쿼리를 사용하지 않으려다가 더 많은 학습과 개발이 필요하고 필연적인 유지보수 여려움 및 DB 성능상의 문제가 생길 수 있음

☞ 서비스가 커지고 고도화 될 수록 JPA로 인한 코드양이 늘어나게 되고 의존성이 커지게 된다. Native Query를 사용해야하는 경우가 증가하고 결국 Native Query와 JPA를 함께 사용해야한다.

7. Software 아키텍쳐 및 Clean Code 관점

  • 코드가 Query DSL에 의존하게된다.
  • 스키마나 쿼리 변경사항이 생기면 코드를 변경해야한다.
  • Query DSL 코드가 점점 복잡해지고 길어져서 수정하기 어렵다.
  • EntityManager와 Transaction등의 내부 동작이 있기 때문에 상세히 제어가 어렵다.

☞ 코드와 쿼리의 분리 필요. ☞ 더 나아가서는 복잡한 쿼리는 프로시저로 만들고 서로 영향성이 없도록 관리하며 사용하는 방법이 유리.

ORM은 꼭 필요한 도구이므로 Hibernate, Mybatis를 ORM 목적으로 사용하고, 서비스의 구조와 방향에 적합한 라이브러리를 검토하여 사용하는 것이 우선되어야 한다. » 장기적으로 볼 때 ORM으로만 사용하는 것이 좋을 것 같다.

Comments