😎N:1 연관관계 알아보자~~
OneToMany 에서는 참조되는 값을 One에서 가지고 있지 않다. 만약 가져야 한다면UserHistoryId가 1,2,3,4,5 즉 어떤 값의 배열이 된다. 그래서 One에 해당하는 PK ID를 Many쪽에서 FK이다.
일반적인 상황에서는 @ManyToOne이 더 자주 사용된다. 해당 Entity가 필요한 FK 값을 Entity가 함께 가지고 있기 때문이다.
실습으로 알아보자.
지난시간에 User와 UserHistory를 수정하여 보자.
@ManyToOne
UserHistory
@Entity
@NoArgsConstructor
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
//@EntityListeners(value = AuditingEntityListener.class)
public class UserHistory extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// user의 insert update에는 영향을 주지 않아야함. // ManyToOne할때는 불필요한 컬럼이다.
// @Column(name = "user_id", insertable = false,updatable = false)
// private Long userId;
private String name;
private String email;
@ManyToOne // 다 대 일
private User user;
}
UserEntityListener
@PostPersist
@PostUpdate
public void prePersistAndPreUpdate(Object o) {
/*
* 여기서 주의해야할 점은 Listener는 @Autowired로 빈을 가져오지 못한다. 그래서 BeanUtils클래스를 이용해서 주입해 줘야 한다.
* */
UserHistoryRepository userHistoryRepository = BeanUtils.getBean(UserHistoryRepository.class);
System.out.println("User 생성 및 수정 하기전에 History남기는 쿼리");
User user = (User) o;
UserHistory userHistory = new UserHistory();
userHistory.setName(user.getName());
userHistory.setEmail(user.getEmail());
userHistory.setUser(user);
userHistoryRepository.save(userHistory);
}
User
@OneToMany(fetch = FetchType.EAGER)
@JoinColumn(name = "user_id", insertable = false, updatable = false)
@ToString.Exclude //순환참조 예방.
private List<UserHistory> userHistories
= new ArrayList<>(); //getUserHistoryes를 했을때 NullPointException이 뜨지 않게 기본리스트 넣어주자.
// Jpa에서 해당값이 존재하지 않으면 빈 리스트를 자동으로 넣어주기는 하지만, persist하기 전에 해당 값이 Null이기 때문에 로직에 따라 오류가 생길수있다.
TEST
//결론은 User와 UserHistory데이터를 모두 가져오기 위헤서는 N : 1을 사용하여 userHistory 즉 N쪽에서 getUser를 사용하여 검색한다.
System.out.println("UserHistory.getUser() - (N : 1) : " + userHistoryRepository.findAll().get(0).getUser());
DDL
create table user (
id bigint generated by default as identity,
created_at timestamp,
updated_at timestamp,
email varchar(255),
gender varchar(255),
name varchar(255),
primary key (id) //PK
)
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, //FK
primary key (id)
)
Query
select
user0_.id as id1_3_0_,
user0_.created_at as created_2_3_0_,
user0_.updated_at as updated_3_3_0_,
user0_.email as email4_3_0_,
user0_.gender as gender5_3_0_,
user0_.name as name6_3_0_,
userhistor1_.user_id as user_id6_4_1_,
userhistor1_.id as id1_4_1_,
userhistor1_.id as id1_4_2_,
userhistor1_.created_at as created_2_4_2_,
userhistor1_.updated_at as updated_3_4_2_,
userhistor1_.email as email4_4_2_,
userhistor1_.name as name5_4_2_,
userhistor1_.user_id as user_id6_4_2_
from
user user0_
left outer join
user_history userhistor1_
on user0_.id=userhistor1_.user_id
where
user0_.id=?
UserHistory.getUser() - (N : 1) : User(super=BaseEntity(createdAt=2022-02-08T21:56:07.620, updatedAt=2022-02-08T21:56:07.761), id=6, name=danielll, email=daniel@fast.com, gender=MALE)
✅ User-review-Book 테이블 관계 만들어보자.
Entity / Review
@Entity
@NoArgsConstructor
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
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;
}
Entity / User
@NoArgsConstructor
@AllArgsConstructor
@RequiredArgsConstructor
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@Builder
@Entity // 해당 객체가 JPA에서 관리하고 있는 객체인것을 정의.
@EntityListeners(value = {UserEntityListener.class})
//@Table(name = "user", indexes = {@Index(columnList = "name")}, uniqueConstraints = {@UniqueConstraint(columnNames = {"email"})})
public class User extends BaseEntity {
@Id // 엔티티에는 식별자가 필요한데 @ID로 표현.
@GeneratedValue(strategy = GenerationType.IDENTITY) // GenerationType (IDENTITY, SEQUENCE, TABLE, AUTO)
private Long id;
@NonNull
private String name;
@NonNull
private String email;
@Enumerated(value = EnumType.STRING)
private Gender gender;
@OneToMany(fetch = FetchType.EAGER)
@JoinColumn(name = "user_id", insertable = false, updatable = false)
@ToString.Exclude //순환참조 예방.
private List<UserHistory> userHistories
= new ArrayList<>(); //getUserHistoryes를 했을때 NullPointException이 뜨지 않게 기본리스트 넣어주자.
// Jpa에서 해당값이 존재하지 않으면 빈 리스트를 자동으로 넣어주기는 하지만, persist하기 전에 해당 값이 Null이기 때문에 로직에 따라 오류가 생길수있다.
@OneToMany
@JoinColumn(name = "user_id")
@ToString.Exclude
private List<Review> reviews = new ArrayList<>();
Entity / Publisher
@Entity
@NoArgsConstructor
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
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<>();
}
Entity / Book
@Data
@ToString(callSuper = true) //상속받은 클래스에 대해 처리해줘야한다. ToString을 재정의 한다.
@EqualsAndHashCode(callSuper = true) //EqualsAndHashCode를 재정의해준다.
//@EntityListeners(value = AuditingEntityListener.class)
public class Book extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String category;
private Long authorId;
@OneToMany // 1 : N \ Book : Review
@JoinColumn(name = "book_id") //중간 테이블 방지
@ToString.Exclude //ToString 순환참조 방지
private List<Review> reviews = new ArrayList<>();
@ManyToOne // N : 1 \ Book : publisher
@ToString.Exclude
private Publisher publisher;
@OneToOne(mappedBy = "book")
@ToString.Exclude // ToString 순환참조 걸린다. 릴레이션은 단방향으로 걸고 ToString은 제외해야한다.
private BookReviewInfo bookReviewInfo;
Repository
ReviewRepository
PublisherRepository
public interface PublisherRepository extends JpaRepository<Publisher, Long> {
}
public interface ReviewRepository extends JpaRepository<Review, Long> {
}
DDL 잘 나오는지 한번 볼까?
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)
)
Hibernate:
create table book_review_info (
id bigint generated by default as identity,
created_at timestamp,
updated_at timestamp,
average_review_score float not null,
review_count integer not null,
book_id bigint not null,
primary key (id)
)
Hibernate:
create table publisher (
id bigint generated by default as identity,
created_at timestamp,
updated_at timestamp,
name varchar(255),
primary key (id)
)
Hibernate:
create table review (
id bigint generated by default as identity,
created_at timestamp,
updated_at timestamp,
content varchar(255),
score float not null,
title varchar(255),
book_id bigint,
user_id bigint,
primary key (id)
)
Hibernate:
create table user (
id bigint generated by default as identity,
created_at timestamp,
updated_at timestamp,
email varchar(255),
gender varchar(255),
name varchar(255),
primary key (id)
)
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)
)
TEST 해볼까?
@SpringBootTest
public class BookRepositoryTest {
@Autowired
private BookRepository bookRepository;
@Autowired
private PublisherRepository publisherRepository;
@Autowired
private ReviewRepository reviewRepository;
@Autowired
private UserRepository userRepository;
@Test
@Transactional //트랜젝션할때 배운다.
void bookRelationTest() {
// 북 정보 저장.
givenBookAndReview();
//유저 값 불러오자. 원래는 인증 Data에서 받아오겠지??
User user = userRepository.findByEmail("martin@fast.com");
System.out.println("Review : " + user.getReviews()); // 유저에 리뷰 보기
System.out.println("Book : " + user.getReviews().get(0).getBook()); // 유저의 리뷰의 책정보 불러오기
System.out.println("Publisher : " + user.getReviews().get(0).getBook().getPublisher()); // 유저의 리뷰의 책정보에 출판사 정보
}
private void givenBookAndReview() {
givenReview(givenUser(), givenBook(givenPublisher()));
}
private User givenUser() {
//유저 값 불러오자.
return userRepository.findByEmail("martin@fast.com");
}
private void givenReview(User user, Book book) {
// 리뷰
Review review = new Review();
review.setTitle("내 인생을 바꾼 책");
review.setContent("너무너무 재미있고 즐거운 책이었어요.");
review.setScore(5.0f);
review.setUser(user); // user와 관계
review.setBook(book); // book과 관계
reviewRepository.save(review);
}
private Book givenBook(Publisher publisher) {
// 책 하나 만들어 주자.
Book book = new Book();
book.setName("JPA 초격차 패키지");
book.setPublisher(publisher); // 출판사는 publisher를 생성해서 받아와야겠지?
return bookRepository.save(book);
}
private Publisher givenPublisher() {
// 출판사 하나 생성해주자.
Publisher publisher = new Publisher();
publisher.setName("패스트캠퍼스");
return publisherRepository.save(publisher);
}
}
DDL은 위에서 봤고
getter를 호출하는 것 만으로 값 들을 조회 할 수 있다.!!!!!!!😮😮😮
select
reviews0_.user_id as user_id8_4_0_,
reviews0_.id as id1_4_0_,
reviews0_.id as id1_4_1_,
reviews0_.created_at as created_2_4_1_,
reviews0_.updated_at as updated_3_4_1_,
reviews0_.book_id as book_id7_4_1_,
reviews0_.content as content4_4_1_,
reviews0_.score as score5_4_1_,
reviews0_.title as title6_4_1_,
reviews0_.user_id as user_id8_4_1_,
book1_.id as id1_1_2_,
book1_.created_at as created_2_1_2_,
book1_.updated_at as updated_3_1_2_,
book1_.author_id as author_i4_1_2_,
book1_.category as category5_1_2_,
book1_.name as name6_1_2_,
book1_.publisher_id as publishe7_1_2_,
publisher2_.id as id1_3_3_,
publisher2_.created_at as created_2_3_3_,
publisher2_.updated_at as updated_3_3_3_,
publisher2_.name as name4_3_3_,
bookreview3_.id as id1_2_4_,
bookreview3_.created_at as created_2_2_4_,
bookreview3_.updated_at as updated_3_2_4_,
bookreview3_.average_review_score as average_4_2_4_,
bookreview3_.book_id as book_id6_2_4_,
bookreview3_.review_count as review_c5_2_4_
from
review reviews0_
left outer join
book book1_
on reviews0_.book_id=book1_.id
left outer join
publisher publisher2_
on book1_.publisher_id=publisher2_.id
left outer join
book_review_info bookreview3_
on book1_.id=bookreview3_.book_id
where
reviews0_.user_id=?
너무 잘 나오죠?
Review : [Review(super=BaseEntity(createdAt=2022-02-09T13:54:56.037, updatedAt=2022-02-09T13:54:56.037), id=1, title=내 인생을 바꾼 책, content=너무너무 재미있고 즐거운 책이었어요., score=5.0, user=User(super=BaseEntity(createdAt=2022-02-09T13:54:55.503, updatedAt=2022-02-09T13:54:55.503), id=1, name=martin, email=martin@fast.com, gender=null), book=Book(super=BaseEntity(createdAt=2022-02-09T13:54:56.026, updatedAt=2022-02-09T13:54:56.026), id=1, name=JPA 초격차 패키지, category=null, authorId=null))]
Book : Book(super=BaseEntity(createdAt=2022-02-09T13:54:56.026, updatedAt=2022-02-09T13:54:56.026), id=1, name=JPA 초격차 패키지, category=null, authorId=null)
Publisher : Publisher(super=BaseEntity(createdAt=2022-02-09T13:54:55.986, updatedAt=2022-02-09T13:54:55.986), id=1, name=패스트캠퍼스, books=[])
위의 쿼리가 물론 어려운 쿼리가 아니다. Join여러게 붙이고 값 불러오는건데, 와.... 이렇게 쉽게 자동으로 만들어 준다고??
'Dev > JPA' 카테고리의 다른 글
JPA M:N 연관관계 (0) | 2022.03.02 |
---|---|
JPA 연관관계 살펴보기(1:1) (0) | 2022.03.02 |
JPA 연관관계 (1:N) (0) | 2022.03.02 |
JPA EntityListener (0) | 2022.03.02 |
JPA Entity의 기본속성 (0) | 2022.03.02 |