엔티티 매핑
• 객체와 테이블 매핑: @Entity, @Table
• 필드와 컬럼 매핑: @Column
• 기본 키 매핑: @Id
• 연관관계 매핑: @ManyToOne , @JoinColumn
@Entity
@Entity가 붙은 클래스는 JPA가 관리, 엔티티라 한다.
• JPA를 사용해서 테이블과 매핑할 클래스는 @Entity 필수
주의
• 기본 생성자 필수 (파라미터가 없는 public 또는 protected 생성자)
• final 클래스, enum, interface, inner 클래스 사용X
• 저장할 필드에 final 사용 X
@Entity 속성 정리
속성: name
• JPA에서 사용할 엔티티 이름을 지정한다.
• 기본값: 클래스 이름을 그대로 사용(예: Member)
• 같은 클래스 이름이 없으면 가급적 기본값을 사용한다.
@Entity // jpa 가 관리해야하는 객체라는것을 알리는 어노테이션
@Getter
@Setter
@ToString
@AllArgsConstructor
// jpa 는 내부에서 리플렉션 같은것을 사용하기위해서는 기본 생성자가 필수로 필요하다.
// 자바에서는 따로설정해둔 생성자가 없으면 기본생성자를 만들어주지만 생성자를 만들었다면 기본 생성자를 추가해준다.
@NoArgsConstructor
public class Member {
@Id // pk가 어떤 컬럼인지 알려줘야한다. == 데이터베이스 PK와 매핑해야줘야한다.
private Long id;
private String name;
}
@Entity (name = "USER")
jpa가 관리할때 사용할 이름을 지정한다. 일반적으로 기본값으로 냅둔다.
기본값은 클래스명으로 설정된다.
@Table
@Table은 엔티티와 매핑할 테이블 지정
기본으로는 해당 엔티티의 이름을 가진 테이블과 매핑 시켜준다.
@Entity
@Table(name = "MBR")
public class Member {
이런식으로 하게되면 Member라는 엔티티는
MBR이라는 테이블과 매핑된다. == 이 엔티티와 관련된 sql은 MBR이란 테이블과 진행되는것.
일반적으로는 @Table 어노테이션을 사용하지않아서 기본값인 엔티티 이름을 이용하여 테이블 매핑을 진행한다.
데이터베이스 스키마 자동 생성
@Table을 통해 어떤 테이블을 가지고있고,
@Entity가 붙어있는 객체를 살펴보면서 필드를 통해서 어떤 쿼리를 만들어야하는지 이미 jpa는 알고있기때문에
로딩 시점에 db 테이블을 생성하는 기능도 지원해준다.
운영단계에서는 사용하면 안되고 개발단계나 로컬pc에서 개발할때 도움이 된다.
• DDL을 애플리케이션 실행 시점에 자동 생성
• 테이블 중심 -> 객체 중심
[ 일반적으로 개발할때 테이블을 다 만들어놓고 객체로 돌아가서 개발하지만 jpa는 객체를 만들고 매핑해놓으면 jpa가 ㄷ알아서 어플리케이션이 실행될때 테이블을 다 만든다.]
• 데이터베이스 방언을 활용해서 데이터베이스에 맞는 적절한 DDL 생성
• 이렇게 생성된 DDL은 개발 장비에서만 사용
• 생성된 DDL은 운영서버에서는 사용하지 않거나, 적절히 다듬은 후 사용
데이터베이스 스키마 자동 생성 - 속성
persistence.xml에
<property name="hibernate.hbm2ddl.auto" value="create" />
다음 속성을 추가하면 기존테이블 삭제 후 다시 생성 (DROP + CREATE)을 해준다.
hibernate.hbm2ddl.auto라는 속성에 value 값을 넣어서 데이터베이스 스키마 자동 생성 속성을 설정할 수 있다.
데이터베이스 스키마 자동생성을 사용하게되면
다음과 같이 해당 테이블을 삭제하고 다시 생성한다. 그리고 난 후 로직이 동작한다.
테이블 생성은 매핑된 엔티티의 정보(필드)를 보고 생성해준다.
[ @id를 통해 어떤필드가 기본키인지 확인하고 기본키설정까지 넣어준다.]
create-drop인경우
<property name="hibernate.hbm2ddl.auto" value="create-drop" />
update인 경우
<property name="hibernate.hbm2ddl.auto" value="update" />
엔티티의 필드의 변경이 있다면?
age라는 필드를 추가하고 재실행
주의 : 지우는것은 동작하지않는다. 추가만 동작한다.
[ 실수로 한 컬럼의 데이터를 모두 날리지않게끔]
validate 인 경우
<property name="hibernate.hbm2ddl.auto" value="validate" />
현재 테이블 컬럼 상태
엔티티의 필드를 매핑한 테이블의 컬럼과 다르게 구성한다면?
@Id // pk가 어떤 컬럼인지 알려줘야한다. == 데이터베이스 PK와 매핑해야줘야한다.
private Long id;
private String name;
private int age22;
validate는 엔티티와 테이블이 정상 매핑되었는지 확인할때 사용한다.
데이터베이스 스키마 자동 생성 - 실습
데이터베이스 방언 별로 달라지는 것 확인
<property name="hibernate.hbm2ddl.auto" value="create" />
create 로 설정
<property name="hibernate.dialect" value="org.hibernate.dialect.OracleDialect"/>
dialect(방언)은 오라클로 설정
데이터베이스 스키마 자동생성 또한 설정한 방언에 따라 sql문도 잘 수정되어있다.
데이터베이스 스키마 자동 생성 - 주의
운영 장비에는 절대 create, create-drop, update 사용하면 안된다.
• 개발 초기 단계는 create 또는 update
• 테스트 서버는 update 또는 validate
• 스테이징과 운영 서버는 validate 또는 none
[ 개발이든 테스트이든 가급적 안쓰는게 좋다. 운영은 절대금지]
db의 application 계정에는 alter과 drop의 권한을 없애는게 좋다.
DDL 생성 기능
@Column(unique = true,length = 10)
private String name;
@Column의 속성으로 제약조건을 추가하게되면
@Column(nullable = false, length = 10)
제약조건 추가: 회원 이름은 필수, 10자 초과X
@Table(uniqueConstraints = {@UniqueConstraint( name = "NAME_AGE_UNIQUE",columnNames = {"NAME", "AGE"} )})
유니크 제약조건 추가
DDL 생성 기능은 DDL을 자동 생성할 때만 사용되고 JPA의 실행 로직에는 영향을 주지 않는다
필드와 컬럼 매핑
요구사항 추가
1. 회원은 일반 회원과 관리자로 구분해야 한다.
2. 회원 가입일과 수정일이 있어야 한다.
3. 회원을 설명할 수 있는 필드가 있어야 한다. 이 필드는 길이 제한이 없다.
수정된 엔티티
@Entity // jpa 가 관리해야하는 객체라는것을 알리는 어노테이션
@Getter
@Setter
@ToString
@AllArgsConstructor
// jpa 는 내부에서 리플렉션 같은것을 사용하기위해서는 기본 생성자가 필수로 필요하다.
// 자바에서는 따로설정해둔 생성자가 없으면 기본생성자를 만들어주지만 생성자를 만들었다면 기본 생성자를 추가해준다.
@NoArgsConstructor
public class Member {
@Id // pk가 어떤 컬럼인지 알려줘야한다. == 데이터베이스 PK와 매핑해야줘야한다.
private Long id;
@Column(name = "name") // 테이블의 컬럼과 해당 필드 매핑
private String username;
private Integer age;
@Enumerated(EnumType.STRING) // enum 타입을 사용하기 위해 사용 ==> enum 타입 매핑
private RoleType roleType;
@Temporal(TemporalType.TIMESTAMP) // 날짜 타입 매핑 , temporal == 시간의~
private Date createdDate;
@Temporal(TemporalType.TIMESTAMP)
private Date lastModifiedDate;
@Lob // BLOB, CLOB 매핑 => 오라클에서 BLOB, CLOB 는 대용량 데이터를 저장하기 위한 타입 String 이면 CLOB 으로 매핑된다.
private String description;
}
<property name="hibernate.hbm2ddl.auto" value="create" />
데이터베이스 스키마 자동생성 속성을 추가하고, 실행해보면
설정한 대로 create ddl이 생성된것을 확인가능.
데이터베이스 스키마 자동생성을 사용할때나 사용하면 되는 어노테이션들.
@Column
@column 의 unique 속성을 사용하면 제약조건의 이름이 랜덤이라 예외가 발생했을때 어떤 제약조건인지 알아보기 힘들다. @Table을 사용하면 제약조건 이름까지 정할수있어서 이걸 많이 사용하는편
@Enumerated
자바 enum 타입을 매핑할 때 사용
주의! ORDINAL 사용X
@Temporal
@Temporal(TemporalType.TIMESTAMP) // 날짜 타입 매핑 , temporal == 시간의~
private Date createdDate;
private LocalDate createDate2;
private LocalDateTime createDate3;
LocalDate, LocalDateTime은 따로 @Temporal 어노테이션을 붙이지않아도
@Lob
데이터베이스 BLOB, CLOB 타입과 매핑
• @Lob에는 지정할 수 있는 속성이 없다.
• 매핑하는 필드 타입이 문자면 CLOB 매핑, 나머지는 BLOB 매핑
• CLOB: String, char[], java.sql.CLOB
• BLOB: byte[], java.sql. BLOB
@Transient
• 필드 매핑X
• 데이터베이스에 저장X, 조회X
• 주로 메모리상에서만 임시로 어떤 값을 보관하고 싶을 때 사용
ex)
@Transient
private Integer temp;
기본 키 매핑
기본 키 매핑 어노테이션
@Id
@GeneratedValue
try {
Member member = new Member(200L, "A");
em.persist(member);
tx.commit(); // 커밋하는 시점에서 영속되어있는것에 관한 쿼리가 날아간다.
}catch (Exception e) {
tx.rollback();
}finally {
em.close();
}
emf.close();
기본 키 매핑 방법
1. 직접 할당
@Id만 사용 -> 아이디 직접 세팅
2. 자동 생성(@GeneratedValue)
@Id // pk가 어떤 컬럼인지 알려줘야한다. == 데이터베이스 PK와 매핑해야줘야한다.
@GeneratedValue(strategy = GenerationType.IDENTITY)
• IDENTITY: 데이터베이스에 위임, MYSQL
@Id // pk가 어떤 컬럼인지 알려줘야한다. == 데이터베이스 PK와 매핑해야줘야한다.
@GeneratedValue(strategy = GenerationType.SEQUENCE)
• SEQUENCE: 데이터베이스 시퀀스 오브젝트 사용, ORACLE @SequenceGenerator 필요
@Id // pk가 어떤 컬럼인지 알려줘야한다. == 데이터베이스 PK와 매핑해야줘야한다.
@GeneratedValue(strategy = GenerationType.TABLE)
• TABLE: 키 생성용 테이블 사용, 모든 DB에서 사용, @TableGenerator 필요
@Id // pk가 어떤 컬럼인지 알려줘야한다. == 데이터베이스 PK와 매핑해야줘야한다.
@GeneratedValue(strategy = GenerationType.AUTO)
• AUTO: 방언에 따라 자동 지정, 기본값
2.1 IDENTITY 전략 - 특징
@GeneratedValue(strategy = GenerationType.IDENTITY)
기본 키 생성을 데이터베이스에 위임
• 주로 MySQL, PostgreSQL, SQL Server, DB2에서 사용
(예: MySQL의 AUTO_ INCREMENT)
• JPA는 보통 트랜잭션 커밋 시점에 INSERT SQL 실행
• AUTO_ INCREMENT는 데이터베이스에 INSERT SQL을 실행한 이후에 ID 값을 알 수 있음
• IDENTITY 전략은 em.persist() 시점에 즉시 INSERT SQL 실행하고 DB에서 식별자를 조회
-> 영속성컨테이너에서 관리하려면 pk를 알고있어야하는데 identity전략은 db에 쿼리가 가고 난후에 pk가 설정되기때문에 persist 명령문이 실행되는 즉시 insert sql를 실행한 후 db에서 pk를 조회한다. [ 조회는 내부적으로 실행 + INSERT가 완료된 후, JPA는 데이터베이스로부터 생성된 기본 키 값을 조회합니다. 이 기본 키 값은 엔티티의 @Id 필드에 설정됩니다.]
SEQUENCE 전략일때
em.persist() 호출 시, INSERT SQL이 실행되지 않고 엔티티가 영속성 컨텍스트에 추가됩니다.
기본 키 값을 얻기 위해 JPA는 시퀀스 값을 먼저 조회합니다 (SELECT nextval('hibernate_sequence')).
조회된 시퀀스 값이 엔티티의 기본 키로 설정됩니다. [조회된 시퀀스 값이 엔티티의 기본 키 필드(@Id가 적용된 필드)에 설정]
트랜잭션이 커밋될 때 INSERT SQL이 실행됩니다.
TABLE 전략
em.persist() 시점에 INSERT SQL이 실행되지 않습니다.
키 생성 테이블에서 기본 키 값을 미리 조회하여 엔티티에 설정합니다.
트랜잭션 커밋 시점에 INSERT SQL이 실행됩니다
[id값은 설정하지않고 persist해주면된다. db에서 알아서 채우기때문]
try {
Member member = new Member();
member.setUsername("AAA");
em.persist(member);
tx.commit(); // 커밋하는 시점에서 영속되어있는것에 관한 쿼리가 날아간다.
}catch (Exception e) {
tx.rollback();
}finally {
em.close();
}
emf.close();
다음과 같이 id를 설정하지않고 persist를 해주면
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>
MySqlDialect로 수정후 실행해보면
2.2 SEQUENCE 전략 - 특징
데이터베이스 시퀀스는 유일한 값을 순서대로 생성하는 특별한 데이터베이스 오브젝트 (예: 오라클 시퀀스)
오라클, PostgreSQL, DB2, H2 데이터베이스에서 사용
@Entity // jpa 가 관리해야하는 객체라는것을 알리는 어노테이션
@Getter
@Setter
@ToString
@AllArgsConstructor
// jpa 는 내부에서 리플렉션 같은것을 사용하기위해서는 기본 생성자가 필수로 필요하다.
// 자바에서는 따로설정해둔 생성자가 없으면 기본생성자를 만들어주지만 생성자를 만들었다면 기본 생성자를 추가해준다.
@NoArgsConstructor
@SequenceGenerator(
name = "MEMBER_SEQ_GENERATOR",
sequenceName = "MEMBER_SEQ", //매핑할 데이터베이스 시퀀스 이름
initialValue = 1, allocationSize = 1)
public class Member {
@Id // pk가 어떤 컬럼인지 알려줘야한다. == 데이터베이스 PK와 매핑해야줘야한다.
@GeneratedValue(strategy = GenerationType.SEQUENCE,
generator = "MEMBER_SEQ_GENERATOR")
private Long id;
@Column(name = "name" ,unique = true) // 테이블의 컬럼과 해당 필드 매핑
private String username;
}
@SequenceGenerator(
name = "MEMBER_SEQ_GENERATOR",
sequenceName = "MEMBER_SEQ", //매핑할 데이터베이스 시퀀스 이름
initialValue = 1, allocationSize = 1)
public class Member {
@Id // pk가 어떤 컬럼인지 알려줘야한다. == 데이터베이스 PK와 매핑해야줘야한다.
@GeneratedValue(strategy = GenerationType.SEQUENCE,
generator = "MEMBER_SEQ_GENERATOR")
private Long id;
<property name="hibernate.hbm2ddl.auto" value="create" />
create 상태에서 실행한 결과 로그
<property name="hibernate.hbm2ddl.auto" value="none" />
테이블이 삭제되고 생성되지않게 none으로 설정하고
새로운 멤버 추가
TABLE 전략
키 생성 전용 테이블을 하나 만들어서 데이터베이스 시퀀스를 흉내내는 전략
• 장점: 모든 데이터베이스에 적용 가능
• 단점: 성능
@Entity // jpa 가 관리해야하는 객체라는것을 알리는 어노테이션
@Getter
@Setter
@ToString
@AllArgsConstructor
// jpa 는 내부에서 리플렉션 같은것을 사용하기위해서는 기본 생성자가 필수로 필요하다.
// 자바에서는 따로설정해둔 생성자가 없으면 기본생성자를 만들어주지만 생성자를 만들었다면 기본 생성자를 추가해준다.
@NoArgsConstructor
@TableGenerator(
name = "MEMBER_SEQ_GENERATOR",
table = "MY_SEQUENCES",
pkColumnValue = "MEMBER_SEQ", allocationSize = 1)
public class Member {
@Id // pk가 어떤 컬럼인지 알려줘야한다. == 데이터베이스 PK와 매핑해야줘야한다.
@GeneratedValue(strategy = GenerationType.TABLE,
generator = "MEMBER_SEQ_GENERATOR")
private Long id;
@Column(name = "name" ,unique = true) // 테이블의 컬럼과 해당 필드 매핑
private String username;
}
@TableGenerator(
name = "MEMBER_SEQ_GENERATOR",
table = "MY_SEQUENCES",
pkColumnValue = "MEMBER_SEQ", allocationSize = 1)
@GeneratedValue(strategy = GenerationType.TABLE,
generator = "MEMBER_SEQ_GENERATOR")
관례상 데이터베이스마다 쓰는방법이 있기에 그걸 쓰는게좋다. ( table 전략은 잘 사용하지않는다. )
권장하는 식별자 전략
기본 키 제약 조건: null 아님, 유일, 변하면 안된다.
미래까지 이 조건을 만족하는 자연키는 찾기 어렵다. 대리키(대체키)를 사용하자.
예를 들어 주민등록번호도 기본 키로 적절하기 않다.
권장: Long형 + 대체키 + 키 생성전략 사용
ex)
@Id // pk가 어떤 컬럼인지 알려줘야한다. == 데이터베이스 PK와 매핑해야줘야한다.
@GeneratedValue(strategy = GenerationType.SEQUENCE,
generator = "MEMBER_SEQ_GENERATOR")
private Long id;
allocationSize로 성능개선
allocationSize를 1로 해두면
insert할때
시퀀스에서 값을 받아오고 이런 쿼리가 매번 실행되어야해서 성능문제가 있을수있다.
만약 allocationSize를 기본값으로 냅둔다면
미리 DB에 50개만큼 생성해두고,메모리에서 그냥 그 범위만큼 사용하기만 하면 된다.
(즉 미리 50개의 자리를 생성해두었고, 그 범위를 알고있으니까 메모리에서는 따로 시퀀스를 불러올 필요없이 그 id에 맞춰 insert를 진행해주면 된다. )
( 1~50만큼 미리 DB에 데이터를 생성해주고, 메모리상에서는 pk값이 1~50 넣을수있다는것을 아니까 시퀀스 호출 없이 insert만 진행해주면 된다. DB에 먼저 올려놓고 메모리에서 그 갯수만큼 쓰는 방식)
sequence 방식
package com.mall.api.domain;
import jakarta.persistence.*;
import lombok.*;
import java.time.LocalDate;
@Entity
@Table(name = "tbl_todo")
@Getter
@ToString
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Builder
@SequenceGenerator(
name = "TODO_SEQ_GENERATOR",
sequenceName = "MEMBER_SEQ",
initialValue = 1,
allocationSize = 1
)
public class Todo {
@Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "TODO_SEQ_GENERATOR")
private Long tno;
private String title;
private String writer;
private boolean complete;
private LocalDate dueDate;
}
allocationSize를 1로 해놓고
@SpringBootTest
@Transactional
//@Slf4j
class TodoRepositoryTest {
@Autowired
private TodoRepository todoRepository;
@Test
@Rollback(false)
public void testInsert() {
for (int i = 0; i <= 100; i++) {
Todo todo = Todo.builder()
.title("Title .." + i)
.dueDate(LocalDate.of(2023, 12, 31))
.writer("user00")
.build();
todoRepository.save(todo);
}
}
}
다음과같이 100번의 insert 쿼리를 발생하는 테스트를 진행해보면
select
member_seq.nextval
from
dual
이렇게 시퀀스값을 가져오는 쿼리를 총 100번 하고 난뒤에
insert
into
tbl_todo
(complete, due_date, title, writer, tno)
values
(?, ?, ?, ?, ?)
그리고 나서야 insert쿼리가 100번 나간다.
즉 한번에 시퀀스값을 다 가져오고 난뒤에야 insert쿼리가 발생한다.
만약 allocationSize를 기본값으로 둔다면
select
member_seq.nextval
from
dual
해당 쿼리가 2번밖에안나간다.
2번만에 총 100개의 시퀀스값을 준비해둘수있으므로
allocationSize를 10으로 해두면
10번의 시퀀스 조회쿼리가 나간뒤 insert 쿼리가 발생한다.
'인프런 > 자바 ORM 표준 JPA 프로그래밍 - 기본편' 카테고리의 다른 글
7) 고급 매핑 [ 상속관계 매핑 -조인,단일,각각] , MappedSuperclass (0) | 2024.05.29 |
---|---|
6) 다양한 연관관계 매핑 (0) | 2024.05.28 |
5)실전 예제, 단방향 연관관계 매핑, 양방향 연관관계,연관관계의 주인 (0) | 2024.05.22 |
3) jpa 구조, 영속성 컨텍스트,영속,준영속,쓰기지연 (0) | 2024.05.20 |
2) 프로젝트설정,jpa 기초 (0) | 2024.05.20 |
댓글