Загружаем каталог…
Загружаем каталог…
김영한님의 자바 ORM 표준 JPA 프로그래밍 강의를 듣고 정리한 글입니다. 실습 환경: PostgreSQL + Hibernate 6(jakarta) 0. 엔티티 매핑이란? JPA가 "이 클래스는 어떤 테이블이고, 이 필드는 어떤 컬럼이야"를 알 수 있도록 어노테이션으로 알려주는 것 입니다. 구분 어노테이션 객체와 테이블 매핑 @Entity , @Table 필드와 컬럼 매핑 @Column 기본 키 매핑 @Id 연관관계 매핑 @ManyToOne , @JoinColumn → 다음 글에서 다룸 1. 객체와 테이블 매핑 @Entity @Entity 가 붙은 클래스는 JPA가 관리하는 엔티티 가 됩니다. 테이블과 매핑할 클래스에는 반드시 붙여야 합니다. 주의사항 규칙 이유 기본 생성자 필수 ( public 또는 protected ) JPA가 리플렉션으로 객체를 만들기 때문 final 클래스, enum , interface , inner 클래스 사용 X 프록시(상속) 객체를 만들 수 없음 저장할 필드에 final 사용 X JPA가 값을 채워 넣을 수 없음 @Entity public class Member { protected Member() {} // JPA용 기본 생성자 public Member(Long id, String name) { this.id = id; this.name = name; } } 💡 기본 생성자를 protected 로 두면 JPA는 쓸 수 있지만 다른 개발자가 무분별하게 new Member() 를 하는 것은 막을 수 있습니다. 실무에서 자주 쓰는 방식입니다. @Entity의 name 속성 @Entity(name = "Member") JPA 내부에서 쓸 엔티티 이름 입니다. (JPQL에서 select m from Member m 의 Member ) 기본값은 클래스 이름 입니다. 다른 패키지에 같은 이름의 클래스가 있는 경우가 아니면 기본값을 그대로 쓰면 됩니다. ⚠️ @Entity(name) 은 테이블 이름이 아닙니다. 테이블 이름은 @Table 로 지정합니다. @Table 엔티티와 매핑할 테이블 을 지정합니다. @Entity @Table(name = "MBR") // member 엔티티를 MBR 테이블에 매핑 public class Member { ... } 속성 기능 기본값 name 매핑할 테이블 이름 엔티티 이름 catalog DB catalog 매핑 schema DB schema 매핑 uniqueConstraints (DDL) DDL 생성 시 유니크 제약 조건 생성 💡 실무에서는 테이블 이름 규칙이 따로 있는 경우가 많습니다. (예: TB_MEMBER ) 이럴 때 클래스 이름은 Member 로 깔끔하게 두고 @Table(name = "TB_MEMBER") 로 연결하면 됩니다. 2. 데이터베이스 스키마 자동 생성 무엇을 해주나? 애플리케이션이 실행될 때 엔티티를 보고 테이블을 자동으로 만들어 줍니다. 테이블을 먼저 만들고 객체를 맞추는 방식 → 객체를 먼저 만들고 테이블이 따라오는 방식 으로 바뀜 방언 을 사용해서 DB에 맞는 DDL을 만들어 줌 이렇게 만든 DDL은 개발 장비에서만 사용합니다. 운영 서버에서는 사용하지 않거나, 생성된 DDL을 다듬어서 사용합니다. hibernate.hbm2ddl.auto 옵션 <property name="hibernate.hbm2ddl.auto" value="create"/> 옵션 설명 create 기존 테이블 삭제 후 다시 생성 (DROP + CREATE) create-drop create와 같지만 종료 시점에 DROP (테스트에서 사용) update 변경분만 반영 ( 운영 DB에 사용하면 안 됨 ) validate 엔티티와 테이블이 정상 매핑되었는지만 확인 none 사용하지 않음 (사실 없는 값이라 아무거나 쓰면 무시됨) 💡 update 는 컬럼 추가만 반영합니다. 필드를 지워도 컬럼은 삭제되지 않습니다. 실수로 운영 데이터가 날아가는 것을 막기 위해서입니다. 💡 validate 로 띄웠는데 매핑이 맞지 않으면 애플리케이션 실행 시 에러가 납니다. 예: 엔티티에는 age 필드가 있는데 테이블에는 컬럼이 없는 경우 방언별로 DDL이 달라진다 같은 String name 필드도 DB마다 다른 타입으로 만들어집니다. DB 생성되는 타입 H2 / PostgreSQL / MySQL varchar(255) Oracle varchar2(255 char) 방언 설정만 바꾸면 DDL이 알아서 바뀝니다. ⚠️ 운영 환경에서 주의 (가장 중요!) 운영 장비에는 절대 create , create-drop , update 를 사용하면 안 됩니다. 환경 권장 옵션 개발 초기 단계 create 또는 update 테스트 서버 update 또는 validate 스테이징, 운영 서버 validate 또는 none 💡 사실 테스트 서버나 개발 서버도 여러 명이 같이 쓰는 DB라면 update 를 쓰지 않는 것이 좋습니다. 수백만 건 테이블에 ALTER가 자동으로 나가면 락이 걸려 서비스가 멈출 수 있습니다. 결국 DDL은 직접 스크립트로 작성해서 검토 후 적용 하는 것이 가장 안전합니다. 자동 생성된 DDL은 그 스크립트를 만들 때 참고용 으로 쓰면 좋습니다. DDL 생성 기능 @Column(nullable = false, length = 10) // not null, varchar(10) private String name; @Table(uniqueConstraints = { @UniqueConstraint(name = "NAME_AGE_UNIQUE", columnNames = {"name", "age"}) }) ⚠️ DDL 생성 기능은 DDL을 만들 때만 사용됩니다. JPA의 실행 로직에는 아무 영향을 주지 않습니다. 즉 length = 10 이라고 써도 JPA가 저장 전에 길이를 검사해 주지 않습니다. 검사는 DB가 합니다. 그래도 엔티티만 보고 제약 조건을 알 수 있어서 문서 역할을 해 주기 때문에 써 두는 것이 좋습니다. 3. 필드와 컬럼 매핑 요구사항 추가 회원은 일반 회원과 관리자 로 구분해야 한다. 회원 가입일과 수정일 이 있어야 한다. 회원을 설명할 수 있는 필드가 있어야 한다. 이 필드는 길이 제한이 없다. 예제 코드 (Hibernate 6 + PostgreSQL 기준) package hellojpa; import jakarta.persistence.*; import java.time.LocalDateTime; @Entity public class Member { @Id private Long id; @Column(name = "name") // 필드명은 username, 컬럼명은 name private String username; private Integer age; @Enumerated(EnumType.STRING) // enum 이름을 저장 private RoleType roleType; private LocalDateTime createdDate; // @Temporal 생략 가능 private LocalDateTime lastModifiedDate; @Column(columnDefinition = "TEXT") // 길이 제한 없는 문자열 (PostgreSQL) private String description; @Transient // DB와 매핑하지 않음 private Integer temp; protected Member() {} } public enum RoleType { USER, ADMIN } 매핑 어노테이션 한눈에 보기 어노테이션 설명 @Column 컬럼 매핑 @Temporal 날짜 타입 매핑 ( java.util.Date 용, 요즘은 거의 안 씀) @Enumerated enum 타입 매핑 @Lob BLOB, CLOB 매핑 @Transient 특정 필드를 컬럼에 매핑하지 않음 (매핑 무시) @Column 속성 설명 기본값 name 매핑할 컬럼 이름 필드 이름 insertable , updatable 등록, 변경 가능 여부. false 면 INSERT/UPDATE 쿼리에서 제외 true nullable (DDL) false 면 not null 제약 조건 생성 true unique (DDL) 한 컬럼에 간단히 유니크 제약 조건 생성 false columnDefinition (DDL) 컬럼 정보를 직접 지정. 예: varchar(100) default 'EMPTY' 자바 타입 + 방언으로 결정 length (DDL) 문자 길이 제약. String 타입에만 사용 255 precision , scale (DDL) BigDecimal , BigInteger 용. 전체 자릿수, 소수점 자릿수 💡 unique = true 는 잘 안 씁니다. 제약 조건 이름이 uk_8a3... 같은 랜덤 값으로 만들어져서, 에러가 났을 때 어떤 제약 조건인지 알아보기 어렵습니다. 이름을 지정할 수 있는 @Table(uniqueConstraints = ...) 를 쓰는 것이 낫습니다. 여러 컬럼을 묶을 수도 있습니다. 💡 updatable = false 는 실무에서 유용합니다. 예를 들어 createdDate 는 한 번 저장되면 바뀌면 안 됩니다. @Column(updatable = false) 를 붙이면 실수로 값을 바꿔도 UPDATE 쿼리에 포함되지 않습니다. 💡 금액은 BigDecimal 을 쓰세요. double , float 은 소수점 계산에서 오차가 생깁니다. ( 0.1 + 0.2 = 0.30000000000000004 ) precision , scale 은 double , float 에는 적용되지 않습니다. @Enumerated 자바 enum을 매핑할 때 사용합니다. 값 저장되는 값 EnumType.ORDINAL (기본값) enum의 순서 (0, 1, 2...) EnumType.STRING enum의 이름 ("USER", "ADMIN") ⚠️ ORDINAL은 절대 쓰면 안 됩니다. 무조건 STRING을 쓰세요. 왜 ORDINAL이 위험할까? // 처음 public enum RoleType { USER, ADMIN } // USER = 0, ADMIN = 1 로 DB에 저장됨 나중에 요구사항이 바뀌어서 GUEST 를 맨 앞에 추가했다고 해 봅시다. // 변경 후 public enum RoleType { GUEST, USER, ADMIN } // GUEST = 0, USER = 1, ADMIN = 2 기존에 0 으로 저장된 회원들은 원래 USER 였는데, 이제는 GUEST 로 읽힙니다. 기존에 1 로 저장된 관리자들은 일반 USER 가 됩니다. 데이터는 그대로인데 의미가 바뀌는 끔찍한 버그 가 생기고, 에러도 나지 않아 찾기도 어렵습니다. STRING 은 공간을 조금 더 쓰지만 이런 문제가 전혀 없습니다. @Temporal → 이제는 LocalDate, LocalDateTime @Temporal 은 예전 날짜 타입( java.util.Date , java.util.Calendar )을 매핑할 때 썼습니다. 값 DB 타입 예 TemporalType.DATE date 2013-10-11 TemporalType.TIME time 11:11:11 TemporalType.TIMESTAMP timestamp 2013-10-11 11:11:11 자바 8부터는 LocalDate , LocalDateTime 을 쓰면 되고, 어노테이션도 필요 없습니다. private LocalDate testLocalDate; // → date private LocalDateTime testLocalDateTime; // → timestamp(6) 💡 Hibernate 6에서는 @Temporal 이 deprecated되었습니다. 새 코드에서는 LocalDate , LocalDateTime 만 쓰면 됩니다. @Lob DB의 BLOB, CLOB 타입과 매핑합니다. 지정할 수 있는 속성이 없습니다. 필드 타입이 문자 면 CLOB, 나머지 는 BLOB으로 매핑됩니다. CLOB: String , char[] , java.sql.Clob BLOB: byte[] , java.sql.Blob ⚠️ PostgreSQL에서는 @Lob String 을 쓰지 마세요! PostgreSQL에는 CLOB 타입이 없습니다. 그래서 Hibernate가 @Lob String 을 oid 타입 (대용량 객체를 별도 저장소에 두고 번호만 저장)으로 만듭니다. 이렇게 되면 DBeaver에서 내용 대신 숫자만 보이고, 조회 시 트랜잭션 관련 에러가 나기도 합니다. PostgreSQL에서 길이 제한 없는 문자열은 TEXT 타입 을 쓰면 됩니다. @Column(columnDefinition = "TEXT") private String description; @Transient @Transient private Integer temp; 필드를 DB에 매핑하지 않습니다. 저장도, 조회도 안 됩니다. 메모리에서만 임시로 값을 들고 있고 싶을 때 사용합니다. 예: 화면에 보여줄 계산 결과, 비밀번호 확인 입력값 등 4. 기본 키 매핑 기본 키 매핑 방법 방법 설명 직접 할당 @Id 만 사용. 개발자가 직접 setId() 자동 생성 @Id + @GeneratedValue 자동 생성 전략은 4가지입니다. 전략 설명 주로 쓰는 DB IDENTITY 기본 키 생성을 DB에 위임 MySQL, PostgreSQL, SQL Server SEQUENCE DB 시퀀스 오브젝트 사용 Oracle, PostgreSQL, H2 TABLE 키 생성용 테이블 을 만들어서 사용 모든 DB AUTO 방언에 따라 자동 선택 (기본값) @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; 💡 PostgreSQL에서 AUTO를 쓰면? Hibernate 6은 SEQUENCE 전략 을 선택하고 member_seq 시퀀스를 만듭니다. IDENTITY 전략 @Entity public class Member { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; } PostgreSQL에서는 이렇게 만들어집니다. create table member ( id bigint generated by default as identity, ... ) 특징: persist() 시점에 바로 INSERT가 나간다 영속성 컨텍스트에서 배운 내용을 떠올려 보면 이상한 점이 있습니다. 영속 상태가 되려면 1차 캐시에 @Id 값이 key로 들어가야 합니다. 그런데 IDENTITY는 DB에 INSERT를 해야 ID 값을 알 수 있습니다. 커밋 시점까지 기다리면 1차 캐시에 넣을 key가 없습니다. 그래서 IDENTITY 전략만 예외적으로 em.persist() 시점에 즉시 INSERT를 실행 하고, DB에서 생성된 ID를 받아옵니다. Member member = new Member(); member.setUsername("C"); System.out.println("==========="); em.persist(member); // 여기서 INSERT 쿼리 실행! System.out.println("member.id = " + member.getId()); // 바로 ID 확인 가능 System.out.println("==========="); tx.commit(); 💡 INSERT 후 다시 SELECT를 하는 것은 아닙니다. JDBC가 INSERT 결과로 생성된 키를 돌려주는 기능( getGeneratedKeys )을 씁니다. 단점 : 쓰기 지연이 안 되므로 INSERT를 모아서 보내는 최적화를 할 수 없습니다. 하지만 한 트랜잭션에서 INSERT를 수십 개씩 하는 경우가 아니면 성능 차이는 거의 느낄 수 없습니다. SEQUENCE 전략 시퀀스 는 유일한 값을 순서대로 만들어 주는 DB 오브젝트입니다. (Oracle, PostgreSQL, H2 등) @Entity @SequenceGenerator( name = "MEMBER_SEQ_GENERATOR", sequenceName = "MEMBER_SEQ", // 매핑할 DB 시퀀스 이름 initialValue = 1, allocationSize = 1) public class Member { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "MEMBER_SEQ_GENERATOR") private Long id; } -- PostgreSQL에서 생성되는 DDL create sequence member_seq start with 1 increment by 1; 동작 방식 em.persist(member); // 1. DB 시퀀스에서 다음 값을 가져옴: select nextval('member_seq') // 2. 그 값을 member.id에 세팅 // 3. 1차 캐시에 저장 (영속) // 4. INSERT는 커밋 시점에! → 쓰기 지연 가능 IDENTITY와 달리 ID를 먼저 받아오고 INSERT는 나중에 하므로 쓰기 지연이 가능합니다. @SequenceGenerator 속성 속성 설명 기본값 name 식별자 생성기 이름 필수 sequenceName DB에 등록된 시퀀스 이름 {엔티티명}_seq (Hibernate 6) initialValue DDL 생성 시에만 사용. 시퀀스 시작 값 1 allocationSize 시퀀스 한 번 호출에 증가하는 수 ( 성능 최적화용 ) 50 catalog , schema DB catalog, schema 이름 ⚠️ allocationSize 기본값이 50입니다! DB 시퀀스가 1씩 증가하도록 설정되어 있다면 반드시 1로 맞춰야 합니다. 안 그러면 ID가 꼬이거나 중복 에러가 날 수 있습니다. allocationSize = 50이 왜 기본값일까? (성능 최적화) 시퀀스 방식은 persist할 때마다 nextval 을 호출하느라 DB에 한 번씩 더 다녀와야 합니다. 회원 100명을 저장하면 nextval 100번 + INSERT 100번입니다. 이걸 줄이는 방법이 allocationSize 입니다. allocationSize = 50 DB 시퀀스: increment by 50 1. 첫 nextval 호출 → DB 시퀀스 1 → 51로 증가 → 애플리케이션 메모리에 "1~50번까지는 내가 써도 된다"고 확보 2. 회원 1~50번까지는 DB 호출 없이 메모리에서 ID 할당 3. 51번째 회원 저장 시 → 다시 nextval 호출 → 51~100번 확보 50번 저장할 때 시퀀스 호출은 한 번 으로 줄어듭니다. 💡 서버가 여러 대여도 괜찮습니다. 서버 A가 1 50을 가져가면 DB 시퀀스는 이미 51이 되어 있습니다. 서버 B는 51 100을 가져갑니다. 겹치지 않습니다. 대신 서버가 재시작되면 메모리에 남아 있던 번호는 버려지므로 ID에 구멍(1, 2, 3, 51, 52...)이 생깁니다. 이건 문제가 되지 않습니다. 💡 너무 크게 잡으면 구멍이 많이 생기니 50~100 정도가 적당합니다. TABLE 전략 키 생성 전용 테이블 을 만들어서 시퀀스를 흉내 내는 전략입니다. 장점 : 모든 DB에서 사용 가능 단점 : 성능 (테이블에 락을 걸고 값을 읽고 갱신해야 함) create table MY_SEQUENCES ( sequence_name varchar(255) not null, next_val bigint, primary key (sequence_name) ) @Entity @TableGenerator( name = "MEMBER_SEQ_GENERATOR", table = "MY_SEQUENCES", pkColumnValue = "MEMBER_SEQ", allocationSize = 1) public class Member { @Id @GeneratedValue(strategy = GenerationType.TABLE, generator = "MEMBER_SEQ_GENERATOR") private Long id; } 속성 설명 기본값 name 식별자 생성기 이름 필수 table 키 생성 테이블명 hibernate_sequences pkColumnName 시퀀스 컬럼명 sequence_name valueColumnName 시퀀스 값 컬럼명 next_val pkColumnValue 키로 사용할 값 이름 엔티티 이름 initialValue 초기 값. 마지막으로 생성
То, что RADAR обнаружил и классифицировал для этой возможности. Это опубликованный источником текст, а не подтверждение, что предложение ещё действует.
엔티티 매핑 - 객체와 테이블, 필드와 컬럼, 기본 키. 김영한님의 자바 ORM 표준 JPA 프로그래밍 강의를 듣고 정리한 글입니다. 실습 환경: PostgreSQL + Hibernate 6(jakarta) 0. 엔티티 매핑이란? JPA가 "이 클래스는 어떤 테이블이고, 이 필드는 어떤 컬럼이야"를 알 수 있도록 어노테이션으로 알려주는 것 입니다. 구분 어노테이션 객체와 테이블 매핑 @Entity , @Table 필드와 컬럼 매핑 @Column 기본 키 매핑 @Id 연관관계 매핑 @ManyToOne , @JoinColumn → 다음 글에서 다룸 1. 객체와 테이블 매핑 @Entity @Entity 가 붙은 클래스는 JPA가 관리하는 엔티티 가 됩니다. 테이블과 매핑할 클래스에는 반드시 붙여야 합니다.…
Открыть источник