본문 바로가기

프로그래밍/JAVA

[JPA] 연관관계(2)

728x90
반응형

[JPA] 연관관계 (1)

 

[JPA] 연관관계 (1)

엔티티들은 대부분 다른 엔티티와 연관관계가 있다 예를 들어 위와 같은 ERD가 있다고 가정하고 영화 엔티티에는 감독이 누구인지 알기 위해 감독 엔티티와 연관관계가 있고 영화 엔티티는 리뷰

ldevlog.tistory.com

이전 글을 안 보고 왔다면 위에 이전 글을 보고 오는 것이 좋다

다대일(N:1) 단방향

import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.*;
import java.time.LocalDateTime;

@Entity
@NoArgsConstructor
@Data
public class Review {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;

    private String content;

    private float score;

    @ManyToOne
    private Movie movie;

    private LocalDateTime createdAt;

    private LocalDateTime updatedAt;
}

Movie 클래스는 건드릴 것이 없으므로 Review 클래스만 위와 같이 작성한다

 

@Test
void test() {
    Movie movie = new Movie();
    movie.setTitle("어벤저스");
    movie.setCategory("SF");
    movie.setDirector_id(1l);
    movie.setCreatedAt(LocalDateTime.now());
    movie.setUpdatedAt(LocalDateTime.now());

    Review review1 = new Review();
    review1.setTitle("너무 재밌어요");
    review1.setMovie(movieRepository.save(movie));
    review1.setScore(4.5f);
    review1.setContent("어벤저스 너무 보고싶었는데 너무 재밌네요");
    review1.setCreatedAt(LocalDateTime.now());
    review1.setUpdatedAt(LocalDateTime.now());

    Review review2 = new Review();
    review2.setTitle("마블 짱!!!!");
    review2.setMovie(movieRepository.save(movie));
    review2.setScore(4.0f);
    review2.setContent("마블은 역시 짱이에요!!!");
    review2.setCreatedAt(LocalDateTime.now());
    review2.setUpdatedAt(LocalDateTime.now());

    reviewRepository.save(review1);
    reviewRepository.save(review2);

    movieRepository.findAll().forEach(System.out::println);

    reviewRepository.findAll().forEach(System.out::println);

    Movie result = reviewRepository.findAll().get(0).getMovie();

    System.out.println(result);

}

테스트 코드를 위와 같이 작성 한 뒤에 로그를 확인해 본다

 

일대일 관계랑 별로 틀린 거는 없다 @ManyToOne 설정만 해주면 된다

 

다대일(N:1) 양방향

@OneToMany(mappedBy = "movie", fetch = FetchType.EAGER)
@ToString.Exclude
private List<Review> reviews = new ArrayList<>();

Movie 클래스에 위와 같은 소스를 추가해 준다

양방향으로 받을 때에는 OneToMany 쪽에 List로 감싸서 받아주면 된다

 

List<Review> result = movieRepository.findById(1l).orElseThrow(RuntimeException::new).getReviews();

result.forEach(System.out::println);

테스트 코드 마지막 부분을 위와 같이 수정해 준 뒤 로그를 확인해 본다

 

정상적으로 리뷰들이 출력되는 걸 확인할 수 있다

 

이것 또 한 전에 했던 일대일 양방향과 별로 다를 게 없다

 

다만 새로 추가된 fetch라는 게 보일 텐데 LazyInitializationException 이 발생하는 경우 처리 방법 중에 하나로 fetch를 쓰는 건데 지금은 별로 신경 쓰지 않아도 된다

 

일대다(1:N)

이 부분은 위에서 했던 movie와 review 테이블의 다대일 관계를 역으로 일대다로 바꿔서 진행해 볼 것이다

우선 소스를 아래와 같이 수정해 준다

import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Entity
@NoArgsConstructor
@Data
public class Movie {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;

    private String category;

    private Long director_id;

    @OneToOne(mappedBy = "movie")
    @ToString.Exclude
    private MovieReviewInfo movieReviewInfo;

//    @OneToMany(mappedBy = "movie", fetch = FetchType.EAGER)
//    @ToString.Exclude
//    private List<Review> reviews = new ArrayList<>();

    @OneToMany(fetch = FetchType.EAGER)
    @JoinColumn(name = "movie_id")
    private List<Review> reviews = new ArrayList<>();

    private LocalDateTime createdAt;

    private LocalDateTime updatedAt;
}

 

import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.*;
import java.time.LocalDateTime;

@Entity
@NoArgsConstructor
@Data
public class Review {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;

    private String content;

    private float score;

    @Column(name = "movie_id")
    private Long movieId;
    
//    @ManyToOne
//    private Movie movie;

    private LocalDateTime createdAt;

    private LocalDateTime updatedAt;
}

 

 

import com.example.jpablog.domain.Movie;
import com.example.jpablog.domain.MovieReviewInfo;
import com.example.jpablog.domain.Review;
import com.example.jpablog.repository.MovieRepository;
import com.example.jpablog.repository.MovieReviewInfoRepository;
import com.example.jpablog.repository.ReviewRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.time.LocalDateTime;
import java.util.List;

@SpringBootTest
public class JpaTest {

    @Autowired
    private MovieRepository movieRepository;

    @Autowired
    private MovieReviewInfoRepository movieReviewInfoRepository;

    @Autowired
    private ReviewRepository reviewRepository;

