자바 ORM 표준 JPA 프로그래밍 1

송은우·2022년 12월 2일
0

TIL

목록 보기
39/61

직접 sql을 다루는 경우 entity를 신뢰할 수 없다
sql 이 변경되어도, 바로 반영되지 않고, 엔티티만 변경해도 sql이 변경되지 않으면 null로 남을 뿐이다

jpa 에서 객체를 찾고, 객체내 값을 변경만 하면, 트랜잭션을 커밋할 때 적절한 updatesql 이 알아서 날아간다

심지어 연관된 객체를 조회하는 시점에, 알아서 sql을 날려서 꺼내온다

엔티티 : 비즈니스 요구사항을 모델링한 객체

연관 저장을 위해서 직렬화, 역직렬화가 있었다
자바는 객체지향이 목적이고, 관계형 db는 객체지향의 개념이 없다는 차이가 모든 문제의 시점이다

패러다임의 불일치를 잘 조율하는 부분에 시간과 코드가 많이 들어간다

abstract class Item{
	Long id;
    String name;
    int price;
}
class Album extends Item{
	~~~~~
}

이런 클래스가 있다고 했을 때 엘범을 저장하기 위해서는 insert를 2개 만들고, 조회도 join을 통해 하고, 객체를 만들고,... 문제가 많다
자바 컬렉션 같은 경우는 당연히 간단하게 된다

jpa는 상속과 관련된 문제도 알아서 해결해준다

알아서 sql을 통해 2 테이블에 나눠서 저장도 해주고, find도 알아서 해주고, join도 알아서 해준다

연관관계 : 객체는 참조를 통해 다른 객체와 연관관계를 가지고, 테이블은 foreign key 참조에 접근해서 연관된 객체를 조회한다

객체는 참조가 있는 방향에서만 조회가 가능하지만, 테이블은 반대도 조회 가능함

연관관계 depth 가 깊어지면 어디까지 참조 해도 되는지를 보기도 힘들다 (객체 그래프 탐색)
실제 객체를 사용하는 시점에서 탐색을 해준다는 지연로딩을 누릴 수 없다는 문제가 있음

db 비교는 row 를 키값으로 비교
객체는 주소 비교+ equlas비교가 있음
jpa는 같은 트랜잭션일 때 같은 객체가 조회되는 것을 보장

Member member1=jpa.find(Member.class,memberId);
Member member2=jpa.find(Member.class,memberId);
member1==member2 //true

"같은 트랜잭션일 때" 보장한다

애플리케이션과 jdbc 사이에서 동작
애플리케이션 <-> jpa <-> jdbc api <-> sql
orm 은 객체와 관계형 데이터 베이스 사이에 불일치를 해결해주는 것이다

하이버네이트 프레임워크가 대부분 사용됨
패러다임 불일치, 성능, 데이터 접근 추상화 + 벤더 독립성
같은 것들 보장해줌
표준이 되기에 다른 라이브러리로 갈아타기 쉬움

sql 매퍼 vs orm
sql 매퍼는 객체와 sql을 매핑함. 하지만 직접 sql을 작성해야함
orm은 테이블과 매핑하면 관련된 처리를 해주기에 sql에 의존하는 개발 피할 수 있음

jpa 는 방언 클래스가 있음(db별로 특수한 명령어)
em 매니저 팩토리는 무조건 재사용 해야됨 무거움
jpa 기본 동작 클래스 + 구현체에 따라 커넥션 풀까지 만듦

모든 변경은 트랜잭션 안에서 일어나야됨. 아니면 예외 발생
엔티티가 변경되었을 때, 변경 추적 기능도 있음

jpa는 엔티티 객체 중심으로 개발한다. 검색을 할 때도 테이블 단위가 아닌 엔티티 객체를 기본으로 검색해야 함
엔티티 객체란 id :1 같은 게 아니라 Member.class

하나만 검색하는 게 불가능함. 그래서 jpql 이라는 sql 추상화 언어가 있음
jpql은 엔티티 객체를 대상으로 쿼리
sql 은 테이블을 대상으로 쿼리함
jpql : "select m from Member m"
멤버 테이블이 아님 member 클래스 엔티티 임

ch3

