JPA

  • Java Persistence API

  • 자바 진영의 ORM 기술 표준

    ORM (Object-relational mapping)

  • JPA는 애플리케이션과 JDBC 사이에서 동작

데이터베이스 방언

  • JPA는 특정 데이터베이스에 종속적이지 않다.
  • 각각의 데이터베이스가 제공하는 SQL 문법과 함수가 조금씩 다른데
    JPA는 'hibernate.dialect' 속성에 지정하여 해당 방언들을 지원한다.

엔티티 매니저 팩토리와 엔티티 매니저

출처:자바 ORM 표준 JPA 프로그래밍 - 기본편 (김영한)

  • EntityManagerFactory
//엔티티 매니저 팩토리 생성, 비용이 많이 들어 하나만들어서 공유해서 사용
//hello는 JPA설정파일 네임
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
  • EntityManager
//엔티티 매니저 생성, 비용이 많이 들지 않으며 공유해서 사용하면 동시성문제 발생
EntityManager em = emf.createEntityManager(); 

영속성 관리

영속성 컨텍스트

  • "엔티티를 영구 저장하는 환경" 이라는 뜻
  • EntityManager.persist(entity)
    엔티티 매니저를 사용해서 엔티티를 영속성 컨텍스트에 저장
  • 엔티티 매니저를 생성할 때 하나 만들어진다.
    엔티티 매니저를 통해 영속성 컨텍스트에 접근하고 관리할 수 있다.

    엔티티의 생명주기

    출처:자바 ORM 표준 JPA 프로그래밍 - 기본편 (김영한)
  • 비영속 (new/transient)
    영속성 컨텍스트와 전혀 관계가 없는 새로운 상태
    //객체를 생성한 상태 (비영속)
    Member member = new Memeber();
    member.setId("member1"):
    member.setName("성명");
  • 영속 (managed)
    영속성 컨텍스트에 관리되는 상태
    //객체를 생성한 상태 (비영속)
    Member member = new Memeber();
    member.setId("member1"):
    member.setName("성명");
    //객체를 저장한 상태 (영속)
    em.persist(member);
    em.find()나 JPQL를 사용해서 조회한 엔티티도 영속 상태이다.
  • 준영속 (detached)
    영속성 컨텍스트에 저장되었다가 분리된 상태
    //회원 엔티티를 영속성 컨텍스트에서 분리
    em.detach(member); //특정 엔티티만 준영속상태
    em.clear();  //영속성 컨텍스트 초기화
    em.close();  //영속성 컨텍스트 종료
  • 삭제 (removed)
    삭제된 상태
    //객체를 삭제한 상태
    em.remove(member);

영속성 컨텍스트의 이점

  • 1차캐시
    영속성컨텍스트에 저장되어 있을 경우 조회시 1차캐시에서 조회
    저장이 되어 있지 않을 경우 데이터베이스에서 1차캐시에 저장 후 조회

  • 동일성 보장
    1차캐시로 반복 가능한 읽기 등급의 트랜잭션 격리 수준을 데이터베이스가 아닌 애플리케이션 차원에서 제공

  • 트랜잭션을 지원하는 쓰기지연

    EntityManager em = emf.createEntityManager();
    EntityTransaction transaction = em.getTransaction();
    // 엔티티 매니저는 데이터 변경 시 트랜잭션을 시작해야 한다.
    transaction.begin();     // 트랜잭션 시작
    em.persist(memberA);
    em.persist(memberB);
    // 여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.
    // 커밋하는 순간 데이터베이스에 INSERT SQL을 보낸다.
    transaction.commit();   // 트랜잭션 커밋
  • 변경감지
    특별한 update 없이 엔티티 변경을 감지하여 데이터 자동변경

    EntityManager em = emf.createEntityManager();
    EntityTransaction transaction = em.getTransaction();
    transaction.begin();     // 트랜잭션 시작
    //영속성 엔티티 조회
    Member findMember = em.find(Member.class, "memberA");
    //영속성 엔티티 수정
    findMember.setName("updateName");
    transaction.commit();   // 트랜잭션 커밋

    변경순서
    트랜잭션 커밋 -> 엔티티 매니저 내부에서 먼저 플러시 호출
    엔티티와 스냅샷을 비교해서 변경된 엔티티 찾는다.
    변경된 엔티티가 있으면 수정 쿼리를 생성해서 쓰기 지연 SQL 저장소로 보낸다.
    쓰기 지연 저장소의 SQL을 데이터베이스로 보낸다.
    데이터베이스 트랜잭션을 커밋

    변경감지는 영속성 컨텍스트가 관리하는 영속 상태의 엔티티에만 적용된다.

  • 지연로딩
    뒤에 자세히 설명할 예정