    @Test
    void test() {
        Movie movie = new Movie();
        movie.setTitle("어벤저스");
        movie.setCategory("SF");
        movie.setDirector_id(1l);
        movie.setCreatedAt(LocalDateTime.now());
        movie.setUpdatedAt(LocalDateTime.now());

        Review review1 = new Review();
        review1.setTitle("너무 재밌어요");
        review1.setScore(4.5f);
        review1.setMovieId(1l);
        review1.setContent("어벤저스 너무 보고싶었는데 너무 재밌네요");
        review1.setCreatedAt(LocalDateTime.now());
        review1.setUpdatedAt(LocalDateTime.now());

        Review review2 = new Review();
        review2.setTitle("마블 짱!!!!");
        review2.setScore(4.0f);
        review2.setMovieId(1l);
        review2.setContent("마블은 역시 짱이에요!!!");
        review2.setCreatedAt(LocalDateTime.now());
        review2.setUpdatedAt(LocalDateTime.now());

        movieRepository.save(movie);
        reviewRepository.save(review1);
        reviewRepository.save(review2);

        movieRepository.findAll().forEach(System.out::println);

        reviewRepository.findAll().forEach(System.out::println);

        List<Review> result = movieRepository.findById(1l).orElseThrow(RuntimeException::new).getReviews();

        result.forEach(System.out::println);
    }

}

사실 일대다 관계 매핑은 추천하지 않는 방법이다

하지만 꼭 써야 하는 상황이 생길 경우를 대비해서 알아두도록 하자

 

우선 봐야 할 것은 Review 테이블에서는 딱히 해줄 것은 없다 movieId 만 생성을 해주면 되고

Movie 테이블에서 @JoinColumn을 꼭 써줘야 한다

만약 @joinColumn을 써주지 않는 다면 조인 테이블 방식을 사용하고 중간에 movie_review라는 테이블이 하나 생성이 될 것이다 테스트 상황에서 @JoinColumn을 지우고 실행시켜 보면 확인이 가능하다

 

그리고 일대다 양방향 매핑은 공식적으로 존재하지 않기 때문에 설명은 생략할 것이다

꼭 써야 한다면 @JoinColumn(updateble = false, insertable = false)을 사용하여 읽기 전용으로 만들겠다는 뜻이다

일대다 단방향, 양방향을 써야 한다면 다대일로 쓰는 것을 추천한다

 

다대다(N:M) 단방향

import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Entity
@NoArgsConstructor
@Data
public class Movie {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;

    private String category;

    @ManyToMany
    private List<Director> directors = new ArrayList<>();

    @OneToOne(mappedBy = "movie")
    @ToString.Exclude
    private MovieReviewInfo movieReviewInfo;

//    @OneToMany(mappedBy = "movie", fetch = FetchType.EAGER)
//    @ToString.Exclude
//    private List<Review> reviews = new ArrayList<>();

    @OneToMany(fetch = FetchType.EAGER)
    @JoinColumn(name = "movie_id")
    private List<Review> reviews = new ArrayList<>();

    private LocalDateTime createdAt;

    private LocalDateTime updatedAt;
}

@ManyToMany 만 넣어 주면 된다

 

다대다(N:M) 양방향

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Entity
@NoArgsConstructor
@Data
public class Director {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @Enumerated(value = EnumType.STRING)
    private Gender gender;

    @ManyToMany(mappedBy = "directors")
    private List<Movie> movies = new ArrayList<>();

    private LocalDateTime createdAt;

    private LocalDateTime updatedAt;
}

 

지금까지 해왔던 일대일, 다대일, 일대다 등 단방향 양방향의 매필 설정은 다 똑같다

단방향은 FK를 갖는 쪽에, 양방향은 FK를 제공해주는 쪽에 mappedBy를 해주면 된다

 

다대다를 하게 되면 중간 테이블이 자동으로 생성이 되는데 자기도 모르는 쿼리가 발생될 수도 있고 중간 테이블에 다른 데이터들이 들어갈 수도 있기 때문에 다대다는 거의 사용이 안된다고 보면 된다

 

그렇다고 우리가 ManyToMany 관계를 사용을 안 할 수가 없는데 이럴 경우는 다대일, 일대다로 만들거나 중간 테이블을 우리가 직접 생성해서 만들어 주면 된다

 

중간테이블을 생성하는 걸 소스로 간단히 설명하자면

import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.*;
import java.time.LocalDateTime;

@Entity
@NoArgsConstructor
@Data
public class MovieAndDirector {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne
    private Movie movie;

    @ManyToOne
    private Director director;

    private LocalDateTime createdAt;

    private LocalDateTime updatedAt;
}

 

 

import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Entity
@NoArgsConstructor
@Data
public class Movie {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;

    private String category;

    @OneToMany(mappedBy = "movie")
    private List<MovieAndDirector> movieAndDirectors = new ArrayList<>();

    @OneToOne(mappedBy = "movie")
    @ToString.Exclude
    private MovieReviewInfo movieReviewInfo;

    @OneToMany(mappedBy = "movie", fetch = FetchType.EAGER)
    @ToString.Exclude
    private List<Review> reviews = new ArrayList<>();

//    @OneToMany(fetch = FetchType.EAGER)
//    @JoinColumn(name = "movie_id")
//    private List<Review> reviews = new ArrayList<>();

    private LocalDateTime createdAt;

    private LocalDateTime updatedAt;
}

 

 

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Entity
@NoArgsConstructor
@Data
public class Director {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @Enumerated(value = EnumType.STRING)
    private Gender gender;

    @OneToMany(mappedBy = "director")
    private List<MovieAndDirector> movieAndDirectors = new ArrayList<>();

    private LocalDateTime createdAt;

    private LocalDateTime updatedAt;
}

중간에 MovieAndDiretor라는 Entity를 만들어 참조해서 사용할 수 있다

728x90
반응형