Notice
Recent Posts
Recent Comments
Link
«   2024/11   »
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
Archives
Today
Total
관리 메뉴

개발자되기 프로젝트

N : 1 연관관계 - @ManyToOne 본문

JPA

N : 1 연관관계 - @ManyToOne

Seung__ 2021. 6. 21. 22:16
 

1 : N 연관관계 - 1

이전에 만들어둔 User entity와 User History는 1:N연관관계를 갖는다. User는 현재 회원정보를 가지고 있는 테이블이고 UserHistory는 특정 user id에 저장된 값이 변경된 내역을 저장하는 table이다. 1. test @..

bsh-developer.tistory.com

앞서 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
Comments