π§ Movie
@Entity
@Table(name="movies")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Movie {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String title;
private Integer releaseYear;
@OneToMany(mappedBy = "movie", cascade = CascadeType.ALL, orphanRemoval = true) π
@JsonManagedReference π
private List<Review> reviews = new ArrayList<>();
}
| ν¬μΈνΈ | μ€λͺ |
|---|---|
@OneToMany(mappedBy = "movie") | Movie 1κ°λ μ¬λ¬ Reviewλ₯Ό κ°μ§ μ μλ€λ μλ―Έ. mappedByλ Review ν΄λμ€μ movie νλλ₯Ό μ°Έμ‘°νμ¬ μ°κ΄ κ΄κ³μ μ£ΌμΈμ΄ μλμ λͺ
μ. μ¦, DBμ μΈλ ν€λ Review ν
μ΄λΈμ μμ±λ¨. |
orphanRemoval = true | reviews 리μ€νΈμμ νλͺ©μ μ κ±°νλ©΄ DBμμλ μλ μμ λ¨. λ¨, CascadeType.ALLμ΄ ν¨κ» μμ΄μΌ μ μ©λ¨. |
cascade = CascadeType.ALL | Movie μν°ν°μ μλͺ μ£ΌκΈ° λ³ν(μ μ₯, μμ , μμ λ±)λ₯Ό Reviewμλ μ νν¨. μ: Movie μμ μ μ°κ²°λ Reviewλ ν¨κ» μμ λ¨. |
@JsonManagedReference | μ§λ ¬ν μ μν μ°Έμ‘°λ₯Ό λ°©μ§νκΈ° μν μ΄λ
Έν
μ΄μ
. μ΄ μͺ½μ΄ μ§λ ¬ν λμμ΄λ©°, Reviewμ @JsonBackReferenceμ μ§μ μ΄λ£Έ. |
π§ Review
@Entity
@Table(name = "reviews")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Review {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String reviewer;
@Min(1)
@Max(5)
private Integer rating;
@Column(columnDefinition = "TEXT")
private String comment;
@ManyToOne(fetch = FetchType.LAZY) π
@JoinColumn(name = "movie_id", nullable = false) π
@JsonBackReference π
private Movie movie;
}
| ν¬μΈνΈ | μ€λͺ |
|---|---|
@ManyToOne(fetch = FetchType.LAZY) | μ¬λ¬ 리뷰λ νλμ μνμ μνλ€λ κ΄κ³. μΈλ ν€κ° Review ν μ΄λΈμ μ‘΄μ¬ν¨. LAZY μ€μ μ μ€μ μ¬μ©ν λλ§ Movie μν°ν°λ₯Ό DBμμ μ‘°νν¨. |
@JoinColumn(name = "movie_id", nullable = false) | Review ν
μ΄λΈμ μμ±λ μΈλ ν€ μ»¬λΌ μ΄λ¦μ μ§μ . nullable = falseλ‘ νμ κ΄κ³μμ λͺ
μ. |
@JsonBackReference | μ§λ ¬ν μ 무ν 루ν λ°©μ§λ₯Ό μν΄ μ¬μ©. @JsonManagedReferenceκ° μλ Movie μͺ½λ§ JSON μλ΅μ ν¬ν¨λ¨. |
π μ§λ ¬ν(Serialization)λ?
π¦ MovieDto
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class MovieDto {
@NotBlank(message = "μ λͺ©μ μ
λ ₯νμΈμ")
private String title;
@NotNull(message = "κ°λ΄ μ°λλ₯Ό μ
λ ₯νμΈμ")
private Integer releaseYear;
}
π¦ ReviewDto
@Getter
@Setter
public class ReviewDto {
private String reviewer;
@NotNull(message = "νμ μ μ
λ ₯νμΈμ")
@Min(value = 1, message = "νμ μ 1~5 μ¬μ΄μ¬μΌ ν©λλ€")
@Max(value = 5, message = "νμ μ 1~5 μ¬μ΄μ¬μΌ ν©λλ€")
private Integer rating;
@NotBlank(message = "λκΈμ μ
λ ₯νμΈμ")
private String comment;
}
π§© MovieService
@Service
@RequiredArgsConstructor
@Transactional π
public class MovieService {
private final MovieRepository movieRepository;
public Page<Movie> getAll(Pageable pageable) {
return movieRepository.findAll(pageable);
}
public Movie getById(Long id) {
return movieRepository.findById(id)
.orElseThrow(() -> new NoSuchElementException("μνκ° μμ΅λλ€: " + id));
}
public Movie create(Movie movie) {
return movieRepository.save(movie);
}
public Movie update(Long id, Movie movie) {
Movie existMovie = getById(id); π
existMovie.setTitle(movie.getTitle());
existMovie.setReleaseYear(movie.getReleaseYear());
return movieRepository.save(existMovie);
}
public void delete(Long id) {
movieRepository.delete(getById(id)); π
}
}
| ν¬μΈνΈ | μ€λͺ |
|---|---|
@Transactional ν΄λμ€ λ¨μ μ μ© | μ 체 λ©μλμμ νΈλμμ μ΄ μλ μ μ©λμ΄, μμΈ λ°μ μ λ‘€λ°± μ²λ¦¬λ¨. |
getById() λ΄λΆ μ¬μ¬μ© | updateλ deleteμμ μ€λ³΅ μ½λλ₯Ό λ°©μ§νκ³ μμΈ μ²λ¦¬ μΌκ΄μ±μ μ μ§. |
π§© ReviewService
@Service
@RequiredArgsConstructor
@Transactional
public class ReviewService {
private final ReviewRepository reviewRepository;
private final MovieRepository movieRepository;
public Page<Review> getByMovie(Long movieId, Pageable pageable) {
return reviewRepository.findByMovieId(movieId, pageable);
}
public Review getById(Long id) {
return reviewRepository.findById(id)
.orElseThrow(() -> new NoSuchElementException("λ¦¬λ·°κ° μμ΅λλ€: " + id));
}
public Review create(Long movieId, Review review) {
Movie movie = movieRepository.findById(movieId)
.orElseThrow(() -> new NoSuchElementException("λ¦¬λ·°κ° μμ΅λλ€: " + movieId));
review.setMovie(movie); π
return reviewRepository.save(review);
}
public Review update(Long id, Review review) { π
Review existReview = getById(id);
existReview.setReviewer(review.getReviewer());
existReview.setRating(review.getRating());
existReview.setComment(review.getComment());
return reviewRepository.save(existReview);
}
public void delete(Long id) {
reviewRepository.delete(getById(id));
}
}
| ν¬μΈνΈ | μ€λͺ |
|---|---|
review.setMovie(movie) | 리뷰 λ±λ‘ μ λ°λμ λΆλͺ¨ μν°ν°(Movie)λ₯Ό μ€μ ν΄μΌ μΈλ ν€κ° μ λλ‘ μ μ₯λ¨. |
Review update(...) λ°©μ | λΆλΆ μμ μ΄ μλ μ 체 νλ κ΅μ²΄ λ°©μμ΄λ―λ‘, null νλκ° μ λ ₯λλ©΄ κΈ°μ‘΄ κ°μ΄ μ§μμ§ μ μμ. |
π‘ MovieController
@RestController
@RequestMapping("/api/movies")
@RequiredArgsConstructor
public class MovieController {
private final MovieService movieService;
@GetMapping
public Page<Movie> list(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size
) {
Pageable pageable = PageRequest.of(page, size, Sort.by("id").descending());
return movieService.getAll(pageable);
}
@GetMapping("/{id}")
public Movie get(@PathVariable Long id) {
return movieService.getById(id);
}
@PostMapping
public Movie create(@Valid @RequestBody MovieDto movieDto) {
Movie movie = new Movie();
movie.setTitle(movieDto.getTitle());
movie.setReleaseYear(movieDto.getReleaseYear());
return movieService.create(movie);
}
@PutMapping("/{id}")
public Movie update(@PathVariable Long id, @Valid @RequestBody MovieDto movieDto) {
Movie movie = new Movie();
movie.setTitle(movieDto.getTitle());
movie.setReleaseYear(movieDto.getReleaseYear());
return movieService.update(id, movie);
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable Long id) {
movieService.delete(id);
return ResponseEntity.noContent().build();
}
}
π‘ ReviewController
@RestController
@RequiredArgsConstructor
public class ReviewController {
private final ReviewService reviewService;
@GetMapping("/api/movies/{movieId}/reviews")
public Page<Review> listByMovie(
@PathVariable Long movieId,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size
) {
Pageable pageable = PageRequest.of(page, size, Sort.by("id").descending());
return reviewService.getByMovie(movieId, pageable);
}
@PostMapping("/api/movies/{movieId}/reviews")
public Review add(
@PathVariable Long movieId,
@Valid @RequestBody ReviewDto reviewDto
) {
Review review = new Review();
review.setReviewer(reviewDto.getReviewer());
review.setRating(reviewDto.getRating());
review.setComment(reviewDto.getComment());
return reviewService.create(movieId, review);
}
@PutMapping("/api/reviews/{id}")
public Review update(@PathVariable Long id, @Valid @RequestBody ReviewDto ReviewDto) {
Review review = new Review();
review.setReviewer(ReviewDto.getReviewer());
review.setRating(ReviewDto.getRating());
review.setComment(ReviewDto.getComment());
return reviewService.update(id, review);
}
@DeleteMapping("/api/reviews/{id}")
public ResponseEntity<Void> delete(@PathVariable Long id) {
reviewService.delete(id);
return ResponseEntity.noContent().build();
}
}