[JPA] 1. 개요

Hyeonsu Bang·2021년 11월 29일
0

JPA

목록 보기
1/1
post-thumbnail

JPA: Java Persistence API

자바 진영 ORM 기술 표준. hibernate를 비롯한 다른 ORM 프레임워크의 기능을 추상화하여 만든 인터페이스, ORM 표준 기술 명세이다.

1. JPA의 등장

기존 애플리케이션 개발 방식에서는 DB와 통신하기 위해서 데이터 접근 계층에 많은 SQL 관련 코드를 작성해야 했다. 쿼리를 일일이 작성하는 것은 물론이고, 요구사항이 바뀌는 경우 DTO부터 SQL 쿼리를 모두 수정해야 해서 유지보수도 힘들었다. 애플리케이션 개발은 점점 데이터 코드 작성에 의존적이게 되었다. 실제로 나도 myBatis로 개발을 할 때 로직보다 SQL을 짜는 데에 더 많은 시간을 보내면서 '이게 개발인가?' 싶은 생각도 들었었다.



1.1 패러다임 불일치

이러한 문제는 객체 지향적인 개발 언어와 테이블의 관계로 구성된 데이터베이스의 근본적인 지향점이 다르기 때문에 발생했다. 객체 지향 언어에서는 상속, 추상, 다형성 등 객체 간의 복잡성을 통해 데이터를 관리한다. 반면 관계형 데이터베이스는 이러한 개념을 거의 사용하지 않으며, 데이터 자체에 중점을 두고 구조화되어 있다.


1.1.1 상속

자바에서는 상속을 통해서 객체 간의 관계를 표현한다. 하지만 DB에 저장될 때 이를 구현하려면 번거롭다. 예를 들어 Cuisine이라는 슈퍼 클래스가 있고 서브 클래스 Chinese, Korean, Japanese가 각각 이를 상속한다고 해보자. 기존의 JDBC template으로 Chinese 객체를 저장한다고 하면, Chinese의 내용과 Cuinese을 따로 SQL로 저장해야 할 것이다. 조회하는 것은 더 귀찮다. 저장한 Chinese 객체를 찾으려면 Cuisine과 Chinese 테이블을 조인해서 조회한 결과를 Chinese 객체를 반환하는 코드를 짜야할 것이다.


JPA에서는 위의 번거로운 로직을 알아서 수행해준다. 서브 클래스인 Chinese를 ``em.persist(chinese)`` 와 같이 저장하면, 알아서 Cuisine과 Chinese 를 insert하는 쿼리를 수행한다. 조회할 때에도 마찬가지로 알아서 조인 쿼리를 작성하여 조회해준다.

1.1.2 연관관계

자바와 RDBMS에서 모델링의 가장 큰 차이점은 객체는 참조를 사용하지만 RDBMS는 외래키를 사용한다는 점이다. 외래키로는 관련된 테이블을 모두 조회할 수 있지만, 자바에서는 참조를 가진 쪽만 데이터에 접근할 수 있다. 객체도 테이블처럼 cuisineId와 같이 외래키와 동일한 필드를 객체 내에 직접 저장하는 방법을 생각해볼 수 있지만, cuinse의 정보를 불러올 때 결국 chinese.getCuisineId()를 사용하고 다시 해당 id로 DB에 접근해야 하므로 Cuisine과 Chinese 간의 관계를 제대로 표현할 수 없는, 객체지향적이지 못한 코드를 써야한다.

Chinese를 조회하는 코드를 보면 아래와 같다.

public class Chinese extends Cuisine{

	String id;
	String cuisineId;
	String ingredient;

}
String cuisineId = chinese.getCuisineId();
Cuinise cuisine = dao.selectCuisine(cuisineId);

좀 더 객체 지향적으로 모델링을 한다고 하면 아래와 같이 관계된 객체의 참조값을 조회하면 될 것이다.

Cuisine cuisine = dao.selectCuisine(chinese.getCuisine());

이렇게 되면 Chinese 객체에 저장된 Cuisine을 불러온다는 표현이 되므로 두 객체 간의 관계는 표현할 수 있지만, DB에도 이런 과정을 적용하기 위해서는 Chinese를 저장할 때 Cuisine의 참조값을 개발자가 직접 저장을 해주어야 한다. 객체 - DB 간의 패러다임을 해소하기 위해 작업이 늘어나는 것이다.

