Study/Java&Spring
JPA 정의와 사용
kdhoooon
2021. 12. 30. 23:30
JPA를 알기전에 우선 ORM과 영속성을 알아야 한다.
ORM 이란?
- Object-relational mapping 객체 관계 매핑
- 객체와 관계형 데이터베이스의 데이터를 자동으로 매핑(연결)해주는 것
- 객체는 객체대로 설계 RDB(관계형 데이터베이스)는 RDB대로 설계
- 객체 지향 프로그래밍은 클래스를 사용하고, 관계형 데이터 베이스는 테이블을 사용한다.
ORM 장단점
- 장점
- 객체 지향적인 코드로 인해 더 직관적이고 비즈니스 로직에 더 집중 할 수 있게 도와준다.
- ORM을 이용하면 SQL Query가 아닌 직관적인 코드로 데이터를 조작 할 수 있어 개발자가 객체 모델로 프로그래밍하는 데 집중 할 수 있도록 도와 준다.
- 선언문, 할당, 종료 같은 부수적인 코드가 없거나 급격히 줄어든다.
- 각종 객체에 대한 코드를 별도로 작성하기 떄문에 코드의 가독성을 올려준다.
- SQL의 절차적이고 순차적인 접근이 아닌 객체 지향적인 접근으로 인해 생선성이 증가한다.
- 재사용 및 유지보수의 편리성이 증가한다.
- ORM은 독립적으로 작성되어 있고 해당 객체들을 재활용 할 수 있다.
- 매핑정보가 명확하여, ERD를 보는 것에 대한 의존도를 낮출 수 있다.
- DBMS에 대한 종송성이 줄어든다.
- 객체 간의 관계를 바탕으로 SQL을 자동으로 생성하기 때문에 RDBMS의 데이터 구조와 Java의 객체지향 모델 사이의 간격을 좁힐 수 있다.
- 대부분 ORM 솔루션은 DB에 종속적이지 않다.
- 종속적이지 않다는 것은 구현 방법 뿐만 아니라 많은 솔루션에서 자료형 타입까지 유효하다.
- 자바에서 가공할 경우 equals, hashCode의 오버라이드 같은 자바의 기능을 이용할 수 있고, 간결하고 빠른 가공이 가능하다.
- 객체 지향적인 코드로 인해 더 직관적이고 비즈니스 로직에 더 집중 할 수 있게 도와준다.
- 단점
- 완벽한 ORM 으로만 서비스를 구현하기가 어렵다.
- 사용하기는 편리하지만 설계는 매우 신중하게 해야한다.
- 프로젝트의 복잡성이 커질경우 난이도 또한 올라갈 수 있다.
- 잘못 구현 된 경우에 속도 저하 및 심각할 경우 일관성이 무너지는 문제점이 생길 수 있다.
- 프로시저가 많은 시스템에선 ORM의 객체 지향적인 장점을 활용하기 어렵다.
- 이미 프로시저가 많은 시스템에선 다시 객체로 바꿔야하며, 그 과정에서 생산성 저하나 리스크가 많이 발생할 수 있다.
- 완벽한 ORM 으로만 서비스를 구현하기가 어렵다.
영속성(Persistence)
- 데이터를 생성한 프로그램이 종료되더라도 사라지지 않는 데이터의 특성을 말함.
- 영속성을 갖지 않는 데이터는 단지 메모리에서만 존재하기 때문에 프로그램을 종료하면 모두 잃어버리게 된다.
- Object Presistence(영구적인 객체)
- 메모리 상의 데이터를 파일 시스템, 관계형 데이터베이스 혹은 객체 데이터베이스 등을 활용하여 영구적으로 저장하여 영속성을 부여한다.
JPA 란?
- Java Presistence API 의 약자
- Java 진영의 ORM 기술 표준
- JPA는 객체 지향 도메인 모델과 관계형 데이터베이스 시스템의 간의 다리 역할
- JPA는 자체적으로 어떤 작업도 수행하지 않는다.
- ORM 도구에서 다른 도구로 애플리케이션을 전화하려는 경우 쉽게 수행 가능하게 해줌
- Java 지속성 쿼리 언어 데이터 베이스 작업을 수행하는 객체 지향 쿼리 언어 JPQL 사용
동작방법
- Java 가 JDBC API에 명령을 내리는 것이 아니라 JPA를 사용하면 JPA가 JDBC API를 사용해서 DB와 통신하고 SQL을 호출하고 반환
- 저장시 JPA가 Entity를 분석해서 SQL을 생성함
- 조회시 JPA가 SQL을 생성하여 SQL을 생성
- 패러다임 불일치 해결
장단점
- 장점
- 개발이 편리하다.
- 기본적인 CRUD용 SQL을 직접 작성하지 않아도 된다.
- 데이터베이스에 독립적인 개발이 가능하다
- JPA는 데이터베이스에 종속적이지 않아서 데이터베이스가 변경되더라도 JPA가 해당 데이터베이스에 맞는 쿼리를 알아서 생성해준다.
- 유지보수가 쉽다.
- 테이블 변경시 JPA의 엔티티만 수정하면 된다.
- 개발이 편리하다.
- 단점
- 학습이 어렵다.
- 특정 데이터베이스의 함수를 사용하지 못한다.
- 테이블을 객체지향 설계가 필요하다.
Spring Datat JPA
- 스프링 부트는 JPA 중에서 Hibernate라는 구현체를 사용한다.
- Spring Data JPA는 이런 Hibernate를 좀 더 쉽게 사용할 수 있는 추가적인 API를 제공한다.
구성요소
- Entity
- 테이블과 직접적으로 매핑되는 클래스
- 생명주기
- 4가지상태
- 비영속(new/transient): 영속성 컨텍스트와 전혀 관계가 없는 상태
- 영속(managed): 영속성 컨텍스트에 저장된 상태
- 준영속(detached): 영속성 컨텍스트에 저장되었다가 분리된 상태
- 삭제(removed): 삭제된 상태
- 4가지상태
- 개발시 주의 할점
- 엔티티 클래스에 Getter는 열어두고, Setter는 꼭 필요한 경우에만 사용할 것
- Setter를 열어두면 엔티티 변경시에 어디서 변경이 일어났는지 찾아야하는 수고를 덜기 위해서 Setter는 변경 지점이 명확하도록 변경을 위한 비즈니스 메서드를 별도로 제공해야 한다.
- 엔티티 클래스에 Getter는 열어두고, Setter는 꼭 필요한 경우에만 사용할 것
- 사용되는 Annotation
- @Entity
- 클레스에 지정, 해당 클래스가 엔티티 클래스라는 걸 명시
- application.properties 파일에서 설정에 따라 테이블 생성도 가능
- @Table
- 엔티티 클래스를 DB의 언떤 테이블과 매핑시킬지 지정
- name 속성을 이용해서 특정 테이블의 이름을 지정
- ex) @Table(name= "[Table Name]")
- @Id
- 기본키에 해당 변수에 지정
- 엔티티 클래스에서 @Id로 지정한 변수가 없으면 에러 발생
- @GeneratedValue
- 사용자가 입력하는 값이 아닌 자동으로 생성되는 값을 사용할 때 사용
- 타입
- AUTO : 기본값, JPA 구현체가 알아서 생성
- IDENTITY : 사용하는 DMS가 키 생성을 결정
- SEQUENCE : DB의 sequence를 이용해서 키를 생성, @SequenceGenrator와 같이 사용
- TABLE : 키 생성 전용 테이블을 생성해서 키 생성, @TableGenrator와 함께 사용
- ex) @GeneratedValue(strategy = GenerationType.IDENTITY)
- @Column
- 추가적인 컬럼이 필요한 경우에 사용
- ex) @Column(name ="member_id")
- @Entity
- Repository
- 상속을 받아 선언하는 것만으로 아래 메소드들을 통해서 CRUD 가능
영속성 컨텍스트
EntityManagerFactory vs EntityManager
EntityManagerFactory
- 만드는 비용이 상당히 크다.
- 한개만 만들어서 어플리케이션 전체에서 공유하도록 설계한다.
- 여러 쓰레드가 동시에 접근해도 안전, 서로다른 스레드간 공유가 가능하다.
EntityManager
- 여러 쓰레드가 동시에 접근하면 동시성 문제 발생
- 스레드간 절대 공유하면 안된다.
- 데이터베이스 연결이 필요한 시점까지 커넥션을 얻지 않는다.
의미
- 엔티티를 영구 저장하는 환경
관련코드
- EntityManger.persist(member)
- 단순하게 회원 엔티티 저장
- 정확하게는 엔티티 매니저를 사용해서 회원 엔티티를 영속성 컨텍스트에 저장
- 논리적인 개념에 가까움
- 엔티티 매니저를 생성할 때 하나 만들어진다.
- 엔티티 매니저를 통해 영속성 컨텍스트에 접근하고 관리 할 수 있다.
비영속
- 엔티티 객체를 생성
- 순수한 객체상태, 아직 저장하지 않음
- 영속성 컨텍스트나 데이터베이스와 상관없음
- EntityManager.persist() 를 호출 전 상태
- 코드예시
Member member = new Member(); member.setName("멤버1");
영속
- 엔티티 매니저를 통해 엔티티를 영속성 컨텍스트에 저장(영속상태 = 영속성 컨텍스트에 의해 관리되다는 의미)
- 영속성 컨텍스트가 관리하는 엔티티를 영속 상태
- EntityManager.find() 나 JPQL을 사용해서 조회한 엔티티도 영속상태
- 비영속 상태의 객체를 Entity.persist() 를 호출하여 영속상태로 만든다.
준영속
- 영속성 컨텍스트가 관리하던 영속 상태의 엔티티를 영속성 컨텍스트가 관리하지 않으면 준영속 상태
- 이미 한번 DB에 저장돼 식별자가 존재, 임의로 만들어낸 엔티티도 기존 식별자를 가지고 있으면 준영속 엔티티
- EntityManger.detach() 호출로 준영속 상태 명시적 호출
- EntityManger.close()를 호출해서 영속성 컨텍스트를 닫음
- EntityManger.clear()로 영속성 컨텍스트 초기화
- 준영속 엔티티 수정 방법
- 변경감지(Dirty Checking) 기능 사용
- 영속성 컨텍스트에서 엔티티를 다시 조회한 후에 데이터를 수정하는 방법
- 트랜젝션 안에서 엔티티를 다시 조회, 변경할 값 선택 후 트랜잭션 commit 시점에서 변경감지가 동작해서데이터 베이스에 UPDATE SQL 실행
- 더 권장하는 방법
- 변경감지 기능은 원하는 속성만 선택해서 변경할 수 있지만, 병합을 사용하면 모든 속성이 변경된다.
- 변합시 값이 없으면 null로 업데이트 할 위험이 있다.
- 참고로 수정에서 아래코드와 같이 Setter를 사용해서 값을 변경하는 것보단 메서드 명으로 선언하여 추적하기 편하게 설계하는 것이 바람직하다.(중요)
- 예시 코드
//준영속성 컨텍스트 객체의 값을 변경하면 변경을 감지해서 UPDATE SQL을 처리한다. Member member = em.find(Member.class, 100L); //TX member.changeName("변경1"); //변경감지(Dirty Checking) //TX commit
- 병합(merge)사용
- 준영속 상태의 엔티티를 영속 상태로 변경할 때 사용하는 기능
- 반환 된 값이 영속상태, 파라미터로 넘긴 값은 영속상태로 변경되지 않는다.
- 변경감지(Dirty Checking) 기능 사용
삭제
- 엔티티를 영속성 컨텍스트와 데이터베이스에서 삭제
- 엔티티를 즉시 삭제하는 것이 아님(삭제 쿼리를 쓰기 지연 SQL 저장소에 등록)
- EntityManger.remove() 메서드로 사용(메서드를 호출하는 순간 영속성 컨텍스트에서 제거)
영속성 컨텍스트 특징
- 영속성 컨텍스트와 식별자 값
- 엔티티를 식별자값(@Id로 테이블의 기본 키와 매핑한 값)으로 구분
- 영속 상태는 식별자 값이 반드시 있어야한다.
- 식별자 값이 없으면 예외 발생
- 영속성 컨텍스트와 데이터 베이스 저장
- JPA는 보통 트랜잭션을 커밋하는 순간 영속성 컨텍스트에 새로 저장된 엔티티를 데이터 베이스에 반영
- 플러시(flush)
- 영속성 컨텍스트가 엔티티를 관리하는 것의 장점
- 1차 캐시
- 동일성 보장
- 트랜잭션을 지원하는 쓰기 지연
- 변경 감지
- 지연 로딩
연관관계 매핑
- 객체의 참조와 테이블의 외래키를 매핑할 수 있다.
방향
- 단방향
- 회원 -> 팀 or 팀 -> 회원
- @ManyToOne
- 다대일(N : 1)관계라는 매핑 정보
- 어노테이션 필수
- @JoinColum(name="연관관계의_주인_id")
- 조인컬럼은 외래 키를 매핑할 때 사용
- name 속성에 매핑할 외래키 이름을 지정
- 생략 가능하다.
- 생략시에는 기본전략 : 필드면 + _ + 참조하는 테이블의 컬럼명
- 양방향
- 회원 -> 팀 & 팀 -> 회원
- 팀에서 회원으로 접근하는 관계, 회원에서 팀으로 접근 가능하고, 팀에서 회원으로 접근가능한 양방향성
다중성
- 다대일(N:1), 일대다(1:N), 일대일(1:1), 다대다(N:N)
- 하지만 데이터베이스 설계에서 다대다관계는 좋지 않으므로 다대일 또는 일대다로 만들 것
연관관계의 주인
- 객체를 양방향 연관관계로 만들면 연관관계의 주인을 정해야 한다.
- 두 객체 연관관계 중 하나를 정해서 테이블의 외래키를 관리해야 하는데 이것을 연관관계 주인이라 한다.
- 양방향 매핑 규칭 : 연관관계의 주인
- 연관관계의 주인만이 데이터베이스 연관관계와 매핑 된다.
- 연관관계의 주인만이 외래키를 관리(등록, 수정, 삭제)할 수 있다.
- 주인이 아닌 쪽은 읽기만 할 수 있다.
- 보통 일대다 관계의 테이블에서 반드시 다(N) 쪽에 외래키(Foreign Key)가 있어야 한다. (즉, 다쪽에 연관관계 주인이 돼야한다.)
- 연관관계 주인만 데이터베이스 연관관계와 매핑, 외래 키를 관리한다.
- 주인이 아닌 반대편은 읽기만 가능, 외래키를 변경하지 못한다.
연관관계를 사용해 조회
- 객체 그래프 탐색
-
Member member = em.find(Member.class, 100L)
-
- 객체 지향 쿼리
-
List<Member> result = em.createQuery("select m from Member m where m.name = :name", Member.class) .setParameter("name", name) .getResultList();
-
참고
- 모든 연관관계는 지연로딩으로 설정해야한다.
- 즉시로딩(FetchType.EAGER): 엔티티를 조회할 때 연관 된 엔티티들을 다 로딩하는 것(트랜젝션의 시간을 단축시킬 수 있음)
- 지연로딩(FetchType.LAZY): 로딩되는 시점이 아닌 후에 실제 객체를 사용하는 시점에 초기화 되는 것(트랜젝션시에 시간이 즉시로딩보다는 느림)
- 이유
- 즉시로딩시에는 예측이 어렵고 추적하기가 어렵다.
- 이럴 경우 쿼리가 n + 1문제가 발생할 수 있다.
- 따라서, 트랜젝션이 느린 것에 대한 이슈는 처리가 가능하므로 지연로딩을 하는것을 권장한다.
- 연관관계 편의 메서드를 사용하는 것을 권장한다.
- 양방향 연관관계에서는 연관관계 편의 메서드를 사용해주면 좋다.
- 연관관계 편의 메서드란?
- 한번에 양방향 모두 세팅해주는 것
- 예시코드(부모와 자식 모두에게 연관관계를 세팅해주는 코드)
-
public void addChildCategory(Category child){ this.child.add(child); child.setParent(this); }
- 엔티티는 식별자를 prameter로 넘겨서 찾아서 사용
- 컨트롤러에서 객체 엔티티를 찾아서 보내는 것보다 식별자만 넘겨서 엔티티에서 처리하는 것이 좋다.
- 엔티티에서 객체를 식별자로 찾아서 처리하는 것이 영속성 컨텍스트가 존재하는 상태에서 값을 처리할 수 있기 때문
- 엔티티를 밖에서 찾아서 파라미터로 넘기면, Transactional과 관계없는 영속성 컨텍스트가 넘어오기 때문에 값을 수정함에 있어서 불편함을 겪는다.
- 컨트롤러에서 객체 엔티티를 찾아서 보내는 것보다 식별자만 넘겨서 엔티티에서 처리하는 것이 좋다.