πŸ“• Backend/Spring Data JPA

[JPA] λ‹€λŒ€μΌ, μΌλŒ€λ‹€, μΌλŒ€μΌ (단방ν–₯, μ–‘λ°©ν–₯) 연관관계 λ§€ν•‘

Dongwoongkim 2023. 5. 1. 19:16

닀쀑성과 단방ν–₯, μ–‘λ°©ν–₯을 κ³ λ €ν•œ κ°€λŠ₯ν•œ λͺ¨λ“  연관관계λ₯Ό ν•˜λ‚˜μ”© μ•Œμ•„λ³΄κ² μŠ΅λ‹ˆλ‹€. (μ™Όμͺ½μ΄ μ—°κ΄€κ΄€κ³„μ˜ 주인)


λ‹€λŒ€μΌ(N:1) 

λ‹€λŒ€μΌ 관계 νŠΉμ§•

  • λ‹€λŒ€μΌ κ΄€κ³„μ˜ λ°˜λŒ€ λ°©ν–₯은 항상 μΌλŒ€λ‹€ κ΄€κ³„μž…λ‹ˆλ‹€.
  • μΌλŒ€λ‹€ κ΄€κ³„μ˜ λ°˜λŒ€ λ°©ν–₯은 항상 λ‹€λŒ€μΌ κ΄€κ³„μž…λ‹ˆλ‹€.
  • ν…Œμ΄λΈ”μ˜ λ‹€λŒ€μΌ, μΌλŒ€λ‹€ κ΄€κ³„μ—μ„œ μ™Έλž˜ν‚€λŠ” 항상 λ‹€ μͺ½μ— μžˆμŠ΅λ‹ˆλ‹€. 

λ‹€λŒ€μΌ 단방ν–₯ μ–‘λ°©ν–₯ 맀핑은 이 μ „ ν¬μŠ€νŒ…μ—μ„œ λ‹€λ£¨μ—ˆμ§€λ§Œ, λ‹€μ‹œ ν•œ 번 λ‹€λ£¨κ² μŠ΅λ‹ˆλ‹€.

 

[JPA] 연관관계 λ§€ν•‘ - μ–‘λ°©ν–₯ 연관관계

λ°”λ‘œ 이전 ν¬μŠ€νŒ…μ—μ„œ 단방ν–₯ 연관관계에 λŒ€ν•΄ μ•Œμ•„λ³΄μ•˜μŠ΅λ‹ˆλ‹€. 단방ν–₯ 연관관계λ₯Ό κ°„λ‹¨νžˆ 짚고 λ„˜μ–΄κ°€μžλ©΄, Member β–ΆοΈŽ Team의 단방ν–₯ 연관관계λ₯Ό κ°€μ§ˆ λ•Œ Member μ—”ν‹°ν‹°μ—μ„œλŠ” Team μ—”ν‹°ν‹°λ₯Ό μ•Œ 수

rlaehddnd0422.tistory.com


λ‹€λŒ€μΌ 단방ν–₯  N ➑️ 1

Member (N) ➑️ Team (1) 

Member Entity

@Entity
public class Member{
    
    @Id @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

    private String name;

    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Team team;

}
  • @JoinColumn으둜 μ°Έμ‘°ν•  μ™Έλž˜ 킀와 λ§€ν•‘, 즉 Memberκ°€ μ—°κ΄€κ΄€κ³„μ˜ 주인이며 νšŒμ› ν…Œμ΄λΈ”μ˜ TEAM_ID μ™Έλž˜ν‚€λ₯Ό κ΄€λ¦¬ν•©λ‹ˆλ‹€.

Team Entity

@Entity
public class Team{
    
    @Id @GeneratedValue
    @Column(name = "TEAM_ID")
    private Long id;

    private String name;
}
  • 단방ν–₯ μ—°κ΄€κ΄€κ³„μ΄λ―€λ‘œ @OneToMany μ‚¬μš©ν•˜μ—¬ members ν•„λ“œλ₯Ό μ„€μ •ν•˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.

