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의 객체 지향적인 장점을 활용하기 어렵다.
      • 이미 프로시저가 많은 시스템에선 다시 객체로 바꿔야하며, 그 과정에서 생산성 저하나 리스크가 많이 발생할 수 있다.

영속성(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가지상태
        1. 비영속(new/transient): 영속성 컨텍스트와 전혀 관계가 없는 상태
        2. 영속(managed): 영속성 컨텍스트에 저장된 상태
        3. 준영속(detached): 영속성 컨텍스트에 저장되었다가 분리된 상태
        4. 삭제(removed): 삭제된 상태
    • 개발시 주의 할점
      • 엔티티 클래스에 Getter는 열어두고, Setter는 꼭 필요한 경우에만 사용할 것
        • Setter를 열어두면 엔티티 변경시에 어디서 변경이 일어났는지 찾아야하는 수고를 덜기 위해서 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")
  • 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)사용
      • 준영속 상태의 엔티티를 영속 상태로 변경할 때 사용하는 기능
      • 반환 된 값이 영속상태, 파라미터로 넘긴 값은 영속상태로 변경되지 않는다.

 

삭제

  • 엔티티를 영속성 컨텍스트와 데이터베이스에서 삭제
  • 엔티티를 즉시 삭제하는 것이 아님(삭제 쿼리를 쓰기 지연 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과 관계없는 영속성 컨텍스트가 넘어오기 때문에 값을 수정함에 있어서 불편함을 겪는다.