엔티티 메니저는 crud 하는 관리자. 엔티티를 저장하는 가상의 데이터베이스라고 봐도 무방함

엔티티 메니저 팩토리는 하나의 데이터 베이스를 사용하면 하나다 -> 여러 데이터베이스면 여러개일 것이다

엔티티메니저를 만드는 비용은 거의 없도록 설계 되어 있음
여러 스레드 팩토리 안전, 매니저 불안전

엔티티메니저는 데이터베이스 커넥션을 필요한 순간까지 얻지 않기에, 막 생성해도 되는 이유가 됨

하이버네이트 같은 jpa 구현체는 emf 만들 때 커넥션 풀도 만듦
이는 J2SE 환경에서 사용하는 방법
https://rok93.tistory.com/entry/J2SEJ2EE%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90
j2ee에서 활용하면 컨테이너가 제공하는 데이터 소스를 사용함

영속성 컨텍스트
"엔티티를 영구 저장하는 환경"
엔티티 메니저로 엔티티를 저장하거나 조회하면 엔티티메니저는 영속성 컨텍스트에 엔티티를 보관하고 관리한다

persist메서드는 "엔티티메니저를 사용해서 회원 엔티티를 영속성 컨텍스트에 저장한다"
영속성 컨텍스트는 논리적인 개념.. 엔티티 메니저를 만들 때 하나 만들어짐

엔티티 생명주기
1. 비영속 : 영속성 컨텍스트와 전혀 관계 없음
2. 영속 : 영속성컨텍스트에 저장된 상태
3. 준영속 : 영속성 컨텍스트에 저장되었다가 분리된 상태
4. 삭제 : 삭제된 상태

detach,em.close 로 영속상태를 준영속으로 돌릴 수 있다
이렇게 되었을 때, 변경해도 직접 db에 반영되지 않음

영속 상태 : 영속성 컨텍스트에 의해 관리된다는 뜻

영속성 컨텍스트와 식별자 값
영속 상테는 반드시 @Id 같은 식별자 값이 있어야 한다
영속성 컨텍스트와 데이터베이스 저장
트랜잭션 커밋 시에 새로 저장된 엔티티를 데이터베이스에 반영하는데, 이를 플러시라 한다

영속성 컨텍스트가 엔티티를 관리했을 때 장점
1. 1차 캐시
내부 캐시가 있음. 내부 맵이 있고, @Id로 식별자, 엔티티 인스턴스를 나눔
find메서드 호출시 1차 캐시에서 먼저 조회함. db는 나중
2. 동일성 보장
영속성 컨텍스트는 성능상 이점 + 엔티티 동일성까지 보장해줘버림
동일성 : ==
동등성 : equals
JPA는 1차캐시를 통한 반복 가능한 읽기 등급의 트랜잭션의 격리 수준을 애플리케이션 차원에서 제공한다는 장점이 있다.
3. 트랜잭션 지원 쓰기 지연
트랜잭션을 커밋하지 않으면 어차피 안 들어가는 쿼리이기에, 나중에 한 번에 보내도 됨
4. 변경 감지
영속성 컨텍스트에 보관할 때 최초 상태를 복사해서 저장한 스냅샷을 만듦. 플러시 시점에 스냅샷과 엔티티를 비교해서 변경된 엔티티를 찾는다
당연하지만 비영속, 준영속 상태에서는 변경 감지가 되면 안됨
jpa는 한 부분만 업데이트 해도 데이터 전체를 다 업데이트함
파싱된 쿼리 재활용의 장점 vs 너무 크다면 따로 생성하는 방법도 있음
@DynamicUPdate 어노테이션
필드 30개가 넘어간다면 동적 쿼리가 빠르다곤 함

엔티티 삭제시 먼저 대상 엔티티 조회하고, flush 에서 반영됨
커밋해서 flush 호출시 실제 데이터베이스에 삭제 쿼리를 전달한다
em.remove 호출시 영속성 컨텍스트에서 제거가 되기에
그냥 새로운 객체로 가비지 컬렉터를 활용해 제거하는 쪽이 좋다

flush 영속성 컨텍스트의 변경 내용을 db에 반영함
1. 변경 감지 동작
2. 쓰기 지연 sql 저장소의 쿼리를 db 에 저장