νšŒμ›μ€ Member.team으둜 νŒ€ μ—”ν‹°ν‹°λ₯Ό μ‘°νšŒν•  수 μžˆμ§€λ§Œ, λ°˜λŒ€λ‘œ νŒ€μ—λŠ” νšŒμ›μ„ μ°Έμ‘°ν•˜λŠ” ν•„λ“œκ°€ μ—†λŠ” 단방ν–₯ μ—°κ΄€κ΄€κ³„μž…λ‹ˆλ‹€.


λ‹€λŒ€μΌ μ–‘λ°©ν–₯ N ↔️ 1

Member (N) ↔️ Team (1) μ–‘λ°©ν–₯ λ§€ν•‘μ—μ„œλŠ” 항상 "λ‹€" μͺ½μ΄ μ™Έλž˜ν‚€λ₯Ό κ΄€λ¦¬ν•˜λŠ” μ—°κ΄€κ΄€κ³„μ˜ 주인이 λ©λ‹ˆλ‹€.

Member Entity

@Entity
public class Member{
    
    @Id @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

    private String name;

    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Team team;

}
  • 변경점 X

Team Entity

@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>();
    
    public void addMember(Member member){
    	this.members.add(member);
        if(member.getTeam() != this){
        	member.setTeam(this);
        }
}
  • @OneToMany둜 μ–‘λ°©ν–₯ 연관관계 λ§€ν•‘ + mappedBy 속성 μΆ”κ°€
  • νŽΈμ˜λ©”μ†Œλ“œ addMember(member) 둜 μ–‘λ°©ν–₯ μžλ™ λ§€ν•‘ μ„€μ •

μΌλŒ€λ‹€(1:N)

μΌλŒ€λ‹€ κ΄€κ³„λŠ” λ‹€λŒ€μΌ κ΄€κ³„μ˜ λ°˜λŒ€ λ°©ν–₯μž…λ‹ˆλ‹€. μΌλŒ€λ‹€ κ΄€κ³„λŠ” μ—”ν‹°ν‹°λ₯Ό ν•˜λ‚˜ 이상 μ°Έμ‘°ν•  수 μžˆμœΌλ―€λ‘œ μžλ°” μ»¬λ ‰μ…˜μΈ Collection, List, Set, Map쀑에 ν•˜λ‚˜λ₯Ό μ‚¬μš©ν•΄μ•Ό ν•©λ‹ˆλ‹€.


μΌλŒ€λ‹€ 단방ν–₯ 1 ➑️ N  

보톡은 μžμ‹ μ΄ λ§€ν•‘ν•œ ν…Œμ΄λΈ”μ˜ μ™Έλž˜ν‚€λ₯Ό κ΄€λ¦¬ν•˜μ§€λ§Œ,  μΌλŒ€λ‹€ κ΄€κ³„μ—μ„œ "일"이 μ™Έλž˜ν‚€ κ΄€λ¦¬μž, μ—°κ΄€κ΄€κ³„μ˜ 주인이 되렀면 μ™Έλž˜ν‚€λŠ” 항상 "λ‹€" μͺ½μ— 있기 λ•Œλ¬Έμ— μ°Έμ‘° ν•„λ“œκ°€ μžˆλŠ” "λ‹€" ν…Œμ΄λΈ”κ³Ό 맀핑을 ν•΄μ£Όμ–΄μ•Ό ν•˜λŠ” νŠΉμ΄ν•œ λͺ¨μŠ΅μ΄ λ‚˜νƒ€λ‚©λ‹ˆλ‹€.

  

Team Entity

@Entity
public class Team{
	
    @Id @GeneratedValue
    @Column(name = "TEAM_ID")
    private Long id;

    private String name;

    @OneToMany
    @JoinColumn(name = "TEAM_ID") // MEMBER ν…Œμ΄λΈ”μ˜ TEAM_ID (FK)
    private List<Member> members = new ArrayList<Member>();

 

Member Entity

@Entity
public class Member{
    
    @Id @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

