일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- JDBC
- Thymeleaf
- Spring Boot
- 자바
- java
- kotlin
- 스프링
- Exception
- 알고리즘
- Android
- 인프런
- AOP
- JPQL
- jpa
- 그리디
- QueryDSL
- 스프링 핵심 원리
- 백준
- springdatajpa
- spring
- 김영한
- SpringBoot
- transaction
- 스프링 핵심 기능
- db
- http
- Greedy
- Servlet
- Proxy
- pointcut
- Today
- Total
개발자되기 프로젝트
N : 1 연관관계 - @ManyToOne 본문
앞서 1 : N의 연관관계를 살펴보았다.
1 : N 관계를 이해하기 어려웠던 이유는 N(many)쪽에서 1(one)의 PK를 FK로 가지고 있었기 때문이다.
일반적으로는 @ManyToOne이 깰끔하게entity를 구성할 수 있따.
해당 entity가 필요로 하는 FK값을 entity가 함께 가지고 있다.
이전에는 User class에서 @OneToMany를 적용했다면,
이번번에는 UserHistory Class에서 @ManyToOne을 적용을 한다.
1. UserHistory Class
먼저 userHistory 클래스를 조금 수정해 주자.
N개의 userHistory와 1개의 user를 맵핑해야한다.
UserHIstory class에서 User를 주입하고 @ManyToOne을 붙여준다.
@Entity
@NoArgsConstructor
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class UserHistory extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
@Column(name = "user_id")
private Long userId;
private String name;
private String email;
@ManyToOne
private User user;
}
그대로 이전에 작성한 test를 실행하면 에러가 난다.
user_id (should be mapped with insert="false" update="false")
user_id라는 column은 insert = "false", update = "false"로 지정해줘야 한다는 의미.
시키는 대로 수정했다.
@Entity
@NoArgsConstructor
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class UserHistory extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
@Column(name = "user_id", insertable = false, updatable = false)
private Long userId;
private String name;
private String email;
@ManyToOne
private User user;
}
정상적으로 작동은 했는데...? 값이 안나온다.
repository에 값은 있는지 보자.
@Test
void userRelationTest(){
User user = new User();
user.setName("David");
user.setEmail("dabid@navergoogle.com");
user.setGender(Gender.MALE);
userRepository.save(user);
user.setName("daniel");
userRepository.save(user);
user.setEmail("daniel@navergoogle.com");
userRepository.save(user);
userHistoryRepository.findAll().forEach(System.out::println);
// List<UserHistory> result = userHistoryRepository.findByUserId(userRepository.findByEmail("daniel@navergoogle.com").getId());
List<UserHistory> result = userRepository.findByEmail("daniel@navergoogle.com")
.getUserHistories();
result.forEach(System.out::println);
}
userHistory값은 정상적으로 출력이 된다! 근데 마지막에 daniel@navergoogle.co에 해당하는 history의 출력이 안된다.
UserHistory(
super=BaseEntity(createdAt=2021-06-21T21:12:59.706791, updatedAt=2021-06-21T21:12:59.706791),
id=1, userId=null, name=David, email=dabid@navergoogle.com, user=null)
UserHistory(
super=BaseEntity(createdAt=2021-06-21T21:12:59.780558, updatedAt=2021-06-21T21:12:59.780558),
id=2, userId=null, name=daniel, email=dabid@navergoogle.com, user=null)
UserHistory(
super=BaseEntity(createdAt=2021-06-21T21:12:59.787816, updatedAt=2021-06-21T21:12:59.787816),
id=3, userId=null, name=daniel, email=daniel@navergoogle.com, user=null)
그리고 user Id값, user가 null이다. 입력이 제대로 안됐다.
UserEntityListener의 .setUserId()가 정상적으로 작동이 안된 것 이다.
userHistory.setUser(user)를 추가해주자.
@PostPersist
@PostUpdate
public void prePersistpreUpdate(Object o){
UserHistoryRepository userHistoryRepository = BeanUtils.getBean(UserHistoryRepository.class);
User user = (User) o;
UserHistory userHistory = new UserHistory();
userHistory.setUserId(user.getId());
userHistory.setName(user.getName());
userHistory.setEmail(user.getEmail());
userHistory.setUser(user);
userHistoryRepository.save(userHistory);
}
}
짜잔 stackOverFlow 에러가 발생했다.
java.lang.StackOverflowError
이 전 글에서 말한 것 처럼 lombok에서 제공하는 ToString method가 entity를 순환참조하면서 log찍을 때 발생한 문제.
@OneToMany에서 수정한 것 처럼 @ToString.Exclude를 적용한다.
@Entity
@NoArgsConstructor
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class UserHistory extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
@Column(name = "user_id", insertable = false, updatable = false)
private Long userId;
private String name;
private String email;
@ManyToOne
@ToString.Exclude
private User user;
}
짠 정상적으로 동작했다. user에서 get.userHistory로 정상적으로 불러올 수 있게됐다.
UserHistory(
super=BaseEntity(createdAt=2021-06-21T21:29:12.631745, updatedAt=2021-06-21T21:29:12.653216),
id=1, userId=6, name=David, email=dabid@navergoogle.com)
UserHistory(
super=BaseEntity(createdAt=2021-06-21T21:29:12.722049, updatedAt=2021-06-21T21:29:12.722049),
id=2, userId=6, name=daniel, email=dabid@navergoogle.com)
UserHistory(
super=BaseEntity(createdAt=2021-06-21T21:29:12.730892, updatedAt=2021-06-21T21:29:12.730892),
id=3, userId=6, name=daniel, email=daniel@navergoogle.com)
이제 UserHistory에서 user_id가 필요 없다. 없애버리자.
@Entity
@NoArgsConstructor
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class UserHistory extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
//@Column(name = "user_id", insertable = false, updatable = false)
//private Long userId;
private String name;
private String email;
@ManyToOne
@ToString.Exclude
private User user;
}
그리고 UserEntityListener에서 history의 UserId관련 내용도 지워주자.
public class UserEntityListener {
@PostPersist
@PostUpdate
public void prePersistpreUpdate(Object o){
UserHistoryRepository userHistoryRepository = BeanUtils.getBean(UserHistoryRepository.class);
User user = (User) o;
UserHistory userHistory = new UserHistory();
//userHistory.setUserId(user.getId());
userHistory.setName(user.getName());
userHistory.setEmail(user.getEmail());
userHistory.setUser(user);
userHistoryRepository.save(userHistory);
}
}
결과를 보자! userHistory table생성결과를 보자!
Hibernate:
create table user_history (
id bigint generated by default as identity,
created_at timestamp,
updated_at timestamp,
email varchar(255),
name varchar(255),
user_id bigint,
primary key (id)
)
위에서 userHistory class에서 userId항목을 지우고, User user를 주입하여 @ManyToOne으로 맵핑했다.
그 결과로 user history table에 user_id가 있는 것을 볼 수 있다.
이렇게 되면 양방향으로 관계를 가지게 된다!
즉, userHistory.getUser가 가능하다.
test code에 아래와 같이 추가해 준다.
System.out.println("UserHistory.getUser() : " + userHistoryRepository.findAll().get(0).getUser());
즉, user history에 있는 첫번 째 값에 해당하는 user를 불러오게 된다.
user history의 첫 번째 값을 보면 이름은 david, 메일은 dabid@navergoogle.com이다.
UserHistory(super=BaseEntity(createdAt=2021-06-21T21:57:48.703910, updatedAt=2021-06-21T21:57:48.720580), id=1, name=David, email=dabid@navergoogle.com)
UserHistory(super=BaseEntity(createdAt=2021-06-21T21:57:48.783257, updatedAt=2021-06-21T21:57:48.783257), id=2, name=daniel, email=dabid@navergoogle.com)
UserHistory(super=BaseEntity(createdAt=2021-06-21T21:57:48.792475, updatedAt=2021-06-21T21:57:48.792475), id=3, name=daniel, email=daniel@navergoogle.com)
에휴 뭔가 주절주절 많이 왔는데, 맞게 불러와졌다. histrory가 다 불러와져서 내용이 길어졌다.
UserHistory.getUser() :
User(
super=BaseEntity(createdAt=2021-06-21T21:57:48.645360, updatedAt=2021-06-21T21:57:48.791005)
, name=daniel, email=daniel@navergoogle.com, id=6
, testData=null, gender=MALE, userHistories=[UserHistory(super=BaseEntity(createdAt=2021-06-21T21:57:48.703910, updatedAt=2021-06-21T21:57:48.720580), id=1, name=David, email=dabid@navergoogle.com), UserHistory(super=BaseEntity(createdAt=2021-06-21T21:57:48.783257, updatedAt=2021-06-21T21:57:48.783257), id=2, name=daniel, email=dabid@navergoogle.com), UserHistory(super=BaseEntity(createdAt=2021-06-21T21:57:48.792475, updatedAt=2021-06-21T21:57:48.792475), id=3, name=daniel, email=daniel@navergoogle.com)])
이전에 @ToString.Exclude를 적용을 했는데.. 지금 user에서 history를 불러오는 데다가 적용을 하는 것이 좋을 것 같다.
@OneToMany(fetch = FetchType.EAGER) //이후에 자세히
@JoinColumn(name = "user_id", insertable = false, updatable = false) //엔티티가 어떤 컬럼으로 조인을 하게 될지 지정해주는 annotation
@ToString.Exclude
private List<UserHistory> userHistories = new ArrayList<>();
;그리고 UserHistory에 적용한 @ToString.Exclude는 지워주자.
public class UserHistory extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
//@Column(name = "user_id", insertable = false, updatable = false)
//private Long userId;
private String name;
private String email;
@ManyToOne
//@ToString.Exclude
private User user;
}
짠 이렇게 되면 userHistory에서 getUser()를 했을 때 정보가 깔끔하게 나온다.
UserHistory.getUser() :
User(super=BaseEntity(createdAt=2021-06-21T22:07:02.400053,
updatedAt=2021-06-21T22:07:02.586932),
name=daniel, email=daniel@navergoogle.com, id=6, testData=null, gender=MALE)
왜냐? User class에서 userHistories에 대해 @ToStirng을 배제했기 때문이당.
2. 정리
이렇게 jpa에서는 연관된 객체를 FK를 통해 조회하는게 아니라 getter를 통해 가져온다.
그래서 @OneToMany인지, @ManyToOne을 사용해야 하는지 또는 둘다 사용해야 하는지..결정은
어느 entity에서 연관 entity가 필요한지를 생각해봐야 한다.
userhistory에서 --> user 객체를 찾는 경우는 많이 없지만.. @ManyToOne을 활용
반면에 user에서 --> user history를 보는 경우는 종종 있을 것.
즉 이 경우에는 @OneToMany를 활용하여 연관관계를 맺어줘야 한다.
'JPA' 카테고리의 다른 글
M : N(다대다) 연관관계 - 1 (0) | 2021.06.22 |
---|---|
N : 1 연관관계 #2 (0) | 2021.06.21 |
1 : N 연관관계 - 1 (0) | 2021.06.21 |
1:1 연관관계 - 2 (0) | 2021.06.20 |
mappedBy() (0) | 2021.06.20 |