본문 바로가기
Java/JPA

[JPA_Basic] 양방향 매핑 시, 값이 입력되지 않는 경우

by HJ0216 2023. 8. 13.

이 글은 김영한의 [자바 ORM 표준 JPA 프로그래밍 - 기본편] 수강하며 정리한 글입니다.

 

👉 기본 환경

- Language: Java

- DB: H2 Database

- IDE: IntelliJ

 

 

1. Entity 상태

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@Entity
public class Member {
 
    @Id @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;
 
    @Column(name = "USERNAME")
    private String name;
 
    @ManyToOne // Member : Team = Many : One
    @JoinColumn(name = "TEAM_ID"// JoinColum = FK
    private Team team;
    // FK가 있는 곳(Many to One에서 Many 부분)이 연관 관계의 주인
 
    public Long getId() {
        return id;
    }
 
    public void setId(Long id) {
        this.id = id;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public Team getTeam() {
        return team;
    }
 
    public void setTeam(Team team) {
        this.team = team;
    }
 
 
}
 
 
 

Member.java

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@Entity
public class Team {
 
    @Id @GeneratedValue
    @Column(name = "TEAM_ID")
    private Long id;
    private String name;
 
    @OneToMany(mappedBy = "team"// mappedBy: 상대 Entity의 FK field 값
    private List<Member> members = new ArrayList<>();
 
    public Long getId() {
        return id;
    }
 
 
    public void setId(Long id) {
        this.id = id;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public List<Member> getMembers() {
        return members;
    }
 
    public void setMembers(List<Member> members) {
        this.members = members;
    }
 
 
}
 
 
 

Team.java

 

Member entity와 Team entity가 양방향으로 매핑되어있음

Member : Team = 다 : 1 관계

 

 

2-1. 데이터 저장

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class Main {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
 
        EntityManager em = emf.createEntityManager();
 
        EntityTransaction tx = em.getTransaction();
        tx.begin();
 
 
        try {
 
            Member member = new Member();
            member.setName("Member1");
            em.persist(member);
 
            Team team = new Team();
            team.setName("TeamA");
            team.getMembers().add(member);
 
            em.persist(team);
 
            em.flush();
            em.clear();
            
            tx.commit();
        } catch (Exception e) {
            tx.rollback();
        } finally {
            em.close();
        }
 
 
        emf.close();
    }
 
}
 
 
 

- Member 객체 생성 후, persist

- Team 객체 생성 후, getter로 member list 호출 후 member 객체 추가

 

Team 객체에 Member를 매핑하였지만, Member 객체의 Team_id가 null값으로 입력됨

 

@OneToMany(mappedBy = "")

양방향 매핑에서 mappedBy 속성은 단순히 매핑되어있음을 안내해주는 역할일 뿐, 실제로 데이터를 매핑시켜주지 않음

 

 

2-2. 데이터 저장 - 수정

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class Main {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
 
        EntityManager em = emf.createEntityManager();
 
        EntityTransaction tx = em.getTransaction();
        tx.begin();
 
 
        try {
 
            Team team = new Team();
            team.setName("TeamA");
            em.persist(team);
 
            Member member = new Member();
            member.setName("Member1");
            member.setTeam(team);
            em.persist(member);
 
            em.flush();
            em.clear();
            
            tx.commit();
        } catch (Exception e) {
            tx.rollback();
        } finally {
            em.close();
        }
 
 
        emf.close();
    }
 
}
 
 
 

- Team 객체 생성 후, persist

- Member 객체 생성 및 team 객체 주입 후, persist

 

Member 객체에 매핑한 Team 객체의 Team_id가 입력됨

 

@ManyToOne
@JoinColumn(name = "TEAM_ID")

양방향 매핑에서 실제 데이터 입력을 담당하는 필드는 @Joincolumn이 선언되어있는 필드

 

 

3. 데이터 조회

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public class Main {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
 
        EntityManager em = emf.createEntityManager();
 
        EntityTransaction tx = em.getTransaction();
        tx.begin();
 
 
        try {
 
            Team team = new Team();
            team.setName("TeamA");
            em.persist(team);
 
            Member member = new Member();
            member.setName("Member1");
            member.setTeam(team);
            em.persist(member);
 
//            em.flush();
//            em.clear();
 
            Team findTeam = em.find(Team.class, team.getId());
            List<Member> members = findTeam.getMembers();
 
            System.out.println("==========");
 
            for(Member m : members){
                System.out.println("member: " + m.getName());
            }
 
            System.out.println("==========");
            
            tx.commit();
        } catch (Exception e) {
            tx.rollback();
        } finally {
            em.close();
        }
 
 
        emf.close();
    }
 
}
 
 
 

- flush(), clear()를 제거하고, 같은 트랜잭션 내에서 1차 캐시에 저장된 team과 member를 바로 조회할 경우,

