
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "TEAM_ID")
private Team team;
}
@Entity
public class Team {
@Id
@GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;
}
Member에 다대일 매핑 관계를 걸어줌으로써 연관 관계를 매핑해줄 수 있다.

@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "TEAM_ID")
private Team team;
}
@Entity
public class Team {
@Id
@GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<Member>();
}
외래 키가 있는 쪽이 연관관계의 주인이다.
다대일의 반대는 일대다이므로 반대인 Team을 mappedBy를 통해 양쪽을 서로 참조하도록 개발할 수 있다.

@Entity
public class Team {
@Id
@GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;
@OneToMany()
@JoinColumn(name = "TEAM_ID")
private List<Member> members = new ArrayList<Member>();
}
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
}
Member member = new Member();
member.setUsername("MemberA");
em.persist(member);
Team team=new Team();
team.setName("TeamA");
team.getMembers().add(member);
em.persist(team);
Hibernate:
/* insert for
hellojpa.Team */insert
into
Team (name, TEAM_ID)
values
(?, ?)
Hibernate:
update
Member
set
TEAM_ID=?
where
MEMBER_ID=?
• 일(1)이 연관관계의 주인이다.
• 객체와 테이블의 차이 때문에 반대편 다(N) 테이블이 외래 키를 관리한다.
• @JoinColumn을 꼭 사용해야 함. 그렇지 않으면 조인 테이블 방식을 사용해야한다.
• 연관관계 관리를 위해 추가로 UPDATE SQL 실행한다.
• 일대다 단방향 매핑보다는 다대일 양방향 매핑을 사용하는것이 좋다.
@JoinColumn을 사용하지 않은 예:
@Entity
public class Team {
@Id
@GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;
@OneToMany()
@JoinColumn(name = "TEAM_ID")
private List<Member> members = new ArrayList<Member>();
}
Team 코드는 유지한다.
Hibernate:
create table Team_Member (
Team_TEAM_ID bigint not null,
members_MEMBER_ID bigint not null unique
)
Hibernate:
alter table if exists Team_Member
add constraint FKpsjmea2e134ab3x3gsdoyxepg
foreign key (members_MEMBER_ID)
references Member
Hibernate:
alter table if exists Team_Member
add constraint FK4u1npo283vgqfk8lyxclihgnl
foreign key (Team_TEAM_ID)
references Team
Hibernate:
/* insert for
hellojpa.Team.members */insert
into
Team_Member (Team_TEAM_ID, members_MEMBER_ID)
values
(?, ?)
log를 보면 내가 계획하지 않은 Team_Member 테이블을 생성하고 insert한다. 아래 DB를 확인한 결과이다.
| TEAM_TEAM_ID | MEMBERS_MEMBER_ID |
|---|---|
| 1 | 1 |

@Entity
public class Team {
@Id
@GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;
@OneToMany()
@JoinColumn(name = "TEAM_ID")
private List<Member> members = new ArrayList<Member>();
}
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
@ManyToOne()
@JoinColumn(name = "",insertable = false, updatable = false)
private Team team;
일대다 양방향 매핑은 공식적으로 존재하지않고 억지로@JoinColumn(insertable=false, updatable=false)을 사용하여 양방향 매핑을 구현해야 한다.
다대일 양방향을 사용하자.
*참고: 주 테이블은 관계에서 외래키를 소유하는 테이블을 의미, 대상 테이블은 외래키를 통해 연결되는 테이블을 의미한다.
일대일 관계는 그 반대도 일대일이다.
주 테이블이나 대상 테이블 중에 외래 키 선택 가능하다.
외래 키에 데이터베이스 유니크(UNI) 제약조건 추가해야 한다.(DB는 UNI 제약 조건을 설정하여야 일대일 관계 가능)
다대일 매핑과 같이 외래키가 있는 곳이 연관 관계의 주인이다.반대편은 mappedBy 적용한다.

