본문 바로가기
Spring

Spring 시험 (7번), Thymleaf 이용 리스트 출력

by code2772 2023. 1. 9.

[ 목차 ]

    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>

     

    ✔ 영화 리스트 출력화면

     

    반응형