플러시 (flush)

  • 플러시는 영속성 컨텍스트의 변경내용을 데이터베이스에 반영
  • 플러시 발생시
    1. 변경감지
    2. 수정된 엔티티 쓰기지연 SQL 저장소에 등록
    3. 쓰기지연 SQL 저장소의 쿼리를 데이터베이스에 전송
  • 플러시 방법
    1. em.flush() (직접 호출)
    2. 트랜잭션 커밋 (플러시 자동 호출)
    3. JPQL 쿼리 실행 (플러시 자동 호출)
  • 플러시는 영속성 컨텍스트를 비우지 않음
    영속성 컨텍스트의 변경내용을 데이터베이스에 동기화
    커밋직전에만 동기화하면 됨

엔티티 매핑

객체와 테이블 매핑

  • @Entity
    해당 에노테이션이 붙은 클래스는 JPA가 관리, 엔티티라 한다.
    JPA를 사용해서 테이블과 매핑할 클래스는 필수.
    기본 생성자 필수(파라미터가 없는 public 또는 protected 생성자)
    final 클래스, enum, interface, inner 클래스에는 사용할 수 없다.
    저장할 필드에 final을 사용하면 안된다.
    • 속성
      name: JPA에서 사용할 엔티티 이름을 지정, 기본값은 클래스명
  • @Table
    엔티티와 매핑할 테이블 지정
    • 속성
      name:매핑할 테이블이름, 기본값은 엔티티 이름을 사용
      catalog:데이터베이스 catalog 매핑
      schema:데이터베이스 schema 매핑
      uniqueConstraints(DDL):DDL생성 시에 유니크 제약 조건 생성

데이터베이스 스키마 자동 생성

  • DDL을 애플리케이션 실행 시점에 자동생성
    <property name="hibernate.hbm2ddl.auto" value="create" />
  • hibernate.hbm2ddl.auto 속성
    create : 기존 테이블 삭제 후 다시 생성
    create-drop : create와 같으나 종료시점에 테이블 drop
    update : 변경분만 반영
    validate : 엔티티와 테이블이 정상 매핑되었는지만 확인
    none : 사용하지 않음

    운영장비에는 절대 create,create-drop,update 사용하면 안됨

  • DDL 생성기능
    DDL 생성기능은 DDL을 자동생성할 때만 사용되고 실행로직에는 영향X

    제약조건추가 : @Column(nullable = false, length =10)
    유니크 제약조건 추가 : @Table(uniqueConstraints = {@UniqueConstraint( name = "NAME_AGE_UNIQUE", columnNames = {"NAME","AGE"} )})

필드와 컬럼 매핑

  • @Column
    속성
    1. name : 필드와 매핑할 테이블의 컬럼 이름, 기본값 필드명
    2. insertable/updatable : 등록/변경 가능여부, 기본값 TRUE
    3. nullable(DDL) : false 설정시 DDL 생성시 not null 제약조건
    4. unique(DDL) : 한 컬럼에 간단히 유니크 제약조건 걸 떄 사용
    5. columnDefinition(DDL) : 데이터베이스 컬럼 정보 직접 부여
      ex) varchar(100) default 'EMPTY'
    6. length(DDL) : 문자길이 제약조건, String타입에만 사용
    7. precision/scale(DDL) : 아주 큰 숫자나 정밀한 소수를 다룰때 사용
  • @Enumerated
    자바 enum 타입 매핑할 때 사용
    속성
    • value = EnumTypes.ORDINAL : enum 순서를 저장
    • value = EnumTypes.STRING : enum 이름을 저장
      기본값은 ORDINAL이지만 사용시에는 STRING만 사용할 것!
  • @Temporal
    날짜 타입을 매핑할 때 사용
    LocalDate,LocalDateTime 사용시에는 생략가능
    속성
    • value = TemporalType.DATE : 날짜 매핑
    • value = TemporalType.TIME : 시간 매핑
    • value = TemporalType.TIMESTAMP : 날짜와 시간 매핑
  • @Lob
    데이터베이스 BLOB, CLOB 타입과 매핑
    속성 없음
    매핑하는 필드 타입이 문자면 CLOB, 나머지는 BLOB매핑
  • @Trasient
    필드매핑을 안하고, 데이터베이스에 저장도 하지 않음
    메모리상에서만 어떤 값을 보관시 사용

