*김영한 님의 JPA 기본 강의를 기반으로 작성하였습니다.
JPQL: SQL 을 추상화하여 사용 가능. 데이터 베이스 SQL 에 종속적이지 않다.( 페이징 처리가 대표적으로 db마다 다른 SQL 문법을 가진다) "엔티티 객체"를 대상으로 쿼리를 날리는 객체 지향 쿼리
SQL: "데이터 베이스 테이블"을 대상으로 쿼리
[영속성 컨텍스트]
엔티티 매니저 & 팩토리
엔티티를 영구히 저장하는 환경.
EntityManager.persist(entity); -> 해당 엔티티를 영속성 컨텍스트에 저장하는 것 (실제 DB 공간에 저장 x)
영속성 컨텍스트는 논리적인 개념이다. 엔티티 매니저를 통해 접근하게된다.
엔티티 생명주기
•비영속
영속성 컨텍스트와 전혀 관계가 없는 새로운 상태
•영속
영속성 컨텍스트에 관리되는 상태 (em.persist() 이후, 엔티티 매니저에 의해 관리되는 상태, 아직 DB에 저장되지 않음)
해당 액션을 포함하는 trasaction이 commit되는 시점에서 실제로 db에 쿼리를 날려서 반영한다.
•준영속
영속성 컨텍스트에 저장되었다가 분리된 상태 (em.detach(),claer(),close() )
- em.detach(entity)
특정 엔티티만 준영속 상태로 전환
- em.clear()
영속성 컨텍스트를 완전히 초기화
- em.close()
영속성 컨텍스트를 종료
•삭제
삭제된 상태 (em.remove() )
삭제 상태와 준영속 상태의 차이
- 영속성 컨텍스트에서의 관리 여부: 준영속 상태에서는 영속성 컨텍스트가 해당 엔티티를 더 이상 관리하지 않으므로, 엔티티의 변경사항은 데이터베이스에 자동으로 반영되지 않습니다. 하지만 삭제 상태에서는 해당 엔티티가 영속성 컨텍스트에서 제거되고, 엔티티 매니저가 제공하는 flush() 메서드를 호출하면 데이터베이스에서 해당 엔티티가 삭제됩니다.
- 영속성 컨텍스트에서의 조회 여부: 준영속 상태에서는 데이터베이스에서 해당 엔티티를 다시 조회할 수 있습니다. 하지만 삭제 상태에서는 해당 엔티티를 데이터베이스에서 다시 조회할 수 없으며, 엔티티를 데이터베이스에서 완전히 제거한 상태입니다.
영속성 컨텍스트의 이점
1차 캐시 동일성(identity) 보장: 같은 트랜잭션 내에서 바로 조회, 캐시를 유지한다.-> db를 찾는게 아닌, 캐시에서 바로 값을 가져오거나, 한번 찾은 데이터를 캐시에 저장해놓는다. ( 같은 트랜잭션 내에서 같은 데이터 여러번 조회시 캐시에서 찾을 수 도 있다) 영속성 컨텍스트 내에서 사용하는 캐시를 1차 캐시, 전체에서 얻을 수 있는 캐시는 2차 캐시라고 한다.
엔티티 매니저의 1차 캐시 범위 내에서( 보통 엔티티 매니저 전, 후를 트랜잭션으로 묶는다.) 비교시.
Java 컬렉션에서 같은 객체를 가져오면 == 비교가되는 것처럼 동일성 보장.
Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");
System.out.println(a == b); //동일성 비교 true
트랜잭션을 지원하는 쓰기 지연 (transactional write-behind)
write에 대한 쿼리를 쓰기 지연 SQL저장소에 저장해 놓고, 트랜잭션에서 최대한 미뤄서, 최신 수정 정보를 데이터베이스에 1번만 반영.
ex) a라는 데이터를 1로 바꾸고 3으로 바꿀 경우, 수정에 대한 쿼리는 3으로 바꾸는 쿼리 1개만 날라간다.
![](https://blog.kakaocdn.net/dn/GJPcx/btr2CxPERYW/RJo3QspCiGRO5MdabY0pZ1/img.png)
-> 버퍼링 기능을 사용가능하다.
변경 감지(Dirty Checking)
수정 후 em,update() 로 수정해줄 필요 없이, 값이 저장되어있는 객체를 setter 등으로 수정하면 자동으로 update쿼리가 나간다.
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin(); // [트랜잭션] 시작
// 영속 엔티티 조회
Member memberA = em.find(Member.class, "memberA");
// 영속 엔티티 데이터 수정
memberA.setUsername("hi");
memberA.setAge(10);
//em.update(member) 이런 코드가 있어야 하지 않을까?
transaction.commit(); // [트랜잭션] 커밋
![](https://blog.kakaocdn.net/dn/mGZw8/btr2wuFRKic/gt0WohnuUNakdlpxbjuy4K/img.png)
transaction 시점에 entity와 스냅샷(최초상태에 대한) 을 비교하고 변경되면 자동으로 update 쿼리를 날린다.
영속성 컨텍스트는 트랜잭션 단위로 동작하기에 다른 트랜잭션에서는 변경 감지를 수행하지 못한다.
플러시(flush): 영속성 컨텍스트의 변경사항을 데이터베이스에 반영
when?
- em.flush() 로 직접 호출
- 트랜잭션 커밋시 자동 호출
- jpql 쿼리 실행시 자동호출 (만약 영속성 컨텍스트에만 있는 데이터를 select등으로 조회시, 조회 되지 않음 따라서 JPQL실행시마다
영속성 컨텍스트 내의 내용을 반영해줘야한다.)
플러시 발생시 작업
- 변경 감지
- 수정된 엔티티를 엔티티 쓰기 지연 SQL 저장소에 등록
-쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송(등록, 수정, 삭제 쿼리)
*flush를 해도 1차 캐시는 유지된다. flush는 그저 위의 3가지 작업 정도만 수행.
*주의점
-flush는 영속성 컨텍스트를 비우는 것이 아닌, 변경내용을 데이터베이스에 반영하는 작업.
-트랜잭션 커밋 직전에만 flush하면 된다.
지연 로딩(Lazy Loading)
ex) member와 관계를 가지는 team을 member.getTeam() 등으로 조회할 때, 해당 테이블을 가져오는 작업을
지연한 뒤, 필요할때 가져온다.
[엔티티 매핑]
엔티티 매핑 종류
객체 <> 테이블 : @Entity, @Table
필드 <> 컬럼: @Column
기본키 매핑: @Id
연관관계 매핑: @ManyToOne, @JoinColumn
@Entity
- 해당 어노테이션이 붙은 클래스는 JPA가 관리, 엔티티라 한다.
*주의
- 기본 생성자 필수(파라미터 없는 public or protected 생성자)
-final 클래스, enum, interface, inner 클래스는 사용불가
-db에 저장할 필드는 final 사용안됨
데이터베이스 스키마 자동생성
-설정 파일에 ddl-auto 설정에서 create, update 등 옵션으로 사용가능
-DDL을 애플리케이션 실행 시점에 자동 생성( 테이터베이스에 맞는 문법으로 적절한 DDL이 생성된다) 하지만 개발 단계에서만 사용해야하고 운영서버에서는 사용하면 안된다. (자동 생성된 것은 불안정함)
ddl 설정
![](https://blog.kakaocdn.net/dn/bjz7Ee/btr2Gs05fMg/65Kgpphp52iF4Tk2Wa12s1/img.png)
- 운영 장비에는 절대 create, create-drop, update 사용하면 안된다.
- 개발 초기 단계는 create 또는 update스테이징과 운영 서버는 validate 또는 none
- 테스트 서버는 update(여기서도 update는 가급적 x), validate
- 스테이징과 운영 서버는 validate 또는 none
DDL 생성기능
제약조건 추가: 회원 이름은 필수, 10자 초과X @Column(nullable = false, length = 10)
• 유니크 제약조건 추가
@Table(uniqueConstraints = {@UniqueConstraint( name = "NAME_AGE_UNIQUE", columnNames = {"NAME", "AGE"} )})
이것들은 실행에 영향을 주지 않고 DDL을 생성할 때만 영향.
필드와 컬럼 매핑
@Column(name = "name") //필드명과 컬럼명을 다르게 하고 싶을 때 설정해준다.
private String username;
private Integer age; //알아서 적절한 타입을 매칭해준다. (Integer과 적절히 호환되는 타입)
@Enumerated(EnumType.STRING) //DB에는 enum타입이 없다. @enumerated 붙여야함.
private RoleType roleType;
@Temporal(TemporalType.TIMESTAMP)
//날짜 타입은 @Temporart( date, type, timestamp) 각 날짜, 시간, (날짜와 시간)
private Date createdDate;
@Temporal(TemporalType.TIMESTAMP)
private Date lastModifiedDate;
@Lob
private String description; //데이터베이서에 varchar 이상의 큰 컨텐츠를 사용할 때.
@Transient. //db와 관계없이, 메모리에서만 임시로 사용할 필드( db에 해당 필드에 대응되는 컬럼 생성x)
private int temp;
![](https://blog.kakaocdn.net/dn/cjEBX7/btr2OqBxvPW/vLItlnghpXVEzBeZjVSvb0/img.png)
@Column
![](https://blog.kakaocdn.net/dn/cJCTrW/btr2D6dkIau/42arukivx61B7MRnwKBOyK/img.png)
위에서 기본값 외의 설정을 사용하고 싶을 때, 해당 옵션의 속성을 변경하면 된다.
nullable: 기본이 true. 만약 faluse 로 세팅시, not null 이 추가 되는 것.
unique 제약 조건: 컬럼 이름이 랜덤하게 생성된다. 따라서 실제에선 사용 비추천. @Table(uniqueConstraint= ~~~). 로 unique 조건 거는걸 추천( 컬럼명 설정 가능)
@Enumberated
![](https://blog.kakaocdn.net/dn/SmmFG/btr2NxnnIDs/F1igRmI7e1yZkEFb5sOrY1/img.png)
ORDINAL 로하면, enum 이 숫자로 들어가서 integer로 해석된다.
만약 ORDINAL을 사용하면 Enum에 적힌 순서에 따라 0,1 과 같은 Integer 값으로 변환된다.
ex) role 이라는 enum을 유저의 권한 (USER, ADMIN) 에 대해 만들었을 때
public enum Role{
USER, ADMIN
}
USER,ADMIN 이라는 이름값이 아닌
USER -> 0
ADMIN -> 1 로 순서쌍으로 생성한다.
만약
GUEST, USER, ADMIN 처럼 앞에 값을 추가하면, 그 의미가 달라져
USER==1 , ADMIN==2 가 되기에 위험하고, 사용하면 안된다. (식별자인 권한 이름에 의존하지 않고 적힌 순서에 의해 식별하게 된다)
@Temporal : 시간관련 설정
최신버전 하이버네이트 사용시 LocalDate, LocaleDateTime 등 타입으로 바로 사용 가능
ex)
//구버전
@Temporal(TemporalType.TIMESTAMP)
private Date createdDate
//최신 하이버네이트 사용시
private LocalDate localTime;
@Transient
필드 매핑X
• 데이터베이스에 저장X, 조회X
주로 메모리상에서만 임시로 어떤 값을 보관하고 싶을 때 사용
@Transient
private Integer temp;
기본키 매핑
직접 할당: @Id만 사용자동 생성(@GeneratedValue)
IDENTITY: 데이터베이스에 위임, MYSQL
SEQUENCE: 데이터베이스 시퀀스 오브젝트 사용, ORACLE 같은 db에서 주로 사용
![](https://blog.kakaocdn.net/dn/cfFuaz/btr2O1uUKE1/yyJFvuaa79xjGyGp3jVPz1/img.png)
TABLE: 키 생성용 테이블 사용, 모든 DB에서 사용 (성능이 나쁘다.)
기본 키 제약 조건: not null, 유일, 불변
불변: 서비스 수명은 알 수 없다. 평생 만족하는 자연키는 찾기 어렵기에 대체 키를 사용하자.
권장: Long + 대체 키(sequence, new id 등등) + 키 생성전략(sequence(auto increment) 등)
주민번호등 비즈니스를 키값으로 가져오면 좋지 않다.
[IDENTITY] VS [SEQUENCE]
- ID 값이 생성될 때마다 데이터베이스에 INSERT 쿼리가 실행되기 때문에, 매우 느릴 수 있다.
- 병렬 처리를 하기 어렵기 때문에, 성능 이슈가 발생할 가능성이 있다
따라서, 데이터베이스의 특성에 따라서 GenerationType.SEQUENCE와 GenerationType.IDENTITY 중에서 선택하여 사용해야 한다. 보통은 GenerationType.IDENTITY보다는 GenerationType.SEQUENCE를 사용하는 것이 성능상 이점이 있을 수 있다.
MariaDB를 사용하는 경우, 기본적으로는 GenerationType.IDENTITY를 사용하는 것이 가장 간단하고 성능적으로도 좋다.
MariaDB에서는 GenerationType.IDENTITY 전략을 사용하는 경우 InnoDB Storage Engine이 자동으로 Auto-Increment 기능을 사용하게 되어서, Auto-Increment 칼럼에 대한 Locking을 최소화하면서 성능 향상을 할 수 있다.
그러나, 멀티 쓰레드 환경에서 대량의 쓰기 작업이 예상되는 경우에는 GenerationType.SEQUENCE나 GenerationType.TABLE을 사용하는 것이 좋다. 이 두 전략은 ID 값을 메모리에서 생성하므로, 데이터베이스와의 I/O가 감소하여 성능상 이점을 얻을 수 있다.
IDENTITY 전략은 db에서 세팅해주는 값. 따라서 db에 들어가야 id 값을 알 수 있다.
jpa에서 영속성 컨텍스트에 넣으려면, PK가 있어야한다. ( 1차 캐시에 PK를 넣는 것으로 식별)
실제론 IDENTITY로 PK전략을 세팅하면, insert를 바로 DB에 날려 실제로 DB에 넣고, 얻은 PK를 가져오게 된다.
따라서 em.perisit() 시점에 즉시 insert 쿼리가 수행된다.
-> 한개의 트랜잭션에 중복된 insert 쿼리가 수행될 수 있다 ( 동일한 데이터를 여러번 변경 시, 마지막 값만 insert하면 되지만, 매번 하게됨) 사실 큰 성능의 차이는 없지만 고려할만한 사항
Sequence 방식도 마찬가지로 db에서 유치중인 sequence 오브젝트 값을 참조하여 아이디를 가져오지만,
IDENTITY전략이 db에 현재 정보를 커밋하여 새롭게 아이디를 생성하는 반면, Sequence전략은 DB에서 이전에 형성된 키값을 가져온 뒤, +1 만하면 되기에, insert쿼리를 날릴 필요가 없다. 따라서 IDENTITY와 다르게 버퍼링을 통한 WB( 모았다가 한번에 commit) 가능
Q)네트워크를 타고 정보를 주고 받는게 insert 쿼리를 여러번 날리는 것보다 별로일 수도? -> allocationSize를 통해 완화
@TableGenerator 속성
![](https://blog.kakaocdn.net/dn/cg46Iy/btr2GuyjWfX/2fPLmR4KA2dOR8S46TGzoK/img.png)
*allocationSize:
JPA에서는 @SequenceGenerator를 통해 시퀀스 값을 가져올 때 일정 범위의 값을 캐싱하여 성능을 향상시키는데 이때, 캐싱할 범위를 결정하는 값이 allocationSize.
allocationSize가 50으로 설정되어 있으면, JPA는 테이블에서 시퀀스 값을 50개씩 가져와 메모리 상에 캐싱한다. 그리고 이 캐시된 시퀀스 값 중에서 1씩 증가시켜서 기본 키를 생성한다.
이와 같이 allocationSize 값이 50으로 설정되어 있으면, JPA는 시퀀스 값을 가져올 때마다 DB에 접근하지 않고, 메모리 상의 캐시된 값을 사용하여 성능을 향상시킨다.
하지만, 만약에 시스템에서 생성하는 테이블이 많거나, 동시에 많은 트랜잭션이 발생하는 경우, allocationSize 값을 더 크게 설정하여 성능을 높이는 것이 좋을 수 있다. 반대로, 작은 규모의 시스템에서는 allocationSize 값을 작게 설정하여 메모리 사용량을 최적화할 수 있다.
AUTO: 방언에 따라 자동 지정, 기본값
'Spring boot' 카테고리의 다른 글
[JPA] Part 3 프록시와 연관관계 (0) | 2023.03.10 |
---|---|
[JPA] Part2 (심화 매핑) (0) | 2023.03.10 |
API 서버 구성과 JWT를 통한 인증, 인가 (1) | 2023.01.30 |
Spring Boot 컨트롤러와 Client간의 관계 정리 (0) | 2023.01.27 |
Spring 소셜 로그인 처리 [OAuth] (0) | 2023.01.26 |