JPA( Java Persistence API )
。JAVA에서ORM 기술표준으로 활용되는API 라이브러리
。ORM에 의해어플리케이션의DB Entity를DB Table과 자동으로Mapping하여영속화된DB Entity가 변화 시Dirty Checking을 통해 변경사항을 감지 후Update Query를DB에 전달하여매핑된DB Table의 데이터도 변경
。JPA를 구현한 대표적인오픈소스 구현체로Hibernate가 존재.
。CRUD를 수행 시 기존JDBC는 개발자가 직접SQL을 작성하는 번거로움이 존재
▶JPA는구현체인Hibernate를 통해영속성 컨텍스트를 포함하는EntityManager의API를 사용하는 경우Hibernate가 자동으로SQL을 생성하여CRUD를 수행하므로 간편
。Spring에서는Spring Data JPA로 구현되어있으며EntityManager가 아닌JpaRepository 인터페이스 구현체로DB Entity의CRUD를 수행
@Entity가 선언된Entity Class내부에클래스 객체 필드를 선언하는 경우Entity 객체로 판단하므로,연관관계 Mapping을 위한어노테이션(@ManyToOne등 )을 선언해야함.
▶ 일반POJO 객체를 내부에 선언할 필요가 존재 시@Embedded로 선언해야한다.
。JPA의Hibernate를 사용하기 전에는 무조건 사용할데이터베이스를 사전에 정의해야한다.
ORM(Object-Relational Mapping) :
。어플리케이션의DB Entity 객체를RDBMS의DB Table에 자동으로영속화하는 것
▶DB Entity 객체와RDBMS DB Table간 지속적으로 자동으로Mapping하여DB Entity가 변화 시Update Query를DB에 전달하여매핑된DB Table의 데이터도 변경
。SQL을 직접 사용하지 않고,DB Entity 객체를 중심으로DB와 상호작용을 수행
▶RDBMS(H2-DB,mySQL,postgreSQL) 의 변경에도 적합한Dialect를 자동으로 설정하여 유연한 적용이 가능.
ORM특징
Dirty Checking
。영속성 컨텍스트내에서 관리되고있는영속 상태의DB Entity가Transaction내에서 변화 시 자동으로 감지하여 변경된 내용을 자동으로 감지하여flush()시점에서UPDATE Query를 생성 및DB로 자동반영하는JPA 기능
▶DB Entity를 변경하는Transaction이COMMIT되는 경우 변경된 정보에 대해서만UPDATE를 자동으로 수행
쓰기 지연( Transactional Write-Behind )
。Entity를 각각생성 / 수정시점에서 즉시DB에쿼리를 전달하지 않고,영속성 컨텍스트내에서 임시로 저장 후COMMIT시점에서 일괄적으로DB에쿼리를 전달하여 반영하는 기능
▶디스크 I/O를 감소하기 위한 용도
。트랜잭션이COMMIT되는 시점에서만Lock이 적용되므로,Lock발생을 최소화 할 수 있음.
Entity 객체수정 / 삭제시조회를 먼저 수행해야하므로 총 2번의Query가 발생
。수정 / 삭제를 수행하는 경우 우선영속화된Entity객체를영속성 컨텍스트에서 찾아와야하므로 각각조회 쿼리가 추가적으로 발생
▶ORM의 단점Order foundedOrder = em.find(Order.class, order.getId()); em.remove(foundedOrder);
JPA사용 시 주의사항
지연로딩( Lazy Loading )
。컴파일시점에서프록시 객체로 초기화 및런타임 시점에서 접근 시DB에Query를 전달하여 실제Entity 객체로 초기화
。주로(fetch = FetchType.LAZY)의연관관계로매핑된Entity객체를추가 조회하여Query를 반복적으로 수행하여N+1 문제가 발생할 수 있다
N+1 문제
。특정Query를 실행 시 여러번Query가 실행되는 문제
ex )댓글조회 시대댓글까지 조회
- 복잡한
Query의 한계
。Query가 복잡한 경우JPA를 통한 실행이 어렵고,Native Query또는MyBatis를 사용
DB Entity의 설계가 중요
。Entity Class를 잘못 설계하는 경우 성능저하를 유발할 수 있음.
JPA 핵심 구성 요소
。EntityManagerFactory/EntityManager/영속성 컨텍스트/Entity가 존재
EntityManagerFactory
。EntityManager을 생성하는팩토리 객체
。EntityManagerFactory는 내부적으로 Connection Pool을 가지고 있으므로,어플리케이션 생애주기내 오직 하나의객체만 생성 및 관리되어야함.
▶ 해당Connection Pool의DB Connection을 기반으로 하는EntityManager가 생성됨.
EntityManagerFactory생성하는 방법
。META-INF/persistence.xml의<persistence-unit> 태그를 기반으로 생성EntityManagerFactory emf = Persistence.createEntityManagerFactory("<persistence-unit>식별자명");。
Hibernate의 기능을 이용해메서드 체이닝을 통해 간단히DB 접속정보를 포함하여 생성
▶xml 설정파일없이어플리케이션 코드에서 간단히 생성가능.HibernatePersistenceConfiguration hfg = new HibernatePersistenceConfiguration( "hibernate-exp-4"); hfg .jdbcDriver("org.h2.Driver") .jdbcUrl("jdbc:h2:mem:test-db") .jdbcUsername("sa") .jdbcPassword("") .managedClass(Comment.class) .managedClass(Post.class) .property("hibernate.hbm2ddl.auto", "create-drop"); EntityManagerFactory emf4 = hfg.createEntityManagerFactory()▶
Entity 클래스를 직접 등록해야한다.
EntityManager
。DB Entity 객체의 전반적인생명주기를 관리하는 역할을 수행하는클래스
▶EntityManager내부에영속성 컨텍스트를 포함하므로,영속성 컨텍스트에 등록된Entity들을 관리
。MyBatis - SqlSession과 같은DAO 역할을 수행하며EntityManagerFactory에 의해DB Connection이 포함된 상태로 생성
▶JPA에서 실제SQL을 수행하는객체로서Entity를DB에 저장, 조회, 수정, 삭제하는 역할을 수행
。EntityManager은 내부에단일 DB Connection을 통한Session을 포함하므로, 사용한 후에는 반드시EntityManager객체.close()를 선언
▶EntityManager객체.close()시 등록된Entity는detached 상태로 전환
。하나의EntityManger는영속성 컨텍스트(First Level Cache + Action Queue + Snapshot)를 포함
EntityManager em = EntityManagerFactory객체.createEntityManager();
ActionQueue
。영속성 컨텍스트내 존재하는자료구조
▶Action Queue내에는DML Query들을 저장하고,flush()가 발생하는 시점에서deque를 수행하여 일괄적으로 순차적으로DB에 전달
。DB 격리수준과 별개로,영속성 컨텍스트에서Repeatable Read 격리수준을 제공
EntityManager의메서드
EntityManager객체.getTransaction()
。JPA에서트랜잭션을 직접 제어 시 사용하는EntityTransaction 객체를 생성
▶ 해당EntityManager객체로DAO 기능을 사용 시tx객체.begin(),tx객체.commit(),tx객체.rollback()등의TCL을 지정
。Spring JPA에서는@Transactional 어노테이션이 해당 과정을추상화하여 자동으로 수행
EntityManager객체.close()
。EntityManager내영속성 컨텍스트를 종료하는메서드
▶ 내부에서 관리하는Entity 객체는비영속화
EntityManager객체.persist(DB Entity 객체)
。해당Entity 객체를EntityManager내영속성 컨텍스트에영속화시 사용하는메서드
▶ 해당Entity 클래스는xml파일내 등록되어야함.
。EntityManager객체.persist(Entity객체)를 수행하여영속화한 경우, 해당Entity 객체에Auto Increment를 통해 자동으로PK값을 할당됨.
▶Test수행 시 해당PK값이NULL인지 검증
EntityManager객체.find(Entity클래스타입, PK값)
。매핑된DB Table에서 해당PK값의데이터를Entity 객체로서 조회하는 경우 사용하는메서드Order foundedOrder = em.find(Order.class, order.getId());▶ 조회할
클래스 객체 Type을클래스 리터럴로 지정해야한다.
EntityManger객체.remove(Entity객체)
。영속화된Entity객체를 삭제하는메서드Order foundedOrder = em.find(Order.class, order.getId()); em.remove(foundedOrder);▶
EntityManager.find()와 함께 사용
EntityManager객체.merge(Entity 객체)
。detached 상태의Entity객체를영속화 상태로 전환한영속화 Entity 객체를 반환
▶ 반환된Entity 객체로 작업 수행
EntityManager객체.createQuery(JPQL문, Entity클래스타입).getResultList()
。JPQL의SELECT문을 통해 조회를 수행할Query 객체를 생성 시 활용하는메서드
。클래스 리터럴을 통해Entity클래스.class를 선언하여 반환될클래스의메타데이터를 지정EntityManager em = emf.createEntityManager(); String jpql = """ SELECT m FROM Member m """; TypedQuery<Member> query = em.createQuery(jpql, Member.class);Member.class).getResultList();
Query 객체를 통해 조회된데이터 반환
Query객체.setParameter("변수명","값"):
。JPQL에매개변수 바인딩수행하는메서드
▶JPQL문내매개변수에변수를바인딩시:변수명으로 지정String jpql = """ SELECT m FROM Member m WHERE m.email = :email """; TypedQuery<Member> query = em.createQuery(jpql, Member.class); query.setParameter("email","wjdtn50@naver.com");
Query객체.setFirstResult(Offset).setMaxResults(Limit)
。JPQL문에offset과limit을 지정하는메서드
▶페이지네이션구현 시 사용
。query.setFirstResult(pageNum * pageSize).setMaxResults(pageSize);
▶setFirstResult:offset지정 :limit * 페이지번호
▶setMaxResults:limit지정Query객체.getResultList()
。JPQL문을 통해다건조회된Entity클래스 객체를List<클래스> 객체로 반환List<Member> resultList = em.createQuery(jpql, Member.class).getResultList();
Query객체.getSingleResult()
。JPQL문을 통해단건조회된Entity클래스 객체를 반환Member resultList = em.createQuery(jpql, Member.class).getSingleResult();
EntityManager사용 시 주의사항
EntityManager를 통한 작업 수행 시 해당EntityManager의Transaction 객체를 생성하여TCL을 적용
。한작업 단위에try ~ catch ~ finally를 적용하여BEGIN / COMMIT / ROLLBACK을 작성
▶ 작업이 모두 끝난 경우EntityManager 객체를 종료// EntityManager의 Transaction 객체 생성saction 객체 생성 EntityTransaction tx1 = entityManager.getTransaction(); try{ // Transaction BEGIN 실행 tx1.begin(); Post post = new Post(1, "title", "contents"); entityManager.persist(post); // Transaction Commit tx1.commit(); }catch (Exception e){ // 트랜잭션 실패 시 rollback. tx1.rollback(); } finally{ entityManager.close(); // EntityManager는 기능 하나당 EntityManager 객체를 사용하므로, // 기능 수행이 모두 끝난 경우 객체를 종료 }
JPA에서는 하나의작업당 하나의EntityManager 객체를 생성 및 할당하여 활용
。해당작업이 모두 종료된 경우EntityManager객체를 종료 및 다음작업에 대한 새로운EntityManager객체를 생성하여 할당
Thread Safe하지 않으므로, 하나의스레드는 하나의EntityManager를 사용하도록 설정
。Hibernate의Session을 감싸는객체로서SqlSession처럼Connection하나만 점유해서 사용하므로Thread Safe하지 않음.
。단.Tomcat의 경우요청당스레드하나씩 생성해서요청을 처리하므로 문제가 되지 않으나,Webflux를 사용하는 경우 해당 문제를 고려해야한다.
영속성 컨텍스트( Persistence Context )
。EntityManager 객체내부에 존재하는DB Entity 객체를영속 상태로서 저장 및 관리하는 용도의저장소
▶메모리상에 존재하여HDD의DB 반영전캐싱 역할을 수행하고, 동시에DB Entity를영구적으로 저장 및 관리하여영속성을 보장
。EntityManager에DB Entity를 등록 시쓰기 지연을 통해영속성 컨텍스트내에서 대기 후flush()가 수행되는 경우매핑된DB Table에 반영됨
。영속성 컨텍스트에서 관리되는DB Entity 객체의 변경 시Dirty Checking에 의해 변경을 감지하여Mapping된DB Table에도 변경쿼리를 전달
컨테이너 부트스트래핑:
EntityManager객체생성 시EntityManagerFactory의Connection Pool에서DB Connection 객체를 꺼내온 후 반영하면서영속성 컨텍스트를 초기화
flush():
。commit시 호출되어 기존Pending 상태인 모든Entity를매핑된DB Table에 일괄적으로 반영하도록DB에DML을 전송하는 역할을 수행
▶Pending:DB Entity변경내용이DB에 반영되지않고영속성 컨텍스트에만 반영된 상태
。flush()이 호출된 시점에서EntityManager내Acition Queue상에 존재하는SQL DML을Deque하여 모두 DB에 전달 및 반영
DB Entity
。ORM에 의해DB의 특정Table를1:1 Mapping한@Entity Class의클래스
。영속성 컨텍스트에 등록되어 영속화된Entity 객체는Entity Manager에 의해DB의 특정Table의데이터로서 취급
。Entity Class는@Entity선언하여 정의
▶ 추가적으로 필요 시@ManyToOne등을 선언하여FK를 설정
。Entity Class의필드는final이 선언되면 안된다.
Entity Class정의 시 주의사항
@Column(nullable = false)를 설정하더라도,Entity 객체생성 시Null값에 대한 검사를 하지 않는다.
。개발자는 입력되는데이터에 대해Spring Validation을 통해무결성을 검사하는 로직을 작성해야한다.
▶nullable = false는Hibernate에게ddl-auto생성 시 해당필드에제약조건 : not null로 설정하는속성으로Entity에 입력되는데이터자체를 검사해주지는 않는다.
DB Entity를클라이언트에게 반환하면 안되며,DTO를 정의하여 필요한데이터만 추출하여클라이언트에게 반환
▶DB Entity는연관관계를 통해 다른DB Entity 객체도 포함하고 있으므로
Entity 클래스의필드에final을 선언하지 말아야한다
PK 역할의@Id 필드를 반드시 정의해야하고,Setter를 적용하면 안된다.
。 해당@Id 필드를 기준으로영속성 컨텍스트에서 식별
。자동으로@GeneratedValue를 통해값할당이 수행되므로Setter를 적용하면 안된다.
필드의자료형은Wrapper Class로 지정
。Null이 들어갈 수 있으므로.
기본생성자는protected를 선언하여외부 패키지에서기본생성자가 호출되지 않도록 설정
▶DB에Null값의Record가 생성되지 않도록 보호
Entity와DB Table차이
Entity:
。DB에서 관리해야할 독립적으로 존재하는 대상
。데이터 모델링시설계 대상이 되는DB Table를 부르는 용어
▶DB Table과 다른 개념으로,모델링단계에서DB Table을 설계하는 틀 역할을 수행
DB Table
。DB에 저장되어 구현된 실제데이터 구조
DB Entity의 상태
。비영속/영속/준영속/삭제
// Transient Post post = new Post(1, "title", "contents"); // Managed entityManager.persist(post); // Removed entityManager.remove(post);
비영속( Transient )
。@Entity가 선언되었으나영속성 컨텍스트에 한번도 등록되지않아 관리되고 있지않은 순수POJO
▶EntityManager.save()에 등록되기전까지는new를 통해 생성된 직후의Entity 객체 상태가비영속으로DB에 반영되지않음
。Class내@OneToMany등의연관관계 매핑이 정의되있더라도영속화되기전까지는 초기화되지않아연관관계가 없는Null상태
영속( Managed )
。DB Entity가영속성 컨텍스트에 등록되어 관리되고 있는 상태
▶JpaRepository객체.save(비영속POJO)를 수행하는 경우Persistence Context에 저장되어 관리
。해당 시점에서@OneToMany등으로 정의된연관관계를 정의하기 위해DB에Query를 전달하여연관관계 Entity객체를 초기화
▶비영속 상태인 경우@OneToMany field는Null인 상태.
。Dirty Checking에 의해DB Entity의 변화를 자동으로 감지하고flush()시점에서 실제DB Table에 반영
。영속화:DB Entity를영속성 컨텍스트에 의해 관리되는 상태로 만드는것을 의미.
준영속( Detached )
。영속성 컨텍스트내 존재하는영속 상태였다가 연결이 끊긴 상태의DB Entity
▶ 더이상영속성 컨텍스트에 의해 관리되지 않음
。EntityManager.close()일 경우 해당EntityManager의영속성 컨텍스트내 등록된Entity는detach로 전환
삭제( Removed )
。Persistence Context에서 완전히 삭제된 상태
▶EntityManager.remove(entity)호출 시 해당 상태로 전환
JPA원리
。Spring Data JPA기준
1.Repository에서JpaRepository를 확장 및JpaRepository에 구현된API(EntityManager.save(DB Entity)) 수행
。Repository:Entity에 접근하기위한인터페이스
。JpaRepository:Entity Manager에 의한CRUD가 이미 구현된인터페이스public interface MemberRepository extends JpaRepository<Member,Long> { }▶
Repository에서JpaRepository<>를 확장해서 해당Entity에 대한CRUD를 수행
2.EntityManager에 의해Persistence Context에DB Entity를 등록
。등록 시 해당Entity의상태(비영속 / 영속 / 준영속 / 삭제)를 추적
3. 추후Transaction에서 등록된DB Entity가 변경된 경우Dirty Checking에 의해변경내용을 감지
。트랜잭션을 통해 변경된 내용은pending 상태로영속성 컨텍스트내에서만 반영
4.트랜잭션을Commit할 경우flush()를 호출하여Pending 상태의변경내용만 갱신하는UPDATE SQL을매핑된DB Table에 전송하여 반영
JPA와 Hibernate의 차이점
。JPA는API/Hibernate는API에 대한구현체로서JPA만으로 사용이 불가능하고 실제Hibernate와 같은JPA 구현체가 존재해야JPA를 통한CRUD를 수행가능
JPA
。API를 정의 ( 객체를 DB의 table로 Mapping하는 방식 정의 )
。@Entity: Interface로서 Entity가 무엇인지 정의
。@Id,@Column: 변수를 DB Table의 Field로 mapping
▶EntityManager에 존재하는 Method를 구현함으로써JPA API사용.
Hibernate
。 대표적인 오픈소스JPA 구현체
。Hibernate JAR을 class path에 추가해서Hibernate를JPA 구현체로 사용.
▶ (ex.@Entity의 경우.jakarta.persistence대신org.hibernate.annotations사용. )
。코드에서 직접Hibernate annotation을 사용하지 않는 이유는 다른 JPA 구현체(Toplink등)이 존재하므로, Hibernate로만 한정해서 사용하지 않기 위해!
。인터페이스처럼 코드는JPA를 사용하여 작성하여추상화하고Hibernate는 코드가 아닌,구현체로만 사용하는것이 좋다.
Dialect:
。JPA또는Hibernate에서 사용하는특정 DB에 해당하는SQL문법과기능을 사용하도록 설정
▶ 각DB는 통일된ANSI SQL를 사용하나 각각 작은 차이가 존재하므로 보완하기위해Dialect를 설정
▶Hibernate는 해당 문법/기능적 차이를Dialect를 통해 자동으로 조정