JPA가 동적으로 무언가를 하거나, 리플랙션과 같이 객체를 프록시 하기 위해서는 기본 생성자가 필요하기 때문
final 클래스, enum, interface, inner 클래스에는 사용 불가
저장할 필드에 final 사용이 불가
데이터베이스 스키마 자동 생성
hibernate.hbm2ddl.auto
create : 기존 테이블 삭제 후 다시 생성(운영DB에는 사용하면 안됨)
create-drop : create와 같으나 종료시점에 테이블 DROP(운영DB에는 사용하면 안됨)
update : 변경된 부분만 반영(운영DB에는 사용하면 안됨)
validate : 엔티티와 테이블이 정상 매핑되었는지만 확인
none : 사용하지 않음
주의
테스트 서버는 update or validate
스테이징, 운영 서버는 validate or none
🚨 경고
ALTER을 잘못 적용할 경우 대장애가 발생할 가능성이 있다. (ex. 데이터베이스 Lock)
가급적 개발자가 직접 스크립트를 테스트 서버에서 적용해보고 확인 후, 운영 서버에 직접 적용하는 방법이 좋다.
또한, 이런 사고를 방지하기 위해 DB 계정별로 권한을 두어 사전에 방지하는 것이 좋다.
DDL 생성 기능은 DDL을 자동 생성할 때만 사용되고 JPA의 실행 로직에는 영향을 주지 않는다.
필드와 컬럼 매핑
매핑 어노테이션 정리
@Column : 컬럼 매핑
name : 필드와 매핑할 테이블의 컬럼 이름
insertable, updatable : 컬럼의 등록, 변경 가능 여부
nullable : null 값의 허용 여부 설정
unique : 한 컬럼에 간단히 유니크 제약조건을 걸 때 사용 (하지만 잘 사용하지 않음)
unique로 제약 조건을 걸면 add constraint UK_ektea~~~~~~~~ unique (name) 과 같이 랜덤 값으로 로그가 나오게 되어 운영상에서 알아보기 힘듬
복합키 설정이 불가능
@Table의 uniqueConstraints 속성을 이용해서 제약조건을 걸면 이름까지 정상으로 나오기 때문에 해당 속성을 사용하는 것을 추천
@Enumerated : enum 타입 매핑
Enumerated.STRING을 사용해서 enum 타입을 매핑
기본값인 ORDINAL을 사용하게 되면 enum 타입을 정의한 순으로 DB에 순서대로 0, 1, 2, 3 저장이 되게 됨
만약 enum 값이 추가 될 경우, 이전의 0과 지금의 0이 다를 수 있기 때문에 사용하면 안됨
@Lob : BLOB, CLOB 매핑
@Transient : 주로 메모리상에서 임시로 어떤 값을 보관하고 싶을 때 사용, DB에는 영향이 없음
날짜시간 : 기존에 @Temporal을 사용했지만 자바 8 이후 부터 LocalDate, LocalDateTime을 사용할 경우 Hibernate에서 자동으로 매핑해줌
기본 키 매핑 방법
@Id, @GeneratedValue 를 통해서, Id 매핑과 수동 매핑, 자동 매핑을 수행할 수 있다.
IDENTITY 전략
기본 키 생성을 데이터베이스에 위임
주로 MySQL, PostgreSQL, SQL Server에서 사용
JPA는 보통 트랜잭션 커밋 시점에 INSERT SQL을 사용하는데, IDENTITY 전략은 em.persist() 시점에, 즉시 INSERT SQL을 실행하고 DB에서 식별자를 조회함
그 이유는, 엔티티가 영속 상태가 되기 위해선 반드시 식별자가 필요하기 때문. IDENTITY 전략은 엔티티가 DB에 저장해야 식별자를 구분할 수 있으므로 em.persist()시 즉시 INSERT 쿼리가 전달됨
SEQUENCE 전략
데이터베이스 시퀀스는 유일한 값을 순서대로 생성하는 특별한 데이터베이스 오브젝트
주로 Oracle, PostgreSQL, H2에서 사용
@SequenceGenerator
initialValue : 시퀀스 DDL을 생성할 때 처음 시작하는 수를 지정한다.
allocationSize : 시퀀스를 한 번 호출할 때 증가하는 수 (미리 DB에 50개를 올려두고 메모리 상에 서 50개를 두고 순차적으로 ID값을 사용)
@Entity
@SequenceGenerator(
name = "member_seq_generator",
sequenceName = "member_seq",
initialValue = 1, allocationSize = 50)
public class Member { ... }
전체적인 진행 플로우
// 최초 시퀀스 값인 1을 리턴
Hibernate:
call next value for member_seq
// 마지막 시퀀스 값인 51을 리턴
Hibernate:
call next value for member_seq
Hibernate:
insert
Member
(name, id)
values
(?, ?)
TABLE 전략
키 생성 전용 테이블을 만들어, SEQUENCE 전략을 흉내내는 전략
모든 데이터베이스에 적용이 가능하지만, 성능 이슈가 있고 관례적으로 잘 사용하지 않음
권장하는 식별자 전략
기본 키 제약 조건 : null 아님, 유일, 변화면 안된다.
비즈니스 키를 식별자로 끌고 오면 안된다.
연관관계 매핑기초
단방향 연관관계
회원과 팀 (N:1) 관계를 기준
회원 객체는 Member.team 필드를 통해 팀 객체와 연관관계를 가짐
즉, 회원 객체와 팀 객체는 단방향 관계
JPA를 사용해서 연관관계를 매핑하면 다음과 같음
@Entity
public class Member {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "team_id")
private Team team;
...
}
@ManyToOne : N : 1 관계라는 매핑 정보를 말함. 회원과 팀은 다대일 관계이며, 이 둘의 연관관계를 매핑하기 위해서 해당 어노테이션을 사용해야 함
@JoinColumn(name = “team_id”) : 조인 컬럼은 외래 키를 매핑할 때 사용. name 속성에는 매핑할 외래 키 이름을 지정하면 됨
@JoinColumn은 생략이 가능하지만, 개인적으로는 명시적으로 주는 것이 좋아 생략하지 않음
양방향 연관관계
단방향에서는 회원에서 팀으로만 접근이 가능하였지만 양방향 연관관계를 사용할 경우 팀에서 회원으로 접근도 가능
팀과 회원의 관계는 1 : N 이기 때문에, 컬렉션을 사용해서 매핑을 수행해야 함
JPA를 사용해서 연관관계를 매핑하면 다음과 같음
@Entity
public class Team {
@Id @GeneratedValue(startegy = GenerationType.IDENTITY)
private Long id;
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
...
}
@OneToMany : 1 : N 관계라는 매핑 정보를 말함
mappedBy 속성은 매핑되고 있는 필드의 이름을 선언하기 위함
연관관계의 주인과 mappedBy
mappedBy를 이해하기 위해서는 객체와 테이블간에 연관관계를 맺는 차이를 이해해야 한다.
객체의 양방향 관계는 사실 양방향 관계가 아니라 서로 다른 단방향 관계 2개
테이블은 외래 키 하나로 두 테이블의 연관관계를 관리 (조인을 통해서)
위의 2가지의 차이에서 문제점이 생김
N:1 관계일 경우 N쪽에서 데이터를 외래키를 관리하는 경우와, 1쪽에서 외래키를 관리하는 경우의 시나리오가 다름
결국 둘 중 하나의 객체에서 외래 키를 관리해야 하는 문제점이 생김
양방향 매핑 규칙
연관관계의 주인만이 외래 키를 관리(등록, 수정)
주인이 아닌쪽은 읽기만 가능
주인은 mappedBy 속성 사용 x, 주인이 아니면 mappedBy 속성으로 주인을 지정
누구를 주인으로 해야하는가?
외래 키가 있는 곳을 주인으로 정하자 (N : 1 관계에서 N 쪽이 연관관계의 주인)
이유 1. 직관적이기 때문
Member, Team의 관계에서 Team에 update 쿼리를 적용했는데 Member에 update 쿼리가 적용될 수 있는 구조는 직관성이 매우 떨어짐. 따라서 Member에 있는 Team을 연관관계의 주인으로 잡음
양방향 매핑시 가장 많이 하는 실수
연관관계의 주인에 값을 입력하지 않음
양방향 매핑시 연관관계의 주인에 값을 입력해야 한다.
하지만, 순수한 객체 관계를 고려하면 항상 양쪽다 값을 입력하는 것이 좋음
이유 1. em.flush(), em.clear()를 통해서 영속성 컨텍스트를 비우지 않은 경우
이렇게 될 경우, 1차 캐시에 있는 값이 그대로 튀어나오게 됨
이유 2. 테스트 케이스 작성
JPA가 없는 상태에서 테스트가 필요할 때, 당연히 한쪽에 값이 비어있을 수 밖에 없음
결론
순수 객체 상태를 고려해서 항상 양쪽에 값을 설정하자
연관관계 편의 메서드를 만들어보자 (상황마다 다르므로 고민이 필요!)
```java
// Member에서 넣어주는 경우
public class Member {
...
@ManyToOne
@JoinColumn(name = "team_id")
private Team team;
...
public void addTeam(Team team) {
this.team = team;
team.getMembers().add(this);
}
// addTeam과 비슷해보이지만 역할이 다르며, 버그 있음
public void changeTeam(Team team) {
this.team = team;
team.getMembers().add(this);
}
}
```
```java
// Team에서 넣어주는 경우
public class Team {
...
@OneToMany
private List<Member> members = new ArrayList<>();
public void addMember(Member member) {
member.setTeam(this);
members.add(member);
}
}
```
양방향 매핑시, 꼭 무한 루프를 조심하자
주의
위의 코드는 버그의 요소가 존재함
```java
Team teamA = new Team("teamA");
em.persist(teamA);
Team teamB = new Team("teamB");
em.persist(teamB);
Member member = new Member("member1");
member.addTeam(teamA);
em.persist(member);
member.changeTeam(teamA);
member.changeTeam(teamB);
Member findMember = teamA.getMember();
```
- `Member findMember = teamA.getMember()`를 하면 여전히 `member1` 이 조회됨
그 이유는, teamB로 변경할 때 teamA → member1의 관계를 지우지 않았기 때문!! 그래서 다음과 같이 변경
public class Member {
...
public void addTeam(Team team) {
this.team = team;
team.getMembers().add(this);
}
// 버그 수정 완료
public void changeTeam(Team team) {
if (this.team != null) {
this.team.getMembers().remove(this);
}
this.team = team;
team.getMembers().add(this);
}
}
위의 과정을 통해, 양방향 매핑을 하기 위해서는 고려해야 할 점이 매우 많다는 것을 알 수 있음
객체와 테이블 매핑
@Entity
@Entity
가 붙은 클래스는 JPA가 관리, 엔티티라 한다.데이터베이스 스키마 자동 생성
ALTER을 잘못 적용할 경우 대장애가 발생할 가능성이 있다. (ex. 데이터베이스 Lock) 가급적 개발자가 직접 스크립트를 테스트 서버에서 적용해보고 확인 후, 운영 서버에 직접 적용하는 방법이 좋다. 또한, 이런 사고를 방지하기 위해 DB 계정별로 권한을 두어 사전에 방지하는 것이 좋다.
DDL 생성 기능
@Column(nullable = false, length = 10)
@Table(uniqueConstraints = {@UniqueConstraint(name = “NAME_AGE_UNIQUE”, columnNames = {”NAME”, “AGE”})})
필드와 컬럼 매핑
@Column
: 컬럼 매핑add constraint UK_ektea~~~~~~~~ unique (name)
과 같이 랜덤 값으로 로그가 나오게 되어 운영상에서 알아보기 힘듬@Table
의uniqueConstraints
속성을 이용해서 제약조건을 걸면 이름까지 정상으로 나오기 때문에 해당 속성을 사용하는 것을 추천@Enumerated
: enum 타입 매핑Enumerated.STRING
을 사용해서 enum 타입을 매핑ORDINAL
을 사용하게 되면 enum 타입을 정의한 순으로 DB에 순서대로 0, 1, 2, 3 저장이 되게 됨@Lob
: BLOB, CLOB 매핑@Transient
: 주로 메모리상에서 임시로 어떤 값을 보관하고 싶을 때 사용, DB에는 영향이 없음@Temporal
을 사용했지만 자바 8 이후 부터LocalDate
,LocalDateTime
을 사용할 경우 Hibernate에서 자동으로 매핑해줌기본 키 매핑 방법
@Id
,@GeneratedValue
를 통해서, Id 매핑과 수동 매핑, 자동 매핑을 수행할 수 있다.IDENTITY
전략IDENTITY
전략은em.persist()
시점에, 즉시 INSERT SQL을 실행하고 DB에서 식별자를 조회함IDENTITY
전략은 엔티티가 DB에 저장해야 식별자를 구분할 수 있으므로em.persist()
시 즉시INSERT
쿼리가 전달됨SEQUENCE
전략@SequenceGenerator
TABLE
전략SEQUENCE
전략을 흉내내는 전략연관관계 매핑기초
단방향 연관관계
Member.team
필드를 통해 팀 객체와 연관관계를 가짐JPA를 사용해서 연관관계를 매핑하면 다음과 같음
@ManyToOne
: N : 1 관계라는 매핑 정보를 말함. 회원과 팀은 다대일 관계이며, 이 둘의 연관관계를 매핑하기 위해서 해당 어노테이션을 사용해야 함@JoinColumn(name = “team_id”)
: 조인 컬럼은 외래 키를 매핑할 때 사용. name 속성에는 매핑할 외래 키 이름을 지정하면 됨@JoinColumn
은 생략이 가능하지만, 개인적으로는 명시적으로 주는 것이 좋아 생략하지 않음양방향 연관관계
양방향 연관관계를 사용할 경우 팀에서 회원으로 접근도 가능
JPA를 사용해서 연관관계를 매핑하면 다음과 같음
@OneToMany
: 1 : N 관계라는 매핑 정보를 말함연관관계의 주인과 mappedBy
mappedBy를 이해하기 위해서는 객체와 테이블간에 연관관계를 맺는 차이를 이해해야 한다.
객체의 양방향 관계는 사실 양방향 관계가 아니라 서로 다른 단방향 관계 2개
테이블은 외래 키 하나로 두 테이블의 연관관계를 관리 (조인을 통해서)
위의 2가지의 차이에서 문제점이 생김
N쪽
에서 데이터를 외래키를 관리하는 경우와,1쪽
에서 외래키를 관리하는 경우의 시나리오가 다름양방향 매핑 규칙
누구를 주인으로 해야하는가?
양방향 매핑시 가장 많이 하는 실수
연관관계의 주인에 값을 입력하지 않음
em.flush()
,em.clear()
를 통해서 영속성 컨텍스트를 비우지 않은 경우그 이유는, teamB로 변경할 때 teamA → member1의 관계를 지우지 않았기 때문!! 그래서 다음과 같이 변경
위의 과정을 통해, 양방향 매핑을 하기 위해서는 고려해야 할 점이 매우 많다는 것을 알 수 있음
물론, 위의 코드들은 하나의 트랜잭션 내에서 수행되었을 때만 발생하는 버그
양방향 매핑 정리