JPA/JPA 기본

15. 연관관계 매핑

sdafdq 2023. 10. 22. 18:38

저번 예제에서 Entity를 테이블 중심으로 설계를 했더니, 객체 입장에서 실제 참조를 가지지 않게 되었다.

 

어떻게 객체지향 적으로 설계를 할 수 있을까?

 

어떻게 실제 Order가 Member 객체를 참조하도록 설계할 수 있을까?

 

 

이렇게 관계형 DB와 객체 사이의 패러다임 불일치를 어떻게 해결할 수 있을까?

 

 

 

객체 : 주소, 참조를 통해 연관관계 설정

테이블 : 외래키를 통해 연관관계 설정

 

 

결국, 객체의 참조 부분과 테이블의 외래키 부분을 매핑 시켜야 한다.

 

이런 상태니,

음 근데 객체는 Member를 직접 가지고 있는 형태이고,

 

그러니까, DB의 저런 관계를 가져와서 객체로 만들어야 하는 것 인데,

일단 솔직히 insert는 쉬워보이고..

 

쿼리를 그냥 Order 쿼리 날릴 때 Order Table에 미리 memberId 있고 거기에다가 member.getId해서 넣어서 날리면 되고,

또 Member table에도 추가로 날리면 되는거니.

 

그럼 DB에서 객체 가져올 때는,

일단 Order도 MemberId를 가지고 있어야 하고,

1. 우선은 Order Table에서 데이터를 가져와서

2. Order의 MemberId로 Member Table에서 Member를 가져온 다음에

3. Member를 객체로 만들어서,

4. 그 다음 Order객체로 만들면서 Member를 넣어줌.

 

 

대충 그렇게 하면 될거 같긴 함.

 

일단은, 배울 건 

방향 : 단방향, 양방향

다중성 : 다대일, 일대다, 일대일, 다대다

연관관계의 주인 : 양방향 연관관계일 경우 누가 주체적으로 관리할 것인지.

 

 

 

객체지향 설계의 목표는 자율적인 객체들의 협력 공동체를 만드는 것이다.

서로간에 유연하고 자율 적 이지만 한 목표를 바라보는.

 

 

 

 

예제 시나리오

회원과 팀이 있다.

회원은 하나의 팀에만 소속될 수 있다.

회원 : 팀은 다 대 일 관계이다.

 

테이블에 의존적인 모델링

 

보통 이렇게 다대일 관계에서 다 쪽이 일의 id를 가지고 있음.

 

 

 

코드

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

    @Column(name="USERNAME", nullable = false)
    private String name;

    @Column(name="TEAM_ID")
    private Long teamId;
}

@GeneratedValue는 기본값이 AUTO인데, 로그 보니까 시퀀스로 됨.

ddl.auto가 create라 시퀀스도 자동 생성해서 그걸로 함.

필드에 열명 명시해줘서 묶어주고

name은 nullable false 로 해 줘서 not null로

 

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

이것도 마찬가지로 시퀀스로 자동선택 됨.

마찬가지로 테이블의 열이름 명시해줘서 묶어주고

 

Team team = new Team();
team.setName("TeamA");

em.persist(team);
System.out.println("persist team");
Member member = new Member();
member.setName("member1");
member.setTeamId(team.getId());
em.persist(member);

tx.commit();

등록해주는 코드

먼저 팀을 만들고, persist해서 영속성 컨텍스트에 넣어줌. id가 없으면 영속성 컨텍스트에 못 들어감. id를 얻어온 후에 들어올 수 있음.

이 경우, 영속성 컨텍스트에 넣기 전에 시퀀스에서 값을 얻어오는 쿼리를 날렸음. 그 후 영속성 컨텍스트에 넣었음.

member도 마찬가지.

그렇게 둘 다 영속성 컨텍스트에 있다가,

commit 순간에 둘 다 insert 쿼리 날림.

 

persist에 바로 쿼리 날리는 건 @GeneratedValue의 전략을 IDENTITY로 해서 DB에 위임했을 경우에만.

 

저거 실행 후

select * from member m join team t on m.team_id = t.team_id;

select * from 테이블명 별명 join 테이블명 별명 on 별명.열명 = 별명.열명

select * from 테이블명

조회 모든것을 로부터 ~~테이블

인데, 추가로 join해서 가져오는 거

별명은 별명 붙여서 저렇게 구분해서 접근할 수 있게 끔.

