728x90
반응형
영화 테이블을 만들어라 (영화번호, 제목, 국가, 장르, 개봉일, 런닝타임)
더미 데이터의 수는 상관 없다. 이를 어떠한 방식으로든 리스트로 출력해라
✔ 영화 테이블 생성 (Entity) - sql 을 만들지 않고 @Table 을 이용하여 컬럼(제목, 국가, 장르, 런닝타임) 선언
package com.koreait.task7.domain;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import net.bytebuddy.asm.Advice;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;
@Getter
@ToString(callSuper = true)// 자기거라 부모것도 불러와라
@Table(indexes = { //@Table은 엔티티와 매핑할 테이블을 지정
@Index(columnList = "title"),
@Index(columnList = "national"),
@Index(columnList = "genre"),
@Index(columnList = "running")
// name을 추가하면 테이블이름이 name값으로 설정이 되고 생략시 Entity이름으로 테이블이 만들어지는 것을 확인할 수 있다.
})
@Entity
//@Entity 어노테이션은 JPA를 사용해 테이블과 매핑할 클래스에 붙여주는 어노테이션이다. 이 어노테이션을 붙임으로써 JPA가 해당 클래스를 관리하게 된다.
public class Movie extends AuditingFields{
@Id// Entity 입력할 경우 프라이머리 키를 선언해 줘야한다.
@GeneratedValue(strategy = GenerationType.IDENTITY) // 기본 키 생성을 DB에 위임 (Mysql)
private Long id;
@Setter @Column(nullable = false) private String title; // 제목
// @Setter @Column(nullable = false) private LocalDateTime open; // 개봉시기
@Setter private String national;
@Setter private String genre;
@Setter private String running;
//JPA에서 DB Table의 Column을 Mapping 할 때 @Column Annotation을 사용한다.
protected Movie() {}
private Movie( String title,LocalDateTime open, String national, String genre, String running) {
this.title = title;
// this.open = open;
this.national = national;
this.genre = genre;
this.running = running;
}
public static Movie of(String title, LocalDateTime open, String national, String genre, String running) { // of함수 객체를 만들 수 있는 함수
return new Movie(title, open, national, genre, running);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
@Override
public boolean equals(Object obj) {
if(this == obj) return true;
if(!(obj instanceof Movie movie)) return false;
return id != null && id.equals(movie.id);
}
}
✔ 영화 테이블의 개봉일을 AudithingFields로 자동 선언
package com.koreait.task7.domain;
import lombok.Getter;
import lombok.ToString;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import org.springframework.format.annotation.DateTimeFormat;
import javax.persistence.Column;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import java.time.LocalDateTime;
@Getter
@ToString
@EntityListeners(AuditingEntityListener.class)
@MappedSuperclass//이 아이는 부모가 될거야(UserAccount는 자식으로 AuditingFields를 상속한다.)
public abstract class AuditingFields {
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) // 데이터의 형태를 사용하기 쉽게 변경하기
@CreatedDate
@Column(nullable = false) private LocalDateTime open; // 생성일시 수정 시 자동 업데이트
✔ 더미 데이터 생성하여 테이블에 값을 넣어주기
✔ 테이블 데이터 들을 Dto 로 선언하여 사용하기
package com.koreait.task7.dto;
import com.koreait.task7.domain.Movie;
import java.time.LocalDateTime;
public record MovieDto(
Long id,
String title,
LocalDateTime open,
String national,
String genre,
String running
)
{
public static MovieDto of( Long id
,String title, LocalDateTime open,
String national, String genre,
String running) {
return new MovieDto( id, title, open,
national, genre, running);
}
public static MovieDto from(Movie entity){
return new MovieDto(
entity.getId(),
entity.getTitle(),
entity.getOpen(),
entity.getNational(),
entity.getGenre(),
entity.getRunning()
);
}
public Movie toEntity(){
return Movie.of(
title,
open,
national,
genre,
running
);
}
}
✔ 게시글에 테이블 데이터를 출력하기 위한 서비스를 만들어 주기
package com.koreait.task7.service;
import com.koreait.task7.domain.Movie;
import com.koreait.task7.dto.MovieDto;
import com.koreait.task7.repository.MovieRepository;
import com.koreait.task7.domain.Type.SearchType;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.EntityNotFoundException;
@Slf4j
@Transactional
@RequiredArgsConstructor
@Service
public class MovieService {
private final MovieRepository movieRepository;
@Transactional(readOnly = true)// 데이터에 권한을 주지 않는다
public Page<MovieDto> searchMovies(SearchType searchType, String searchKeyword, Pageable pageable){
if (searchKeyword == null || searchKeyword.isBlank()){
return movieRepository.findAll(pageable).map(MovieDto::from);
}
return switch (searchType){
case TITLE -> movieRepository.findByTitleContaining(searchKeyword, pageable).map(MovieDto::from);
case OPEN -> movieRepository.findByOpenContaining(searchKeyword, pageable).map(MovieDto::from);
case ID -> movieRepository.findByNationalContaining(searchKeyword, pageable).map(MovieDto::from);
case GENRE -> movieRepository.findByGenre(searchKeyword, pageable).map(MovieDto::from);
case NATIONAL -> movieRepository.findByNationalContaining("#"+ searchKeyword, pageable).map(MovieDto::from);
case RUNNING -> movieRepository.findByRunning("#"+ searchKeyword, pageable).map(MovieDto::from);
};
}
@Transactional(readOnly = true)
public MovieDto getMovie(Long movieId){
return movieRepository.findById(movieId)
.map(MovieDto::from)
.orElseThrow(() -> new EntityNotFoundException("게시글이 없습니다 - movieId : " + movieId));
}
public void saveMovie(MovieDto dto){
movieRepository.save(dto.toEntity());
}
public void updateMovie(MovieDto dto) {// 바로 세이브 하는것이 아니라 있는지 없는지 확인을 하고 업데이트를 해야한다. getReferenceById : 셀렉트 없이 실행
try {
Movie movie = movieRepository.getReferenceById(dto.id());
if (dto.title() != null) {
movie.setTitle(dto.title());
}
if (dto.title() != null) {
movie.setTitle(dto.title());
}
movie.setRunning(dto.running());
// dto.title() 없으면 get ()안에 머가 있으면 set -> 자바17버전
}catch (EntityNotFoundException e){
log.warn("게시글 업데이트 실패. 게시글을 찾을 수 없음 - dto: {}", dto);
}
}
}
✔ 출력할 리스트의 페이지를 만들어 주고 데이터를 전달 할 부분을 만들어 주기
package com.koreait.task7.controller;
import com.koreait.task7.dto.response.MovieResponse;
import com.koreait.task7.service.MovieService;
import com.koreait.task7.domain.Type.SearchType;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@RequiredArgsConstructor// 생성자 추가시 작성
@RequestMapping("movies")
@Controller
public class MovieController {
private final MovieService movieService;// 생성자
@GetMapping
public String movies(
@RequestParam(required =false) SearchType searchType,
@RequestParam(required =false)String searchValue,
@PageableDefault(size = 10, sort = "open",direction = Sort.Direction.DESC) Pageable pageable, ModelMap map
){
map.addAttribute("movies", movieService
.searchMovies(searchType,searchValue,pageable).map(MovieResponse::from));
return "movies/index"; // index 전달
}
✔ 출력할 페이지의 HTML
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="hunseop">
<title>게시판 페이지</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-0evHe/X+R7YkIZDRvuzKMRqM+OrBnVFBL6DOitfPri4tjfHxaWutUpFmBp4vmVor" crossorigin="anonymous">
<!-- <link href="/css/search-bar.css" rel="stylesheet">-->
</head>
<body>
<header id="header">
헤더 삽입
<hr>
</header>
<main class="container">
<div class="row">
<div class="card card-margin search-form">
<div class="card-body p-0">
<form id="card search-form">
<div class="row">
<div class="col-12">
<div class="row no-gutters">
<div class="col-lg-3 col-md-3 col-sm-12 p-0">
<label for="search-type" hidden>검색 유형</label>
<select class="form-control" id="search-type">
<option>제목</option>
<option>본문</option>
<option>id</option>
<option>닉네임</option>
<option>해시태그</option>
</select>
</div>
<div class="col-lg-8 col-md-6 col-sm-12 p-0">
<label for="search-value" hidden>검색어</label>
<input type="text" placeholder="검색어..." class="form-control" id="search-value" name="search-value">
</div>
<div class="col-lg-1 col-md-3 col-sm-12 p-0">
<button type="submit" class="btn btn-base">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-search">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
</button>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
<table class="table" id="movie-table">
<thead>
<tr>
<th class="title">제목</th>
<th class="open">개봉시기</th>
<th class="national">국가</th>
<th class="genre">장르</th>
<th class="running ">런닝타임</th>
</tr>
</thead>
<tbody>
<tr>
<td class = "title">아바타</td>
<td class = "open"><time>2023-01-03</time></td>
<td class = "national">한국</td>
<td class = "genre">액션</td>
<td class = "running">10분</td>
</tr>
<tr>
<td>두번째글</td>
<td>#spring</td>
<td>김사과</td>
<td>2023-01-03</td>
<td>2023-01-03</td>
</tr>
<tr>
<td>세번째글</td>
<td>#java</td>
<td>김사과</td>
<td>2023-01-03</td>
<td>2023-01-03</td>
</tr>
</tbody>
</table>
<nav aria-label="Page navigation example">
<ul class="pagination justify-content-center">
<li class="page-item"><a class="page-link" href="#">Previous</a></li>
<li class="page-item"><a class="page-link" href="#">1</a></li>
<li class="page-item"><a class="page-link" href="#">2</a></li>
<li class="page-item"><a class="page-link" href="#">3</a></li>
<li class="page-item"><a class="page-link" href="#">Next</a></li>
</ul>
</nav>
</main>
<footer id="footer">
푸터 삽입
<hr>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-pprn3073KE6tl6bjs2QrFaJGz5/SUsLqktiwsUTF55Jfv3qYSDhgCecCxMW52nD2" crossorigin="anonymous"></script>
</body>
</html>
✔ HTML 에 데이터를 보내주기 위한 부분
<?xml version="1.0"?>
<thlogic>
<attr sel="#movie-table">
<attr sel="tbody" th:remove="all-but-first">
<attr sel="tr[0]" th:each="movie : ${movies}">
<attr sel="td.title" th:text="${movie.title}"/>
<attr sel="td.open/time" th:datetime="${movie.open}" th:text="${#temporals.format(movie.open, 'yyyy-MM-dd')}"/>
<attr sel="td.national" th:text="${movie.national}"/>
<attr sel="td.genre" th:text="${movie.genre}"/>
<attr sel="td.running" th:text="${movie.running}"/>
</attr>
</attr>
</attr>
</thlogic>
✔ 영화 리스트 출력화면
반응형
'Spring' 카테고리의 다른 글
Spring Boot 비동기 처리(@Async)로 성능 향상 원리와 동기/비동기 (0) | 2025.02.19 |
---|---|
Spring Security 기본(세션, 스프링시큐리티) (0) | 2023.03.13 |
Spring Project Board 2 (Repository, DTO, PageController) - 타임리프 SQL 출력 (1) | 2023.01.08 |
Spring Project Board(게시판) 1 (0) | 2023.01.02 |
Spring 유용한 Setting, 사이트 (0) | 2022.12.29 |