@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "LOCKER_ID",unique = true)
private Locker locker;
//getter setter
}
@Entity
public class Locker {
@Id
@GeneratedValue
@Column(name = "LOCKER_ID")
private Long id;
private String name;
@OneToOne(mappedBy = "locker")
private Member member;
//getter setter
}
주테이블(member)에 외래키 양방향인 경우 주객체가 대상 객체(locker)의 참조를 가지는 것처럼 주테이블의 외래키(locker_id)를 가지고 찾을수 있다.
Locker locker = new Locker();
locker.setName("lockerB");
em.persist(locker);
Member member = new Member();
member.setUsername("member4");
member.setLocker(locker);
em.persist(member);
em.flush();
em.clear();
System.out.println("=====================");
Member findMember = em.find(Member.class, 1L);
System.out.println("findMember.getClass() = " + findMember.getClass());
System.out.println("findMember.getId() = " + findMember.getId());
System.out.println("findMember.getUsername() = " + findMember.getUsername());
System.out.println("=====================");
Locker locker1 = findMember.getLocker();
System.out.println("locker1.getClass() = " + locker1.getClass());
System.out.println("locker1.getId() = " + locker1.getName());
실행 결과:
=====================
Hibernate:
select
m1_0.MEMBER_ID,
m1_0.LOCKER_ID,
m1_0.TEAM_ID,
m1_0.USERNAME
from
Member m1_0
where
m1_0.MEMBER_ID=?
findMember.getClass() = class hellojpa.Member
findMember.getId() = 1
findMember.getUsername() = member4
=====================
locker1.getClass() = class hellojpa.Locker$HibernateProxy$xmWthfWJ
Hibernate:
select
l1_0.LOCKER_ID,
m1_0.MEMBER_ID,
m1_0.TEAM_ID,
m1_0.USERNAME,
l1_0.name
from
Locker l1_0
left join
Member m1_0
on l1_0.LOCKER_ID=m1_0.LOCKER_ID
where
l1_0.LOCKER_ID=?
locker1.getId() = lockerB
| LOCKER_ID | MEMBER_ID | USERNAME |
|---|---|---|
| 1 | 1 | member4 |
주테이블에서 외래키 locker의 값이 없을 경우:
Member findMember = em.find(Member.class, 1L);
System.out.println("findMember.getClass() = " + findMember.getClass());
System.out.println("findMember.getId() = " + findMember.getId());
System.out.println("findMember.getUsername() = " + findMember.getUsername());
if (findMember.getLocker() == null) {
System.out.println("NPE 발생");
}
findMember.getClass() = class hellojpa.Member
findMember.getId() = 1
findMember.getUsername() = member4
NPE 발생
| LOCKER_ID | MEMBER_ID | USERNAME |
|---|---|---|
| null | 1 | member4 |
외래키 값이 있을 경우 프록시가 적용된것을 볼 수 있는데
이말은 주테이블(member)만 조회해도 대상테이블(locker)에 데이터가 있는지 확인이 가능하다.
그러므로 객체지향 개발자에게는 편리한 구조이다.
하지만 locker값이 없으면 null 허용이 된다.

JPA에서 지원도 안되고 연관 관계를 매핑해줄 수 있는 방법이 존재하지 않는다.

