[Spring boot] Hibernate, JPA 그리고 Spring Data JPA

지난 포스트에서 다뤄 본 JDBC와 Spring JDBC는 자바와 데이터베이스를 연결하기 위한 최초 인터페이스였고, 이들 코드를 개선하기 위해 리팩토링하는 작업들, 연결 세션, 그리고 관심점의 분리 등 다양한 발전된 모습을 보였는데요.

이번 포스트에서는 그에 이어서 Hibernate, JPA, Spring Data JPA에 대해서 알아보도록 하겠습니다.

 

JPA

JPA는 Java Persistence API의 약자로 자바에서 관계형 데이터베이스를 사용하기 위한 양식을 정의한 인터페이스입니다. 말그대로 인터페이스이기 때문에 어떤 코드가 구현되어 있는 것은 아닙니다. 단지, Java 라는 객체 지향 프로그래밍 언어에서 관계형 데이터베이스를 객체 지향적으로 설계하기 위한 Best Practice를 JPA라고 하는 것입니다.

즉, 자바 진영에서 ORM 기술 표준으로 애플리케이션과 JDBC 사이에서 동작하는 녀석입니다. 여기서 ORM이란, Object RelationShip Mapper의 약자로 JDBC나 Spring JDBC, iBatis에서는 DB 처리를 위해 로직을 별도로 작성하고, 이를 SQL 쿼리로 질의하였는데, ORM은 SQL 코드를 구현하지 않고, 객체 지향 프로그래밍 방식으로 DB를 사용하는 것입니다. 대표적인 프레임워크로 Hibernate가 있습니다.

 

Hibernate

Hibernate는 JPA의 구현체입니다. 실제로 JPA의 핵심체인 EntityManagerFactory, EntityManager, EntityTransaction을 Hibernate에서는 SessionFactorySessionTransaction으로 상속받고 각각 Impl로 구현하고 있음을 확인할 수 있습니다. 

위 사진에서 인터페이스로 그려져 있는 것은 JPA, 클래스로 그려져 있는 것은 Hibernate의 클래스입니다. 

중간에 ORM이 JDBC API를 직접 사용하여 DB에 접근하는 방식입니다.

 

어떠한 점이 좋을까?

우리가 iBatis, JDBC를 사용했을 때 불편했던 것은 커넥션, 세션 등 수 많은 인스턴스가 섞여 있는 코드들을 DB에 접근할 때마다 사용하여 중복된 코드가 발생하고, 관심점 분리가 어려웠던 점이 있었습니다. 이 문제들은 iBatis를 통해서 어느 정도 해결이 되었지만 또 다른 문제점이 있었습니다.

바로 관계형 데이터베이스를 매핑하는 작업인데요. 애플리케이션을 개발하다 보면 각 엔티티들을 관계해야 할 때가 생기게 됩니다. 우리가 관계형 데이터베이스를 사용하는 이유는 개발하려는 애플리케이션의 도메인 모델들을 관계형으로 묶기 위함인데, 애플리케이션 개발 중에 이러한 작업을 도맡아하는 것은 쉬운 일이 아닙니다.

그래서 가능한 개발자가 이런 번거로운 작업을 줄이고, 알아서 관계를 매핑해주는 점은 아주 큰 메리트라고 할 수 있죠.

그 외에도 다른 좋은 점들이 있습니다.

  • 생산성 향상: 반복저인 SQL 코드 구현, CRUD 작업을 알아서 해줌
  • 유지보수: 객체 수정에 따른 SQL 코드 수정 작업이 필요 없음
  • 데이터 관계 매핑: 객체와 관계형 데이터를 매핑하는 과정을 자동으로 처리함
  • 성능: 캐싱을 지원하여 수행한 결과를 빠르게 도출
  • 데이터 접근 추상화 및 벤더 독립: 데이터의 관계를 매핑하는 과정은 DB마다 다른데, 이러한 작업의 번거로움이 없음

 

하지만 다 좋은 것은 아니다

ORM이 기존의 코드 가독성을 높이고, 개발자가 SQL 코드를 가능한 건드리지 않는다는 것을 목표로 한 프레임워크지만 이것이 모두 좋지만은 않습니다. SQL 코드를 한 번 쯤 짜봤다면 이해하시는 부분이지만 SQL 코드도 어떻게 짜기에 따라서는 성능이 좋은 코드가 될 수 있고, 그렇지 않은 코드가 될 수 있기 때문입니다.

하지만 ORM은 편한만큼 실제 쿼리를 작성하는 것에 비해 성능은 좋지 않습니다. 그래서 가벼운 프로젝트나 간단한 쿼리를 사용하는 프로젝트에는 유리하지만, 서비스의 요구 사항이 커지거나 데이터의 양이 커지기 시작하면 한계치에 도달하죠. 또 내부 로직의 정확한 동작을 알 수 없는 상태에서 사용하기 어려운 프로젝트라면 더더욱 힘들 것입니다. 그래서 JPA에서 JPQL을 지원하여 자체적으로 Query를 정의할 수 있는 방법도 있습니다.

