Cohe
SpringBoot Project 게시판 만들기 2 본문
728x90
반응형
우선 프로젝트의 기본 구조를 설명하자면 mvc2 패턴으로 앞으로 작성해야 할 패키지는 controller, service, repository, dto, entity, html 코드이다.
mvc 모델은 다음과 같다.
- Client: 사용자가 웹 브라우저를 통해 서버에 요청을 보낸다
- Controller: 클라이언트의 요청을 받아 적절한 서비스 메서드를 호출한다. 서비스에서 반환된 결과를 바탕으로 HTML 페이지를 생성하여 클라이언트에게 응답한다.
- Service: 비즈니스 로직을 처리하는 계층으로, 데이터 조작 및 변환을 수행한다. 필요한 경우 DTO를 사용하여 데이터를 전달한다
- Repository: 데이터베이스와 상호 작용하는 계층으로, 엔티티 객체를 사용하여 데이터를 저장하고 조회한다.
- DTO (Data Transfer Object): 계층 간 데이터 전달을 위한 객체로, 필요한 데이터만 포함한다.
- Entity: 데이터베이스의 테이블과 매핑되는 객체로, 데이터베이스 스키마와 밀접한 관련이 있다.
- HTML: 클라이언트에게 응답으로 전달되는 웹 페이지를 나타낸다.
더불어 프로젝트를 시작하기 전에 mapper를 설정해야 하기 때문에 config 설정을 해준다. 우선 config 설정을 위한 폴더를 만들고, RootConfig를 만들어준다.
RootConfig
package org.example.demo.config;
import org.modelmapper.ModelMapper;
import org.modelmapper.convention.MatchingStrategies;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration //설정과 관련된 컴포넌트이다.
public class RootConfig {
@Bean //spring setting을 코드로 하고 있다
public ModelMapper getMapper(){
ModelMapper modelMapper = new ModelMapper();
modelMapper.getConfiguration().setFieldMatchingEnabled(true)
.setFieldAccessLevel(org.modelmapper.config.Configuration.AccessLevel.PRIVATE)
.setMatchingStrategy(MatchingStrategies.STRICT); // 딱딱하게 matching하는 것
return modelMapper;
}
}
springboot project 게시판 만들기 2
가장 먼저 할 일은 Board table에 맞춰 작성하는 것이다! 보통 패턴은 entity 작성 -> repository 작성 (test code 작성)-> dto 작성 -> service 작성(test code 작성) -> controller 작성 -> html 작성으로 넘어간다.
폴더를 아래 사진처럼 만들고
가장 먼저 생성한 날짜와 수정한 날짜의 경우 board와 reply에 동시에 들어갈 수 있으니, baseEntity로 생성해주자!
package org.example.demo.domain;
import jakarta.persistence.Column;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.MappedSuperclass;
import lombok.Getter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.time.LocalDateTime;
@MappedSuperclass
@EntityListeners(value = {AuditingEntityListener.class})
@Getter
abstract class BaseEntity {
//시간 설정 - 등록, 수정과 관련된 시간 설정
@CreatedDate //생성 시간 설정
@Column(name = "regdate", updatable = false) //이름은 regdate, update는 불가능
private LocalDateTime regDate;
@LastModifiedDate //마지막 수정 날짜
@Column(name = "moddate")
private LocalDateTime modDate;
}
Repository
엔티티를 만든 다음에 Repository를 작성하면 된다.
package org.example.demo.repository;
import org.example.demo.domain.Board;
import org.example.demo.repository.search.BoardSearch;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
// 이 인터페이스는 JpaRepository를 확장하여 Board 엔티티에 대한 CRUD 작업을 제공합니다.
public interface BoardRepository extends JpaRepository<Board, Long>, BoardSearch { // 타입 = Board, Board bno의 타입인 id
// JpaRepository는 Board 엔티티에 대한 DB 관련 작업을 수행하기 위해 사용됩니다.
// 제네릭 타입 매개변수는 엔티티 타입이 Board이고 ID 타입이 Long임을 지정합니다.
// JPA로 DB 관련 작업을 수행하기 위한 id 값
// 하나면 OPTIONAL, 여러 개면 LIST -> 쿼리 메서드 방식
// 이 메서드는 제목에 주어진 키워드가 포함된 Board 엔티티의 페이지를 반환합니다.
// 결과는 bno (Board 번호)를 기준으로 내림차순으로 정렬됩니다.
Page<Board> findByTitleContainingOrderByBnoDesc(String keyword, Pageable pageable);
// @Query 어노테이션에서 사용하는 구문은 JPQL을 사용합니다.
// JPQL은 SQL과 유사하게 JPA에서 사용하는 쿼리 언어입니다.
// @Query를 이용하는 경우
// 1. 조인과 같이 복잡한 쿼리를 실행하려고 할 때
// 2. 원하는 속성만 추출해서 Object[]로 처리하거나 DTO로 처리 가능
// 3. 속성 값 중 nativeQuery 속성 값을 true로 지정하면 SQL 구문으로 사용이 가능합니다.
// 이 메서드는 JPQL을 사용하여 제목에 주어진 키워드가 포함된 Board를 찾습니다.
// 결과는 Pageable 파라미터에 따라 페이지로 나뉩니다.
@Query("select b FROM Board b where b.title like concat('%',:keyword,'%')")
Page<Board> findKeyword(String keyword, Pageable pageable);
// 이 메서드는 네이티브 SQL을 사용하여 데이터베이스에서 현재 시간을 가져옵니다.
// 현재 시간을 String으로 반환합니다.
@Query(value = "select now()", nativeQuery = true)
String getTime();
}
상세 설명:
- 인터페이스 정의:
BoardRepository
인터페이스는JpaRepository
를 확장하여Board
엔티티에 대한 CRUD 작업을 상속받습니다.- 또한,
BoardSearch
를 확장하여 추가 검색 기능을 제공합니다.
- JpaRepository:
JpaRepository<Board, Long>
는 리포지토리가Board
엔티티를 관리하며, ID 타입이Long
임을 지정합니다.JpaRepository
는save()
,findById()
,findAll()
,deleteById()
등의 메서드를 제공합니다.
- 커스텀 쿼리 메서드:
findByTitleContainingOrderByBnoDesc
:- 이 메서드는 제목에 주어진 키워드가 포함된 모든
Board
엔티티를 찾습니다. - 결과는
bno
기준으로 내림차순으로 정렬됩니다. Page<Board>
를 반환하여 페이징을 지원합니다.
- 이 메서드는 제목에 주어진 키워드가 포함된 모든
- JPQL 쿼리:
findKeyword
:- 이 메서드는 JPQL(Java Persistence Query Language)을 사용하여 쿼리를 실행합니다.
- 제목에 주어진 키워드가 포함된 모든
Board
엔티티를 선택합니다. Pageable
파라미터를 통해 페이징을 지원합니다.
- 네이티브 SQL 쿼리:
getTime
:- 이 메서드는 네이티브 SQL 쿼리를 사용하여 데이터베이스에서 현재 시간을 가져옵니다.
@Query
어노테이션에서nativeQuery
속성을true
로 설정하여 네이티브 SQL 구문을 사용할 수 있습니다.
@Query 사용 사례:
- 복잡한 쿼리: 조인과 같이 복잡한 쿼리를 수행해야 할 때 사용합니다.
- 부분 데이터 추출: 전체 엔티티 대신 특정 속성만 추출해야 할 때, 원하는 필드를 선택하고 DTO 또는
Object[]
로 매핑할 수 있습니다. - 네이티브 쿼리: 원시 SQL 쿼리를 실행해야 할 때,
nativeQuery
를true
로 설정하여 데이터베이스 특정 SQL을 사용할 수 있습니다.
TestCode 작성
이제 repository가 잘 작동되는지 테스트를 해야 한다. Test 폴더에 repository라는 폴더를 만들고, BoardRepositoryTests 클래스를 만든다.
코드는 다음과 같다.
package org.example.demo.repository;
import lombok.extern.log4j.Log4j2;
import org.example.demo.domain.Board;
import org.example.demo.dto.BoardListReplyCountDTO;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import java.util.List;
import java.util.Optional;
import java.util.stream.IntStream;
@SpringBootTest
@Log4j2
public class BoardRepositoryTests {
@Autowired
private BoardRepository boardRepository;
//insert
@Test
public void testInsert() {
IntStream.rangeClosed(1,100).forEach(i -> {
Board board = Board.builder()
.title("title.............."+i)
.content("content...................."+i)
.writer("user"+(i%10)) //사용자는 0~9번까지
.build();
Board result = boardRepository.save(board); //JPA는 자동으로 만들어주기 때문에 내가 만들지 않은 save 메소드도 나온다.
log.info(result);
});
}
@Test
public void testSelct() {
Long bno =100L;
Optional<Board> result = boardRepository.findById(bno); //optional Type으로 받아서 처리해야 함
Board board = result.orElseThrow();
log.info(board);
}
@Test
public void testUpdate() {
//Entity는 생성시 불변이면 좋으나 변경이 일어날 경우 최소한으로 설계한다.
Long bno =100L;
Optional<Board> result = boardRepository.findById(bno); //optional Type으로 받아서 처리해야 함
Board board = result.orElseThrow();
board.change("update title","update contents100"); //modDate 시간은 바뀌나 regdate 시간은 바뀌지 않는다.
boardRepository.save(board);
log.info(board);
}
@Test
public void testDelete() {
Long bno =100L;
Optional<Board> result = boardRepository.findById(bno);
Board board = result.orElseThrow();
boardRepository.delete(board);
}
// Pageable과 page<E> 타입을 이용한 페이징 처리
// 페이징 처리는 Pagealbe이라는 타입의 객체를 구성해서 파라미터로 전달
// pageable은 인터페이스로 설계되어 있고, 일반적으로 PageRequest.of()를 이용해서 개발함
// PageRequest.of(페이지번호, 사이즈) : 페이지번호는 0번부터
// PageRequest.of(페이지번호, 사이즈, Sort) : sort객체를 통한 정렬조건 추가
// PageRequest.of(페이지번호, 사이즈, Sort.Direction, 속성) : 정렬 방향과 여러 속성 추가 지정
// Pageable로 값을 넘기면 반환타입은 Page<T>를 이용하게 됨
@Test
public void testFindAll() {
//1. PAGE order by bno desc
Pageable pageable = PageRequest.of(0,10, Sort.by("bno").descending()); //domain이 알아서 생성해주기 때문에 따로 코드 진행하지 않아도 괜찮다
Page<Board> result =boardRepository.findAll(pageable);
log.info("total count : "+result.getTotalElements()); //전체 게시글의 수가 있음
log.info("total pages : "+result.getTotalPages()); //전체 페이지 개수
log.info("pages number : "+result.getNumber()); //페이지 번호
log.info("pages size : "+result.getSize()); //페이지 사이즈
log.info("pages has previous : "+result.hasPrevious()); //이전 페이지가 있냐
log.info("pages has Next : "+result.hasNext()); //다음 페이지가 있냐
List<Board> boardList=result.getContent();
boardList.forEach(board -> {
log.info(board);
});
}
//쿼리 메서드 및 @Query 테스트
@Test
public void testQueryMethod() {
Pageable pageable = PageRequest.of(0,10);
String title = "title";
Page<Board> result = boardRepository.findByTitleContainingOrderByBnoDesc(
title,
pageable
);
result.getContent().forEach(board -> log.info(board));
}
@Test
public void testQueryAnnotation() {
Pageable pageable = PageRequest.of(0,10, Sort.by("bno").descending());
String title = "title";
Page<Board> result = boardRepository.findByTitleContainingOrderByBnoDesc(title,pageable);
result.getContent().forEach(board -> log.info(board));
}
@Test
public void testGetTime(){
log.info(boardRepository.getTime());
}
@Test
public void testSearch(){
//2번 페이지에 있는 order By bno desc
Pageable pageable = PageRequest.of(1,10, Sort.by("bno").descending());
boardRepository.searchOne(pageable);
}
@Test
public void testSearchAll() {
String[] types = {"t","c","w"};
String keyword = "1";
Pageable pageable = PageRequest.of(1,10,Sort.by("bno").descending());
Page<Board> result = boardRepository.searchAll(types,keyword,pageable);
result.getContent().forEach(board -> log.info(board));
log.info("사이즈 : "+ result.getSize());
log.info("페이지번호 : "+ result.getNumber());
log.info("이전페이지 : "+ result.hasPrevious());
log.info("다음페이지 : "+ result.hasNext());
}
@Test
public void testSearchWithReplyCount(){
String[] types = {"t","c","w"};
String keyword = "1";
Pageable pageable = PageRequest.of(0,10,Sort.by("bno").descending());
Page<BoardListReplyCountDTO> result = boardRepository.searchWithReplyCount(types, keyword,pageable);
result.getContent().forEach(boardListReplyCountDTO -> {
log.info(boardListReplyCountDTO);
});
}
}
- testInsert: 1부터 100까지의 게시물을 생성하여 데이터베이스에 추가합니다.
- testSelect: 게시물 번호 100번을 조회하여 확인합니다.
- testUpdate: 게시물 번호 100번의 제목과 내용을 업데이트하고 확인합니다.
- testDelete: 게시물 번호 100번을 삭제하고 확인합니다.
- testFindAll: 페이징 처리를 하여 게시물을 조회하고 확인합니다.
- testQueryMethod: 제목에 특정 키워드가 포함된 게시물을 쿼리 메서드를 통해 조회하고 확인합니다.
- testQueryAnnotation: 제목에 특정 키워드가 포함된 게시물을 쿼리 어노테이션을 통해 조회하고 확인합니다.
- testGetTime: 데이터베이스의 현재 시간을 조회하여 확인합니다.
- testSearch: 특정 페이지에 있는 게시물을 조회하고 확인합니다.
- testSearchAll: 다양한 조건으로 게시물을 조회하고 확인합니다.
- testSearchWithReplyCount: 답글 수와 함께 다양한 조건으로 게시물을 조회하고 확인합니다.
728x90
반응형
'Spring, SpringBoot' 카테고리의 다른 글
Spring Security와 사용자 역할 관리: 오늘의 학습 내용 정리 (0) | 2024.08.27 |
---|---|
JWT를 이용한 Spring Security 인증 구현하기 (0) | 2024.08.27 |
SpringBoot Project 게시판 만들기 (1) | 2024.05.22 |
MyBatis 스프링 연동 (0) | 2024.04.19 |