호출 방식
1. em.flush 호출
테스트용을 제외하면 거의 일어나지 않음
2. 트랜잭션 커밋
jpa가 자동호출
3. jpql 쿼리 실행시 플러시 자동 호출
jpql에 알아서 호출됨

식별자를 기준으로 실행되는 find메서드에는 flush가 없음
em에 플러시 모드 직접 설정시 javax.persistence.FlushModeType을 사용하면 됨
FlushModeType.AUTO 커밋, 쿼리 실행시 플러시(기본값)
FlushModeType.COMMIT 커밋할 때만 플러시 (성능 최적화를 위해서 사용할 수 있음)
플러시는 영속성 컨텍스트에 엔티티를 지우는 것이 아님. 영속성 컨텍스트의 변경 내용을 데이터베이스와 동기화 시키는 것을 플러시라고 함
준영속 : em.detach(entity) em.clear(완전히 초기화), em.close(종료)를 통해 만들 수 있음
준영속시, 1차캐시, 쓰기 지연 sql 저장소에 동시에 제거됨

em.persist(entity)
em.detach(entity)
transaction.commit

작성시에도 반영 안됨

영속성 컨텍스트가 종료되어 memberA, memberB가 관리되지 않는다

준영속의 특징
준영속은 거의 비영속과 가까움
식별자 값이 무조건 있다는 정도의 차이가 있음
지연 로딩이 불가능함

merge(entity) 준영속 상태->영속 상태로 변경함
이때 당연하지만, 변경이 된다 X 새로운 영속성 컨텍스트에 저장되어있는 객체가 반환됨
그래서 이렇게 하는게 안저낳ㅁ

Member member=em2.merge(member)

merge는 비영속 엔티티도 영속 상태로 만들 수 잇다.
merge는 식별자로 영속성 컨텍스트 조회, 찾는 엔티티 없으면 새로운 엔티티 생성

병합은 준영속, 비영속 관계가 없음. 식별자 있어 조회 가능하면 그대로 쓰고, 없어도 새로 생성하기에 큰 차이 없음. save or update 기능

엔티티 매핑
대표 어노테이션
1. 객체, 테이블 : @Entity, @Table
2. 기본키 : @Id
3. 필드, 컬럼 @Column
4. 연관관계 : @ManyToOne, @JoinColumn

@Entity 가 붙은 클래스는 jpa가 관리하는 것으로 엔티티라 부른다
1. name : 엔티티 이름. 기본값 클래스명
2. 기본생성자 필수(파라미터 없는 public, protected)
3. final클래스, enum, interface, innerclass는 불가능
4. 저장할 필드에 final 불가능

@Table
1. name : 테이블명
2. 카타로그 : catalog 기능 있는 경우 catalog에 매핑
3. schema : schema 있으면 매핑
4. uniqueConstraints(DDL): DDL 생성시 유니크 제약조건 생성. 2개 이상 복합 유니크도 가능. schema 자동 생성시 이루어짐

Roletype을 enum으로 관리함
@Enumerated(EnumType.STRING)
으로 관리 가능함

@Temporal 날짜 타입을 관리함
@Lob 길이 제한 없는 string
clob, blob 타입으로 매핑 가능

스키마 자동 생성 hibernate.hbm2ddl.auto value=create같은 것으로 알아서 생성도 가능

hibernate.hbm2ddl.auto 속성
create: drop+ create
create-drop : drop + create+ drop 순으로 작동함
update : 변경사항만 수정
validate : 엔티티 매핑과 비교했을 때 문제가 있다는 경고만 하고 실행 안됨
none : 자동 생성 기능 사용하지 않은 경우

실무에서 절대 금지
create, create-drop, update 부분은 절대 사용 금지

초기에는 create,update. ci는 create, create-drop
테스트는 update, validate
스테이징과 운영 서버는 validate, none

hibernate.ejb.naming_strategy 를 설정했을 경우
value=org.hibernate.cfg.ImprovedNamingStrategy
자동으로 이름 매핑 변경을 해줌

직접 매핑 할 필요 없이 알아서 변경해주는 typeorm naming strategy 인듯?

