beadss / jpa-study

jpa슽터디입니다
1 stars 2 forks source link

5장 요약 #10

Open 2xel opened 5 years ago

2xel commented 5 years ago

연관관계 매핑 기초

1. 단방향 연관관계

객체 연관관계 vs 테이블 연관관계 정리

객체 연관관계 테이블 연관관계
참조(주소)로 연관관계를 맺는다. 외래키로 연관관계를 맺는다.
a.getB().getC() JOIN
단방향 연관관계 양방향 연관관계
// 단방향 연관관계
class A{
    B b;
}
class B{}

// 양방향 연관관계
class A{
    B b;
}
class B{
    A a;
}

1) 순수한 객체 연관관계

// 회원 클래스
public class Member{
    private String id;
    private String username;

    private Team team;

    public void setTeam(Team team){
        this.team = team;
    }
    // Getter, Setter ...
}

// 팀 클래스
public class Team{
    private String id;
    private String name;
    // Getter, Setter ...
}
// 동작코드
public static void main(String[] args) {
    Member member1 = new Member("member1", "회원1");
    Member member2 = new Member("member2", "회원2");
    Team team = new Team("team1", "팀1");

    member1.setTeam(team1);
    member2.setTeam(team2);

    // 회원1이 속한 팀1을 조회할 수 있다.
    Team findTeam = member1.getTeam();
}

2) 테이블 연관관계

SELECT T.*
FROM MEMBER M
    JOIN TEAM T ON M.TEAM_ID = T.ID
WHERE M.MEMBER_ID = 'member1'

3) 객체 관계 매핑

// 매핑한 회원 엔티티
@Entity
public class Member{
    @Id
    @Column(name = "MEMBER_ID")
    private String id;

    private String username;

    // 연관관계 매핑
    @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;
    // Getter, Setter ...
}

4) @JoinColumn

속성 기능 기본값
name 매핑할 외래 키 이름 필드명 + _ + 참조하는 테이블의 기본 키 컬럼명
referencedColumnName 외래 키가 참조하는 대상 테이블의 컬럼명 참조하는 테이블의 기본키 컬럼명
foreignKey(DDL) 외래 키 제약조건을 직접 지정할 수 있다.
이 속성은 테이블을 생성할 때만 사용한다.
unique
nullable
insertable
updatable
columnDefinition
table
@Column의 속성과 같다.

5) @ManyToOne

속성 기능 기본값
optional false로 설정하면 연관된 엔티티가 항상 있어야 한다. true
fetch 글로벌 페치 전략을 설정한다. @ManyToOne=FetchType.EAGER
@OneToMany=FetchType.LAZY
cascade 영속성 전이 기능을 사용한다.
targetEntity 연관된 엔티티의 타입 정보를 설정한다.
이 기능은 거의 사용하지 않는다.
컬렉션을 사용해도 제네릭으로 타입 정보를 알 수 있다.

2. 연관관계 사용

1) 저장

public void testSave() {
    // 팀1 저장
    Team team1 = new Team("team1", "팀1");
    em.persist(team1);

    // 회원1 저장
    Member member1 = new Member("member1", "회원1");
    member1.setTeam(team1); // 연관관계 설정 member1 -> team1
    em.persist(member1);

    // 회원2 저장
    Member member2 = new Member("member2", "회원2");
    member2.setTeam(team1);
    em.persist(member2);
}
INSERT INTO TEAM(TEAM_ID, NAME) VALUES('team1', '팀1')
INSERT INTO MEMBER(MEMBER_ID, NAME, TEAM_ID) VALUES('member1', '회원1', 'team1')
INSERT INTO MEMBER(MEMBER_ID, NAME, TEAM_ID) VALUES('member2', '화원2', 'team2')

2) 조회

Member member = em.find(Member.class, "member1");
Team team = member.getTeam();   // 객체 그래프 탐색
System.out.println("팀 이름 = " + team.getName());

// 출력 결과 : 팀 이름 = 팀1
private static void queryLogicJoin(EntityManager em) {
    String jpql = "select m from Member m join m.team t where " + "t.name=:teamName";

    List<Member> resultList = 
        em.createQuery(jpql, Member.class)
            .setParameter("teamName", "팀1")
            .getResultList();

    for(Member member : resultList) {
        System.out.println("[query] member.username=" + member.getUsername());
    }
}
// 결과: [query] member.username=회원1
// 결과: [query] member.username=회원2
SELECT M.* FROM MEMBER MEMBER
INNER JOIN
    TEAM TEAM ON MEMBER.TEAM_ID = TEAM1_.ID
WHERE
    TEAM1_.NAME = '팀1'

