JPA 기본

Seung jun Cha·2022년 7월 1일
0
post-thumbnail

1. JPA

1-1 특징

  • 자바에서 객체 관계 매핑(ORM)을 위한 표준 API입니다. JPA는 데이터베이스와 자바 객체 간의 변환을 담당합니다.
  1. JPA는 특정 데이터베이스에 종속되지 않으며 hibernate.dialect 속성에 DB 지정해서 모든 DB방언을 사용할 수 있다.

  2. 엔티티 매니저 팩토리는 하나만 생성해서 애플리케이션 전체에서 공유하고, 팩토리에서 만들어진 @PersistenceContext 엔티티 매니저는 쓰레드간에 공유X (사용하고 버려야 한다)

  3. JPA의 모든 데이터 변경은 트랜잭션(@Transactional(readOnly=false)) 안에서 실행된다.

  4. JPA를 사용하면 엔티티 객체를 중심으로 개발하며 검색을 할 때도 테이블이 아닌 엔티티 객체를 대상으로 검색한다. 따라서 데이터를 찾기 위해 JPA는 SQL을 추상화한 JPQL이라는 객체 지향 쿼리 언어 제공한다.

  • 영속성 관리 : “엔티티를 영구 저장하는 환경”이라는 뜻

1-2 영속성 컨텍스트

1-2-1 개념

  • 엔티티(Entity)들을 관리하는 환경으로 상태와 생명주기를 관리하고 트랜잭션과 캐싱을 지원한다.
  1. 엔티티 매니저를 통해서 영속성 컨텍스트에 접근 : EntityManager.persist()

  2. 영속(em.persist(member)), 비영속(new Member), 준영속(분리, detach), 삭제(em.remove)

//객체를 생성한 상태(비영속) 
Member member = new Member(); 
member.setId("member1"); 
member.setUsername(“회원1”);

EntityManager em = emf.createEntityManager();
em.getTransaction().begin();

//객체를 저장한 상태(영속)
em.persist(member);

1-2-2 장점

  1. 1차 캐시 : 영속성 컨텍스트 내부에 엔티티를 저장하는 곳 persist하는 순간 1차캐시에 저장, 또는 1차캐시에 없어서 DB에서 가지고 올때 1차캐시에 저장하고 가지고 옴
  2. 동일성 보장 : a == b
  3. 쓰기 지연 : persist할 때가 아닌 flush 또는 commit할 때 SQL을 한 번에 날림
em.persist(memberA);
em.persist(memberB);
//여기까지 INSERT SQL을 데이터베이스에 보내지 않는다. SQL저장소에 저장해 놓음

//커밋하는 순간 데이터베이스에 INSERT SQL을 보낸다.
transaction.commit(); 
  1. 변경감지 : EntityManger는 트랜잭션 안에서 엔티티를 조회한 다음에 데이터를 변경하면, 트랜잭션 종료 시점에 변경감지 기능이 작동해서 변경된 엔티티를 감지하고 UPDATE SQL을 실행한다. 따라서 따로 updata쿼리를 작성할 필요가 없다.
memberA.setUsername("hi");
memberA.setAge(10);
//em.update(member) -> update 쿼리 필요없음
  1. 지연로딩
  • flush : 영속성 컨텍스트의 변경내용을 데이터베이스에 동기화
    • flush가 호출되는 경우 : 직접호출, 트랙잭션 커밋시점, JPQL실행 시점
      (DB에서 최종변경된 데이터를 찾기위해)
      => 영속성 컨텍스트의 변경내용을 데이터베이스에 동기화

1-3 Entity와 DB 매핑

  • Entity 매핑
  1. @Entity : @Entity가 붙은 클래스는 JPA가 관리함. 테이블과 매핑할 클래스는 @Entity 필수이며, 기본 생성자 역시 필수(파라미터가 없는 public 또는 protected 생성자)
  2. @Table로 테이블명을 설정하지 않으면 Entity 클래스의 이름이 테이블 명이 됨
  3. @Enumerated(EnumType.STRING) : 자바 enum 타입을 매핑할 때 사용