또 다른 방법으로는 ORM과 MyBatis를 혼용해서 사용하는 방법도 있는가하면, QueryDSL 조합을 이용하는 방법도 있죠.

그리고 JPA를 사용하려면 무엇보다 잘 알고 사용해야한다는 점도 한 몫합니다. JPA는 데이터를 처리하기 위해 Entity의 생명주기, 캐시, 쓰기 지연, 상속 전략, 컬렉션 매퍼 등 DB를 사용할 수 있는 다양한 옵션들이 존재하는데, 이를 어떻게 쓰느냐에 따라서 성능이 많이 달라지고, 오류의 발생 영향 등이 갈리기 때문에 어떻게 보면 SQL 코드를 안써도 된다는 장점 하나에 러닝 커브가 낮아보일 수 있지만, 위와 같은 상황을 고려한다면, 결코 러닝 커브가 낮은 디펜던시는 아니라는 점이 있습니다.

 

 

Spring Data JPA

Spring Data JPA는 JPA를 Spring Framework에서 편하게 사용할 수 있도록 만들어놓은 모듈입니다. 여태까지 REST API 개발했을 때, 우리는 @Entity 어노테이션, @Column 어노테이션 등 간단한 어노테이션만을 가지고, 엔티티를 정의하고 DDL을 수행하는 등의 작업을 하였죠. 이것이 바로 Spring Data JPA입니다.

애플리케이션에서 JpaRepository를 사용하여 JPA의 구현체인 Hibernate로 요청을 전달하고, Hibernate는 이에 맞게 JDBC API를 사용하여 DB에 접근하고 그 결과를 애플리케이션에 돌려주는 방식입니다.

위처럼 User라는 테이블을 만들고 싶을 때, 간단한 어노테이션을 이용하고, 트랜잭션 객체 등을 이용하지 않는 점은 굉장히 편리합니다.

그런데, 만약 이건 어떨까요? 위에처럼 사용자의 권한을 정의하기 위한 엔티티를 구성하였습니다. Role 엔티티에서 해당 역할이 어떤 역할인지를 저장하는 테이블이고, Role_Permisssion은 해당 역할이 어떤 권한을 가지고 있는지 권한을 저장하는 테이블이 있습니다.

그렇다면 각 역할은 여러 개의 권한을 가질 수 있다고 가정했을 때, 두 데이터의 관계는 1:N이어야 합니다. 만약 Batis, JDBC를 사용한다면 이를 SQL 코드로 직접 모든 걸 정의해야 하지만, JPA에서는 위와 같이 어노테이션으로 정의해주고 있죠.

하지만 보다시피 이 부분도 쉽지는 않습니다. SQL 코드를 건드리지 않을 뿐, Fetch나 Cascade 등 애플리케이션에 따라서 로딩 기법이나 엔티티 변경 전략을 잡아줘야 하고, 디버깅 모드를 통해 SQL 쿼리를 직접 봄으로써 이를 판단하고, 수정할 수 있는 방법도 있지만 디펜던시가 변경됨에 따라서 실제 SQL 코드가 바뀔 수도 있는 부분은 단점일 수도 있기 때문에, 만약 이러한 작업이 불확실하다고 느껴진다면, JPA는 그다지 좋은 것만은 아니지요.

 

 

마치며...

여기까지 간단하게 JPA와 Hibernate, Spring Data JPA를 살펴봤습니다. 어떤 애플리케이션이든 DB를 사용하는 경우는 언제든지 생길 수 있고, 이러한 애플리케이션을 구현하기 위해 어떤 디펜던시를 사용해야 하는지에 대한 고민, 적절한 선택은 개발자의 몫입니다.

따라서 좋은 애플리케이션, 원활히 동작하는 애플리케이션을 개발하기 위해 사용하는 디펜던시의 특징을 잘 알고 쓰는 것은 매우 중요한 덕목이라고 생각합니다. 따라서 저는 JPA를 이렇게 보고 있습니다.

JPA는 객체지향 프로그래밍 언어에서 관계형 데이터베이스를 쉽게 짜기 위한 구조일 뿐이다.

내부에서는 JDBC API가 실제로 동작하고 있으며, 개발자가 직접 SQL 코드만 작성하지 않을 뿐이지, 동작하는 내부는 똑같다는 것입니다. 따라서 성능에 대한 이슈, 코드의 가독성 등 장단점이 존재할 수 있다는 점. 반드시 이 프레임워크를 쓰는 게 정답이 아니라는 점을 알려드리고 싶네요.

최근 스프링 프로젝트에서 JPA가 러닝 커브가 높고, 오히려 SQL 코드를 직접 쓰는 프로젝트가 많아지면서 Spring Data JDBC라는 프로젝트가 진행되고 있는데요. 어떻게 나올지 기대됩니다.

comments powered by Disqus

Tistory Comments 0