[JPA] JPA 기초 2
26 Nov 2022JPA 기초 2
엔티티 삭제
Member memberA = em.find(Member.class, "memberA");
em.remove(memberA);
- em.remove()에 삭제 대상 엔티티를 넘겨주면 엔티티를 삭제한다. 즉시 삭제하는 것이 아니라, 엔티티 등록과 비슷하게 삭제 쿼리를 쓰기 지연 SQL 저장소에 등록한 후, 트랜잭션을 커밋해서 플러시를 호출하면 실제 데이터베이스에 삭제 쿼리를 전달한다.
- memberA는 영속성 컨텍스트에서 제거된다.
영속석 컨텍스트를 플러시 하는 3가지 방법
- 플러시란?
- 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영하는 것
- 변경 감지가 동작해서 영속성 컨텍스트에 있는 모든 엔티티를 스냅샷과 비교해서 수정된 엔티티를 찾는다.
- 수정된 엔티티는 수정 쿼리를 만들어 쓰기 지연 SQL 저장소에 등록한다.
- 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송한다.(등록, 수정, 삭제 쿼리)
- 영속성 컨텍스트를 비우지는 않는다.
- 트랜잭션이라는 작업 단위가 중요함 → 커밋 직전에만 동기화하면 됨
- 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영하는 것
-
em.flush() - 직접 호출
엔티티 매니저의 flush() 메서드를 직접 호출해서 영속성 컨텍스트를 강제로 플러시한다.
-
트랜잭션 커밋 - 플러시 자동 호출
데이터베이스에 변경 내용을 SQL로 전달하지 않고 트랜잭션만 커밋하면 어떤 데이터도 데이터베이스에 반영되지 않는다. 따라서 트랜잭션을 커밋하기 전에 꼭 플러시를 호출해서 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영해야 한다. JPA는 이런 문제를 예방하기 위해 트랜잭션을 커밋할 때 플러시를 자동으로 호출한다.
-
JPQL 쿼리 실행 - 플러시 자동 호출
JPQL이나 Criteria 같은 객체지향 쿼리를 호출할 때도 플러시가 실행된다.
데이터베이스 스키마 자동 생성
- JPA는 데이터베이스 스키마를 자동으로 생성하는 기능을 지원한다. (애플리케이션 실행 시점)
- 데이터베이스에 맞는 적절한 DDL을 생성
- 생성된 DDL은 개발 장비에서만 사용하길 권장하며 운영서버에서는 사용하지 않거나, 적절히 다듬은 후에 사용해야 한다.
- 운영 장비에는 create, create-drop, update 사용 금지
- 개발 초기 단계는 create 또는 update
- 테스트 서버는 update 또는 validate
- 스테이징과 운영 서버는 validate 또는 none
- hibernate.hbm2ddl.auto 속성

엔티티 매핑
- 객체와 테이블 매핑
- @Entity, @Table
- 기본 키 매핑
- @Id
- 필드와 컬럼 매핑
- @Column
- 연관관계 매핑
- @ManyToOne, @JoinColumn
객체와 테이블 매핑
- @Entity
- JPA를 사용해서 테이블과 매핑할 클래스는 @Entity 어노테이션을 필수로 붙여야 한다.
- @Entity가 붙은 클래스는 JPA가 관리하고, 엔티티라 부른다.
- @Entity 적용 시 주의사항
- 기본 생성자가 필수(파라미터가 없는 public 또는 protected 생성자)
- final 클래스, enum, interface, inner 클래스에는 사용 불가
- 저장할 필드에 final 사용 불가
- @Entity 속성

- @Table
- 엔티티와 매핑할 테이블을 지정
- 생략하면 매핑한 엔티티 이름을 테이블 이름으로 사용
- @Table 속성

기본 키 매핑
-
직접 할당과 자동 생성
- 직접 할당 : 기본 키를 애플리케이션에서 직접 할당, @Id만 사용
-
자동 생성 : 대리 키 사용 방식
-
IDENTITY 전략
- 기본 키 생성을 DB에 위임하는 전략, DB에 값을 저장하고 나서야 기본 키 값을 구할 수 있을 때 사용한다.
- 주로 MySQL, PostgreSQL, SQL Server, DB2에서 사용
// IDENTITY 매핑 코드 @Entity public class Member{ @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; ... }private static void logic(EntityManager em) { Member member = new Member(); em.persist(member); System.out.println("member.id = " + member.getId()); } // 출력: member.id = 1-
주의사항
엔티티가 영속 상태가 되려면 식별자가 반드시 필요한데 IDENTITY 식별자 생성 전략은 엔티티를 데이터베이스에 저장해야 식별자를 구할 수 있으므로 em.persist()를 호출하는 즉시 INSERT SQL이 데이터베이스에 전달된다. (이 전략은 트랜잭션을 지원하는 쓰기 지연이 동작하지 않음)
-
SEQUENCE 전략
- 시퀀스는 유일한 값을 순서대로 생성하는 특별한 데이터베이스 오브젝트이다. SEQUENCE 전략은 이 시퀀스를 사용해서 기본 키를 생성한다.
- 시퀀스를 지원하는 Oracle, PostgreSQL, DB2, H2 데이터베이스에 사용
// 시퀀스 DDL CREATE TABLE MEMBER ( ID BIGINT NOT NULL PRIMARY KEY, DATA VARCHAR(255) ) // 시퀀스 생성 CREATE SEQUENCE MEMBER_SEQ START WITH 1 INCREMENT BY 1;// 시퀀스 매핑 코드 @Entity @SequenceGenerator( name = "MEMBER_SEQ_GENERATOR", sequenceName = "MEMBER_SEQ", //매핑할 데이터베이스 시퀀스 이름 initialValue = 1, allocationSize = 1) public class Member{ @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "MEMBER_SEQ_GENERATOR") private Long id; ... }// 시퀀스 매핑 코드 @Entity @SequenceGenerator( name = "MEMBER_SEQ_GENERATOR", sequenceName = "MEMBER_SEQ", //매핑할 데이터베이스 시퀀스 이름 initialValue = 1, allocationSize = 1) public class Member{ @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "MEMBER_SEQ_GENERATOR") private Long id; ... }- IDENTITY 전략 - 먼저 엔티티를 데이터베이스에 저장한 후에 식별자를 조회해서 엔티티의 식별자에 할당한다.
- SEQUENCE 전략 - em.persist()를 호출할 때 먼저 데이터베이스 시퀀스를 사용해서 식별자를 조회하고 해당 식별자를 엔티티에 할당하여 영속성 컨텍스트에 저장한다. 이후 트랜잭션을 커밋해서 플러시가 일어나면 엔티티를 데이터베이스에 저장한다.
- @SequenceGenerator 속성
-

