아래 내용은 김영한 님의 자바 ORM 표준 JPA 프로그래밍의 내용에 기반하여 정리한 글입니다.
엔티티 매핑
매핑 정보는 XML/어노테이션 중에 선택 가능하다. 아래는 좀더 쉽고 직관적인 어노테이션 매핑기반으로 설명하겠다.
@Entity
- 테이블과 매핑할 클래스에 붙이는 필수 어노테이션
- 기본 생성자 필수(public or protected 생성자)
- final 클래스, enum, interface, inner 클래스에 사용 불가
- 저장할 필드에 final 사용불가
@Entity
public class Member {
...
}
@Table
- 엔티티와 매핑할 테이블을 지정하는 어노테이션
- 생략 가능 (생략시 엔티티 이름으로 사용)
- name, catalog, schema, uniqueConstraints(ddl용) 와 같은 속성이 있다.
@Entity
@Table(name="MEMBER")
public class Member {
...
}
데이터베이스 스키마 자동 생성
- 매핑정보와 데이터베이스 방언을 사용해 데이터베이스 스키마를 자동 생성하는 기능 지원
- hibernate.hbm2ddl.auto 값은
- create (drop 후 create)
- create-drop (위에 create + 애플리케이션 종료시 drop)
- update (테이블과 매핑정보를 비교해 변경사항만 수정)
- validate (테이블과 매핑정보를 비교해 차이가 있으면 경고후 애플리케이션 실행 종료)
- none (관례상 쓰는 값으로 실제로는 존재하지 않는 값이다. 즉 자동 생성 기능 사용 안하겠다는 의미)
- JPA 공식 스펙은 update,validate를 지원하지 않음
- 권장하는 방법
- 로컬 개발환경 자유
- 공용 테스트 서버 update or validate (가급적 쓰지말자)
- 스테이징 및 운영 validate or none (가급적 쓰지말자)
- 그래도 사고를 막기위해서는 웹 어플리케이션의 디비 접근 계정은 ddl를 못하게 막아야 한다.
- hibernate.ejb.naming_strategy 속성 값을 org.hibernate.cfg.ImprovedNamingStrategy로 설정하면 @Column name속성 생략시 자바의 카멜 표기법을 테이블의 언더스코어 표기법으로 자동 매핑해준다.
DDL 생성 기능
- @Column의 nullable,length,unique 등
- @Table의 uniqueConstraints 속성
- 위에 속성들은 DDL자동 생성시에만 영향을 주는것들 이다.
- DDL 자동생성을 사용하지 않는다면 굳이 필요가 없으나 개발자가 코드만 보고 제약조건을 파악할 수 있다는 장점은 있다.
필드 매핑 어노테이션 간단한 정리
- @Column (기본 필드 매핑)
- @Temporal (날짜 매핑)
- Date객체를 매핑시에만 필요 요즘은 LocalDateTime, LocaleDate를 사용하기 때문에 필요없음
- @Enumerated (Enum 매핑)
- value 속성중 ORDINAL은 enum 순서로(1,2..) 저장 됨으로 이름을 저장하기 위해서는 STRING을 사용!
- @Lob
- 큰문자열을 위해 사용
- 문자면 CLOB, 나머지는 BLOB으로 자동 매핑
- @Transient (필드 매핑 무시)
- @Access
- AccessType.FIELD (테이블과 객체 매핑시 필드값 기준)
- AccessType.PROPERTY (테이블과 객체 값 매핑시 메서드값 기준 ex)getter setter )
- @Access를 설정하지 않으면 @Id가 필드에 있냐 메서드에 있냐를 기준으로 삼어 적용됨
기본 키 매핑
- 직접할당 (@Id만 사용)
- 자동 생성 (@Id & @GeneratedValue )
- IDENTITY
- 데이터베이스에 엔티티를 저장해서 식별자 값 획득
- 기본 키 생성을 데이터베이스에 위임하는 전략으로 주로 MySQL,PostgreSQL, SQL Server, DB2 사용
- 엔티티를 저장해야 아이디를 가져올 수 있기 때문에 쓰기 지연이 작동하지 않고 em.persist()를 호출하는 즉시 INSERT SQL이 데이터베이스에 전달된다. (사실상 쓰기지연이 안된다고 성능에 크게 영향을 주지 않는다)
- SEQUENCE
- 데이터베이스 시퀀스에서 식별자 값 획득
- 시퀀스를 지원하는 오라클, PostgreSQL, DB2, H2 사용가능
- SequenceGenerator.allocationSize로 동시성 이슈없이 시퀀스 조회 작업을 줄여 성능을 최적화 가능 (50-100 권장 너무 많으면 문제는 없지만 웹서버 다운시 중간에 시퀀스 구멍이 생길수 있음)
- TABLE
- 시퀀스 생성용 테이블에서 식별자 값 획득
- 테이블로 시퀀스 기능을 흉내낸 것으로 시퀀스 대신에 테이블을 사용한다는 것만 제외하면 SEQUENCE 전략과 내부 동작방식이 같다.
- @TableGenerator.allocationSize로 SequenceGenerator.allocationSize와 동일하게 성능 최적화가 가능하다.
- AUTO
- 데이터베이스 방언에 따라 자동 선택
- Mysql-IDENTIY, oracle-SEQUENCE
권장하는 식별자 전략 선택
- 3가지(not null, 유일, 변경 x)조건을 만족하는 대리키 사용 (auto increate, sequnce, uuid 등)
- 주민등록과 같은 자연키 즉 비지니스 적으로 의미있는 키는 변경에 여지가 있기 때문에 피하자.
- 기본키의 형 타입은 Long을 권장!
- 기본형(int, long)은 0이라는 값이 있어 피하자
- Integer의 숫자 범위를 기본키의 값이 넘어 간다면 중간에 바꿀때 비용이 발생하기 때문에 피하자 (바꾸는 비용이 Long을 사용함으로써 낭비되는 공간의 비용보다 비싸다)
연관관계 매핑 기초
객체와 연관관계와 테이블 연관관계의 차이
- 객체는 참조로 연관관계를 맺고 단방향이다. 양향방으로 참조하려면 단방향 연관관계를 2개 만들어야 한다.
예) A -> B (a.getB())
- 테이블은 외래키로 연관관계를 맺고 양방향이다.
예) A JOIN B 가 가능하면 반대로 B JOIN A도 가능
단방향 연관관계
매핑예시
@Entity
public class Member {
@Id
@Column(name = "MEMBER_ID")
private String id;
private String username;
//연관관계 매핑. @OneToManY
@ManyToOne
@JoinColumn(name="TEAM_ID")
private Team team;
//연관관계 설정
public void setTeam(Team team) {
this.team = team;
}
//Getter, Setter ...
}
사용법
- 저장 (이미 영속상태인 엔티티만 연관관계로 저장 가능하다.)
Team team1 = new Team("team1", "팀1");
entityManger.persist(team1);
Member member1 = new Member("member1", "회원1");
member1.setTeam(team1);
entityManger.persist(member1);
- 조회
Member member = entityManger.find(Member.class, "member1");
member.getTeam();
List<Member> members = entityManger.createQuery("select m from Member m join m.team t, Member.class)
members.get(0).getTeam();
- 수정 (자연스럽게 변경하면 트랜잭션 커밋시 변경사항이 반영됨)
- 연관관계 제거 (연관관계를 null로 설정하면 외래키에 null이 들어감)
- 연관된 엔티티 삭제 (외래키 제약조건이 있을 경우 연관관계를 먼저 제거한 후에 연관된 엔티티를 삭제해야함)
member.setTeam(null)
entityManger.remove(team);
양방향 연관관계
매핑예시
@Entity
public class Member {
@Id
@Column(name = "MEMBER_ID")
private String id;
private String username;
//연관관계 매핑. @OneToManY
@ManyToOne
@JoinColumn(name="TEAM_ID")
private Team team;
//연관관계 설정
public void setTeam(Team team) {
this.team = team;
}
//Getter, Setter ...
}
@Entity
public class Team {
@Id
@Column(name = "TEAM_ID")
private String id;
private String name;
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<Member>();
//Getter, Setter ...
}
연관관계 주인 (mappedBy 속성)
- 객체를 양방향 연관관계를 설정하면 외래키는 하나인데 객체 참조가 둘로 늘어난다. 그렇게 되면 두 객체중 하나가 테이블의 외래키를 관리해야 하는데 이것을 연관관계 주인이라고 한다.
- 주인이 아닌쪽에 mappedBy 속성을 설정하면 된다.
- 연관관계 주인은 비지니스 중요도가 아닌 외래키가 있는곳으로 설정해야 한다.
양방향 연관관계 설정시 주의점
- 연관관계 주인쪽에 값을 저장해야지만 데이터베이스에 반영된다.
- 객체관점에서는 연관관계 주인은 물론 양쪽뱡향 모두에 값을 추가,수정,삭제 해주는 편이 안전하다.
- 연관관계 편의 메소드를 활용해 메소드 하나로 양방향 관계를 모두 설정하도록 하자.
public void setTeam(Team team) {
if (this.team != null) {
this.team.getMembers().remove(this);
}
this.team = team;
team.getMembers().add(this);
}
- 서로가 서로를 호출하여 생기는 무한 루프를 조심하자.
다양한 연관관계 매핑
다대일
- 단방향 양방향 모두 연관관계 주인이 다쪽이다.(외래키가 있는 쪽)
일대다
- 단방향
- 매핑한 객체가 관리하는 외래 키가 다른테이블에 있어 성능에 문제가 있고 관리도 부담스럽다.
- 항상 @JoinColumn을 명시해야 한다. 그렇지 않으면 조인 테이블 전략을 사용해서 매핑된다.
- 이런 상황에서는 객체지향적으로 조금 손해를 보더라도 다대일 양방향 매핑을 사용하는 편이 좋다.
- 양방향
- 일대다 단뱡향 매핑과 반대편에 다대일 단방향 매핑(insertable = false, updatable =false)를 추가해 구현할 수 있다.
- 일대다 단방향의 단점이 그대로 존재함으로 다대일 양방향을 사용하자.
일대일
자신의 외래키는 무조건 자신이 관리해야 하고 양방향시에는 반대쪽에 mapped by를 사용
-
주 테이블에 외래키
- 다대일 단방향, 양방향과 똑같고 @OneToOne 에노테이션만 붙혀주면된다.
- 장점
- JPA 매핑이 편리해 객체지향 개발자가 선호
- 주테이블만 조회해도 대상 테이블에 데이터가 있는지 확인가능
- 단점
- 연관관계가 없을때를 대비해 외래키에 널을 허용해야 한다.
-
대상 테이블에 외래키
- 장점
- DBA선호
- 일대다 관계로 (대상 테이블이 다) 변경되어도 테이블 구조는 유지
- 단점
- 단방향 불가 양방향만 가능
- 프록시의 한계로 연관관계가 무조건 즉시로딩됨
개인적으로 관계가 변경되는 경우와 같이 너무 먼미래는 생각하지 않고 주로 주 테이블에 외래키 구조 선택
다대다
- @ManyToMany로 설정하면 자동으로 중간에 연결 테이블를 가지고 매핑한다.
- 실무에서는 연결테이블에는 보통 다양한 컬럼들이 추가 되기 때문에 @ManyToMany보다는 연결테이블을 생성 후 일대다, 다대일 관계로 풀어낸다.
- 연결된 테이블과의 관계는 식별 관계(받아온 식별자를 기본 키 + 외래 키) 또는 비식별 관계(받아온 식별자는 외래 키로만 사용)로 풀어낼수 있다.
비식별 관계를 가져가도록 추천한다. 그 이유는 복합키를 사용하지 않아 편리하게 ORM 매핑이 가능하고 id가 다른테이블에 종속되지 않아 시스템을 유연하게 유지할 수 있기 때문이다.