1-3-1 기본 키 매핑 방법

  1. IDENTITY: 데이터베이스에 위임, MYSQL , IDENTITY 전략은 commit()시점이 아닌 em.persist() 시점에 즉시 INSERT SQL 실행하고 DB에서 식별자를 조회

  2. SEQUENCE: 데이터베이스 시퀀스 오브젝트 사용, ORACLE , @SequenceGenerator 필요
    성능 최적화에 allocationSize 사용(시퀀스 한 번 호출에 증가하는 수)

  3. TABLE: 키 생성용 테이블 사용, 모든 DB에서 사용 ,@TableGenerator 필요

  4. AUTO: 방언에 따라 자동 지정, 기본값

    1-3-2 DB 스키마 생성

    • 데이터베이스 스키마 자동 생성 : hibernate.hbm2ddl.auto=

  5. create : 기존테이블 삭제 후 다시 생성 (DROP + CREATE)

  6. create-drop : create와 같으나 종료시점에 테이블 DROP

  7. update : 변경분만 반영(운영DB에는 사용하면 안됨)

  8. validate : 엔티티와 테이블이 정상 매핑되었는지만 확인

  9. none : 사용하지 않음

운영 장비에는 절대 create, create-drop, update 사용하면 안된다.
• 개발 초기 단계는 create 또는 update
• 테스트 서버는 update 또는 validate
• 스테이징과 운영 서버는 validate 또는 none