기본키 매핑

  • @Id
  • @GeneratedValue
    기본키 직접 할당시에 @Id만 사용
    @GeneratedValue 사용시 전략에 맞게 기본키 자동생성

    IDENTITY : 데이터베이스에 위임, MYSQL
    SEQUENCE : 데이터베이스 시퀀스 오브젝트 사용,ORACLE
    @SequenceGenerator 필요
    TABLE : 키 생성용 테이블 사용
    @TableGenerator 필요
    AUTO : 데이터베이스 방언에 따라 자동 지정, 기본값

    @Id @GeneratedValue(strategy = GeneratedType.AUTO)
    private Long id;

연관관계 매핑

단방향 연관관계

출처:자바 ORM 표준 JPA 프로그래밍 - 기본편 (김영한)

  • 객체의 참조와 테이블의 외래 키를 매핑
    @Entity
    public class Member {
    	@Id @GeneratedValue
       private Long id;
       
       @Column(name = "USERNAME")
       private String name;
       private int age;
       
       //@Column(name = "TEAM_ID")
       //private Long teamId;
       //팀의 기본키대신 팀이라는 객체와 직접매핑
       @ManyToOne
       @JoinColumn(name = "TEAM_ID")
       private Team team;
    	...
    }
  • 연관관계 사용 예시
    //저장
    Team team = new Team();
    team.setName("Team");
    em.persist(team);
    Member member = new Memeber();
    member.setName("member1");
    member.setTeam(team);  //단방향 연관관계 설정으로 참조 저장
    em.persist(member)
    //조회
    Member findMember = em.find(Member.class, member.getId());
    Team findTeam = findMember.getTeam();
    //수정
    Team teamB = new Team();
    teamB.setName("TeamB");
    em.persist(teamB);
    member.setTeam(teamB); //새로운 팀으로 변경

양방향 매핑과 연관관계 주인

출처:자바 ORM 표준 JPA 프로그래밍 - 기본편 (김영한)

  • Team 엔티티에 컬렉션 추가 (Member 엔티티는 기존과 동일)

    @Entity
    public class Team {
    	 @Id @GeneratedValue
        private Long id;
        
        private String name;
        
        @OneToMany(mappedBy = "team")
        List<Member> members = new ArrayList<Member>();
        ...
    }
  • 반대 방향으로 객체 그래프 탐색

    //조회
    Team findTeam = em.find(Team.class, team.getId());
    int memberSize = findTeam.getMembers().size(); //역방향 조회

    테이블의 양방향 한개의 양방향 관계이지만,
    객체의 양방향 관계는 서로 다른 단방향 2개다.

  • 연관관계의 주인
    객체의 두 관계중 하나를 연관관계의 주인으로 지정
    연관관계의 주인만이 외래키를 관리 (등록,수정이 가능)
    주인이 아닌 쪽은 읽기만 가능
    주인은 mappedBy 속성 사용불가능 / 주인이 아니면 mappedBy 속성으로 지정
    외래키가 있는 곳을 주인으로 정해라 / 예시에서는 Memeber.team이 주인

     Team team = new Team();
     team.setName("teamA");
     em.persist(team);
    
     Member member = new Memeber();
     member.setName("member1");
    
     team.getMembers().add(member); //주인이 아닐 경우 저장되지 않음
     member.setTema(team); //연관관계주인에 값 설정
     //순수 객체 상태를 고려해서 항상 양쪽에 값 설정
     //연관관계 편의 메소드로 편하게 값설정
    

