|
| 1 | +# 엔티티 매핑 |
| 2 | +- 객체와 테이블 매핑 @Entity, @Table |
| 3 | +- 기본키 매핑 @Id |
| 4 | +- 필드와 컬럼 매핑 @Column |
| 5 | +- 연관관계 매핑 @ManyToOne, @JoinColumn |
| 6 | + |
| 7 | +매핑정보는 xml 이나 어노테이션 중에 선택해서 기술하면 되는데 책에서는 어노테이션만 사용. |
| 8 | +어노테이션이 더 쉽고 직관적이다. |
| 9 | + |
| 10 | +## @Entity |
| 11 | +@Entity 가 붙은 클래스는 JPA 가 관리 |
| 12 | +### 속성 |
| 13 | +- name : 기본값은 클래스 이름. 다른 패키지에 동일한 이름의 엔티티가 있으면 충돌하지 않게 해아함 |
| 14 | +### 주의사항 |
| 15 | +- 기본 생성자는 필수다 (public, protected) |
| 16 | +- final 클래스, enum, interface, inner 클래스에는 사용할 수 없다 |
| 17 | +- 저장할 필드에 final 을 사용하면 안된다 |
| 18 | + |
| 19 | +## @Table |
| 20 | +- 엔티티와 매핑할 테이블을 지정 |
| 21 | +- 생략하면 매핑한 엔티티 이름을 테이블 이름으로 지정 |
| 22 | +### 속성 |
| 23 | +- name : 테이블 이름. 기본값은 엔티티 이름 |
| 24 | +- catalog : catalog 매핑 |
| 25 | +- schema : schema 매핑 |
| 26 | +- uniqueConstraints : ddl 을 만들 때만 사용 |
| 27 | + |
| 28 | +## 다양한 매핑 사용 |
| 29 | +```java |
| 30 | +@Entity |
| 31 | +@Table(name="MEMBER") |
| 32 | +public class Member { |
| 33 | + |
| 34 | + @Id |
| 35 | + @Column(name = "ID") |
| 36 | + private String id; |
| 37 | + |
| 38 | + @Column(name = "NAME") |
| 39 | + private String username; |
| 40 | + |
| 41 | + private Integer age; |
| 42 | + |
| 43 | + @Enumerated(EnumType.STRING) // enum 을 사용하려면 @Enumerated 매핑 |
| 44 | + private RoleType roleType; |
| 45 | + |
| 46 | + @Temporal(TemporalType.TIMESTAMP) // 날짜 타입은 @Temporal 을 사용해서 매핑 |
| 47 | + private Date createdDate; |
| 48 | + |
| 49 | + @Temporal(TemporalType.TIMESTAMP) |
| 50 | + private Date lastModifiedDate; |
| 51 | + |
| 52 | + @Lob |
| 53 | + private String description; // @Lob 을 사용하면 CLOB, BLOB 타입 매핑 |
| 54 | + ... |
| 55 | +} |
| 56 | +``` |
| 57 | +## 데이터베이스 스키마 자동 생성 |
| 58 | +### feature/h2 |
| 59 | +```log |
| 60 | +Hibernate: |
| 61 | + drop table MEMBER if exists |
| 62 | +Hibernate: |
| 63 | + create table MEMBER ( |
| 64 | + ID varchar(255) not null, |
| 65 | + age integer, |
| 66 | + createdDate timestamp, |
| 67 | + description clob, |
| 68 | + lastModifiedDate timestamp, |
| 69 | + roleType varchar(255), |
| 70 | + NAME varchar(10) not null, |
| 71 | + primary key (ID) |
| 72 | + ) |
| 73 | +``` |
| 74 | + |
| 75 | +### feature/mysql |
| 76 | +```log |
| 77 | +Hibernate: |
| 78 | + drop table if exists MEMBER |
| 79 | +Hibernate: |
| 80 | + create table MEMBER ( |
| 81 | + ID varchar(255) not null, |
| 82 | + age integer, |
| 83 | + createdDate datetime, # datetime |
| 84 | + description longtext, # longtext |
| 85 | + lastModifiedDate datetime, |
| 86 | + roleType varchar(255), |
| 87 | + NAME varchar(10) not null, |
| 88 | + primary key (ID) |
| 89 | + ) |
| 90 | +``` |
| 91 | +### hibernate.hbm2ddl.auto |
| 92 | +- create |
| 93 | +- create-drop |
| 94 | +- update |
| 95 | +- validate |
| 96 | +- none: *none 은 유효하지 않은 옵션 값이다. |
| 97 | + |
| 98 | +### 이름 매핑 전략 |
| 99 | +ImproveNamingStrategy: 테이블 명이나 컬럼 명이 생략되면 자바의 카멜 표기법을 테이블의 언더스코어 표기법으로 매핑 |
| 100 | + |
| 101 | +https://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.html#naming |
| 102 | + |
| 103 | +## DDL 생성 기능 |
| 104 | +### @Column |
| 105 | +- nullable: 자동 생성되는 ddl 에 notnull 제약조건을 추가할 수 있음 |
| 106 | +- length: 문자의 크기 지정할 수 있음 |
| 107 | +### @Table |
| 108 | +- uniqueConstaint: 유니크 제약조건 추가 |
| 109 | + |
| 110 | +## 기본 키 매핑 |
| 111 | +### 직접 할당 |
| 112 | +- @Id 어노테이션 사용해서 기본키를 애플리케이션에서 할당 |
| 113 | +### 자동 생성 |
| 114 | +- IDENTITY: 기본 키 생성을 데이터베이스에 위임 |
| 115 | +- SEQUENCE: 데이터베이스 시퀀스를 사용해서 기본 키를 할당 |
| 116 | +- TABLE: 키 생성 테이블을 사용 |
| 117 | + |
| 118 | +SEQUENCE 나 IDENTITY 전략은 사용하는 데이터베이스에 의존함 |
| 119 | +- oracle 은 시퀀스를 제공하지만 mysql 은 제공하지 않음 |
| 120 | +- mysql 은 AUTO_INCREMENT 기능 제공 |
| 121 | + |
| 122 | +### hibernate.id.new_generator_mappings |
| 123 | +책에는 기본값 false, 현재는 true |
| 124 | +https://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.html#configurations-mapping |
| 125 | + |
| 126 | +하이버네이트 5부터 기본값이 true 임 |
| 127 | +``` |
| 128 | +By the way, starting with Hibernate 5 (which is in WildFly 10.0.0.Final), hibernate.id.new_generator_mappings defaults to true. I like the idea of being able to specify default persistence unit properties to be used, although I think that is a separate concern than the one raised by your post. Currently, you have to update all of your applications persistence.xml. |
| 129 | +``` |
| 130 | +https://developer.jboss.org/thread/267613 |
| 131 | + |
| 132 | +### 기본키 직접 할당 전략 |
| 133 | +em.persist() 로 엔티티를 저장하기 전에 애플리케이션에서 기본 키를 직접 할당하는 방식 |
| 134 | +- 자바 기본형 (Primitive) |
| 135 | +- 자바 wrapper 형 |
| 136 | +- String |
| 137 | +- java.util.Date |
| 138 | +- java.sql.Date (https://docs.oracle.com/javase/7/docs/api/java/sql/Date.html) |
| 139 | +- java.math.BigDecimal |
| 140 | +- java.math.BigInteger |
| 141 | +- java.util.UUID |
| 142 | + An interesting feature introduced in Hibernate 5 is the UUIDGenerator. To use this, all we need to do is declare an id of type UUID with @GeneratedValue annotation: |
| 143 | + ``` |
| 144 | + @Entity |
| 145 | + public class Course { |
| 146 | +
|
| 147 | + @Id |
| 148 | + @GeneratedValue |
| 149 | + private UUID courseId; |
| 150 | +
|
| 151 | + // ... |
| 152 | + } |
| 153 | + ``` |
| 154 | + https://www.baeldung.com/hibernate-identifiers |
| 155 | +
|
| 156 | +### IDENTITY 전략 |
| 157 | +- 기본키 생성을 데이터베이스에 위임. |
| 158 | +- MySQL, PostgreSQL, SQL Server, DB2 에서 주로 사용 |
| 159 | +
|
| 160 | +#### mysql auto_increment |
| 161 | +```sql |
| 162 | +CREATE TABEL BOARD ( |
| 163 | + ID INT NOT NULL AUTO_INCREMENT PRIMARY KEY, |
| 164 | + DATA VARCHAR(255) |
| 165 | +); |
| 166 | +INSERT INTO BOARD(DATE) VALUES('A'); |
| 167 | +INSERT INTO BOARD(DATE) VALUES('B'); |
| 168 | +``` |
| 169 | +데이터베이스에 값을 저장할 때 ID 컬럼을 비워두면 데이터베이스가 순서대로 값을 채워준다 |
| 170 | + |
| 171 | +#### 최적화 |
| 172 | +Statement.getGeneratedKeys() 를 사용하면 데이터를 저장하면서 동시에 생성된 기본 키 값도 얻어올 수 있다. |
| 173 | +하이버네이트는 이 메소드를 사용해서 데이터베이스와 한 번만 통신한다 |
| 174 | + |
| 175 | +#### 쓰기 지연 |
| 176 | +IDENTITY 식별자 생성 전략은 엔티티를 데이터베이스에 저장해야 식별자를 구할 수 있으므로 em.persist() 를 호출하는 즉시 INSERT SQL 이 데이터베이스에 저장된다. 따라서 IDENTITY 전략에서는 트랜잭션을 지원하는 쓰기 지연이 동작하지 않는다. |
| 177 | + |
| 178 | +### SEQUENCE 전략 |
| 179 | +데이터베이스 시퀀스는 유일한 값을 순서대로 생성하는 특별한 데이터베이스 오브젝트다. |
| 180 | + |
| 181 | +이 전략은 시퀀스를 지원하는 오라클, PostgreSQL, DB2, H2 데이터베이스에서 사용할 수 있다. |
| 182 | + |
| 183 | + |
| 184 | +코드는 IDENTITY 전략과 같지만 내부 동작 방식은 다르다. |
| 185 | + |
| 186 | +SEQUENCE 전략은 em.persist() 를 호출할 때 먼저 데이터베이스 시퀀스를 사용해서 식별자를 조회한다. |
| 187 | +그리고 조회한 식별자를 엔티티에 할당한 후에 엔티티를 영속성 컨텍스트에 저장한다. |
| 188 | +이후 트랜잭션을 커밋해서 플러시가 일어나면 엔티티를 데이터베이스에 저장한다. |
| 189 | +반대로 이전에 설명했던 IDENTITY 전략은 먼저 엔티티를 데이터베이스에 저장한 후에 식별자를 조회해서 엔티티의 식별자에 할당한다. |
| 190 | + |
| 191 | +#### @SequenceGenerator |
| 192 | +- name |
| 193 | +- sequenceName |
| 194 | +- initialValue |
| 195 | +- allocationSize |
| 196 | + - SequenceGenerator.allocationSize 의 기본값이 50인것에 주의. |
| 197 | + - 데이터베이스 시퀀스 값이 하나씩 증가하도록 설정되어 있으면 이 값을 반드시 1로 설정해야 한다. |
| 198 | +- catalog, schema |
| 199 | + |
| 200 | + |
| 201 | +#### mysql 에서 sequence 사용 |
| 202 | +```log |
| 203 | +Hibernate: |
| 204 | + drop table if exists MEMBER |
| 205 | +Hibernate: |
| 206 | + drop table if exists hibernate_sequence |
| 207 | +Hibernate: |
| 208 | + create table MEMBER ( |
| 209 | + ID bigint not null, |
| 210 | + age integer, |
| 211 | + createdDate datetime, |
| 212 | + description longtext, |
| 213 | + lastModifiedDate datetime, |
| 214 | + roleType varchar(255), |
| 215 | + NAME varchar(10) not null, |
| 216 | + primary key (ID) |
| 217 | + ) |
| 218 | +Hibernate: |
| 219 | + alter table MEMBER |
| 220 | + add constraint NAME_AGE_UNIQUE unique (NAME, age) |
| 221 | +Hibernate: |
| 222 | + create table hibernate_sequence ( |
| 223 | + next_val bigint |
| 224 | + ) |
| 225 | +Hibernate: |
| 226 | + insert into hibernate_sequence values ( 1 ) |
| 227 | +... |
| 228 | +
|
| 229 | +Hibernate: |
| 230 | + select |
| 231 | + next_val as id_val |
| 232 | + from |
| 233 | + hibernate_sequence for update |
| 234 | + |
| 235 | +Hibernate: |
| 236 | + update |
| 237 | + hibernate_sequence |
| 238 | + set |
| 239 | + next_val= ? |
| 240 | + where |
| 241 | + next_val=? |
| 242 | +
|
| 243 | +Hibernate: |
| 244 | + /* insert jpabook.start.Member |
| 245 | + */ insert |
| 246 | + into |
| 247 | + MEMBER |
| 248 | + (age, createdDate, description, lastModifiedDate, roleType, NAME, ID) |
| 249 | + values |
| 250 | + (?, ?, ?, ?, ?, ?, ?) |
| 251 | +... |
| 252 | +``` |
| 253 | + |
| 254 | +### TABLE 전략 |
| 255 | +키 생성 전용 테이블을 하나 만들고 여기에 이름과 값으로 사용할 컬럼을 만들어 데이터베이스 시퀀스를 흉내내는 전략이다. |
| 256 | +시퀀스 대신에 테이블을 사용한다는 것만 제외하면 SEQUENCE 전략과 내부 동작방식이 같다. |
| 257 | + |
| 258 | +```sql |
| 259 | +create table MY_SEQUENCES ( |
| 260 | + sequence_name varchar(255) not null, |
| 261 | + next_val bigint, |
| 262 | + primary key ( sequence_name ) |
| 263 | +) |
| 264 | +``` |
| 265 | + |
| 266 | +#### @TableGenerator |
| 267 | +- name |
| 268 | +- table |
| 269 | +- pkColumnName |
| 270 | +- valueColumnName |
| 271 | +- initialValue |
| 272 | +- allocationSize |
| 273 | +- catalog, schema |
| 274 | +- uniqueConstraints(DDL) |
| 275 | + |
| 276 | + |
| 277 | +### AUTO |
| 278 | +데이터베이스 방언에 따라 전략 중 하나를 자동으로 선택한다. |
| 279 | +예를 들어 오라클은 SEQUENCE, MySQL 은 IDENTITY 를 사용한다. |
| 280 | + |
| 281 | + |
| 282 | +## 필드와 컬럼 매핑: 레퍼런스 |
| 283 | +필요할 때 코드, 주석, 문서를 보자. |
| 284 | + |
| 285 | +## 정리 |
| 286 | +생략 |
| 287 | + |
| 288 | +## 실전 예제 1. 요구사항 분석과 기본 매핑 |
| 289 | + |
| 290 | + |
| 291 | +## 질문 |
| 292 | +- 왜 기본생성자는 필수이고, public, protected 만 가능한지 |
| 293 | + - 상속을 쓰나? |
| 294 | +- 저장할 필드에 왜 final 을 사용하면 안되는지 |
| 295 | + - kotlin 은 val 써도되던데.. |
| 296 | +- mysql auto_increment 어떻게 동작하는지 |
| 297 | +- kotlin `@Id @GeneratedValue val id: Long = 0L` 으로 지정하면 query 어떻게 만들어지는지 |
0 commit comments