    private String name;
	
}

μΌλŒ€λ‹€ 단방ν–₯ 관계λ₯Ό λ§€ν•‘ν•  λ•ŒλŠ” @JoinColumn을 λ°˜λ“œμ‹œ λͺ…μ‹œν•΄μ•Ό ν•©λ‹ˆλ‹€.

κ·Έλ ‡μ§€ μ•ŠμœΌλ©΄, JPAλŠ” μ—°κ²° ν…Œμ΄λΈ”μ„ 쀑간에 두고 연관관계λ₯Ό κ΄€λ¦¬ν•˜λŠ” 쑰인 ν…Œμ΄λΈ” μ „λž΅μ„ 기본으둜 μ‚¬μš©ν•΄μ„œ λ§€ν•‘ν•©λ‹ˆλ‹€.

 

μΌλŒ€λ‹€ 단방ν–₯ λ§€ν•‘μ˜ 단점

  • λ§€ν•‘ν•œ 객체과 κ΄€λ¦¬ν•˜λŠ” μ™Έλž˜ ν‚€κ°€ λ‹€λ₯Έν…Œμ΄λΈ”에 μžˆμ–΄ 연관관계 처리λ₯Ό μœ„ν•œ UPDATE SQL을 μΆ”κ°€μ μœΌλ‘œ μ‹€ν–‰ν•΄μ•Ό ν•©λ‹ˆλ‹€.
  • Team μ—”ν‹°ν‹°λ₯Ό μ €μž₯ν•  λ•Œ Team.members의 μ°Έμ‘° 값을 ν™•μΈν•΄μ„œ νšŒμ› ν…Œμ΄λΈ”μ— μžˆλŠ” TEAM_ID μ™Έλž˜ ν‚€λ₯Ό μ—…λ°μ΄νŠΈν•©λ‹ˆλ‹€.
  • ν‚€ 관리가 λ³΅μž‘ν•˜κ³  λ‹¨μˆœν•˜μ§€ μ•ŠκΈ° λ•Œλ¬Έμ— μΌλŒ€λ‹€ 단방ν–₯ λ§€ν•‘λ³΄λ‹€λŠ” λ‹€λŒ€μΌ μ–‘λ°©ν–₯ 맀핑을 μ‚¬μš©ν•©μ‹œλ‹€.

μΌλŒ€λ‹€ μ–‘λ°©ν–₯ λ§€ν•‘ 

μ–‘λ°©ν–₯ λ§€ν•‘μ—μ„œ @OneToManyλŠ” μ—°κ΄€κ΄€κ³„μ˜ 주인이 될 수 μ—†κΈ° λ•Œλ¬Έμ—, μΌλŒ€λ‹€ μ–‘λ°©ν–₯ 맀핑은 μ‘΄μž¬ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. 


μΌλŒ€μΌ(1:1)

μΌλŒ€μΌ κ΄€κ³„λŠ” μ–‘μͺ½μ΄ μ„œλ‘œ ν•˜λ‚˜μ˜ κ΄€κ³„λ§Œ κ°€μ§‘λ‹ˆλ‹€. 예λ₯Ό λ“€μ–΄ νšŒμ›μ€ ν•˜λ‚˜μ˜ μ‚¬λ¬Όν•¨λ§Œ μ‚¬μš©ν•˜κ³  사물함도 ν•˜λ‚˜μ˜ νšŒμ›μ— μ˜ν•΄μ„œλ§Œ μ‚¬μš©λ©λ‹ˆλ‹€.

νŠΉμ§•

  • μΌλŒ€μΌ κ΄€κ³„μ˜ λ°˜λŒ€λ„ μΌλŒ€μΌ 관계.
  • ν…Œμ΄λΈ” κ΄€κ³„μ—μ„œ μΌλŒ€λ‹€, λ‹€λŒ€μΌμ€ 항상 λ‹€μͺ½μ΄ μ™Έλž˜ν‚€λ₯Ό κ°€μ§‘λ‹ˆλ‹€. ν•˜μ§€λ§Œ μΌλŒ€μΌ κ΄€κ³„μ—μ„œλŠ” μ£Ό ν…Œμ΄λΈ”μ΄λ‚˜ λŒ€μƒ ν…Œμ΄λΈ” λ‘˜ 쀑 μ–΄λŠκ³³μ—μ„œλ‚˜ μ™Έλž˜ν‚€λ₯Ό κ°€μ§ˆ 수 μžˆμŠ΅λ‹ˆλ‹€.

μ£Ό ν…Œμ΄λΈ”μ— μ™Έλž˜ν‚€ μ„€μ •

:  μ£Ό 객체가 λŒ€μƒ 객체λ₯Ό μ°Έμ‘°ν•˜λŠ” κ²ƒμ²˜λŸΌ μ£Ό ν…Œμ΄λΈ”μ— μ™Έλž˜ν‚€λ₯Ό 두고 λŒ€μƒ ν…Œμ΄λΈ”μ„ μ°Έμ‘°ν•˜λŠ” λ°©μ‹μž…λ‹ˆλ‹€.

일반적으둜 이 방식을 μ±„νƒν•©λ‹ˆλ‹€. JPA도 μ£Ό ν…Œμ΄λΈ”μ— μ™Έλž˜ ν‚€κ°€ 있으면 μ’€ 더 νŽΈλ¦¬ν•˜κ²Œ λ§€ν•‘ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

