참고 : 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() => 쿼리 실행으로 정보를 조회
연관관계의 주인 : 외래키를 가지고 있는 테이블이 연관관계의 주인이 되며, 주인만 외래키를 관리할 수 있습니다.
단방향 관계매핑 예제
시나리오
- Team, Member의 Repository를 만듭니다.
- MySql과 연동하여 데이터를 넣습니다.object references an unsaved transient instance ...
2-2. Member를 생성합니다.
2-1. Team을 먼저 생성해야 합니다. Member는 Team의 id값이 필요하기 때문에 Member를 먼저 생성하면 아래와 같은 에러를 볼 수 있습니다.
- 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);
}
}
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
*/
파트너스 활동을 통해 일정액의 수수료를 제공받을 수 있음
'Spring > JPA' 카테고리의 다른 글
[Lock] 동시성 제어를 위한 JPA Lock 3. 비관적 락(Pessimistic Lock) (0) | 2022.12.08 |
---|---|
[Lock] 동시성 제어를 위한 JPA Lock 2. 낙관적 락(Optimistic Lock) (0) | 2022.12.07 |
[Lock] 동시성 제어를 위한 JPA Lock 1. 격리수준과 잠금(락) (0) | 2022.12.06 |
식별 관계 비식별 관계 (0) | 2021.07.13 |
[JPA] Hibernate의 ddl-auto (0) | 2021.04.09 |