    member 객체에 매핑된 team 객체를 조회할 수 있지만, team 객체에서 매핑된 member 조회할 수 없음

- 1차 캐시 상태에서는 Team 객체와 Member 객체의 관계가 명시적으로 코드로 작성한 부분을 제외하고는 인식되지 않음

    ▶ 양방향으로 객체 주입

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public class Main {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
 
        EntityManager em = emf.createEntityManager();
 
        EntityTransaction tx = em.getTransaction();
        tx.begin();
 
 
        try {
 
            Team team = new Team();
            team.setName("TeamA");
            em.persist(team);
 
            Member member = new Member();
            member.setName("Member1");
            member.setTeam(team);
            em.persist(member);
 
            team.getMembers().add(member);
 
//            em.flush();
//            em.clear();
 
            Team findTeam = em.find(Team.class, team.getId());
            List<Member> members = findTeam.getMembers();
 
            System.out.println("==========");
 
            for(Member m : members){
                System.out.println("member: " + m.getName());
            }
 
            System.out.println("==========");
            
            tx.commit();
        } catch (Exception e) {
            tx.rollback();
        } finally {
            em.close();
        }
 
 
        emf.close();
    }
 
}
 
 
 

 

 

4. 코드 리팩토링

양방향으로 값을 세팅해주는 과정(setTeam, addMember)에서 일부 값만 세팅하는 실수가 발생할 수 있음

▶ 연관관계 편의 메서드를 생성하여, 하나의 메서드로 2개의 값을 세팅

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class Main {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
 
        EntityManager em = emf.createEntityManager();
 
        EntityTransaction tx = em.getTransaction();
        tx.begin();
 
 
        try {
 
            Team team = new Team();
            team.setName("TeamA");
            em.persist(team);
 
            Member member = new Member();
            member.setName("Member1");
            member.changeTeam(team);
            em.persist(member);
 
            Team findTeam = em.find(Team.class, team.getId());
            List<Member> members = findTeam.getMembers();
 
            System.out.println("==========");
            for(Member m : members){
                System.out.println("member: " + m.getName());
            }
            System.out.println("==========");
            
            tx.commit();
        } catch (Exception e) {
            tx.rollback();
        } finally {
            em.close();
        }
 
 
        emf.close();
    }
 
}
 
 
 

- setTeam() 대신 changeTeam을 활용하여, setTeam과 addMember 수행

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
@Entity
public class Member {
 
    @Id @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;
 
    @Column(name = "USERNAME")
    private String name;
 
    @ManyToOne // Member : Team = Many : One
    @JoinColumn(name = "TEAM_ID"// JoinColum = FK
    private Team team;
 
 
    public Long getId() {
        return id;
    }
 
    public void setId(Long id) {
        this.id = id;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public Team getTeam() {
        return team;
    }
 
    public void setTeam(Team team) {
        this.team = team;
    }
 
 
    public void changeTeam(Team team) {
        this.team = team;
        team.getMembers().add(this);
    }
 
}
 
 
 

 

cf. changeTeam() 대신 addMember() 사용 가능

⭐ 단, 연관관계 편의 메서드는 동일한 역할을 수행하므로 하나의 메서드만 생성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
@Entity
public class Team {
 
    @Id @GeneratedValue
    @Column(name = "TEAM_ID")
    private Long id;
    private String name;
 
    @OneToMany(mappedBy = "team"// mappedBy: 상대 Entity의 FK field 값
    private List<Member> members = new ArrayList<>();
 
 
    public Long getId() {
        return id;
    }
 
    public void setId(Long id) {
        this.id = id;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public List<Member> getMembers() {
        return members;
    }
 
    public void setMembers(List<Member> members) {
        this.members = members;
    }
 
    public void addMember(Member member) {
        member.setTeam(this);
        this.getMembers().add(member);
    }
 
}