User
public class User extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NonNull
private String name;
@NonNull
private String email;
@Enumerated(value = EnumType.STRING)
private Gender gender;
@OneToMany
//persist전에는 null로 인해 nullPointerException이 발생할 수 있으므로
// 기본 생성자 넣어주는 것도 좋음
private List<UserHistory> userHistories = new ArrayList<>();
}
UserHistory
public class UserHistory extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long userId;
private String name;
private String email;
@Enumerated(value = EnumType.STRING)
private Gender gender;
}
test
User user = new User();
user.setName("david");
user.setEmail("david@fastcampus.com");
user.setGender(Gender.MALE);
userRepository.save(user);
user.setName("daniel");
userRepository.save(user);
user.setEmail("daniel@naver.com");
userRepository.save(user);
List<UserHistory> result = userRepository.findByEmail("daniel@naver.com").getUserHistories();
result.forEach(System.out::println);
처음에 이렇게 테스트 진행 시 LazyInitializationException이 뜸 -> @OneToMany(fetch = FetchType.EAGER)
이렇게 적용하면 테스트는 통과하지만 inner Join을 사용하고 있음
또한 중간 매핑 테이블이 생성됨
create table user_user_histories (
user_id bigint not null,
user_histories_id bigint not null
)
@JoinColumn
엔티티 속성이 조인에 대상인지 지정, 외래키 매핑시 사용
name
: 참조할 테이블의 어떤 컬럼으로 조인 할 것인지 지정
insertable
, updatable
: 참조 테이블을 insert, update 처리 여부 (default: true)
→ insertable, updatable을 설정하지 않으면 cascade, 트랜잭션 등
JPA에서 자동으로 처리하는 것이 많아, 예상치 못한 오작동과 쿼리를 발생
@JoinColumn 지정 후
create table user_history (
id bigint generated by default as identity,
created_at timestamp,
updated_at timestamp,
email varchar(255),
gender varchar(255),
name varchar(255),
user_id bigint,
user_histories_id bigint,
primary key (id)
)
이 경우에도 forEach로 프린트는 안됨
// user.java
@OneToMany(fetch = FetchType.EAGER)
@JoinColumn(name="user_Id")
private List<UserHistory> userHistories = new ArrayList<>();
// userHistory.java
@Column(name="user_Id")
private Long userId;
위와 같이 name 지정 시 정상 출력
하지만 보통 History라는 값은 UserEntity에서 수정하거나 추가하면 안됨!
-> 즉 Read Only로 되게끔 해야함
// 디폴트 값은 true
@OneToMany(fetch = FetchType.EAGER)
@JoinColumn(name = "user_id", insertable = false, updatable = false)
private List<UserHistory> userHistories = new ArrayList<>();
@OneToMany에서 참조하는 값은 One에 해당하는 PK값을 Many쪽에서 FK로 가짐
→ UserHistory 테이블에서는 User에 Id값을 가짐
일반적인 상황에서는 @ManyToOne이 깔끔하게 엔티티를 구성
→ 해당 엔티티가 필요로 하는 FK 엔티티가 함께 가지고 있기 때문
UserHistory.java
@Entity
...
public class UserHistory extends BaseEntity{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
@ManyToOne
private User user;
}
User.java
@Entity
...
public class User extends BaseEntity{
...
@OneToMany(fetch = FetchType.EAGER)
@JoinColumn(name = "user_Id", insertable = false, updatable = false)
@ToString.Exclude
private List<UserHistory> userHistories = new ArrayList<>();
}
UserEntityListener.java
public class UserEntityListener {
@PostPersist
@PostUpdate
public void postPersistAndPostUpdate(Object o){
UserHistoryRepository userHistoryRepository = BeanUtils.getBean(UserHistoryRepository.class);
User user = (User) o;
UserHistory userHistory = new UserHistory();
userHistory.setName(user.getName());
userHistory.setEmail(user.getEmail());
userHistory.setUser(user);
userHistoryRepository.save(userHistory);
}
}
System.out.println("userHistory.getUser" + userHistoryRepository.findAll().get(0).getUser());
위와 같이 양방향 설정시 히스토리 리파지토리를 통해서도 getUser 할 수 있다
userHistory.getUserUser(super=BaseEntity(createdAt=2022-11-20T14:07:53.384675, updatedAt=2022-11-20T14:07:53.424240), id=6, name=daniel, email=daniel@naver.com, gender=MALE)
JPA는 연관된 객체를 FK로 조회하는 것이 아니라 getter를 통해 가져오게 됌
@OneToMany, @ManyToOne, 양방향 중 선택은 어느 엔티티에 연관 엔티티가 필요한지 알아야함
book.java
...
public class Book extends BaseEntity{
...
@OneToOne(mappedBy = "book")
@ToString.Exclude
private BookReviewInfo bookReviewInfo;
@OneToMany
@JoinColumn(name="book_id")
@ToString.Exclude
private List<Review> reviews = new ArrayList<>();
@ManyToOne
@ToString.Exclude
private Publisher publisher;
}
create table book (
id bigint generated by default as identity,
created_at timestamp,
updated_at timestamp,
author_id bigint,
category varchar(255),
name varchar(255),
publisher_id bigint,
primary key (id)
)
위 3개 칼럼중 publisher_id만을 DDL에서 다루고 있음
bookReviewInfo의 경우 mappedBy
를 통해 BookReviewInfo에서만 외래키로 다루도록(테이블에 book_id
존재) -> Owner가 BookReviewInfo
reviews의 경우 @JoinColumn(name = "book_id")로 외래키 매핑
해줌 -> Book 테이블에는 review id 존재 X
review에는 book_id(bigint)로 칼럼 존재
@ManyToOne인 Publisher
는 자신이 Owner이면서 외래키를 가지고 있음
//publisher -> 에는 book_id 칼럼이 존재하지 않음
@OneToMany
@JoinColumn(name = "publisher_id")
private List<Book> book = new ArrayList<>();
결론적으로 두 테이블중 외래키를 가지고 있는 테이블은 한 테이블이고, 외래키를 가진 테이블이 Owner라고 이해해자!
BookReviewInfo
...
public class BookReviewInfo extends BaseEntity{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToOne(optional = false)
private Book book;
private float averageReviewScore;
private int reviewCount;
}
Publisher
...
public class Publisher extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany
@JoinColumn(name = "publisher_id")
private List<Book> books = new ArrayList<>();
}
Review
...
public class Review extends BaseEntity{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String content;
private float score;
@ManyToOne
private User user;
@ManyToOne
private Book book;
}
User
public class User extends BaseEntity{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NonNull
private String name;
@NonNull
private String email;
@Enumerated
private Gender gender;
@OneToMany(fetch = FetchType.EAGER)
@JoinColumn(name = "user_Id", insertable = false, updatable = false)
@ToString.Exclude
private List<UserHistory> userHistories = new ArrayList<>();
@OneToMany
@JoinColumn(name = "user_id")
@ToString.Exclude
private List<Review> reviews = new ArrayList<>();
}
UserHistory
public class UserHistory extends BaseEntity{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
@ManyToOne
private User user;
}