@Column 에서 name, nulilable, length 같은 거 가능함
@Table 안에 uniqueConstraints= @UniqueConstraint 같은 뭔가를 추가할 수 있다

자동 생성에만 영향을 주고, 로직에는 당연히 영향을 주지 안흔다

기본키 매핑
1. 직접 할당 : 애플리케이션에 직접 할당
2. 자동 생성 : 대리 키 사용 방식
IDENTITY: db 에 위임
SEQUENCE: 데이터베이스 시퀀스를 사용해서 기본 키를 할당한다
TABLE : 키 생성 테이블을 사용한다
SEQUENCE, IDENTITY 같은 경우에는 db 벤더에 따라서 문제가 있음
직접 할당시 @Id
자동 할당은 @GeneratedValue + 방식을 하면 된다

@Id 타입은
기본형, 래퍼형, String, util.Date, sql.Date, math.BigDecimal, math.BigInteger 같은 것들이 가능함

직접 할당은 board.setId("id1");같은 방식으로 함
직접 할당시, 식별값 없이 저장시 예외가 발생함.

IDENTITY 는 알아서 위임하는 전략
우너래는 JPA를 추가로 db 조회하고, 하이버네이트는Statement.getGeneratedKeys()를 사용하면 데이터를 저장하면서 동시에 생성된 키를 받아올 수 있어 한 번만 통신함

영속 상태가 되려면 식별자가 무조건 있어야함
em.persist를 호출하는 즉시 insert를 전달해서 식별자를 얻어와야 되기에 트랜잭션을 지원하는 쓰기 지연이 발생하지 않는다.

sequence는 순서대로 생성함. oracle, postgre, db2, h2 에서는 사용 가능함
@SequenceGenerator를 통해서 먼저 등록을 해야됨
Identityh와 Sequence는 코드는 차이가 크지 않지만, 데이터베이스 시퀀스를 통해 먼저 조회하고, 영속성 컨텍스트에 저장하고, flush 때 저장한다

identy 와 작동은 거의 비슷하지만, 순서가 반대

@SequenceGenerator 은
1. name: 식별자 생성기 이름
2. sequenceName : 데이터베이스에 등록되어있는 시퀀스 이름
3. initialValue : ddl 생성시 처음 시작하는 수
4. allocationSize : 한 번 호출에 증가하는 수
5. catalog, schema : 각각의 이름

시퀀스는 2번 통신함. 1. 시퀀스 조회 2. db 접근
allocationSize 는 한 번 요청마다 이 수만큼 jvm에 할당해버림
시퀀스 부분을 선점하다고 생각해도 됨
성능이 괜찮다면 allocationSize를 1로 하면 됨
generator_mappings 속성을 true로 해야 최적화가 된다

Table 전략 : 키 전용 테이블을 만들고, 이름과 키값을 만든다
@GeneratedValue(strategy=GenerationType.TABLE,generator="BOARD_SEQ_GENERATOR") 같은 방식으로 가능함
칼럼 이름은 변경할 수 있음

@TableGenerator
1. name : 식별자 생성기 이름
2. table : 키 생성 테이블명
3. pkColumnName: 시퀀스 컬럼명
4. valueColumnName: 시퀀스 값 컬럼명
5. pkColumnValue : 키로 사용할 값 이름
6. initial value : 초기값. 마지막 생성된 값 기준
7. allocationSize: 한 번 호출에 증가하는 수
8. catalog, schema : 데이터베이스 catalog, schema 이름
uniqueConstraints(DDL): 유니크 제약 조건을 지정할 수 있다

Auto 전략은 알아서 적절하게 선택함. oracle이라면 sequence, mysql 이라면 identity 같은 옵션들을 만들어줌

웬만하면 식별자를 추가해라
1,2...(이를 대리키라고 함)
저장된 기본키는 변경되지 않는다. setId같은 메서드를 외부로 공개하지 않는 것이 좋을듯?

@Column이 없다면 기본값을 적용함
기본값에는 nullable에 예외가 잇음.
객체가 아니면 nullable하지 않기에, notnull같은 뭔가를 붙히는 쪽이 좋음

@EnumType.STRING이라면 이름 그대로 저장됨. 단 사이즈가 크긴 함
@EnumType.ORDINAL 같은 경우 크기는 작지만 순서 변경보장 안됨

