JPA 개요

고재석·2021년 4월 16일
0

JPA를 깊게 파보자!

목록 보기
1/4

JPA 기본 개념

JPA 등장 전부터 사용되던 JDBC나 MyBatis의 경우에는 SQL을 Java위에서 작성해서 DB에서 적절한 ResultSet을 받아오는 방식이었다. JDBC는 하나의 쿼리마다 각 컬럼을 모두 get/set 해주다보니 코드가 지저분하고 길어지게되었다. MyBatis는 mapper를 구현하여 각 컬럼까지 일일히 매핑하지는 않게 되었지만 Java 위에서 SQL을 잘 작성해야만 했다.

그 이유는 관계형 데이터베이스와 객체지향언어의 패러다임이 다르기 때문이었다.

JPA는 이러한 패러다임의 차이를 개발자 대신 메꿔주는 역할을 하는 ORM이다. 기존에 존재하던 다양한 문제를 JPA를 사용함으로써 상당히 많이 해소할 수 있다. 지금까지 존재하던 대표적인 몇 가지 문제점을 JPA가 어떤 식으로 해소하는지 알아보자.


구체적인 문제점들

SQL에 의존적인 개발을 피하기 어렵다.

사실 이것이 가장 근본적인 문제점이다. DAO에 작성된 SQL이 잘못된 경우에는 무조건 에러가 발생할 수 밖에 없다. 그만큼 SQL을 잘 작성해야하고 이는 객체지향적인 관점에서 해소할 수 있는 문제가 아니다.

진정한 의미의 계층 분할이 어렵다.

Spring에서는 Controller, Service, DAO등 다양한 Layer를 분할하여 서버가 동작하게 된다. 비즈니스 로직은 Service Layer에서 구현되고 DAO는 단순 CRUD 메소드만 가지고 있는 것이 올바른 설계일 것이다. 그런데 수정사항이 생겼거나 에러가 생겨 코드를 열어봐야하는 경우에도 DAO를 열어보고 어떻게 조회되는지 확인하거나 쿼리를 직접 수정해야한다. 이는 진정한 의미의 계층 분할이라고 할 수 없다. 사실 이것 조차도 근본적인 문제는 SQL에 의존적인 개발을 피할 수 없기 때문인 것이다.

엔티티를 신뢰할 수 없다.

이것 역시 마찬가지다. SQL에 의존적인 상황에서 개발자가 엔티티에 신뢰를 가질 수 없다. SQL이 잘못된 경우 테이블에서는 not null이지만, 엔티티에는 null 값이 들어가기도 한다.


JPA를 활용한 CRUD

그렇다면 JPA로 CRUD하는 것을 보고 기존의 문제점들이 얼마나 해소되었는지 알아보자.

INSERT

jpa.persist(member);

SELECT

// ID로 member 테이블 조회
String memberId = "helloId";
Member member = jpa.find(Member.class, memberId);

// 조회된 테이블의 FK로 team 테이블 조회
Team team = member.getTeam();

UPDATE

Member member = jpa.find(Member.class, memberId);
member.setName("updated name");

기본적으로 위의 코드들을 보면 SQL에 의존적이지 않다. CRUD하고 싶은 테이블에 알맞는 엔티티만 잘 생성하고 persist, find등의 메소드로 SQL없이 처리할 수 있다.

또한, SELECT 코드의 두번째 코드를 보면 Member 엔티티에 Team 참조 변수를 가지고 있는 것을 확인할 수 있다. RDB의 경우에는 외래키로 참조를 설정하지만 객체지향언어의 경우엔 참조변수를 직접 가지고 있음으로써 참조를 설정할 수 있다. 따라서 member.getTeam만 하더라도 자동으로 member의 team_id로 team 테이블을 조회한다.


객체 그래프 탐색

SELECT *
FROM MEMBER A JOIN TEAM B
ON A.TEAM_ID = B.TEAM_ID;

위의 쿼리를 JDBC로 실행시켜서 그에 맞는 resultSet을 가져왔다고 생각해보자. 그렇다면 Member, Team 엔티티를 가져올 것이다. 여기서 member와 연관관계가 있는 Order 엔티티를 가져오고 싶으면 어떻게 해야 할까?

다른 메소드를 만들거나 쿼리를 수정해야할 것이다. 이렇게 SQL에 종속적인 개발을 피할 수 없기 때문에 함부로 비즈니스 로직을 수정할 수 없다.

Member member = jpa.find(Member.class, memberId);

Team team = member.getTeam();
String team_name = team.getName();

Order order = member.getOrder();
String order_price = order.getPrice();

그렇다면 위의 코드를 보자. 처음에 가져온 Member 엔티티에서 getTeam 메소드로 Team 엔티티를 가져와서 사용한다. 그리고 Order 엔티티를 가져오고 싶다는 요구사항이 추가되었기 때문에 아래와 같이 Order 엔티티도 member에서 바로 가져와서 사용하는 것을 확인할 수 있다.

여기서 사실 실제로 SQL이 실행되어 team 테이블을 조회하는 부분은 team.getName()이다. 실제로 엔티티를 사용하는 부분까지 기다렸다가 사용할 때 쿼리를 실행시킨다. 이를 지연 로딩이라고 한다.

어떤 관점에서는 member, team, order 테이블에 한번에 접근해서 데이터를 받아오는 것이 더 효율적이라고 보여지기도 한다. 이를 즉시 로딩이라고 한다. 지연 로딩과 즉시 로딩은 프로그램의 성향이나 정책상 더 알맞는 것을 사용하는 것이 올바르다.

profile
명확하게 말하고, 꼼꼼하게 개발하자

0개의 댓글