3) 수정

private static void updateRelation(EntityManager em) {
    // 새로운 팀2
    Team team2 = new Team("team2", "팀2");
    em.persist(team2);

    // 회원1에 새로운 팀2 설정
    Member member = em.find(Member.class, "member1");
    member.setTeam(team2);
}
UPDATE MEMBER
SET
    TEAM_ID = 'team2', ...
WHERE
    ID = 'member1'

4) 연관관계 제거

private static void deleteRelation(EntityManager em) {
    Member member1 = em.find(Member.class, "member1");
    member1.setTeam(null);  // 연관관계 제거
}
UPDATE MEMBER
SET
    TEAM_ID = null, ...
WHERE
    ID = 'member1'

5) 연관된 엔티티 삭제

member1.setTeam(null);  // 회원1 연관관계 제거
member2.setTeam(null);  // 회원2 연관관계 제거
em.remove(team);        // 팀 삭제

3. 양방향 연관관계

1) 양방향 연관관계 매핑

// 매핑한 회원 엔티티
@Entity
public class Member{
    @Id
    @Column(name="MEMBER_ID")
    private String id;
    private String username;

    @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 ...
}
public void biDirection() {
    Team team = em.find(Team.class, "team1");
    List<Member> members = team.getMembers();   // (팀 -> 회원) 객체 그래프 탐색

    for(Member member : members) {
        System.out.println("member.username = " + member.getUsername());
    }

    // 결과
    // member.username = 회원1
    // member.username = 회원2
}

4. 연관관계의 주인

1) 양방향 매핑의 규칙

2) 연관관계의 주인

5. 양방향 연관관계 저장

public void testSave() {

    // 팀1 저장
    Team team1 = new Team("team1", "팀1");
    em.persist(team1;

    // 회원1 저장
    Member member1 = new Member("member1", "회원1");
    member1.setTeam(team1); // 연관관계 설정 member1 -> team1
    em.persist(member1);

    // 회원2 저장
    Member member2 = new Member("member2", "회원2");
    member2.setTeam(team1); // 연관관계 설정 member2 -> team1
    em.persist(member2);
}

6. 양방향 연관관계의 주의점

public void testSaveNonOwner() {

    // 회원1 저장
    Member member1 = new Member("member1", "회원1");
    em.persist(member1);

    // 회원2 저장
    Member member2 = new Member("member2", "회원2");
    em.persist(member2);

    Team team1 = new Team("team1", "팀1");
    // 주인이 아닌 곳만 연관관계 설정
    team1.getMember().add(member1);
    team1.getMember().add(member2);

    em.persist(team1);
}

1) 순수한 객체까지 고려한 양방향 연관관계

public void test순수한객체_양방향() {

    // 팀1
    Team team1 = new Team("team1", "팀1");
    Member member1 = new Member("member1", "회원1");
    Member member2 = new Member("member2", "회원2");

    member1.setTeam(team1); // 연관관계 설정 member1 -> team1
    member2.setTeam(team1); // 연관관계 설정 member2 -> team1

    List<Member> members = team1.getMembers();
    System.out.println("member.size = " + members.size());

    // 결과: member.size = 0
}
public void test순수한객체_양방향() {

    // 팀1
    Team team1 = new Team("team1", "팀1");
    Member member1 = new Member("member1", "회원1");
    Member member2 = new Member("member2", "회원2");

    member1.setTeam(team1);
    team1.getMembes().add(member1);

    member2.setTeam(team1);
    team1.getMembes().add(member2);

    List<Member> members = team1.getMembers();
    System.out.println("member.size = " + members.size());

    // 결과: members.size = 2
}
public void testORM_양방향() {

    // 팀1 저장
    Team team1 = new Team("team1", "팀1");
    em.persist(team1);

    Member member1 = new Member("member1", "회원1");

    // 양방향 연관관계 설정
    member1.setTeam(team1);
    team1.getMember().add(member1);
    em.persist(member1);

    Member member2 = new Member("member2", "회원2");

    // 양방향 연관관계 설정
    member2.setTeam(team1);
    team1.getMember().add(member2);
    em.persist(member2);
}

2) 연관관계 편의 메소드

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);
    }
}
...

3. 연관관계 편의 메소드 작성 시 주의사항

member1.setTeam(teamA); // 1
member1.setTeam(teamB); // 2
Member findMember = teamA.getMember();  // member1이 여전히 조회된다.
public void setTeam(Team team) {

    // 기존 팀과 관계를 제거
    if(this.team != null) {
        this.team.getMembers().remove(this);
    }
    this.team = team;
    team.getMembers().add(this);
}