@Transient 매핑 없이 임시 저장
@Access : JPA가 데이터에 접근하는 방식 지정
AccessType.FIELD : 필드 접근. private이여도 접근 가능
AccessType.PROPERTY : 프로퍼티 접근. getter 사용

기본값은 id를 가져오는 방식에 있음

ch5

방향 : 단방향, 양방향이 있음 객체관계에만 방향이 있고, 테이블은 항상 양방향
다중성 : n:1, n:m..1:n 1:1 이 있음
연관관계의 주인 : 객체를 양방향으로 한다면 연관관계의 주인을 정해야 함

단방향
n:1관계
객체 기준으로 n에서 접근은 가능하지만 반대는 안됨
테이블 단위에서는 양방향

참조하는 것은 단방향임. 양쪽에서 서로 참조하는 경우 다른 단방향이 2개다
객체는 참조, 테이블은 외래키

mappedby 가 존재하는 이유 : member 가 teamId 1 을 가지고 있다고 했을 때, 양방향 참조가 있다고 치면
member에서 teamId를 변경했을 때, team 에는 member의 참조가 그대로 남아있는 문제가 나온다
실제와 다르기에 이런 부분을 해결할 수 있음
연관관계의 주인만이 외래키를 관리 가능함
주인은 mappedby가 아닌 쪽임
보통 주인은 키가 있는 곳을 바탕으로 설정함
ManyToOne인 경우에는, 당연하지만 다쪽이 키가 있는 쪽이니 mappedBy가 없음
주인이 아닌 쪽에 주면 전혀 영향이 없는 경우가 생김
따라서 그냥 무시되어버림
양쪽 모두 설정하는 쪽이 더 좋긴 한듯?

setTeam 메서드 같은 세터를 양쪽 동시에 할 수 있도록 만드는 방식이 있음
이때 제거도 같이 해줘야 되기에 생각보다 귀찮음
양방향은 진짜 빡빡하게 설정해야된다

양쪽에 setter를 다 제거하는 과정이 생기면, 무한 루프도 나옴
저장 순서에 따라서 1:다의 경우 업데이트 쿼리까지 날악는 경우가 많음
그래서 효율이 구림
1:1 관계에서 주 테이블, 대상테이블로 봤을 때
주 테이블에 외래 키 : 외래키를 객체 참조와 비슷하게 사용할 수 있어서 객체지향 개발자들이 선호. 주 테이블만 봐도 대상 테이블과의 연관관계를 볼 수 있었음
대상 테이블에 외래 키 : 1:1에서 1:다로 변경했을 때 테이블 구조가 그대로 유지됨

1:1에서 대상 테이블에 외래키가 있는 경우는 jpa에서 지원하지 않음
외래키를 직접 관리하지 않는 일대일 관계는 지연 로딩이 되지 않음
프록시의 한계로 이루어지는 것.
bytecode instrumentation 을 통해서 할 수 있음

ManyToMany같은 경우 객체는 너무 자연스럽게 가능함
JoinTable 을 통해서 해결 가능

이때 초기화를 new ArrayList같은걸로 해줘야 됨
name, joincolumn, inversejoincolumn같은 것까지 다 정해줘야 됨

다대다 관계에서 가운데 테이블에 추가하는 경우에는 연결 엔티티가 필요함

@IdClass를 통해 복합 기본키를 매핑한다
복합 기본키는 n:m의 중간 테이블에 양쪽 모두에 의존하는 키
복합키를 사용하려면 별도의 식별자 클래스를 만들어야 함
복합 키는 별도의 식별자 클래스를 만들어야 함
Serializable을 구현해야 함
equals,hashCode를 구현해야됨
기본 생성자가 되어야 됨
식별자 클래스가 public
@EmbeddedId를 통한 것도 가능함
식별 관계
자신의 기본 키 + 외래 키로 사용하는 것을 데이터베이스 용어로 식별 관계라고 함

다대다를 복합키로 하면 너무 어려움
대리키를 만든다
훨씬 간단해지는 경우가 많음

식별 : 받아온 식별자를 기본키 + 외래키로 활용
비식별 : 외래키로만 사용, 기본키 생성