-
주의사항
allocationSize의 기본값 : 50
-
-
TABLE 전략
- 키 생성 전용 테이블을 하나 만들고 여기에 이름과 값으로 사용할 컬럼을 만들어 데이터베이스 시퀀스를 흉내내는 전략
- 모든 DB에 적용이 가능하지만 성능이 떨어진다.
// TABLE 전략 키 생성 DDL create table MY_SEQUENCE( sequence_name varchar(255) not null , next_val bigint, primary key ( sequence_name ) )- sequence_name 컬럼을 시퀀스 이름으로 사용하고 next_val 컬럼을 시퀀스 값으로 사용
// TABLE 전략 매핑 코드 @Entity @TableGenerator( name = "BOARD_SEQ_GENERATOR", table = "MY_SEQUENCES", pkColumnValue = "BOARD_SEQ", allocationSize = 1) public class Member { @Id @GeneratedValue(strategy = GenerationType.TABLE, generator = "BOARD_SEQ_GENERATOR") private Long id; ... }// TABLE 전략 매핑 코드 @Entity @TableGenerator( name = "BOARD_SEQ_GENERATOR", table = "MY_SEQUENCES", pkColumnValue = "BOARD_SEQ", allocationSize = 1) public class Member { @Id @GeneratedValue(strategy = GenerationType.TABLE, generator = "BOARD_SEQ_GENERATOR") private Long id; ... }- @TableGenerator 속성
-

-
주의사항
allocationSize의 기본값 : 50
-
-
권장하는 식별자 전략
- DB의 기본 키 제약 : NOT NULL, UNIQUE
- 미래까지 위 조건을 만족하는 자연키를 찾기는 어렵다. 대리키(대체키)를 사용하는 것이 낫다.
- Long형 + 대체키 + 키 생성전략 사용
- AUTO_INCREMENT나 SEQUENCE 오브젝트 등을 사용
allocationSize의 기본값이 50인 이유
- DB에 여러번 접근하는 것을 방지하기 때문에 성능적으로 우위
- 동작 방식
- allocation 값이 50이면 시퀀스를 한 번에 50을 증가시킨 다음에 1 ~ 50까지는 메모리에 시퀀스 값을 할당
- 51이 되면 시퀀스 값을 100으로 증가시킨 다음 51 ~ 100까지 메모리에서 식별자를 할당
- 위 방법은 시퀀스 값을 선점하므로 여러 JVM이 동시에 동작해도 기본 키 값이 충돌하지 않는 장점이 있다. 반면에 데이터베이스에 직접 접근해서 데이터를 등록할 때 시퀀스 값이 한 번에 많이 증가한다는 점을 염두해두어야 한다. 이런 상황이 부담스럽고 INSERT 성능이 중요하지 않으면 allocationSize의 값을 1로 설정하면 됨
필드와 컬럼 매핑
- @Column
- 객체 필드를 테이블 컬럼에 매핑
- 속성 중에 name, nullabe이 주로 사용되고 나머지는 잘 사용되지 않는 편
- insert, updatable 속성은 데이터베이스에 저장되어 있는 정보를 읽기만 하고 실수로 변경하는 것을 방지하고 싶을 때 사용
- @Column을 생략할 경우
- 객체 타입일 때는 nullable 속성이 기본값으로 적용 (true)
- 자바 기본 타입일 때는 JPA가 not null 제약조건을 추가
- nullable 속성의 값을 따로 지정하여 관리하는 것이 안전
- @Column 속성

- @Enumerated
- enum 타입을 매핑할 때 사용
- @Enumerated 속성

- 주의사항
- 기본값인 ORDINAL은 enum의 순서가 바뀌면 안되므로 EnumType.STRING을 권장
- @Lob
- BLOB, CLOB 타입과 매핑
- 지정할 수 있는 속성이 없으며, 매핑하는 필드 타입이 문자면 CLOB로 매핑하고 나머지는 BLOB로 매핑
- CLOB: String, char[], java.sql.CLOB
- BLOB: byte[], java.sql.BLOB
- @Transient
- 이 필드는 매핑하지 않는다.
- DB에 저장하지도 조회하지도 않는다.
- 객체에 임시로 어떤 값을 보관하고 싶을 때 사용
- @Access
- JPA가 엔티티 데이터에 접근하는 방식을 지정
- 필드 접근
- AccessType.FIELD는 필드에 직접 접근
- 필드 접근 권한이 private이어도 접근할 수 있다.
- 프로퍼티 접근
- AccessType.PROPERTY로 지정
- 접근자(getter)를 사용
“[JPA] JPA 기초” 시리즈는 인프런 김영한 강사님의 강좌를 기반으로 쓰여졌습니다.
(참고 : https://www.inflearn.com/users/@yh)