  • Member - μ£Ό ν…Œμ΄λΈ”
  • Locker - λŒ€μƒ ν…Œμ΄λΈ”

단방ν–₯ 

μΌλŒ€μΌ κ΄€κ³„μ΄λ―€λ‘œ 객체 맀핑에 @OneToOne을 μ‚¬μš©ν•΄ 연관관계λ₯Ό λ§€ν•‘ν•΄μ€λ‹ˆλ‹€.

@Entity
public class Member{

    @Id @GeneratedValue
    @Column(name="MEMBER_ID")
    private Long id;

    private String username;

    @OneToOne
    @JoinColumn(name = "LOCKER_ID")
    private Locker locker;

}

@Entity
public class Locker{

    @Id @GeneratedValue
    @Column(name="LOCKER_ID")
    private Long id;

    private String name;
}

μ–‘λ°©ν–₯

λ°˜λŒ€ λ°©ν–₯에도 μΆ”κ°€μ μœΌλ‘œ @OneToOne을 μ„€μ •ν•΄μ€λ‹ˆλ‹€. 단, μ–‘λ°©ν–₯ μ—°κ΄€κ΄€κ³„μ˜ λŒ€μƒμ΄ λ˜λŠ” ν…Œμ΄λΈ”μ΄λ―€λ‘œ λ‹€λŒ€μΌ μ–‘λ°©ν–₯ 연관관계 λ§€ν•‘κ³Ό λ§ˆμ°¬κ°€μ§€λ‘œ mappedBy 속성을 μΆ”κ°€ν•΄μ£Όμ–΄μ•Όν•©λ‹ˆλ‹€.

// 주 객체
@Entity
public class Member{

    @Id @GeneratedValue
    @Column(name="MEMBER_ID")
    private Long id;

    private String username;

    @OneToOne
    @JoinColumn(name = "LOCKER_ID")
    private Locker locker;

}


// λŒ€μƒ 객체
@Entity
public class Locker{

    @Id @GeneratedValue
    @Column(name="LOCKER_ID")
    private Long id;

    private String name;
    
    @OneToOne(mappedBy = "locker")
    private Member member; 
}

λŒ€μƒ ν…Œμ΄λΈ”μ— μ™Έλž˜ν‚€ μ„€μ •

μ΄λ²ˆμ—λŠ” λŒ€μƒ ν…Œμ΄λΈ”μ— μ™Έλž˜ ν‚€κ°€ μžˆλŠ” μΌλŒ€μΌ 관계λ₯Ό μ•Œμ•„λ³΄κ² μŠ΅λ‹ˆλ‹€.

 

단방ν–₯ 

JPAμ—μ„œ μΌλŒ€λ‹€ 단방ν–₯ 관계은 λŒ€μƒ ν…Œμ΄λΈ”λ‘œ 연관관계λ₯Ό 맀핑을 μ§€μ›ν•˜μ§€λ§Œ, λŒ€μƒ ν…Œμ΄λΈ”μ— μ™Έλž˜ν‚€κ°€ μžˆλŠ” μΌλŒ€μΌ 단방ν–₯ κ΄€κ³„λŠ” 맀핑을 μ§€μ›ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

이 λ•ŒλŠ” 단방ν–₯ 관계λ₯Ό μ•„λž˜μ™€ 같이 Locker ➑️ Member둜 μˆ˜μ •ν•˜κ±°λ‚˜, μ–‘λ°©ν–₯ κ΄€κ³„λ‘œ λ§Œλ“€κ³  Lockerλ₯Ό μ—°κ΄€κ΄€κ³„μ˜ 주인으둜 μ„€μ •ν•΄μ•Ό ν•©λ‹ˆλ‹€.

 

