Skip to content

Instantly share code, notes, and snippets.

@QuadFlask
Created September 28, 2018 09:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save QuadFlask/cb73933026d86c081e09184ee1f470bd to your computer and use it in GitHub Desktop.
Save QuadFlask/cb73933026d86c081e09184ee1f470bd to your computer and use it in GitHub Desktop.

JPA

1. JPA 소개

왜 JPA? 객체지향 모델링을 할 수 있음. 연관관계와 관련된 패러다임 불일치를 해결해줌. 객체 그래프 탐색이 가능함.(sql 을 직접 사용할 경우 sql 에 따라 객체 그래프를 어디까지 탐색할 수 있는지 정해짐). 비교에 있어서 sql 을 통해 같은 로우를 가져올 경우 인스턴스가 다르기 때문에 동일성 비교에 실패(이건 equals 매소드 구현이 꼭 필요함)

생산성, 유지보수, 성능, 데이터 점근 추상화, 벤더 독립성, 표준

2. JPA 시작

객체 매핑: 클래스와 테이블을 매핑

매핑정보 | 회원 클래스 | 회원 테이블

이름 | Member | MEMBER 기본키 | id | ID 필드/컬럼 | username | NAME 필드/컬럼 | age | AGE

@Entity
@Table(name=“MEMBER”)
public class Member {
	@Id
	@Column(name=“ID”)
	private String id;
	
	@Column(name=“NAME”)
	private String username;
	
	private Integer age;
}

@Entity: 이 클래스를 테이블과 매핑한다고 알려줌 - 엔티티 클래스라 함

@Table: 클래스에 매핑할 테이블 정보를 알려줌. 생략할경우 클래스 이름(엔티티 이름)으로 테이블 매핑

@Id: 기본키 매핑

@Column: 필드를 컬럼에 매핑.

매핑 정보가 없는 필드: 필드명을 사용해서 매핑

2.5

JPA는 특정 데이터베이스에 종속적이지 않은 기술임. 그런데 각 데이터베이스마다 제공하는 sql문법, 함수가 조금씩 다름 -> 데이터베이스 방언 클래스를 제공해 문제 해결 -> 따라서 다른 데이터베이스로 쉽게 교체 가능

2.6

코드는 크게 3부분으로 나뉨

  • 엔티티 메니저 설정
  • 트렌젝션 관리
  • 비즈니스 로직

엔티티 매니저 설정

엔티티 매니저 팩토리 생성

EntityManagerFactory emf = Persistence.CreateEntityFactory(‘jpabook’);

엔티티 매니저 생성

EntityManager em = emf.createEntityManager();

종료

em.close(); // 사용이 끝난 엔티티 매니저는 반드시 종료해야함
emf.close(); // 애플리케이션을 종료할때 팩토리도 종료해야함

트렌젝션 관리

EntityTransaction tx = em.getTransaction();
try {
	tx.begin(); // 트렌젝션 시작
	logic(em); // 비즈니스 로직 실행
	tx.commit(); // 트렌젝션 커밋
} catch (Exception e) {
	tx.rollback(); // 트렌젝션 롤백
}

비즈니스 로직

public static void logic(EntityManager em) {
	String id = “id1”;
	Member member = new Member();
	member.setId(id);
	member.setUsername(“username”);
	member.setAge(99);
	
	em.persist(member); // 저장
	
	member.setAge(100); // 수정
	
	Member findMember = em.find(Member.class, id); // 1개 조회

	List<Member> members = em.createQuery(“select m from Member m“, Member.class).getResultList(); // 리스트 조회
	
	em.remove(member); // 삭제
}

JPA 는 엔티티 변경을 추적하는 기능이 있어 수정후 em.update()같은 메소드 호출이 필요 없음(실제로도 없음)

JPQL

em.createQuery에 사용한 쿼리 언어

JPQL: 엔티티 객체를 대상으로 쿼리(클래스, 필드 대상), 테이블을 전혀 알지 못함

SQL: 데이터베이스 테이블 대상으로 쿼리

