자바는 JDBC API를 사용해 SQL을 데이터 베이스에 전달하기 때문에 중요하지만 SQL 중심적인 개발을 하게 되면 많은 문제가 발생한다.
JPA
Java persistence API는 자바의 ORM 기술의 표준이다
ORM❓
Object-Relational Mapping
객체와 관계형 데이터베이스를 매핑한다는 뜻
ORM 프레임워크는 객체와 테이블을 매핑해 패러다임의 불일치를 개발자 대신 해결해준다. 객체는 객체대로 생성하고, 데이터베이스는 데이터베이스에 맞도록 설계를 가능하게 해준다. 개발자는 이를 매핑하는 방법만 전달해주면 된다.
JPA란
자바 ORM에 대한 API 표준 명세이고, 인터페이스의 모음이다. 따라서 구현체가 없고, 사용하기 위해서는 ORM프레임워크를 선택해야한다.
다양한 프레임워크가 존재하지만 가장 대중적인 것은 하이버 네이트이다.
동작 과정
JPA는 애플리케이션과 JDBC 사이에서 동작한다. JPA 내부에서 JDBC API를 사용하여 SQL을 호출하여 DB와 통신한다.
개발자가 ORM 프레임워크에 저장하면 적절한 INSERT SQL을 생성해 데이터베이스에 저장해주고, 검색을 하면 적절한 SELECT SQL을 생성해 결과를 객체에 매핑하고 전달해 준다.
사용 이유
1. 생산성
JPA를 사용하면 자바 컬렉션에 저장하듯이 JPA에게 저장할 객체를 전달하면 된다.
지루하고 반복적인 코드를 개발자가 직접 작성하지 않아도 되며, DDL문도 자동으로 생성해주기 때문에 데이터베이스 설계 중심을 객체 설계 중심으로 변경할 수 있다.
2. 유지보수
필드를 하나만 추가해도 관련된 SQL과 JDBC 코드를 전부 수행해야 했지만 JPA는 이를 대신 처리해주기 때문에 개발자가 유지보수해야하는 코드가 줄어든다.
3. 패러다임의 불일치 해결
JPA는 연관된 객체를 사용하는 시점에 SQL을 전달할 수 있고, 같은 트랜잭션 내에서 조회할 때 동일성도 보장하기 때문에 다양한 패러다임의 불일치를 해결한다.
4. 성능
애플리케이션과 데이터베이스 사이에서 성능 최적화 기회를 제공한다.
같은 트랜잭션안에서는 같은 엔티티를 반환하기 때문에 데이터 베이스와의 통신 횟수를 줄일 수 있다. 또한, 트랜잭션을 commit하기 전까지 메모리에 쌓고 한번에 SQL을 전송한다.
5. 데이터 접근 추상화와 벤더 독립성
RDB는 같은 기능이라도 벤더마다 사용법이 다르기 때문에 처음 선택한 데이터베이스에 종속되고 변경이 어렵다. JPA는 애플리케이션과 데이터베이스 사이에서 추상화된 데이터 접근을 제공하기 때문에 종속이 되지 않도록한다.
만약 DB가 변경되더라도 JPA에게 알려주면 간단하게 변경이 가능하다.
SQL을 직접 다룰 때의 문제점
1. 반복적인 코드의 작성
테이블이 100개 존재한다면 100개의 CRUD를 작성해야 한다.
- SQL 작성 -> JDBC API로 SQL 실행 -> 결과를 객체로 매핑 등
2. SQL 의존적 개발
만약 테이블에 하나의 Column을 추가해야 한다면?
- 모든 SQL의 변경이 필요하다.
- INSERT, UPDATE, SELECT 등 관련된 모든 쿼리와 메소드가 변경되어야 한다.
- 만약 제대로 동작하지 않다면 직접 DAO를 열어 SQL을 확인해야 한다.
- 논리적인 계층 분할이 어렵게 된다.
3. 패러다임의 불일치
자바는 객체지향 언어지만, 관계형 데이터베이스는 객체지향이 다루는 개념이 존재하지 않고 서로 지향하는 목적이 다르기때문에 이로 인해 패러다임의 불일치가 발생한다.
객체 지향 프로그래밍은 추상화, 캡슐화, 정보은닉, 상속, 다형성 등 시스템의 복잡성을 제어할 수 있는 다양한 장치들을 제공한다. 객체로 모델링을 하게 되면 이러한 많은 장점을 얻을 수 있다.
하지만, 객체를 저장하려고 할 때, 상속을 받았거나 다른 객체를 참조하고 있어 문제가 발생한다. 회원을 저장할 때 팀을 함께 저장하지 않았다면 팀이 사라져버릴 수 있다.
서로 다른 목적 때문에 개발자가 중간에서 문제를 해결하기 위해 코드를 작성해 매핑해야 한다.
상속
객체는 상속이 있지만 테이블은 상속이 없다. 그나마 유사한 슈퍼타입-서브타입이 존재
JDBC에서 상속 객체를 저장하기 위해서는 부모 객체용 INSERT와 자식 객체용 INSERT를 따로 작성해야 하고, 조회도 JOIN을 통해 결과를 얻어 객체를 생성하는 모든 코드를 작성해야 한다.
연관관계
객체는 참조를 통해 연관관계를 가지고 참조에 접근해 연관된 객체를 조회
- 참조가 있는 방향으로만의 참조가 가능(단방향)
테이블은 외래키를 사용해 다른 테이블과 연관관계를 가지고 조인을 통해 테이블을 조회
- 양방향 모두의 참조가 가능하다.
이를 맞추기 위해 객체에 외래키로 모델링을 하게 되면 저장은 편리하지만 객체는 연관된 객체를 참조할 수 없다는 문제가 발생한다. 반대로 연관된 객체를 저장하면 테이블에 저장하거나 조회할 때 개발자가 모든 변환 역할을 해줘야 한다.
4. 객체 그래프 탐색
객체는 자유롭게 객체 그래프를 탐색할 수 있어야 하지만 SQL을 이용해 조회를 했다면 실행된 처음 SQL에 따라 탐색의 범위가 저장되기 때문에 제약이 발생한다.
따라서 개발자는 어디까지 탐색이 가능한지 모르게 되고, DAO를 통해 실행된 SQL을 직접 확인해야만 탐색이 가능하다.
SELECT M.*, O.*
FROM MEMBER M
JOIN ORDER O ON M.MEMBER_ID = O.MEMBER_ID
member.getOrder(); // OK
member.getOrder().getOrderItem(); // null
비교
데이터베이스는 기본키의 값으로 구분하지만 객체는 동일성 비교(==
)와 동등성 비교(equals()
)의 두가지 방법이 존재한다.
SQL을 통해 기본키가 같은 객체를 불러와 매핑하면 동일성을 비교할 때 false
가 반환된다. 같은 로우지만 객체로 볼 때 다른 인스턴스이기 때문이다.
long memberId = 100;
Member member1 = memberDao.getMember(memberId);
Member member2 = memberDao.getMember(memberId);
member1 == member2; // false
MemberDao.getMember()
는 호출할 때마다 new Member()
로 새로운 인스턴스가 반환되기 때문이다.
📚 Reference
김영한, 『자바 ORM 표준 JPA 프로그래밍』, 에이콘출판(2015)
'Programming > JPA' 카테고리의 다른 글
[JPA] 영속성 전이(cascade)와 고아 객체 (0) | 2021.05.20 |
---|---|
[JPA] 프록시, 즉시 로딩과 지연로딩 (0) | 2021.05.20 |
[JPA] Entity 생명주기(Entity LifeCycle) (0) | 2021.05.05 |
[JPA] 플러시(flush) (0) | 2021.05.05 |
[JPA] 영속성 컨텍스트(Persistence Context) (0) | 2021.05.05 |