[SpringBoot] 스프링부트 + JPA를 이용한 CRUD 구현 4. 리스트 출력하기
🎋 JPA Auditing
src/main/java/com/test/spring/boot_crud/domain/BaseTimeEntity.java
package com.test.spring.boot_crud.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
@EntityListeners(AuditingEntityListener.class)
public class BaseTimeEntity {
@CreatedDate
private LocalDateTime createdDate;
@LastModifiedDate
private LocalDateTime modifiedDate;
}
Posts 클래스도 수정해주자.
...
public class Posts extends BaseTimeEntity {
...
-
JPA Auditing : Spring Data JPA에서 시간에 대해 자동으로 데이터베이스의 테이블에 값을 넣어주는 기능
-
@MappedSuperclass : JPA Entity 클래스들이 해당 추상 클래스를 상속할 경우 CreatedDate와 ModifiedDate를 컬럼으로 인식
-
@EntityListeners(AuditingEntityListener.class) : 해당 클래스에 Auditing 기능 포함
Application 클래스에도 JPA Auditing 어노테이션을 활성화 할 수 있는 @EnableJpaAuditing
어노테이션을 추가해주자.
package com.test.spring.boot_crud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
@EnableJpaAuditing
@SpringBootApplication
public class BootCrudApplication {
public static void main(String[] args) {
SpringApplication.run(BootCrudApplication.class, args);
}
}
🎋 index 화면 수정
index 화면을 수정 해 게시글 리스트를 출력해보자.
src/main/resources/templates/index.mustache
<h1>스프링 부트 + JPA 게시판 만들기</h1>
<a href="/posts/save" class="btn btn-info">글 등록</a>
<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">
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
위 코드는 아래에서 수정할 예정 ㅎ.ㅎ
🎋 Repository
src/main/java/com/test/spring/boot_crud/domain/posts/PostsRepository.java
package com.test.spring.boot_crud.domain.posts;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import java.util.List;
public interface PostsRepository extends JpaRepository<Posts, Long> {
@Query("SELECT p FROM Posts p ORDER BY p.id DESC")
List<Posts> findAllDesc();
}
- @Query : JPA가 자동 생성하는 쿼리가 아닌 사용자가 정의하는 쿼리 생성 또는 데이터베이스에 종속적인 Native Query 생성을 위해 사용
🎋 Service
src/main/java/com/test/spring/boot_crud/service/PostsService.java
package com.test.spring.boot_crud.service.posts;
import com.test.spring.boot_crud.domain.posts.PostsRepository;
import com.test.spring.boot_crud.web.dto.PostsListResponseDto;
import com.test.spring.boot_crud.web.dto.PostsSaveRequestDto;
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 {
...
@Transactional(readOnly = true)
public List<PostsListResponseDto> findAllDesc(){
return postsRepository.findAllDesc().stream()
.map(PostsListResponseDto::new)
.collect(Collectors.toList());
}
}
- .map(PostsListResponseDto::new) : .map(posts -> new PostListResponseDto(posts))
🎋 Dto
src/main/java/com/test/spring/boot_crud/web/dto/PostsListResponseDto.java
package com.test.spring.boot_crud.web.dto;
import com.test.spring.boot_crud.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;
}
}
🎋 Controller
src/main/java/com/test/spring/boot_crud/web/IndexController.java
package com.test.spring.boot_crud.web;
import com.test.spring.boot_crud.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";
}
}
🎋Mustache - No key, method or field with name 에러
실행했더니 지겨운 500 에러가 떴다.
Mustache에서 Key 값이 Null이거나 empty인 경우 위와 같은 에러가 발생한다고 한다.
처음 등록한 글의 modifiedDate 값이 없어서 에러가 났나보다.
index.mustache로 가서
<td></td>
이 부분을
<td>
N
</td>
이렇게 수정하자.
값이 있다면 위의 `` 안 값을 출력, null이거나 empty인 경우 가운데(N 자리) 값을 출력한다는 의미이다.
수정 후 다시 실행 ㄱㄱ, 새로운 글을 등록해보자.
위에서 설정한대로 처음 작성한 글에는 N이, 두 번째로 작성한 글에는 modifiedDate가 뜨는 걸 볼 수 있다.