ch7 고급 매핑

상속 관계 매핑 : 상속 관계 매핑 방식
@MappedSuperclass : 공통 정보 상속 받는 방식
복합 키와 식별 관계 매핑 : 하나 이상의 식별자를 매핑하는 방식. 식별관계와 비식별 관계에 대해서 다룸
조인 테이블 : 외래키로 연관관계도 가능하지만, 연결관리 테이블을 두는 방식이 있음.
에닡티 하나에 여러 테이블 매핑 : 엔티티 하나에 하나 매핑이 아니라 여러 테이블도 가능함

상속 매핑
1. 각각의 테이블로 변환. JPA에서는 조인 전략
각각을 전부 테이블로 만듦
2. 통합 테이블로 만듦
단일 테이블 전략. 하나의 테이블로 전부 다 모아서 관리하는 전략
3. 서브타입 테이블로 변환
서브타입 별로 테이블을 하나씩 만든다 : 테이블 전략

각각의 테이블로 만들 때 어떤 테이블에 뭐가 있는지를 알 수 있는 방법이 없음 그래서 타입을 구분하는 컬럼을 추가함.
@DiscriminatorColumn(name="DTYPE")
@Inheritance(strategy=InheritanceType.JOINED)
같은 뭔가 타입을 통해 검증해야함
자식에서는 @DiscriminatorValue를 이용해서 구분함

기본적으로 자식 테이블은 부모 테이블의 id 컬럼명을 그대로 사용함.
자식 테이블의 기본키 칼럼명을 변경하고 싶다면 @PrimaryKeyJoinColumn 을 통해 바굴 수 있다

조인 전략의 장점
테이블 정규화
외래키 참조 무결성 제야곶건 활용 가능
저장 공간 효율적 사용

단점
조인이 많이 사용되기에 성능 이슈 가능
조회 쿼리 복잡
등록시 insert 2회

jpa 표준 명세는 구분 컬럼이 있음
하이버네이트등 몇몇 구현체는 구분 컬럼(@DiscriminatorColumn)없이도 동작하기도 함

관련 어노테이션 @PrimaryhKeyJoinedColumn, @DiscriminatorColumn, @DiscrimatorValue 3가지가 있음

단일 테이블 전략
하나만 사용함
DTYPE부터 자식, 부모 다 id 묶어버리는 테이블 전략
속도가 가장 빠르긴 함
자식 엔티티가 매핑한 컬럼은 모두 null 허용 가능해야 함
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
을 통해 가능
장점
성능 빠름
조회 쿼리 단순

단점 자식 엔티티 모두 null 허용
테이블이 커질 수 있어서 상황에 따라 오래 걸릴 수 있음
특징 구분 칼럼이 꼭 필요함
@DiscrimatorValue 설정 없으면 기본으로 엔티티명 사용함

구현 클래스마다 테이블 전략
@InheritanceType.TABLE_PER_CLASS
일반적으로 추천하지 않는다

장점 : 서브타입 구분이 효과적
notnull 도 가능
여러 자식 테이블을 함께 조회할 때 성능이 느리다(UNION 사용해야 함)
자식 테이블을 통합해 쿼리가 너무 힘듦

구분 컬럼을 사용하지 않음

@MappedSuperclass
부모클래스를 상속받는 자식 클래스에게 매핑 정보만 제공하고 싶으면 @MappedSuperclass를 사용하면 됨

@MappedSuperclass
추상 클래스와 비슷한데, @Entity 는 실제 테이블과 매핑되지만, @MappedSuperclass는 실제 테이블과는 매핑되지 않는다
매핑 정보만 상속하기 위해 사용된다

created_at, 같은 것들 상속을 위해서 사용하는 느낌
재정의하려면
@AttributeOverrides, @AttributeOverride 를 사용하는 것이 매핑정보를 수정하려면
@AssociationOverrides, @AssociationOverride 를 사용하면 연관관계를 재정의 가능함

@MappedSuperclass로 지정한 클래스는 엔티티가 아님. em.find나 jpql에서 접근 불가능

복합키와 식별관계 매핑
식별관계 vs 비식별 관계

profile
학생의 마음가짐으로 최선을 다하자

0개의 댓글