3. 영속성 관리

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

엔티티 매니저 팩토리는 애플리케이션 전역에서 하나만 생성하여 공유해서 사용. 스레드-세이프. 하지만 엔티티 매니저는 그렇지 않기 때문에 스레드간 공유하면 안됨.

[그림 3.1 - page91]

EntityManagerFactory 에서 다수의 앤티티 매니저를 생성했고 EntityManager1은 아직 데이터베이스 커넥션을 사용하지 않는데, 엔티티 메니저는 꼭 필요한 시점까지 커넥션을 얻지 않음. 보통 트렌젝션을 시작할때 커넥션을 획득함.

3.1 영속성 컨텍스트

제일 중요한 용어임ㅋㅋ. persistence context: 엔티티를 영구 저장하는 환경

em.persist(member) : 엔티티매니저를 사용하여 회원 엔티티를 영속성 컨텍스트에 저장한다

3.3 엔티티 생명주기

4가지 상태

  • 비영속(new, transient): 영속성 컨텍스트와 전혀 관계 없는 상태
  • 영속(managed): 영속성 컨텍스트에 저장된 상태
  • 준영속(detached): 영속성 컨텍스트에 저장되었다가 분리된 상태
  • 삭제(removed): 삭제된 상태

[그림 3.2 - page93]

3.4 영속성 컨텍스트 특징

  • 영속성 컨테그트와 식별자 값 @Id로 구분함 -> 반드시 필요함 -> 없으면 예외 발생

  • 영속성 컨텍스트와 데이터베이스 저장 보통 트렌젝션을 커밋하는 순간 영속성 컨텍스트에 새로 저장된 엔티티를 데이터베이스에 반영하는데 이것을 flush라고 함

  • 영속성 컨텍스트가 엔티티를 관리하면 다음과 같은 장점이 있다

    • 1차 캐시
    • 동일성 보장
    • 트렌젝션을 지원하는 쓰기 지연
    • 변경 감지
    • 지연 로딩

3.4.1 엔티티 조회

기본적으로 식별자 값으로 조회함

  • 1차 캐시에서 조회

em.find() 를 호출하면 우선 1차 캐시에서 식별자 값으로 엔티티를 찾고 없을 경우 데이터베이스 조회하고 엔티티 생성후 1차 캐시에 저장한 뒤 영속 상태의 엔티티 리턴

  • 영속 엔티티의 동일성 보장
Member a = em.find(Member.class, “member1”);
Member b = em.find(Member.class, “member1”);

sout(a == b); // 1차 캐시에 있는 같은 엔티티 인스턴스기 때문에 true 임

동일성 vs 동등성 동일성(identity): 실제 인스턴스가 같다 (참조 값 비교) == 동등성(equality): 실제 인스턴스는 다르지만 인스턴스가 가진 값이 같다 equals()

3.4.2 엔티티 등록

커밋하기 전까지 데이터베이스에 엔티티를 저장하지 않고 내부 쿼리 저장소에 insert sql 을 차곡차곡 모아둔다. 커밋할때 모아둔 쿼리를 데이터베이스로 보냄 -> 이것을 트랜젝션을 지원하는 쓰기 지연이라고 함

3.4.3 엔티티 수정

변경 감지(dirty checking)

엔티티를 영속성 컨텍스트에 보관할때, 최초 상태를 복사해서 저장함(스냅샷). 플러시 시점에 스냅샷과 엔티티를 비교해서 변경된 엔티티를 찾는다.

JPA 기본 전략은 엔티티의 모든 필드를 업데이트 한다.

모든 필드를 수정하면 수정 쿼리가 항상 같다 -> 애플리케이션 로딩 시점에 수정 쿼리를 미리 생성하고 재사용 가능 데이터베이스에서 한번 파싱된 쿼리를 재사용할 수 있다 필드가 너무 많을 경우(30개 이상??) 변경된 필드만 업데이트 하는 것도 가능 -> 하이버네이트 확장 기능 필요(@org.hibernate.annotations.DynamicUpdate) -> 근데 이건 테이블 설계가 잘못됬었을 수 있음

