일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- Oracle 18c HR
- 오라클 캐릭터셋 변경
- Oracle 18c 설치
- oracle 18c
- 오라클 캐릭터셋 확인
- Orace 18c
- ORA-12899
- ORA-00922
- Oracle 윈도우 설치
- 무료 오라클 설치
- 오라클 캐릭터셋 조회
- Oracle 사용자명 입력
- ora-01722
- 비전공자를 위한 데이터베이스 입문
- 서평단
- Oracle 테이블 대소문자
- 무료 오라클 데이터베이스
- Oracle 초기 사용자
- Oracle Express Edition
- Oracle 테이블 띄어쓰기
- oracle
- Oracle 사용자명
- Oracle 18c HR schema
- 윈도우 Oracle
- Today
- Total
The Nirsa Way
[Spring Data JPA] 연관 관계 매핑 (1) : 단방향 매핑 (@ManyToOne, @OneToMany) 본문
[Spring Data JPA] 연관 관계 매핑 (1) : 단방향 매핑 (@ManyToOne, @OneToMany)
KoreaNirsa 2025. 7. 17. 10:41
연관 관계 매핑이란?
엔티티간의 관계를 DB의 FK와 매핑하여 연결하는 형태를 가질 수 있습니다. 즉 객체 간의 참조 방식을 DB의 외래키(FK)와 연결하여 사용할 수 있습니다. 이번에 살펴볼 어노테이션은 크게 2가지 인데, 아래와 같은 의미를 지닙니다.
1. @ManyToOne
일반적으로 단방향 매핑에서 가장 권장하는 방식입니다. 두 개의 엔티티를 만들어 관리하고 여러 명인 쪽에서 @ManyToOne과 @JoinColumn을 사용하여 단방향 매핑을 가집니다. fetch는 추후 연관관계 매핑 포스팅이 끝날 때 쯤 작성할 예정입니다.
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "team_id") // FK 컬럼명
private Team team;
}
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
private String name;
}
2. @OneToMany
각종 성능이슈로 인해 단방향 매핑 관계에서는 권장하지 않는 방식이며 @OneToMany는 양방향 매핑 관계일 때 주로 사용됩니다. 참고로 OneToMany에서 사용한 cascade = CascadeType.PERSIST는 예제를 확인하기 위해 Team 저장 시 Member도 함께 저장되도록 추가된 형태입니다.
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
}
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
private String name;
@OneToMany(cascade = CascadeType.PERSIST)
@JoinColumn(name = "team_id") // Member 테이블의 team_id 컬럼을 통해 조인
private List<Member> members = new ArrayList<>();
}
아래의 코드는 OneToMany 단방향 매핑 관계일때 개발자가 실수하기 쉬운 케이스 입니다.
@Service
@RequiredArgsConstructor
public class TeamService {
private final TeamRepository teamRepository;
@Transactional
public void createTeamWithMembers() {
// 1. 팀 객체 생성
Team team = new Team();
team.setName("팀A");
// 2. 회원 객체 생성
Member m1 = new Member();
m1.setName("회원1");
Member m2 = new Member();
m2.setName("회원2");
// 3. 팀에 회원 추가 (단방향 관계이므로 Member에 team 설정은 없음)
team.getMembers().add(m1);
team.getMembers().add(m2);
// 4. 저장 (CascadeType.PERSIST로 인해 Member도 함께 저장됨)
teamRepository.save(team);
}
}
위의 코드를 실행하면 save() 시점에서 아래의 쿼리가 호출됩니다. casecadeType.PERSIST로 인하여 member도 insert되니 team 1번, member 2번의 쿼리가 실행되는 것은 맞으나 member 테이블에 데이터를 추가할 때 team_id에 null이 들어가며 update는 2회가 추가적으로 실행되는 모습을 확인할 수 있습니다.
1. INSERT INTO TEAM (id, name) VALUES (?, ?)
2. INSERT INTO MEMBER (id, name, team_id) VALUES (?, ?, null)
3. INSERT INTO MEMBER (id, name, team_id) VALUES (?, ?, null)
4. UPDATE MEMBER SET team_id = ? WHERE id = ?
5. UPDATE MEMBER SET team_id = ? WHERE id = ?
위와 같이 UPDATE가 2회가 추가적으로 호출되는 이유는 아래와 같습니다.
- teamRepository.save(team)이 호출되며 team에 대한 insert 수행
- casecadeType.PERSIST에 의해 m1과 m2 엔티티도 persist()되며 각각 insert 수행 (총 2회)
단 m1, m2 엔티티에는 team에 대한 정보가 없기에 team_id를 null로 저장 - flush 시점에 @JoinColumn(name = "team_id")을 확인하여 team → member 간 FK가 일치할 수 있도록 update 수행
- UPDATE MEMBER SET team_id = [team 엔티티 id] WHERE id = [member엔티티 id]
참고로 team 엔티티 id가 자동으로 할당되는 이유는 @GenerateValue 어노테이션에 의해 insert 실행 후 DB에 생성된 PK가 team 엔티티의 id로 자동 할당되기 때문입니다. 즉, 1번에서 insert가 수행되며 team 엔티티는 생성되었던 pk를 자동 할당되었으며 해당 값을 4번의 과정에서 update 하는 것 입니다.