소스
https://github.com/rkwhr0010/jpa
SQL을 직접 다룰 때 발생하는 문제점
지루한 반복 코드
JDBC API를 사용해 DB에 CRUD를 하려면 너무 많은 직접 작성한 유사한 SQL과 DB 연결을 위한 반복성 코드가 생긴다.
데이터 접근 계층(DAO)는 대부분 반복성 코드와 유사한 SQL이 많다.
SQL에 의존적인 개발
비즈니스 요구사항은 언제나 변한다.
public class Member { private Long id; private String name; private String email; // 새로 추가된 컬럼 } |
비즈니스 요구사항을 모델링한 객체를 엔티티라 한다.
요구사항의 변경으로 위 Member 엔티티에서 email을 추가로 저장해야 하는 상황이 오면, 모든 관계된 CRUD 코드를 확인하고 SQL을 수정해야 한다.
JPA와 문제 해결
JPA는 객체를 DB에 저장할 때 JPA가 제공하는 API를 사용하면 된다.
JPA가 자동으로 SQL를 생성해서 DB에게 전달한다.
다음은 CRUD 에 대표적인 메서드들이다
저장
조회
수정
수정의 경우 특별하다. find()로 조회한 엔티티를 수정하면, 트랜잭션을 커밋할 때 DB에 수정사항이 반영된다.
삭제
페러다임의 불일치
애플리케이션은 발전하면서 복잡성도 증대된다.
개발자는 복잡성을 잘 제어해 유지보수하기 좋은 코드를 만들려 노력한다.
객체지향 캡상추다, SOLID, 디자인패턴 등은 복잡성을 제어하기 위한 도구이다.
비즈니스 요구사항은 코드로 구현을 해도 결국 산출물로 데이터가 나오고 이는 영구히 저장할 곳이 필요하다. 이때 RDB(관계형 DB)가 그 역할을 한다. 여기서 DB는 편의상 RDB로 한정한다.
런타임에 객체로 존재하는 데이터를 RDB에 저장해야 한다.
문제는 객체와 RDB는 지향하는 목적이 다르다는 것이다.
RDB에는 객체지향이라는 개념이 존재하지 않는다. 데이터 중심으로 구조화되어 있다.
객체 구조를 테이블 구조에 저장하는 것에 한계가 있다. 이를 패러다임 불일치라 한다.
패러다임 불일치를 해소하기 위해 결국 개발자가 많은 노력을 기울여야 한다.
상속
객체는 상속이라는 개념이 존재한다.
DB 모델링에는 유사한 개념인 슈퍼타입 서브타입 관계가 존재한다.
비슷해 보이지만, 다르다. 객체는 extends 키워드로 부모의 속성을 상속 받지만, DB의 경우 슈퍼타입과 서브타입 간 KEY 로만 관계를 맺는다.
즉, 1개의 서브타입 객체를 DB에 저장하려면 2개의 INSERT 쿼리가 필요하다.
이게 패러다임 불일치를 해소하기 위한 비용이다.
JPA와 상속
JPA는 마치 자바 컬렉션에 저장하듯 사용하면, 나머지는 JPA가 알아서 처리해 준다.
JPA가 알아서 2개의 INSERT 쿼리로 처리해줌
package study.jpa.entity; import jakarta.persistence.Column; import jakarta.persistence.DiscriminatorColumn; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; import jakarta.persistence.Inheritance; import jakarta.persistence.InheritanceType; @Entity @Inheritance(strategy = InheritanceType.JOINED) @DiscriminatorColumn(name = "DTYPE") public abstract class Item { @Id @GeneratedValue @Column(name = "ITEM_ID") private Long id; private String name; private Integer price; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getPrice() { return price; } public void setPrice(Integer price) { this.price = price; } } |
package study.jpa.entity; import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; @Entity @DiscriminatorValue("A") public class Album extends Item{ private String artist; public String getArtist() { return artist; } public void setArtist(String artist) { this.artist = artist; } } |
package study.jpa.entity; import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; @Entity @DiscriminatorValue("B") public class Book extends Item{ private String author; private String isbn; public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } public String getIsbn() { return isbn; } public void setIsbn(String isbn) { this.isbn = isbn; } } |
package study.jpa.entity; import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; @Entity @DiscriminatorValue("M") public class Movie extends Item{ private String director; private String actor; public String getDirector() { return director; } public void setDirector(String director) { this.director = director; } public String getActor() { return actor; } public void setActor(String actor) { this.actor = actor; } } |
package study.jpa; import java.util.function.Consumer; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import jakarta.persistence.EntityManager; import jakarta.persistence.EntityManagerFactory; import jakarta.persistence.EntityTransaction; import jakarta.persistence.Persistence; import study.jpa.entity.Album; public class Main { static Logger logger = LogManager.getLogger(); static EntityManagerFactory emf = Persistence.createEntityManagerFactory("studyjpa"); public static void main(String[] args) { createPersistenceContext(em -> { Album album = new Album(); album.setName("앨범"); album.setArtist("홍길동"); album.setPrice(1000); em.persist(album); }); } private static void createPersistenceContext(Consumer<EntityManager> consumer) { EntityManager em = emf.createEntityManager(); EntityTransaction tx = em.getTransaction(); try { tx.begin(); consumer.accept(em); tx.commit(); } catch (Exception e) { tx.rollback(); } finally { em.close(); } emf.close(); } } |
자동으로 2개 SQL
JPA 자동으로 객체와 테이블 간 간극을 매핑해준다.
연관관계
객체는 참조를 통해 해당 객체에 접근한다.
테이블은 외래 키를 통해 조인으로 연관 테이블을 조회한다.
객체의 참조와 RDB의 외래 키 사이 패러다임 불일치는 RDB 구조를 객체지향 모델링에 적용 시키는 것이 어렵다.
참조의 경우 단방향이다.
테이블의 경우 외래 키로 관계를 맺으므로, 방향이라는 것이 없다. 굳이 따지면 양방향이다.
객체를 테이블에 맞추어 모델링
패러다임 불일치를 가장 크게 체감할 수 있는 방법은 객체를 테이블에 맞춰서 설계해보는 것이다.
위와 같은 설계는 데이터를 DB에 저장하기는 편하다.
문제는 RDB의 경우 키 값으로 조인을 해 관계를 맺어준다.
객체는 값으로 관계를 맺어줄 수가 없다. 참조로 연결해야 연관된 객체를 알 수 있다.
위 처럼 객체지향에 맞춰서 설계를 하면, DB 데이터를 객체에 맞춰야한다.
이 과정을 JPA가 자동으로 해준다.
즉, 개발자는 마치 컬렉션에서 객체를 꺼내듯 사용하면, DB에서 엔티티가 조회된다.
JPA가 알아서 패러다임 불일치를 해결해준다.
위 처럼 객체 참조를 통해 이동하는 것을 객체 그래프 탐색이라고 한다.
JPA 비교와 그냥 비교
객체 비교의 두가지 방법
동일성은 == 비교이다.
동등성 비교는 equals() 비교이다
동일성은 참조 변수의 경우 같은 인스턴스인지 확인한다.
동등성은 그 서로 다른 객체여도 그 내용이 같은지 확인한다.
여기서 중요한 점은 JPA로 조회한 회원 객체이다.
같은 영속성 컨텍스트에서 조회한 멤버는 동일성을 보증한다.
정리
객체 모델과 RDB 모델은 지향하는 패러다임이 다르다.
객체지향 어플리케이션을 더욱 더 객체지향스럽게 디자인 할 수록
패러다임 불일치가 더 커진다.
JPA는 이런 패러다임 불일치 문제를 해결해준다.
JPA는 개발자로 하여금 계속 객체지향 모델링을 유지하도록 도와준다.
JPA란 무엇인가
JPA Java Persistence API
자바 진영의 ORM 기술 표준
ORM Object-Relational Mapping
객체와 RDB를 매핑한다는 뜻
JPA를 제외하고도 모든 자바 기반 기술은 DB 접속은 JDBC API를 사용한다.
ORM 프레임워크는 객체와 테이블 매핑에서 오는 패러다임 불일치 문제를 해결해준다.
개발자는 자바 코드에서 컬렉션에 객체를 저장하듯 ORM 프레임워크에 저장하면, 나머지는 작업은 ORM 프레임워크가 알아서 해준다.
가장 유명한 ORM 프레임워크는 하이버네이트다.
JPA는 자바 ORM 기술에 대한 API 표준 명세로, 바꿔 말하면, 인터페이스 묶음이다.
즉, 실질적인 구현체 중 하나를 선택해 사용한다.
왜 JPA를 사용해야 하나
장점으로 생산성, 유지보수성, 패러다임 불일치 해결, 성능, 데이터 접근 추상화(벤더 독립성)가 있다.
지루하고 반복적인 CRUD SQL과 SQL과 객체 사이 데이터 매핑을 할 필요가 없다.
엔티티에 필드가 변경되어도 이전 JPA사용하기 전보다 훨씬 적은 양의 코드만 손보면 된다.
JPA가 객체 RDB간 매핑을 해주기 때문에 개발자는 객체의 참조를 그대로 사용해 객체 그래프 탐색을 할 수 있다. 원래라면 개발자가 조인 쿼리를 짜고, 나온 데이터를 전부 객체에 알맞게 매핑해줘야하는 번거로운 일이다. 이 때문에 과거에는 객체를 테이블에 맞춰 데이터 중심으로 사용했다.
JPA는 애플리케이션과 DB 사이에서 동작한다. 그 사이에서 캐시 역할 등으로 성능 최적화 기회를 얻는다.
데이터 접근이 한 단계 더 추상화된 방언(Dialect) 계층이 있다.
따라서 DB 변경이 자유롭다. 알맞은 벤더의 방언으로 교체하기만 하면 방언이 알아서 SQL을 변경해준다.
극단적으로 개발은 Mysql 운영은 오라클로 바꿀 수 있다.
'개발 > JPA' 카테고리의 다른 글
06 다양한 연관관계 매핑 (0) | 2024.04.14 |
---|---|
05 연관관계 매핑 기초 (1) | 2024.03.22 |
04 엔티티 매핑 (1) | 2024.02.26 |
03 영속성 관리 (1) | 2024.02.19 |
01 JPA 환경 설정 (0) | 2023.11.23 |