Team team1 = new Team("team1", "팀1");
em.persist(tema1);
Member member1 = new Member("member1", "회원1");
member1.setTeam(team1);
em.persist(member1);
엔티티 저장 시 연관된 엔티티는 반드시 영속상태여야 함
Member → Team 참조이므로 연관관계를 맺어줄 때 Team은 반드시 영속상태여야 함
MEMBER 테이블의 TEAM_ID 값으로 team1의 id값이 들어감
연관관계 조회
연관관계를 조회하는 방법은 2가지
객체 그래프 탐색
Member member = em.find(Member.class, "member1");
Team team = member.getTeam();
객체지향 쿼리 사용(JPQL)
String jpql = "select m from Member m join m.team t where t.name = :teamName";
List<Member> members = em.createQuery(jpql, Member.class)
.setParameter("teamName", "팀1")
.getResultList();
회원이 팀과 관계를 가지고 있는 필드(m.team)을 통해 Member와 Team을 조인
연관관계 수정
Team team2 = new Team("team2", "팀2");
em.persist(team2);
Member member = em.find(Member.class, "member1");
member.setTeam(team2);
member.setTeam으로 참조하는 대상만 바꿔주면 JPA가 트랜잭션이 커밋되는 시점에 변경 사항을 데이터베이스에 반영
연관관계 제거
Member membe1 = em.find(Member.class, "member1");
member.setTeam(null);
참조를 null로 바꿔주면 FK도 null로 바꿔서 연관관계가 제거됨
연관된 엔티티를 삭제하려면 기존에 있던 연관관계를 먼저 제거하고 삭제해야 함
양방향 연관관계
Team → Member로 접근하는 관계를 추가
회원 → 팀 (Member.team)
팀 → 회원 (Team.members; 다대일이므로 컬렉션)
데이터베이스는 외래 키 하나로 양방향 조회가 가능하므로 DB는 변화 없음
객체 매핑만 추가해주면 됨
@Entity
@Getter
public class Team {
@Id
@Column(name = "TEAM_ID")
private String id;
private String name;
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
...
}
Team team = em.find(Team.class, "team1");
List<Member> members = team.getMembers();
양방향 연관관계 저장
Team team1 = new Team("team1", "팀1");
em.persist(team1);
Member member1 = new Member("member1", "회원1");
member1.setTeam(team1);
em.persist(member1);
// 연관관계의 주인이 아닌 방향은 값을 설정하지 않아도 데이터베이스에 외래 키 값이 정상 입력됨
// team1.getMembers().add(member1);
주의
Member member1 = new Member("member1", "회원1");
em.persist(member1);
Team team1 = new Team("team1", "팀1");
team1.getMembers().add(member1);
em.persist(team1);
연관관계의 주인이 아닌 방향에는 값을 설정하지 않아도 데이터베이스에 외래 키 값이 정상 입력됨
연관관계의 주인이 아닌 방향에만 값을 설정하고 연관관계의 주인에 값을 설정하지 않으면 데이터베이스에 외래 키 값이 입력되지 않음
Team team1 = new Team("team1", "팀1");
em.persist(team1);
Member member1 = new Member("member1", "회원1");
member1.setTeam(team1);
em.persist(member1);
team1.getMembers().add(member1);
why? JPA 없이 순수한 객체 상태로 사용할 때 문제를 방지하기 위함
연관관계 편의 메서드
member.setTeam(team)과 team.getMembers().add(member)를 모두 호출할 때 실수로 누락 가능성 있음
→ 두 코드가 하나의 코드인 것 처럼 사용해야 안전
public class Member {
...
private Team team;
public void setTeam(Team team) {
this.team = team;
team.getMembers().add(this);
}
}
이렇게 Member.setTeam 하나로 양방향의 연관관계를 모두 입력해주도록 하면 연관관계를 맺어주는 메서드를 한 번만 호출해도 됨 (연관관계 편의 메서드)
단, 객체 관점에서는 주인이 아닌 부분의 연관관계가 사라지지 않는다는 것에 주의
member1.setTeam(teamA);
member1.setTeam(teamB);
Member findMember = teamA.getMembers.get(0); // member1 조회됨
기존 연관관계를 끊지 않으면 외래 키는 정상적으로 변경됨
주인인 쪽의 객체 연관관계도 정상적으로 수정됨
그러나 같은 영속성 컨텍스트 내에서 기존의 team 객체의 members 에는 아직 member 이 남아있음
따라서 다음과 같이 기존 관계를 제거하는 것이 안전
public void setTeam(Team team) {
if (this.team != null) {
this.team.getMembers().remove(this);
}
this.team = team;
team.getMembers().add(this);
}
양방향 연관관계 시 순환참조 주의
Member.toString에서 getTeam을 호출하고, Team.toString에서 getMember를 호출하면 어느쪽에서 toString을 호출하든 무한루프에 빠짐(엔티티를 JSON으로 변환할 때 많이 생기는 문제)
섹션 4. 엔티티 매핑
객체와 테이블 매핑 어노테이션
@Entity
JPA를 사용해서 테이블과 매핑할 클래스에 붙이는 어노테이션
@Table
엔티티와 매핑할 테이블을 지정하는 어노테이션
데이터베이스 스키마 자동 생성
JPA는 데이터베이스 스키마를 자동으로 생성하는 기능을 지원
properties
yaml
ddl-auto: 자동으로 테이블을 생성할 지 결정하는 속성
JPA 자체적으로도 스키마 자동 생성 기능을 지원(update, validate 지원 x)
이름 매핑 전략 변경
@Column(name="role_type")
과 같은 식으로 컬럼명을 지정하지 않으면 필드명을 그대로 컬럼명으로 사용hibernate.naming.implicit-strategy
속성을 통해 전략을 변경해 줄 수 있음hibernate.naming.physical-strategy
속성을 통해 물리적 이름도 변경할 수 있음DDL 생성 시 제약 조건 걸기
length
속성을 통해 문자 크기 지정(varchar(10)
),nullable=false
속성을 통해 not null 제약 조건 적용기본 키 매핑
@Id
어노테이션을 통해서 기본 키 매핑 가능기본 키 조건
기본키로는 자연 키와 대리 키 사용 가능
@GeneratedValue
어노테이션을 추가하여기본 키 직접 생성 대신 데이터베이스에서 제공하는 기본 키 자동 생성 기능 사용 가능AUTO_INCREMENT
)em.persist
와 동시에 INSERT를 해줘야 함@SequenceGenerator
를 사용해서 시퀀스 생성기 등록em.persist
를 호출할 때 데이터베이스 시퀀스를 사용해서 식별자 조회 → 엔티티에 식별자 할당 → 영속성 컨텍스트에 저장 → 트랜잭션 플러시할 때 데이터베이스에 저장@TableGenerator
를 사용해서 테이블 키 생성기를 등록SELECT
쿼리를 사용하고 값을 증가시키기 위해UPDATE
쿼리 사용IDENTITY
,SEQUENCE
,TABLE
중 하나를 선택필드와 컬럼 매핑
@Column
: 객체 필드를 테이블 컬럼에 매핑@Column
을 생략하면 대부분@Column
속성의 기본값이 적용@Enumerated
: enum 타입을 매핑할 때 사용@Temporal
: 날짜 타입(Date
,Calendar
)을 매핑할 때 사용Date
와 가장 유사한 datetime(MySQL) 또는 timestamp(H2, 오라클, PostgreSQL) 타입으로 매핑@Lob
: BLOB, CLOB 타입과 매핑@Transient
: 해당 필드를 매핑하지 않음@Access
: JPA가 엔티티 데이터에 접근하는 방식 지정@Id
어노테이션이 붙은 위치를 기준으로 결정섹션 5. 연관관계 매핑 기초
객체와 DB의 연관관계 차이점
Member
가Team
을 필드로 가지는 연관관계meber.getTeam
으로 가능단방향 연관관계
가장 기초적인 다대일[N:1] 연관관계(
Member
(N)가Team
(1)을 필드로 가지는 연관관계)Member
→Team
단방향 연관관계(Member → Team만 조회 가능)@ManyToOne
@ManyToOne
의 기본값은 FetchType.EAGER@OneToMany
의 기본값은 FetchType.LAZY@JoinColumn(name=”TEAM_ID”)
TEAM_ID
를 FK로 하여 연관관계를 맺음@Column
의 속성과 같음연관 관계 저장
Member
→Team
참조이므로 연관관계를 맺어줄 때Team
은 반드시 영속상태여야 함MEMBER
테이블의TEAM_ID
값으로 team1의 id값이 들어감연관관계 조회
연관관계를 조회하는 방법은 2가지
객체 그래프 탐색
객체지향 쿼리 사용(JPQL)
연관관계 수정
member.setTeam
으로 참조하는 대상만 바꿔주면 JPA가 트랜잭션이 커밋되는 시점에 변경 사항을 데이터베이스에 반영연관관계 제거
양방향 연관관계
Team
→Member
로 접근하는 관계를 추가@OneToMany
@ManyToOne
의 반대연관관계의 주인
엄밀히 말해 객체에는 양방향 연관관계란 존재하지 않음. 단방향 연관관계 2개가 존재.
반면 데이터베이스 테이블은 외래 키 하나로 양방향 조인이 가능.
Member.team
이 연관관계의 주인이므로Team.members
에는 mappedBy 속성을 지정해주어야 함team
으로 매핑되므로 mappedBy 값으로team
을 주면 됨@ManyToOne
에는 mappedBy 속성이 존재하지 않음일대다 컬렉션 조회
양방향 연관관계 저장
주의
연관관계 편의 메서드
member.setTeam(team)
과team.getMembers().add(member)
를 모두 호출할 때 실수로 누락 가능성 있음→ 두 코드가 하나의 코드인 것 처럼 사용해야 안전
이렇게 Member.setTeam 하나로 양방향의 연관관계를 모두 입력해주도록 하면 연관관계를 맺어주는 메서드를 한 번만 호출해도 됨 (연관관계 편의 메서드)
단, 객체 관점에서는 주인이 아닌 부분의 연관관계가 사라지지 않는다는 것에 주의
team
객체의members
에는 아직member
이 남아있음따라서 다음과 같이 기존 관계를 제거하는 것이 안전
양방향 연관관계 시 순환참조 주의
Member.toString
에서getTeam
을 호출하고,Team.toString
에서getMember
를 호출하면 어느쪽에서toString
을 호출하든 무한루프에 빠짐(엔티티를 JSON으로 변환할 때 많이 생기는 문제)@ToString
사용 시에 해당 문제가 자주 발생정리