1-3-3 연관관계 매핑

  1. 단방향 연관관계
  2. 양방향 연관관계 : 연관관계의 주인 설정(N이 주인 따라서 N-1이 진짜 매핑으로 외래키를 가지고 있고, 데이터를 등록,수정할 수 있음(ex Member.team) , 순수 객체 상태를 고려해서 항상 양쪽에 값을 설정하자. 단방향 매핑을 잘 하고 양방향은 필요할 때 추가해도 됨 (테이블에 영향을 주지 않음)
    @JoinColumn("1의 Id명") : 주인 Entity에 선언(N쪽), 외래키를 매핑할 때 사용하는 어노테이션으로 참조하는 테이블의 기본 키 컬럼명을 쓴다.
    (MappedBy= "필드명") : 1의 Entity에 선언, 반대쪽에 자신이 매핑되어 있는 필드명
    테이블은 외래 키 하나로 두 테이블이 연관관계를 맺기 때문에 방향의 개념이 없음
@Entity
 public class Member { 
   @ManyToOne
   @JoinColumn(name = "TEAM_ID")
   private Team team;
 
 @Entity
 public class Team {
   @OneToMany(mappedBy = "team")
   List<Member> members = new ArrayList<Member>();
   …

양방향일 때는 연관관계 메서드를 만들어서 순수한 객체상태일 때도 작동하도록 해야한다.
양쪽 모두의 관계를 맺어주는 것을 하나의 코드처럼 사용하는 것이 안전하다.

멤버에 팀을 세팅할 때, 멤버가 다른 팀에 속해있을 수 있으므로 확인 후, 그 팀과의 연관관계를 끊고 팀을 세팅해야 한다. 위보다 아래의 코드가 더 적절하다.
N 대 1의 관계에서 N쪽 엔티티에 메서드를 작성한다.

1-3-4 상속관계 매핑

• 상속관계 매핑
DB에는 객체의 상속관계와 유사한 슈퍼타입,서브타입 관계라는 모델링 기법이 있음
@Inheritance(strategy=InheritanceType.XXX)
@DiscriminatorColumn(name=“DTYPE”) : 부모 클래스에 선언
@DiscriminatorValue(“XXX”) : 하위 클래스에 선언
①조인 전략(JOINED) : 테이블 정규화, 쿼리가 복잡하고 조인으로 성능저하
②단일 테이블 전략(SINGLE_TABLE) : 모든 엔티티의 필드를 부모 클래스에 넣음
③클래스 모두 테이블로 구현(TABLE_PER_CLASS)

• @MappedSuperclass : 엔티티가 공통으로 사용하는 매핑 정보를 모으는 역할을 하는 클래스에 사용하는 어노테이션. 부모 클래스로서 역할을 하며 자식 클래스에 매핑정보만 제공하고 엔티티로 만들어지지 않음, 직접 생성해서 사용할 일이 없으므로 추상 클래스 권장

• 프록시

실제 객체를 상속 받아서 만들어지며, 실제 객체의 참조(target)를 보관한다. 프록시 객체를 처음 사용할 때, 초기화가 되면 프록시 객체를 통해서 실제 엔티티에 접근이 가능해진다. 그래서 프록시 객체는 실제 사용되기 전까지 데이터를 로딩 하지 않고 실제 사용시점에 쿼리문이 실행된다. 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드 호출한다. 지연로딩을 가능하게 함

1-4 즉시로딩, 지연로딩

• 즉시로딩, 지연로딩(fetch= FetchType)
@ManyToOne, @OneToOne은 기본설정은 즉시 로딩
실무에서는 다 지연로딩으로 설정하고 JPQL fetch 조인이나, 엔티티 그래프 기능을 사용
지연로딩으로 설정하면 N 대 1에서 1의 엔티티는, 실제 엔티티 대신에 프록시 객체를 넣어둔다.

1-5 영속성 전이

특정 엔티티의 상태를 바꿀 때, 연관된 엔티티의 상태도 같이 바꾸고 싶을 때 자식쪽에 선언 (All, persist, Remove ..)
=>확실하게 자식을 관리하는 엔티티가 부모 하나일때만 사용해야 하며, 라이프 사이클이 같을 때 써야한다. 영속성 전이는 연관관계를 매핑하는 것과 아무 관련이 없다.

1-6 고아객체

• 고아 객체 제거(orphanRemoval = true) : 부모 엔티티와 연관관계가 끊어진(참조가 제거된 엔티티) 자식 엔티티를 자동으로 삭제, 자식이 참조하는 부모가 하나뿐일 때만 사용
(=> @OneToOne, @OneToMany만 가능)
parent1.getChildren().remove(0); // 자식 엔티티를 컬렉션에서 제거 => 고아가 됨
CascadeType.ALL + orphanRemovel=true : 두 옵션을 모두 활성화 하면 부모 엔티티를 통해서 자식의 생명주기를 관리할 수 있음

1-7 값 타입

  • 값 타입
  1. 기본 값타입 : 기본타입(int, double), 래퍼 클래스(Integer, Long), String,
    생명주기를 엔티티의 의존, 기본 타입은 항상 값을 복사하기 때문에 공유 불가(ex int a = b)

  2. 임베디드 타입 : 새로운 값 타입을 직접 정의할 수 있음 , 주로 기본 값 타입을 모아서 만들어서 복합 값 타입이라고도 함 (ex - Address(city, street, zipcode)) , 기본 생성자 필수
    임베디드 타입은 기본타입이 아니라 객체타입이므로 참조를 공유함
    => 따라서 값 타입은 불변객체로 만들어야함 (@Setter를 제거하고, 생성자에서 값을 모두 초기화해서 변경 불가능한 클래스를 만들자)

  3. 동일성(identity) 비교: 인스턴스의 참조 값을 비교, == 사용
    동등성(equivalence) 비교: 인스턴스의 값을 비교, equals() 사용

1-8 Auditing 공통속성 공동화

  • 등록시간, 수정시간, 등록자, 수정자, 데이터를 업데이트 했을 때 변경된 대상을 찾을 때 등의 경우에서 사용
@Configuration
@EnableJpaAuditing
public class AuditingField {

    @Bean
    public AuditorAware<String> auditorAware(){
        return new AuditorAwareImpl();
    }
}
public class AuditorAwareImpl implements AuditorAware<String> {
    @Override
    public Optional<String> getCurrentAuditor() {
        Authentication authentication = SecurityContextHolder.getContext()
        .getAuthentication();
        
        String userId = "";
        
        if (authentication != null){
            userId = authentication.getName();
        }
        return Optional.of(userId);
    }
}

0개의 댓글