select 가져올것 from 테이블명 별명 join 추가로가져올테이블 별명 on 조건

 

이렇게 조인 해서 가져와 봄.

잘 가져와짐.

이거를 코드에서 가져올 경우..

Long memberId = 1L;
Member member = em.find(Member.class, memberId);
Team team = em.find(Team.class, member.getTeamId());
System.out.println("team.getName() = " + team.getName());

먼저 member 가져온 다음에,

member가 가지고 있는 teamId로 Team을 가져옴.

 

객체를 테이블에 맞추어 설계를 하면 협력관계를 만들 수 없음.

 

테이블은 외래키로 조인하여 연관관계를 맺음.

객체는 참조를 통해서 연관관계를 맺음.

 

패러다임 불일치

 

 

그럼 이제 한번 객체는 객체지향적 모델링을 해 보겠음.

우선 단방향 연관관계

Member가 Team을 바로 참조하고 있음.

 

먼저, 도메인

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

    @Column(name="USERNAME", nullable = false)
    private String name;

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

Team이라는 것을 @ManyToOne 다 대 일

연관관계 라는 것을 명시해 줌. join의 기준은 TEAM_ID이고.

저건 Member 테이블의 TEAM_ID열을 말하는 거임. Member테이블의 외래키 TEAM_ID를 참조.

그리고 TEAM 테이블 에서는 Team의 id를 참조하겠지.

@JoinColumn은 내가 가지고 있는 외래키와, 그 외래키의 주인의 primary key와의 관계.

Team도 엔티티니 알아서 id그런 거 다 앎.

@ManyToOne 이것도 명시를 해줘야 함.

앞이 나고 참조가 뒤임.

Many가 나고 One이 Team

이걸 명시해 줘야 개발자가 관계를 이해하는 것은 차치 하더라도, DB가 아, 그럼 뭐 첫번째꺼 하나만 가져오면 되겠네? 

뭐 이런 식으로 최적화 되지 않을까? 여튼 꼭 명시해 줘야 함.

 

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

team 손댄 거 없음.

 

 

 

코드

Team team = new Team();
team.setName("TeamA");
em.persist(team);

Member member = new Member();
member.setName("member1");
member.setTeam(team);
em.persist(member);

tx.commit();

저장시.

팀을 만들고 영속성 컨텍스트에 등록.

 

그 다음 그 팀 자체를 member에 등록.

member를 영속성 컨텍스트에 등록.

 

잘 됨.

 

알아서 테이블에 teamId에 team객체의 id를 가져와서 넣어줌.

그 다음 영속성 컨텍스트에 넣고 

commit 시점에 insert 쿼리 둘다 날림

Hibernate: 
    select
        next value for Team_SEQ
Hibernate: 
    select
        next value for Member_SEQ
Hibernate: 
    /* insert for
        hellojpa.Team */insert 
    into
        Team (name,TEAM_ID) 
    values
        (?,?)
Hibernate: 
    /* insert for
        hellojpa.Member */insert 
    into
        Member (USERNAME,TEAM_ID,MEMBER_ID) 
    values
        (?,?,?)

 

 

조회시

Long memberId = 1L;
Member member = em.find(Member.class, memberId);
System.out.println("team name = " + member.getTeam().getName());

tx.commit();

이렇게 하면, 그냥 member 가져올 때 team도 알아서 join해서 가져옴.

Hibernate: 
    select
        m1_0.MEMBER_ID,
        m1_0.USERNAME,
        t1_0.TEAM_ID,
        t1_0.name 
    from
        Member m1_0 
    left join
        Team t1_0 
            on t1_0.TEAM_ID=m1_0.TEAM_ID 
    where
        m1_0.MEMBER_ID=?

 

만약 팀을 수정하고 싶다고 하면

Member findedMember = em.find(Member.class, 1L);
Team findedTeam = em.find(Team.class, 2L);
findedMember.setTeam(findedTeam);
System.out.println("findedMember = " + findedMember.getTeam().getName());

Member 테이블에서는 그냥 외래키를 바꾸는 것 뿐이니..

 

 

'JPA > JPA 기본' 카테고리의 다른 글

17. 양방향 연관관계 주의점  (0) 2023.10.23
16. 양방향 연관관계와 연관관계의 주인 1  (0) 2023.10.22
14. 예제  (0) 2023.10.22
13. 기본 키 매핑  (0) 2023.10.21
12. 필드와 컬럼 매핑  (0) 2023.10.21