본문 바로가기
Spring/JPA

[JPA 연관관계매핑] 단방향, 양방향 연관 관계 매핑

by 행운의나무 2021. 4. 11.
728x90
반응형

참고 : https://gonyda.tistory.com/12

연관관계란?

객체의 참조와 테이블의 외래키를 매핑하는 것을 의미합니다.

JPA에서는 JDBC를 사용했을 때와 달리 연관관계에 있는 상대 테이블의 외래키를 멤버변수로 갖지 않고, 엔티티 객체 자체를 통째로 참조합니다.

//Mybatis
private Intger post;

//JPA
private Post post;

연관관계를 사용하는 이유

객체가 서로 관계가 있는 경우, 객체를 참조하여 연관된 내용들을 확인할 수 있습니다.

테이블의 입장에서는 연관관계를 통해 불필요한 칼럼을 생성하지 않아도 됩니다.

예를 들어, Team Entity와 Member Entity가 연관관계에 있다고 한다면 Member객체에 연관된 Team의 정보를 확인할 수 있습니다.( ex. 리오넬 메시 의 팀은 PSG)


단방향 관계 매핑, 양방향 관계 매핑, 연관관계 주인

단방향 관계 매핑 : 두 Entity의 관계가 있을 때, 한쪽의 Entity에서만 참조하고 있는 것을 의미합니다.

@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @ManyToOne //여러명의 멤버는 하나의 팀에 포함됩니다.
    @JoinColumn(name = "team_id") //team의 id가 외래키가 되어 Member와 연관 관계 매핑이 됩니다. Member는 연관관계의 주인이됩니다.
    private Team team;
}
// ======================================
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
public class Team {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    private String description; //팀 설명
}

양방향 관계 매핑 : 두 Entity의 관계가 있을 때, 양쪽의 Entity가 서로 참조하고 있는 것을 의미합니다. => 서로 단방향 관계 매핑을 통해 양방향 관계를 맺습니다.

@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
public class Team {
   //양방향관계매핑
    @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    private String description; //팀 설명핑

    //1:N의 연결. Team은 여러명의 Member를 가질 수 있습니다.
    //fetch : Lazy는 Member에서 정보를 가져올 때, Team의 정보를 가져오는 것을 지연로딩 시킵니다. => 실제로 Team의 객체를 이용할 때만 Team Entity 조회
    //연결할 테이블 명인 mappedBy를 반드시 적어줘야합니다. 주인은 mappedBy를 사용할 수 없습니다. => mappedBy가 없는게 주인
        @OneToMany(fetch = FetchType.LAZY, mappedBy = "team")
    private List<Member> memberList;
}
  • fetch Eager 전략
    • member.getTeam()을 할 때 member와 team의 모든 엔티티 정보를 동시에 가져옵니다.
  • fetch Lazy 전략
    • member.getName() => Team 엔티티를 가져오지 않음.
    • member.getTeam() => Team에 관한 동작이 필요할 때 프록시 객체 조회
    • member.getTeam().getDescription() => 쿼리 실행으로 정보를 조회

연관관계의 주인 : 외래키를 가지고 있는 테이블이 연관관계의 주인이 되며, 주인만 외래키를 관리할 수 있습니다.


단방향 관계매핑 예제

시나리오

  1. Team, Member의 Repository를 만듭니다.
  2. MySql과 연동하여 데이터를 넣습니다.object references an unsaved transient instance ...
    2-2. Member를 생성합니다.
    2-1. Team을 먼저 생성해야 합니다. Member는 Team의 id값이 필요하기 때문에 Member를 먼저 생성하면 아래와 같은 에러를 볼 수 있습니다.
  1. MemberRepositoryTest에서 값들을 읽어봅니다.

1. Team, Member의 Repository를 만듭니다.

public interface MemberRepository extends JpaRepository<Member, Long> {}

public interface TeamRepository extends JpaRepository<Team, Long> {}

2. MySql과 연동하여 데이터를 넣습니다.

application.yml 설정

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/DatabaseName?allowPublicKeyRetrieval=true&useSSL=false&useUnicode=true&serverTimezone=Asia/Seoul
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: username
    password: password

  jpa:
    database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
    generate-ddl: true #앱실행 시 create 여부 설정
    database: mysql
    show-sql: true
    hibernate:
      ddl-auto: update

2-1. Team 생성

@SpringBootTest
class TeamRepositoryTest {

    @Autowired
    private TeamRepository teamRepository;

    @Test
    public void create(){
        Team team = Team.builder()
                .name("Team1")
                .description("Team1입니다.")
                .build();

        teamRepository.save(team);

        List<Team> newTeam = teamRepository.findAll();

        assertNotNull(newTeam);

    }
}

2-2. Member 생성

@SpringBootTest
class MemberRepositoryTest {

    @Autowired
    private MemberRepository memberRepository;

    @Autowired
    private TeamRepository teamRepository;

    @Test
    public void create() {

               //TeamRepository를 이용해 팀을 조회하고, 첫번째 값을 가져옵니다.
        Team team1 = teamRepository.findAll().get(0);

        Member member = Member.builder()
                .name("m1")
                .team(team1) //가져온 팀 객체를 저장합니다.
                .build();

        memberRepository.save(member);

        List<Member> newMember = memberRepository.findAll();
        assertNotNull(newMember);
    }
}

 

Member Table
Team Table

3. MemberRepositoryTest에서 값들을 읽어봅니다.

@Test
public void read(){
  List<Member> members = memberRepository.findAll();

  Member member = members.get(0);

  //member객체에서 getTeam()을 할 수 있고, 체이닝을 통해 getDescription()을 요청할 수 있습니다.
  System.out.println(member.getTeam().getDescription()); //Team1입니다.
}

양방향 관계 매핑 예제

목표 : Team에 소속된 member를 확인하고 싶다.

주의 사항 : team에서 memberList를 호출 시 Lazy Fetch 전략으로 인해 아래와 같은 에러가 발생합니다.

failed to lazily initialize a collection of role : ... , could not initialize proxy - no Session

해당 에러는 지연된 로딩 전략으로 인해 Session의 범위를 벗어나기 때문에 생긴 에러이며 해결 방법은 Eagar Fetch 전략을 이용하거나 아래의 예와 같이 @Transaction 어노테이션을 이용해 Session의 해제를 지연시켜주면 됩니다.

더 자세한 내용들은 이 곳을 참고하세요. (https://www.baeldung.com/hibernate-initialize-proxy-exception)

@SpringBootTest
class TeamRepositoryTest {

    @Autowired
    private TeamRepository teamRepository;

    @Autowired
    private MemberRepository memberRepository;

      @Transactional //없으면 에러
    @Test
    public void read(){

        //team에 소속된 member를 얻고 싶다. memberList는 lazy 전략을 썼다

        List<Team> teams = teamRepository.findAll();
        Team team1 = teams.get(0);

        List<Member> memberList = team1.getMemberList();

        for (Member m : memberList) {
            System.out.println(m.getName());
        }
    }
}

/*
m1
m2
m3
*/

쿠팡으로 연결 클릭

 

제주삼다수 그린

COUPANG

www.coupang.com

파트너스 활동을 통해 일정액의 수수료를 제공받을 수 있음

반응형