JPA를 통해 엔티티 간의 관계를 설정해놓고, 아래와 같이 수행하면 chinese에 cuisine으로 접근할 수 있는 참조값을 외래키로 변환하여 적절하게 insert 쿼리를 작성하여 저장해준다.

chinese.setCuisine(cuisine);
jpa.persist(chinese);


후에 데이터를 조회할 때, 이와 같이 작성하면 외래키를 참조로 변환시켜 chinese 에 관련된 cuisine을 손쉽게 얻어올 수 있다.

Chinese chinese = jpa.find(Chinese.class, chineseId);
Cuisine cuisine = chinese.getCuisine();


객체 간의 연관관계를 객체 지향적인 코드를 사용하면서도 실제 DB에도 편리하게 적용할 수 있게 된다.



1.2 객체 그래프 (object graph) 탐색

객체 간의 관계를 통해 표현되는 네트워크, 관계도. 객체 참조를 통해 객체를 찾는 것을 객체 그래프 탐색이라고 한다. 예를 들어 Member, Team, Order, OrderItem 등 여러 객체가 관계를 맺고 있을 때,



	member.getOrder().getOrderItem()...

이와 같은 방식으로 객체 그래프를 탐색할 수 있다.



SQL을 사용하여 직접 객체 그래프를 탐색하게 되면 탐색할 범위를 정해놔야 한다. 위 코드를 쿼리로 보면 아래와 같이 짜야 한다.

SELECT member.*, order.*, orderItem.*
FROM member m
JOIN order o ON o.id = m.order_id 
....

이와 같은 상황에서 검색할 컬럼이 하나 더 있거나, order_id를 통해 또 다른 테이블의 정보를 얻어오고자 한다면 쿼리 일부와 dao 일부를 모두 건드려야 할 것이다.



따라서 아래와 같은 코드가 있다고 할 때

Member member = dao.find(memberId);
member.getOrder();
member.getOrder().getOrderItem();


기존 myBatis와 같이 ORM을 사용하지 않는 환경에서는 2번 3번 라인의 코드가 제대로 작동하는지는 확신할 수가 없다. order 필드가 null이라면 3번 라인에서 NPE가 발생할 것이다. 이를 방지하려면 개발자가 SQL코드를 일일이 확인해서 사용할 수 있는 범위를 파악할 수 밖에 없다. 그래서 위와 같이 개발을 하다보면 조인한 테이블마다 조회하는 메서드를 만들게 된다. 코드가 불필요하게 많아지고, 쿼리가 길어지고 복잡해지니 내가 작성하지 않은 경우 어떻게 써야 하나 막막해서 안쓰게 된다. 그래서 또 새로운 쿼리를 만들게 되고, 그러면 코드 관리는 더욱 어렵게 된다.



1.2.1 JPA에서의 객체 그래프 탐색

JPA에서는 객체를 사용하는 시점에 적절히 쿼리를 만들어 수행해준다. 그래서 따로 조인 쿼리를 짜지 않더라도 관계 설정이 되어 있는 여러 엔티티에서 다른 엔티티를 호출하는 메서드를 사용하면, 해당 엔티티의 정보를 객체로 반환 받을 수 있다. 따라서 연관관계를 맺어놓은 객체들을 마음껏 탐색할 수 있게 된다. 이것이 가능한 것은 프록시 객체를 이용한 지연 로딩 기능이 있기 때문인데, 이는 뒤에서 천천히 설명한다.



1.3 동일성 비교

같은 조건으로 데이터를 검색했을 떄, 기존의 JDBC에서는 동일성을 보장하기 위해서는 대부분의 객체마다 equals()와 hashcode()를 오버라이딩해서 사용하여야 했다.

public Member getMember(String memberId){

	String sql = "SELECT * FROM MEMBERS WHERE MEMBER_ID = ?";

	/..../
	return new Member(....);
}

String memberId = "100";
Member m1 = dao.getMember("100");
Member m2 = dao.getMember("100");


하지만 JPA는 같은 트랜잭션에서 같은 객체가 조회되는 것을 보장한다.

String memberId = "100";
Member m1 = jpa.find(Member.class, memberId);
Member m2 = jpa.find(Member.class, memberId);

위와 같은 코드에서 m1==m2는 true를 리턴한다. 이는 JPA에서 사용하는 영속성 컨텍스트를 통해 가능한 것인데, 자세한 것은 이 다음 장에서부터 확인할 수 있다.






reference: **「자바ORM표준JPA」, 김영한 **
profile
chop chop. mish mash. 재밌게 개발하고 있습니다.

0개의 댓글