@Entity
public class Locker {
@Id
@GeneratedValue
@Column(name = "LOCKER_ID")
private Long id;
private String name;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "MEMBER_ID2", unique = true)
private Member2 member2;
//getter setter
}
@Entity
public class Member2 {
@Id @GeneratedValue
@Column(name = "MEMBER_ID2")
private Long id;
@Column(name = "USERNAME")
private String username;
@OneToOne(mappedBy = "member2")
private Locker locker;
//getter setter
}
Member2 member2 = new Member2();
member2.setUsername("memberA");
em.persist(member2);
Locker locker = new Locker();
locker.setName("lockerA");
locker.setMember2(member2);
em.persist(locker);
em.flush();
em.clear();
System.out.println("=====================");
Member2 findMember2 = em.find(Member2.class, 1L);
System.out.println("findMember2.getClass() = " + findMember2.getClass());
System.out.println("findMember2.getId() = " + findMember2.getId());
System.out.println("findMember2.getUsername() = " + findMember2.getUsername());
System.out.println("=====================");
Locker locker1 = findMember2.getLocker();
System.out.println("locker1.getClass() = " + locker1.getClass());
System.out.println("findMember2.getLocker().getName() = " + locker1.getName());
실행 결과:
=====================
Hibernate:
select
m1_0.MEMBER_ID2,
l1_0.LOCKER_ID,
l1_0.name,
m1_0.USERNAME
from
Member2 m1_0
left join
Locker l1_0
on m1_0.MEMBER_ID2=l1_0.MEMBER_ID2
where
m1_0.MEMBER_ID2=?
findMember2.getClass() = class hellojpa.Member2
findMember2.getId() = 1
findMember2.getUsername() = memberA
=====================
locker1.getClass() = class hellojpa.Locker
findMember2.getLocker().getName() = lockerA
| LOCKER_ID | MEMBER_ID2 | NAME |
|---|---|---|
| 1 | 1 | lockerA |
locker 조회 예제:
System.out.println("=====================");
Locker findLocker = em.find(Locker.class, 1L);
System.out.println("findLocker.getClass() = " + findLocker.getClass());
System.out.println("findLocker.getName() = " + findLocker.getName());
System.out.println("=====================");
Member2 findLockerMember = findLocker.getMember2();
System.out.println("findLockerMember.getClass() = " + findLockerMember.getClass());
System.out.println("findLockerMember.getUsername() = " + findLockerMember.getUsername());
실행 결과:
=====================
Hibernate:
select
l1_0.LOCKER_ID,
l1_0.MEMBER_ID2,
l1_0.name
from
Locker l1_0
where
l1_0.LOCKER_ID=?
findLocker.getClass() = class hellojpa.Locker
findLocker.getName() = lockerA
=====================
findLockerMember.getClass() = class hellojpa.Member2$HibernateProxy$frn8HIqN
Hibernate:
select
m1_0.MEMBER_ID2,
l1_0.LOCKER_ID,
l1_0.name,
m1_0.USERNAME
from
Member2 m1_0
left join
Locker l1_0
on m1_0.MEMBER_ID2=l1_0.MEMBER_ID2
where
m1_0.MEMBER_ID2=?
findLockerMember.getUsername() = memberA
member를 조회시 대상테이블(locker)에 외래키가 존재하므로 지연 로딩으로 설정해도 항상 즉시 로딩된다.(프록시 x)
반대로 locker를 조회하면 정상적으로 설정된 지연로딩으로 동작할것이다.(member가 외래키를 가지고 있는 상황에서도 locker를 먼저 조회하면 당연히 즉시 로딩 됨)

@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
@ManyToMany
@JoinTable(name = "MEMBER_PRODUCT")
private List<Product> products = new ArrayList<Product>();
}
@Entity
public class Product {
@Id @GeneratedValue
private Long id;
private String name;
}
Member 코드는 유지.
@Entity
public class Product {
@Id @GeneratedValue
private Long id;
private String name;
@ManyToMany(mappedBy = "products")
private List<Member> members=new ArrayList<>();
}
편리해 보이지만 실무에서 사용하지 않는다. 실무에서는 연결 테이블이 단순히 연결만 하고 끝나지 않기 때문이다.(ex: 주문시간, 수량 같은 데이터가 들어올 수 있다.)
실무에서는 @ManyToMany는 @OneToMany, @ManyToOne로 풀어서 해결할 수 있다.
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
@OneToMany(mappedBy = "member")
private List<MemberProduct> MemberProducts = new ArrayList<MemberProduct>();
}
@Entity
public class MemberProduct {
@Id @GeneratedValue
private Long id;
@ManyToOne
@JoinColumn(name = "MEMBER_ID")
private Member member;
@ManyToOne
@JoinColumn(name = "PRODUCT_ID")
private Product product;
private int count;
private int price;
private LocalDateTime orderDateTime;
}
@Entity
public class Product {
@Id @GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "product")
private List<MemberProduct> memberProducts=new ArrayList<>();
}
Member member=new Member();
member.setUsername("memberA");
em.persist(member);
Product product = new Product();
product.setName("ProductA");
em.persist(product);
MemberProduct memberProduct=new MemberProduct();
memberProduct.setProduct(product);
memberProduct.setMember(member);
em.persist(memberProduct);
실행 결과:
Hibernate:
/* insert for
hellojpa.Member */insert
into
Member (USERNAME, MEMBER_ID)
values
(?, ?)
Hibernate:
/* insert for
hellojpa.Product */insert
into
Product (count, name, orderDateTime, price, id)
values
(?, ?, ?, ?, ?)
Hibernate:
/* insert for
hellojpa.MemberProduct */insert
into
MemberProduct (MEMBER_ID, PRODUCT_ID, id)
values
(?, ?, ?)