다양한 연관관계 매핑

  • 다대일 (@ManyToOne)
    위에서 예시가 다대일
  • 일대다 (@OneToMany)
    일이 연관관계 주인
  • 일대일 (@OneToOne)
  • 다대다 (@ManyToMany)

    실질적으로는 다대일 매핑을 주로 사용

  • @JoinColumn 외래키 매핑시 사용
    속성
    • name : 매핑할 외래키 이름, 기본값 필드명_참조하는 테이블기본키 컬럼명
    • referencedColumnName : 외래키가 참조하는 대상 테이블의 컬러명
    • foreignKey(DDL) : 외래키 제약조건 직접 지정
  • @ManyToOne 다대일 관계 매핑
    속성
    • optional : false로 설정하면 연관된 엔티티가 항상 있어야한다,기본값 TRUE
    • fetch : 글로벌 페치 전략을 사용한다
      기본값 @ManyToOne=FetchType.EAGER
      기본값 @OneToMany=FetchType.LAZY
    • cascade : 영속성 전이 기능을 사용한다.
  • @OneToMay 다대일 관계 매핑
    속성
    • mappedBy : 연관관계의 주인 필드를 선택한다.
    • fetch : 글로벌 페치 전략을 사용한다
      기본값 @ManyToOne=FetchType.EAGER
      기본값 @OneToMany=FetchType.LAZY
    • cascade : 영속성 전이 기능을 사용한다.

상속관계 매핑

  • @Inheritance(strategy=InheritanceType.XXX)
    • JOINED : 조인전략
    • SINGLE_TABLE : 단일 테이블 전략
    • TABLE_PER_CLASS : 구현 클래스마다 테이블 전략
  • @DiscriminatorColumn(name="DTYPE")
    부모 클래스에 선언한다. 하위 클래스를 구분하는 용도의 컬럼이다.
    관례는 default = DTYPE
  • @DiscriminatorValue("XXX")
    하위 클래스에 선언한다. 엔티티를 저장할 때 슈퍼타입의 구분 컬럼에 저장할 값을 지정한다.
  • 조인전략
    출처:자바 ORM 표준 JPA 프로그래밍 - 기본편 (김영한)

    • 장점
      테이블 정규화, 외래키 참조 무결성 제약조건 활용가능
      저장공간 효율화
    • 단점
      조회시 조인을 많이 사용, 성능 저하, 조회 쿼리 복잡
      데이터 저장시 insert 2번 호출
  • 단일 테이블 전략
    출처:자바 ORM 표준 JPA 프로그래밍 - 기본편 (김영한)

    • 장점
      조인이 필요 없으므로 조회성능이 빠름
    • 단점
      자식엔티티가 매핑한 컬럼은 모두 null 허용
      단일테이블에 모든것을 저장하기에 테이블이 커질 수 있다.
      상황에 따라 조회 성능이 느려질수도 있음
  • 구현 클래스마다 테이블 전략
    출처:자바 ORM 표준 JPA 프로그래밍 - 기본편 (김영한)

    • 장점
      서브타입을 명확하게 구분해서 처리시 효과적
      not null 제약조건 사용가능
    • 단점
      여러자식 테이블 함께 조회시 성능이 느림 (union 사용해야함)

일반적으로 조인전략을 사용하고 간단한 구조일 경우 단일 테이블 전략 사용
구현 클래스마다 테이블전략은 사용하지 않도록

  • @MappedSuperclass
    공통 매핑 정보가 필요시에 사용

    • 단순히 엔티티가 공통으로 사용하는 매핑정보를 모으는 역할
      예를들면 등록자,등록일,수정자,수정일 같은 공통으로 적용하는 정보

    • 직접생성해서 사용하지 않으므로 추상클래스 권장

      @MappedSuperclass
      public abstract class BaseEntity {
      
         @Column(name = "INSERT_BY")
         private String createBy;
         private LocalDateTime createDate;
         private String modifiedBy;
         private LocalDateTime modifiedDate;
         ...
      }
      //사용시
      public class Item extends BaseEntity {
      ...
      }
profile
한번 해봅시다.

0개의 댓글