3.5.1 플러시

  • 변경감지가 동작해서 영속성 컨텍스트에 있는 모든 엔티티를 스냅샷과 비교해서 수정된 엔티티를 찾는다. 수정된 엔티티는 수정 쿼리를 만들어 쓰기 지연 SQL 저장소에 등록한다
  • 쓰기 지연 SQL저장소의 쿼리를 데이터베이스에 전송한다

플러시 하는 방법

  1. em.flush()
  2. 트랜젝션 커밋 -> 자동 호출
  3. JPQL 쿼리 실행 -> 자동 호출

3.6 준영속

영속 -> 준영속 상태 변화

준영속상태의 엔티티는 영속성 컨텍스트가 제공하는 기능을 사용할 수 없다. 준영속 상태로 만드는 방법

  1. em.detach(entity) : 특정 엔티티만 준영속 상태로 전환
  2. em.clear() : 완전히 초기화
  3. en.close() : 컨텍스트 종료

3.6.5 병합: merge()

준영속 상태의 엔티티를 영속 상태로 변경

4 엔티티 매핑

다양한 매핑 어노테이션

  • 객체와 테이블 매핑: @Entiiy, @Table
  • 기본 키 매핑: @Id
  • 필드와 컬럼 매핑: @Column
  • 연관관계 매핑: @ManyToOne, @JoinColumn

4.1 @Entity

테이블과 매핑할 클래스는 필수로 붙여야함

  • name: JPA에서 사용할 엔티티 이름을 지정한다. 보통 기본값인 클래스 이름을 사용. 다른 패키지에 이름이 같은 엔티티 클래스가 있다면 이름을 지정해서 출동하지 않도록 해야함.

기본값: 클래스 이름을 그대로 사용

@Entity 적용시 주의 사항

  • 기본 생성자 필수(파라미터 없는 public / protected)
  • final 클래스, enum, interface, inner 클래스에는 사용 할 수 없음
  • 저장할 필드에 final을 사용할 수 없음

4.2 @Table

엔티티와 매핑할 테이블을 지정. 생략시 매핑한 엔티티 이름을 테이블 이름으로 사용

  • name: 매핑할 테이블 이름
  • catalog: catalog기능이 있는 데이터베이스에서 catalog 매핑
  • schema: schema기능이 있는 데이터베이스에서 schema 매핑
  • uniqueConstranits: DDL생성 시에 유니크 제약조건을 만든다. 스키마 자동생성기능을 사용해서 DDL을 만들때만 사용됨.

4.3 다양한 매핑 사용

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

클래스의 매핑정보로 어떤 테이블의 어떤 컬럼을 사용하는지 알 수 있음 -> 애플리케이션 실행 시점에 데이터베이스 방언으로 스키마 생성(테이블을 자동으로 생성)

이때 생성되는 DDL을 출력하고 싶다면 hibernate.show_Sql=true

hibernate.hbm2ddl.auto 속성

create: 기존 테이블 삭제, 새로 생성 (drop + create) create-drop: create 속성에 추가로, 애플리케이션 종료할때 생성한 ddl제거 (drop + create + drop) update: 변경사항만 수정 validate: 차이가 있을경우 경고하고 애플리케이션 실행 안함 - ddl 을 수정하지 않는다 none: 자동 생성 기능을 끈다(사실 유효하지 않은 옵션을 주면 됨)

참고) 이름 매핑 전략

자바 컨벤션은 카멜 케이스, 데이터베이스는 스네이크인데, 이걸 자동으로 hibernate.ejb.naming_strategy를 사용하면 컬럼명 생략시 자동으로 스네이크로 바꿔줌

4.5 DDL 생성 기능

4.6 기본키 매핑