 Locker ➑️ Member둜 μˆ˜μ •

// 주 객체
@Entity
public class Locker{

    @Id @GeneratedValue
    @Column(name="LOCKER_ID")
    private Long id;

    private String name;
    
    @OneToOne
    private Member member; 
}


// λŒ€μƒ 객체
@Entity
public class Member{

    @Id @GeneratedValue
    @Column(name="MEMBER_ID")
    private Long id;

    private String username;

    @OneToOne(mappedBy = "member")
    @JoinColumn(name = "LOCKER_ID")
    private Locker locker;
}

 

μ–‘λ°©ν–₯ κ΄€κ³„λ‘œ μˆ˜μ •

@Entity
public class Member{

    @Id @GeneratedValue
    @Column(name="MEMBER_ID")
    private Long id;

    private String username;

    @OneToOne
    @JoinColumn(name = "LOCKER_ID")
    private Locker locker;

}

@Entity
public class Locker{

    @Id @GeneratedValue
    @Column(name="LOCKER_ID")
    private Long id;

    private String name;
    
    @OneToOne(mappedBy = "locker")
    private Member member;
}

μ–‘λ°©ν–₯

λŒ€μƒ ν…Œμ΄λΈ”μ— μ™Έλž˜ν‚€κ°€ μžˆλŠ” μ–‘λ°©ν–₯ 관계λ₯Ό μ•Œμ•„λ΄…μ‹œλ‹€.

// 주 객체
@Entity
public class Member{

    @Id @GeneratedValue
    @Column(name="MEMBER_ID")
    private Long id;

    private String username;

    @OneToOne(mappedBy = "member")
    private Locker locker;

}


// λŒ€μƒ 객체
@Entity
public class Locker{

    @Id @GeneratedValue
    @Column(name="LOCKER_ID")
    private Long id;

    private String name;
    
    @OneToOne
    @JoinColumn(name = "MEMBER_ID")
    private Member member; 
}
  • μΌλŒ€μΌ λ§€ν•‘μ—μ„œ λŒ€μƒ ν…Œμ΄λΈ”μ— μ™Έλž˜ν‚€λ₯Ό 두고 μ‹ΆμœΌλ©΄ μ–‘λ°©ν–₯으둜 λ§€ν•‘ν•˜κ³ , λŒ€μƒ 엔티티인 Lockerλ₯Ό μ—°κ΄€κ΄€κ³„μ˜ 주인으둜 λ§Œλ“€μ–΄μ„œ Locker ν…Œμ΄λΈ”μ˜ μ™Έλž˜ν‚€λ₯Ό κ΄€λ¦¬ν•˜λ„λ‘ μ„€μ •ν•˜λ©΄ λ©λ‹ˆλ‹€.

 

<정리>

  • λ‹€λŒ€μΌ, μΌλŒ€λ‹€, μΌλŒ€μΌ 단방ν–₯ μ–‘λ°©ν–₯ 연관관계에 λŒ€ν•΄ μ•Œμ•„λ³΄μ•˜μŠ΅λ‹ˆλ‹€.
  • μΌλŒ€λ‹€ 단방ν–₯ 관계λ₯Ό λ§€ν•‘ν•  λ•ŒλŠ” @JoinColumn을 λ°˜λ“œμ‹œ λͺ…μ‹œν•΄μ•Ό ν•©λ‹ˆλ‹€.
  • μΌλŒ€μΌ λ§€ν•‘μ—μ„œ μ™Έλž˜ν‚€λŠ” μ£Ό ν…Œμ΄λΈ”μ—λ„ λŒ€μƒ ν…Œμ΄λΈ”μ—μ„œλ„ 관리할 수 μžˆμŠ΅λ‹ˆλ‹€.
  • JPAλŠ” μΌλŒ€λ‹€ 단방ν–₯ 맀핑은 μ§€μ›ν•˜μ§€λ§Œ, μΌλŒ€μΌ 단방ν–₯ 맀핑은 μ§€μ›ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

<참고자료>