@Entity
@Setter @Getter
@ToString
public class User {
@Id @GeneratedValue
@Column(name = "USER_ID")
private Long id;
private String username;
}
@Entity
@Setter @Getter
@ToString
public class Team {
@Id @GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String teamname;
}
@ManyToOne 단방향
M:1 관계에서 일반적으로 M에 1의 PK 가 외래키로 생성된다.
JPA에서는 연관관계의 주인이 외래키를 관리하게 된다.
따라서 Foreign Key가 존재하는 M쪽이 연관관계의 주인이 된다.
연관관계의 주인이되는 엔티티에 연관관계가 있는 엔티티를 참조할 수 있도록 필드에 클래스 타입의 엔티티 객체 선언한다.
@JoinColumn("연관관계 1 엔티티 PK") 와 같이 선언하면 1의 PK를 연관관계의 주인 테이블에 FK로 설정이 된다.
@Entity(name = "USERS") // H2 데이터베이스에서 User는 예약어로 지정되어있다.
@Setter @Getter
@ToString
public class User {
@Id @GeneratedValue
@Column(name = "USER_ID")
private Long id;
private String username;
@ManyToOne(fetch = FetchType.LAZY) // 다대일 연관관계 지정
@JoinColumn(name = "TEAM_ID") // TEAM_ID를 외래키로 지정
private Team team; // 다대일 관계에서 일 테이블 선언
}
@ManyToOne 양방향
데이터베이스 테이블과 JPA의 Entity 클래스(객체) 간에는 패러다임의 불일치가 존재한다.
객체는 참조를 하고 테이블은 외래키를 통해 JOIN을 활용한다.
이 차이를 고려하여 Team에서 member를 조회하기 위해서는 어떤 member가 존재하는지 담아둘 List 레퍼런스 객체를 선언해줘야 한다.
M:1 관계에서 양방향 연관관계를 맺기 위해서는 1에 해당하는 엔터티 클래스에 아래와 같이 선언한다.
@OneToMany(mappedBy = "team") // 다 쪽에 team테이블을 매핑 - Member 엔터티의 team 레퍼런스를 바라본다.
private List<Member> users = new ArrayList<>();
이것은 단순히 ReadOnly 읽기전용 필드가 된다.
Member 클래스로부터 team래퍼런스에 접근하여 members에 member값을 추가하더라도 FK 값의 변동은 일어나지 않는다.
연관관계의 주인은 TEAM_ID FK를 관리하는 Member 엔터티로 지정했기 때문에 Team에서는 FK를 관리 할 수 없다.
이는 순수 객체상태를 고려하여 단순하게 반대방향으로 조회(객체 그래프 탐색) 하는 기능만을 위해 양쪽에 값을 설정할수 있게 끔 추가된 기능일 뿐이다.
@Entity
@Setter @Getter
@ToString
public class Team {
@Id @GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String teamname;
@OneToMany(mappedBy = "team") // 다 쪽에 team테이블을 매핑 - Member 엔터티의 team 레퍼런스를 바라본다.
private List<User> users = new ArrayList<>();
}
추가적으로 Team에 존재하는 Users를 조회할때 다음과 같은 문제가 발생할 수 있다.
@SpringBootTest
@Transactional
public class JpaTest {
@PersistenceContext
EntityManager em;
@Test
public void xToOneTest() {
Team team = new Team();
team.setTeamname("teamA");
em.persist(team);
User user = new User();
user.setUsername("userA");
user.setTeam(team);
em.persist(user);
//em.flush();
//em.clear();
User findUser = em.find(User.class, user.getId());
System.out.println("findUser = " + findUser);
System.out.println("findUser.getTeam() = " + findUser.getTeam());
Team findTeam = em.find(Team.class, findUser.getTeam().getId());
System.out.println("findTeam = " + findTeam);
System.out.println("findTeam.getUsers() = " + findTeam.getUsers());
}
}
[콘솔 출력 내용]
findUser = User(id=2, username=userA)
findUser.getTeam() = Team(id=1, teamname=teamA, users=[])
findTeam = Team(id=1, teamname=teamA, users=[])
findTeam.getUsers() = []
위와 같이 실행해보면 flush와 clear를 하지 않았기 때문에 team으로부터 member를 확인할 수 없게 된다.
(flush clear를 따로 선언하지 않으면 Transaction이 끝나는 시점에 flush를 진행한다.)
persist 즉, 영속성 컨텍스트에 영속화만 진행하게 되었으므로 em.find시 영속성 컨텍스트에 존재하는 1차 캐시에 저장된 Team 프록시 객체를 불러오기 때문이다. (해당 프록시 객체에는 users값이 저장되어 있지 않음)
team.getUsers().add(user);
따라서 위와 같은 코드를 추가하여 객체에 존재하는 List 타입의 users 필드에 값을 추가해 주게 되면 프록시 객체에 값이 저장된다.
이것은 flush나 clear를 하지 않는 상황에서만 하는것 보다는, flush를 하지 않더라도 추가해 주는게 좋다.
DB와 객체 간의 패러다임의 불일치 현상을 줄이기 위해서이다.
(필자는 편하게 영속화 하지 않았을때에도 조회를 할 수 있어야 한다고 생각했다.)
보통은 양방향 연관관계 편의 메소드로 객체 시점에서 M에 1을 추가할 경우 다시말해 member에 team을 추가할 때 동시에 team에 users를 관리하는 값을 초기화해 주도록 User 엔터티 클래스에 다음과 같은 메소드를 추가한다.
public relatedTeamSet(Team team) {
this.team = team;
team.getUsers().add(this); // this는 현재 member를 가르킨다.
}
[완성코드]
@Entity(name = "USERS") // H2 데이터베이스에서 User는 예약어로 지정되어있다.
@Setter @Getter
@ToString
public class User {
@Id @GeneratedValue
@Column(name = "USER_ID")
private Long id;
private String username;
@ToString.Exclude // 양방향 연관관계에서 ToString 순환참조 StackOverFlow 대안
@ManyToOne(fetch = FetchType.LAZY) // 다대일 연관관계 지정
@JoinColumn(name = "TEAM_ID") // TEAM_ID를 외래키로 지정
private Team team; // 다대일 관계에서 일 테이블 선언
public relatedTeamSet(Team team) {
this.team = team;
team.getUsers().add(this); // this는 현재 member를 가르킨다.
}
}
'Hibernate > JPA(EntityManager)' 카테고리의 다른 글
[JPA] @OneToOne 단방향/양방향 - 외래키 정책 기준 (1) | 2023.06.29 |
---|---|
[JPA] @OneToMany 단방향 / 양방향 - 외래키 관리 이해한 내용 정리 (0) | 2023.06.29 |
[JPA] JPQL 및 queryDSL 사용자정의 함수 등록법 (MySql) (0) | 2023.06.21 |
JPA EntityManager - Entity 기본 구현 및 SELECT, INSERT, UPDATE, DELETE (0) | 2023.04.15 |
[Spring Data JPA] 연관관계 매핑 LeftJOIN / InnerJOIN , @ManyToOne / @OneToOne , 단방향 / 양방향 (0) | 2022.11.18 |