JPA 가 제공하는 기본키 생성 전략

  • 직접 할당: 애플리케이션에서 직접 할당
  • 자동 생성: 대리키 사용 방식
    • IDENTITY: 생성을 데이터베이스에 위임
    • SEQUENCE: 데이터베이스 시퀀스를 사용해서 할당
    • TABLE: 키생성 테이블 사용

직접할당하려면 @Id만 사용, 자동생성 전략을 사용할경우 @GeneratedValue에 전략을 선택

@Id 적용 가능 타입

  • 기본형
  • 래퍼형
  • String
  • java.util.Date
  • java.sql.Date
  • java.math.BigDecimal
  • java.math.BigInteger

IDENTITY 전략

AUTO_INCREMENT처럼 데이터베이스에 값을 저장하고 나서야 기본키값을 구할 수 있음

데이터베이스에 INSERT 한 후에 기본키값을 조회할 수 있어서 식별자값을 할당하려면 추가로 데이터베이스를 조회해야 한다. JDBC3에 추가된 Statement.getGeneratedKeys()를 사용하면 데이터를 저장하면서 동시에 생성된 기본키값도 알 수 있다. 하이버테이트는 이 매소드를 통해 데이터베이스와 한번만 통신한다.

주의) 엔티티가 영속상태가 되려면 식별자가 반드시 필요하다. 그런데 IDENTITY식별자 생성 전략은 엔티티를 데이터베이스에 저장해야 식별자를 구할 수 있으므로 호출 즉시 INSERT SQL이 데이터베이스에 전달됨 -> 트랜잭션을 지원하는 쓰기 지연 동작이 안됨

4.7 필드와 컬럼 매핑: 레퍼런스

필드와 컬럼 매핑: @Column: 컬럼 매핑 @Enumerated: enum타입 매핑

  • ORDINAL: 순서를 저장 데이터 크기가 작음, 순서 변경 안됨
  • STRING: 이름을 저장 순서가 바뀌거나 추가되어도 안전, 크기가 큼

@Temporal: 날짜 타입 반드시 타입을 지정해야함

@Lob: BLOB, CLOB 타입 매핑 타입에 따라 스트링일 경우 CLOB, 바티으의 경우 BLOB

@Transient: 매핑하지 않음

기타: @Access: JPA가 엔티티에 접근하는 방식지정

  • 필드 접근: AccessType.FIELD: 필드 접근권한이 private이어도 접근 가능
  • 프로퍼티 접근: AccessType.PROPERTY: getter 사용

5 연관관계 매핑 기초

  • 방향(Direction): 단방향/양방향 -> 방향은 객체관계에서만 존재. 테이블 관계는 항상 양방향임
  • 다중성(Multiplicity): 다대일, 일대다, 일대일, 다대다
  • 연관관계 주인(Owner): 객체 관계를 양방향 연관관계로 만들면 주인을 정해야함

5.1 단방향 연관관계

다대일(N:1) 단방향 관계

회원과 팀 회원은 하나의 팀에만 소속됨 회원과 팀은 다대 일 관계

  • 객체 연관관계 회원 객체는 team 필드(맴버변수)로 팀 객체와 연관관계를 맺음 회원 객체와 팀 객체는 단방향 관계. 회원은 team 필드로 팀을 알 수 있지만 팀은 모름

  • 테이블 연관관계 회원 테이블은 TEAM_ID 외래키로 팀 테이블과 연관관계를 맺음 회원 테이블과 팀 테이블은 양방향 관계 -> 회원 테이블의 TEAM_ID 외래키를 통해 회원과 팀을 조인할 수 있고, 반대로 팀과 회원도 조인 가능

select *
from member m
join team t on m.team_id = t.id

select *
from team t
join member m on t.team_id = m.id
  • 객체 연관관계와 테이블 연관관계의 차이 참조를 통한 연관관계는 항상 단방향. 객체간 양방향을 만들려면 반대쪽에도 필드를추가해서 참조를 보관해야함. 결국 연관관계를 하나 더 만들어야되고 사실 서로다른 단방향이 두개가 있는거임. 반면에 테이블은 외래키 하나로 양방향 조인할 수 있음

