개발/Spring & Springboot

[Springboot] mustache template으로 게시판 만들기 - 2 (조회)

뚜키 💻 2022. 2. 21. 00:29
반응형

[Springboot] mustache template으로 게시판 만들기 - 2 (조회)

 

목차

[Springboot] mustache template 설정
[Springboot] mustache template으로 게시판 만들기 - 1 (등록)

> [Springboot] mustache template으로 게시판 만들기 - 2 (조회)

[Springboot] mustache template으로 게시판 만들기 - 3 (수정)

[Springboot] mustache template으로 게시판 만들기 - 4 (삭제)

 

 

2. 조회

 

index.mustache

UI 변경 - 목록 출력 영역 추가

{{>layout/header}}

    <h1>스프링 부트로 시작하는 웹 서비스 Ver.2</h1>
    <div class="col-md-12">
        <div class="row">
            <div class="col-md-6">
                <a href="/posts/save" role="button" class="btn btn-primary">글 등록</a>
            </div>
        </div>
        <br>
        <!-- 목록 출력 영역 -->
        <table class="table table-horizontal table-bordered">
            <thead class="thead-strong">
            <tr>
                <th>게시글번호</th>
                <th>제목</th>
                <th>작성자</th>
                <th>최종수정일</th>
            </tr>
            </thead>
            <tbody id="tbody">
            {{#posts}}
                <tr>
                    <td>{{id}}</td>
                    <td>{{title}}</td>
                    <td>{{author}}</td>
                    <td>{{modifiedDate}}</td>
                </tr>
            {{/posts}}
            </tbody>
        </table>
    </div>

{{>layout/footer}}

머스테치 문법

{{#posts}} - posts라는 List 순회

{{id}} 등 - {{변수명}}, List에서 뽑아낸 객체의 필드 사용

 

 

Posts, PostsRepository, BaseTimeEntity 추가

Posts.java

package com.sooki.book.springboot.domain.posts;

import com.sooki.book.springboot.domain.BaseTimeEntity;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import javax.persistence.*;

@Getter
@NoArgsConstructor
@Entity
public class Posts extends BaseTimeEntity {

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

    @Column(length = 500, nullable = false)
    private String title;

    @Column(columnDefinition = "TEXT", nullable = false)
    private String content;

    private String author;

    @Builder
    public Posts(String title, String content, String author) {
        this.title = title;
        this.content = content;
        this.author = author;
    }

    public void update(String title, String content) {
        this.title = title;
        this.content = content;
    }
}

 

 

 

BaseTimeEntity.java

package com.sooki.book.springboot.domain;

import lombok.Getter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import java.time.LocalDateTime;

@Getter
@MappedSuperclass                                   // JPA Entity 클래스들이 BaseTimeEntity을 상속할 경우 필드들(createdDate, modifiedDate)도 칼럼으로 인식하도록 함
@EntityListeners(AuditingEntityListener.class)      // BaseTimeEntity 클래스에 Auditing 기능 포함시킴
public abstract class BaseTimeEntity {

    @CreatedDate                                    // Entity가 생성되어 저장될 때 시간이 자동 저장됨
    private LocalDateTime createdDate;

    @LastModifiedDate                               // 조회한 Entity의 값이 변경될 때 시간이 자동 저장됨
    private LocalDateTime modifiedDate;
}

 

PostsRepository 인터페이스

package com.sooki.book.springboot.domain.posts;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface PostsRepository extends JpaRepository<Posts, Long> {

    @Query("SELECT p FROM Posts p ORDER BY p.id DESC")
    List<Posts> findAllDesc();
}

 

<참고>
보통 규모가 있는 프로젝트에서는 조인 혹은 복잡한 조건등의 여러가지 이유로 Entity 클래스만으로 처리하기 어렵다. 그래서 조회용 프레임워크를 추가로 많이 사용하는데 대표적으로 querydsl, jooq, MyBatis 등을 사용한다.
조회는 위 3가지 프레임워크 중 하나를 통해 조회하고 등록,수정,삭제 등은 SpringDataJpa를 이용한다.

 

PostsService.java

- 조회 메소드 추가(findAllDesc)

 

package com.sooki.book.springboot.service.posts;

import com.sooki.book.springboot.domain.posts.Posts;
import com.sooki.book.springboot.domain.posts.PostsRepository;
import com.sooki.book.springboot.web.dto.PostsListResponseDto;
import com.sooki.book.springboot.web.dto.PostsResponseDto;
import com.sooki.book.springboot.web.dto.PostsSaveRequestDto;
import com.sooki.book.springboot.web.dto.PostsUpdateRequestDto;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.stream.Collectors;

@RequiredArgsConstructor
@Service
public class PostsService {
    private final PostsRepository postsRepository;

    @Transactional
    public Long save(PostsSaveRequestDto requestDto) {
        return postsRepository.save(requestDto.toEntity()).getId();
    }

    @Transactional
    public Long update(Long id, PostsUpdateRequestDto requestDto) {
        Posts posts = postsRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("해당 게시글이 없습니다. id=" +id));

        posts.update(requestDto.getTitle(), requestDto.getContent());

        return id;
    }

    public PostsResponseDto findById (Long id) {
        Posts entity = postsRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("해당 게시글이 없습니다. id=" +id));

        return new PostsResponseDto(entity);
    }

    @Transactional(readOnly = true)
    public List<PostsListResponseDto> findAllDesc() {
        return postsRepository.findAllDesc().stream()
                .map(PostsListResponseDto::new)
                .collect(Collectors.toList());
    }
}

 

@Transactional(readOnly = true)를 추가하면 트랜잭션 범위는 유지하되, 조회 기능만 남겨두기때문에 조회 속도가 개선된다. 단순 조회기능만 있는 서비스 메소드에서는 위 어노테이션을 추가해주는것이 좋다.

 

PostsListResponseDto.java 추가

package com.sooki.book.springboot.web.dto;

import com.sooki.book.springboot.domain.posts.Posts;
import lombok.Getter;

import java.time.LocalDateTime;

@Getter
public class PostsListResponseDto {
    private Long id;
    private String title;
    private String author;
    private LocalDateTime modifiedDate;

    public PostsListResponseDto(Posts entity) {
        this.id = entity.getId();
        this.title = entity.getTitle();
        this.author = entity.getAuthor();
        this.modifiedDate = entity.getModifiedDate();
    }
}

 

 

IndexController.java 수정

- private final PostsService postsService; 추가

- index 메소드 수정

Model

- 서버 템플릿 엔진에서 사용할 수 있는 객체 저장

- 조회해서 posts에 담고 index.mustache에 전달

package com.sooki.book.springboot.web;

import com.sooki.book.springboot.service.posts.PostsService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@RequiredArgsConstructor
@Controller
public class IndexController {

    private final PostsService postsService;

    @GetMapping("/")
    public String index(Model model) {
        model.addAttribute("posts", postsService.findAllDesc());
        return "index";
    }

    @GetMapping("/posts/save")
    public String postsSave() {
        return "posts-save";
    }
}

 

결과 확인

- 서버켜서 테스트 데이터를 넣고 조회가 잘 되는지 확인한다

 

Reference
책 스프링 부트와 AWS로 혼자 구현하는 웹 서비스 (이동욱 지음)
반응형