@ManyToOne 어노테이션으로 매핑 정의 @JoinColumn(name=“team_id”) 외래키 매핑할때 사용. 생략 가능. 생략시 기본 전략을 사용하는데,

@ManyToOne
Team team;

필드명+”_”+참조하는 테이블명 -> team_team_id 외래키 사용

5.3 양방향 연관관계

회원 -> 팀 (Member.team) 팀 -> 회원 (Team.members)

@OneToMany(mappedBy=“team”)
List<Member> members;

mappedBy 속성은 양방향 매핑일때 사용하는데 반대쪽 매핑의 필드 이름으로 값을 준다 - 연관관계의 주인과 관련 된 내용

5.4 연관관계의 주인

mappedBy 속성? -> 객체엔 양방향 연관관계가 없음. 테이블은 외래키 하나로 두 테이블의 연관관계를 관리. 단방향 2개를 써서 양방향처럼 하면 참조는 2갠데 외래키는 하나 -> 두 객체 연관관계중 하나를 정해서 테이블의 외래키를 관리해야함 -> 연관관계의 주인(Owner)

5.4.1 양방향 매핑의 규칙: 연관관계의 주인

양방향에선 연관관계중 하나를 주인으로 정해야함 -> 주인만이데이터베이스 연관관계와 매핑되고 외래키를 관리할 수 있음(반면에 주인이 아닌쪽은 읽기만 가능)

어떤 연관관계를 주인으로 정할지는 mappedBy 속성 사용

  • 주인은 mappedBy 속성을 사용하지 않는다
  • 주인이 아니면 mappedBy 속성으로 연관관계의 주인을 지정해야함

�Member.team, Team.members 중 어떤것을 주인으로??

-> 연관관계의 주인을 정하는것은 외래키 관리자를 선택하는것 -> 주인은 외래키가 있는곳 -> 회원 테이블이 외래키를 가지고 있으므로 Member.team 이 주인이 됨 -> 주인이 아닌 Team 은 mappedBy 속성으로 주인이 아님을 설정한다. 값으로는 주인인 Member 의 team 필드 이름을 준다.

참고) 데이터베이스 테이블의 다대일, 일대다 관계에선 항상 다 쪽이 외래키를 가진다. 따라서 @ManyToOne에는 mappedBy속성이 없다

5.5 양방향 연관관계 저장

5.6 양방향 연관관계의 주의점

흔히하는 실수는 연관관계의 주인에 값을 입력하지 않고 주인이 아닌곳에만 값을 입력하는것. -> 연관관계의 주인만이 외래키의 값을 변경할 수 있음

새로 맴버가 팀에 추가되었다

team.getMembers().add(newMember); // 주인이 아닌곳에만 연관관계 설정

5.6.1 순수한 객체까지 고려한 양방향 연관관계

객체관점에서 양쪽 방향에 모두 값을 입력해주는것이 안전

newMember.setTeam(team); // 주인에 값 입력
team.getMembers().add(newMember); // 주인이 아닌곳에 연관관계 설정

-> 실수할 수 있으므로 MembersetTeam 을 수정해준다

public void setTeam(Team team) {
	this.team = team;
	team.getMembers().add(this);
}

하지만 기존에 연결되어있는 팀의 연관관계가 남아있다.

public void setTeam(Team team) {
	if (this.team != null)
		this.team.getMembers().remove(this);
	this.team = team;
	team.getMembers().add(this);
}

6 다양한 연관관계 매핑

매핑시 3가지 고려

  • 다중성
  • 단뱡항 / 양방향
  • 연관관계의 주인

보통 다대일, 일대다 많이 사용, 다대다는 거의 사용 안함

6.2 일대다

엔티티를 하나이상 참조할 수 있으므로 자바 컬렉션을 사용해야함

@JoinColumn 을 명시해줘야함. 그렇지 않으면 조인 테이블 전략을 기본으로 사용해서 매핑함

단점: 매핑한 객체가 관리하는 외래키가 다른 테이블에 있음

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment