본문 바로가기
Spring/GuestBook

GuestBook #2 Querydsl 사용을 위한 설정부터 까지 처리 메서드선언까지

by java나유 2022. 10. 29.

6.Querydsl 사용을 위한 설정

1)build.gradle 파일 수정

 


  
//QueryDSL
-------맨위작성-----------------------------------------
buildscript {
ext {
queryDslVersion = "5.0.0"
}
}
-----------------------------------------------
id "com.ewerk.gradle.plugins.querydsl" version "1.0.10"
--------------------------------------------------------------------
implementation "com.querydsl:querydsl-jpa:${queryDslVersion}"
implementation "com.querydsl:querydsl-apt:${queryDslVersion}"
------------------dependencies 다음에---------------------------------------
def querydslDir = "$buildDir/generated/querydsl"
querydsl {
jpa = true
querydslSourcesDir = querydslDir
}
sourceSets {
main.java.srcDir querydslDir
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
querydsl.extendsFrom compileClasspath
}
compileQuerydsl {
options.annotationProcessorPath = configurations.querydsl
}
tasks.named('test') {
useJUnitPlatform()
}

2) Gradle-Tasks-build-jar 더블 클릭

 

3)GuestBookRepository 인터페이스의 선언 부분 수정

GuestBookRepository


  
public interface GuestBookRepository extends JpaRepository<GuestBook, Long>,
QuerydslPredicateExecutor<GuestBook> {
}

 

4)Test 클래스에서 title에 1이 포함된 데이터를 조회 && 출력하는 메서드 생성하고 확인


  
//데이터 querydsl을 이용한 조회
@Test
public void testQuery1() {
//gno의 내림차순 정렬 후 0페이지 10를 가져오기 위한 Pageable 객체 생성
Pageable pageable = PageRequest.of(0, 10, Sort.by("gno")
.descending()); //뒤에서 부터 gno에 1이 들어가는 10개
//Entity에 동적 쿼리를 수행할 수 있는 도메인 클래스 찾아오기 Querydsl 설정을 해야 사용 가능
//컬럼들을 속성으로 포함시켜 조건을 설정하는 것이 가능
QGuestBook qGuestBook = QGuestBook.guestBook; //I
//검색어 생성
String keyword = "1";
//검색을 적용하기 위한 Builder 객체 생성
BooleanBuilder builder = new BooleanBuilder(); //2
//조건 표현식 생성
BooleanExpression expression =
qGuestBook.title.contains(keyword); //3
//검색 객체에 표현식 추가
builder.and(expression); //4
Page<GuestBook> result = guestBookRepository.findAll(
builder, pageable); //5
result.stream().forEach(guestbook -> {
System.out.println(guestbook);
});
}

 

5)Test 클래스에 title이나 content에 1이 포함된 데이터를 조회해서 출력하는 메서드를 생성하고 확인


  
@Test
public void testQuery2() {
//gno의 내림차순 정렬 후 0페이지 10를 가져오기 위한 Pageable 객체 생성
Pageable pageable = PageRequest.of(0, 10, Sort.by("gno")
.descending()); //뒤에서 부터 gno에 1이 들어가는 10개
//Entity에 동적 쿼리를 수행할 수 있는 도메인 클래스 찾아오기 Querydsl 설정을 해야 사용 가능
//컬럼들을 속성으로 포함시켜 조건을 설정하는 것이 가능
QGuestBook qGuestBook = QGuestBook.guestBook;
//검색어 생성
String keyword = "1";
//검색을 적용하기 위한 Builder 객체 생성
BooleanBuilder builder = new BooleanBuilder();
//조건 표현식 생성
BooleanExpression ex1 =
qGuestBook.title.contains(keyword);
BooleanExpression ex2 =
qGuestBook.content.contains(keyword);
//2개의 표현식을 or로 연결
BooleanExpression expression =
ex1.or(ex2);
//검색 객체에 표현식 추가
builder.and(expression); //4
Page<GuestBook> result = guestBookRepository.findAll(
builder, pageable); //5
result.stream().forEach(guestbook -> {
System.out.println(guestbook);
});
}

7.Service Layer

1)domain 패키지에 GuestBookDTO 클래스를 생성

GuestBookDTO


  
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public class GuestBookDTO {
private Long gno;
private String title;
private String content;
private String writer;
private LocalDateTime regDate;
private LocalDateTime modDate;
}

 

2)service 패키지에 Service 인터페이스를 생성하고 DTO와 Entity사이의 변환을 위한 메서드 작성

GuestBookService


  
public interface GuestBookService {
//DTO를 Entity로 변환해주는 메서드
default GuestBook dtoToEntity(GuestBookDTO dto){
GuestBook entity = GuestBook.builder()
.gno(dto.getGno())
.title(dto.getTitle())
.content(dto.getContent())
.writer(dto.getWriter())
.build();
return entity;
}
}

 

GuestBookService'


  
//Entity를 DTO로 변환해주는 메서드
default GuestBookDTO entiBookDto(GuestBook entity){
GuestBookDTO dto = GuestBookDTO.builder()
.gno(entity.getGno())
.title(entity.getTitle())
.content(entity.getContent())
.writer(entity.getWriter())
.regDate(entity.getRegDate())
.modDate(entity.getModDate())
.build();
return dto;
}

 

3)클라이언트의 요청을 처리할 메서드를 구현 한 Service 클래스를 생성

GuestBookServiceImpl


  
@RequiredArgsConstructor
@Service
@Log4j2
public class GuestBookServiceImpl implements GuestBookService{
private final GuestBookRepository guestBookRepository;
}

⇒실제 업무에서는 Repository가 여러개 인 경우가 많음

⇒항상 이곳에서는 implements를 하고, Repository를 주입 해야 함

⇒Service 많이 안 읽어보고 Repository랑 entity을 위주로 코드를 많이 읽어봄

 

 

4)데이터 삽입을 위한 메서드 선언

GuestBookService


  
public interface GuestBookService {
//데이터 삽입을 위한 메서드
public Long register(GuestBookDTO dto);

 

5) ServiceImpl 클래스에 데이터 삽입을 위한 메서드 구현

GuestBookServiceImpl


  
@RequiredArgsConstructor
@Service
@Log4j2
public class GuestBookServiceImpl implements GuestBookService{
private final GuestBookRepository guestBookRepository;
//데이터 삽입을 위한 메서드
@Override
public Long register(GuestBookDTO dto){
log.info("데이터 삽입");
log.info(dto);
//Repository에서 사용하기 위해 DTO를 Entity로 변환
GuestBook entity =dtoToEntity(dto);
//데이터 삽입
GuestBook result =guestBookRepository.save(entity);
//삽입한 후 리턴받은 데이터의 gno 리턴
return result.getGno();
}
}

 

6)test디렉토리에 Service 테스트를 위한 클래스를 생성하고 삽입 메서트 테스트

ServiceTest


  
@SpringBootTest
public class ServiceTest {
@Autowired
private GuestBookService guestBookService;
@Test
public void insertTest(){
GuestBookDTO dto = GuestBookDTO.builder()
.title("삽입 테스트")
.content("서비스에서 삽입")
.writer("yn")
.build();
Long gno=guestBookService.register(dto);
System.out.println("삽입된 번호:"+gno);
}

 

7)목록 가져오기 구현

⇒데이터 목록을 요청할 때 전체 데이터가 아니라 페이지 단위로 요청하는 경우가 있는데 이 경우는 페이지 번호와 데이터 개수를 매개변수로 대입해주어야 한다.

페이지번호와 데이터개수를 받아서 Pageable인스턴스를 생성해주는 DTO를 생성

PageRequestDTO


  
@Builder
@AllArgsConstructor
@Data
public class PageRequestDTO {
//페이지 번호
private int page;
//페이지 당 출력할 데이터 개수
private int size;
//기본값을 설정하기 위한 생성자
public PageRequestDTO() {
this.page = 1;
this.size = 10;
}
//페이지 번호와 데이터 개수를 가지고 Pageable 객체를 만들어주는 메서드
public Pageable getPageable(Sort sort) {
return PageRequest.of(page - 1, size, sort);
}
}

 

⇒목록 가져오기 요청을 했을 때 응답 받기 위한 DTO클래스를 생성

-다른 곳에서 재사용을 하기 위해 Generic을 사용 -domain.PageResponseDTO

PageResponseDTO


  
import lombok.Data;
import org.springframework.data.domain.Page;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
//목록보기 응답을 위한 클래스 (Generic)
@Data
public class PageResponseDTO<DTO, EN> {
//응답 목록을 저장할 List
private List<DTO> dtoList;
//Page 객체와 변환 함수를 넘겨받아서 dtoList를 만들어주는tjem
//Page객체를 순회하면서 fn함수로 변환한 후 List로 만들기
public PageResponseDTO(Page<EN> result, Function<EN,DTO> fn ){
dtoList = result.stream().map(fn).collect(Collectors.toList());
}
}

 

⇒목록 보기를 위한 메서드를 Service 인터페이스에 선언

GuestBookService


  
//목록보기를 위한 메서드
public PageResponseDTO<GuestBookDTO, GuestBook>
getList(PageRequestDTO requestDTO);

 

⇒목록 보기를 위한 메서드를 ServiceImpl 클래스에 구현


  
@Override
public PageResponseDTO<GuestBookDTO, GuestBook> getList(PageRequestDTO requestDTO) {
//페이지 단위 요청을 위한 Pageable 객체를 생성
Pageable pageable = requestDTO.getPageable(
Sort.by("gno").descending());
//데이터베이스에서 조회
Page<GuestBook> result =
guestBookRepository.findAll(pageable);
//Entity를 DTO로 변환하기 위한 객체 생성
Function<GuestBook, GuestBookDTO> fn =
(entity -> entityToDTO(entity));
//데이터 목록 생성
return new PageResponseDTO<>(result, fn);
}

 

⇒Test클래스에 테스트를 위한 메서드를 생성하고 확인


  
//목록보기 테스트
@Test
public void listTest(){
//페이지 번호 와 데이터 개수 설정
PageRequestDTO pageRequestDTO =
PageRequestDTO.builder()
.page(1)
.size(10)
.build();
//메서드 호출
PageResponseDTO<GuestBookDTO, GuestBook> resultDTO =
guestBookService.getList(pageRequestDTO);
//확인
for(GuestBookDTO dto : resultDTO.getDtoList()){
System.out.println(dto);
}
}

 

⇒페이지 번호 목록을 하단에 출력하고자 하는 경우는 PageResponseDTo 클래스에 추가


  
@Data
public class PageResponseDTO<DTO, EN> {
//응답 목록을 저장할 List
private List<DTO> dtoList;
//전체 페이지 개수
private int totalPage;
//현재 페이지 번호
private int page;
//한 페이지에 출력되는 데이터 개수
private int size;
//페이지 번호 목록의 시작 번호 와 종료 번호
private int start, end;
//이전 과 다음 존재 여부
private boolean prev, next;
//페이지 번호 목록
private List<Integer> pageList;
//페이지 번호 목록을 만들어주는 메서드
private void makePageList(Pageable pageable){
//현재 페이지 번호 와 페이지 당 데이터 개수 가져오기
this.page = pageable.getPageNumber() + 1;
this.size = pageable.getPageSize();
//임시 종료 페이지 번호
//페이지 번호를 10개 출력할 것이라서 10으로 나누고 곱함
//페이지 번호 개수를 다르게 하고자 하면 숫자를 변경
int tempEnd = (int)(Math.ceil(page/10.0)) * 10;
//시작 페이지 번호
start = tempEnd - 9;
//이전 존재 여부
prev = start > 1;
//종료 페이지 번호
end = totalPage > tempEnd ? tempEnd : totalPage;
next = totalPage > tempEnd;
//페이지 번호 목록 만들기
pageList = IntStream.rangeClosed(start, end)
.boxed().collect(Collectors.toList());
}
//Page 객체 와 변환 함수를 넘겨받아서
//dtoList를 만들어주는 메서드
public PageResponseDTO(Page<EN> result,
Function<EN, DTO> fn){
//Page 객체를 순회하면서 fn 함수로 변환한 후
//List로 만들기
dtoList = result.stream().map(fn)
.collect(Collectors.toList());
//페이지 번호 목록 만들기
totalPage = result.getTotalPages();
makePageList(result.getPageable());
}
}

 

⇒이전에 만든 테스트 메서드를 수정해서 확인


  
//목록보기 테스트
@Test
public void listTest(){
//페이지 번호 와 데이터 개수 설정
PageRequestDTO pageRequestDTO =
PageRequestDTO.builder()
.page(1)
.size(10)
.build();
//메서드 호출
PageResponseDTO<GuestBookDTO, GuestBook> resultDTO =
guestBookService.getList(pageRequestDTO);
//확인
for(GuestBookDTO dto : resultDTO.getDtoList()){
System.out.println(dto);
}
//이전 페이지 번호 와 다음 페이지 존재 여부
System.out.println("이전 여부:" + resultDTO.isPrev());
System.out.println("다음 여부:" + resultDTO.isNext());
//전체 페이지 개수
System.out.println("전체 페이지 개수:"
+ resultDTO.getTotalPage());
//페이지 번호 목록
resultDTO.getPageList()
.forEach(i -> System.out.println(i));
}

8.Controller Layer

1)목록 보기 구현

⇒Controller클래스 요청 처리 메서드를 수정


  
@Controller
@Log4j2
@RequiredArgsConstructor
public class GuestBookController {
//서비스 객체 주입
private final GuestBookService guestBookService;
@GetMapping({"/"})
public String main(){
log.info("/");
return "redirect:/guestbook/list";
}
//void를 리턴하면 요청 URL이 View의 이름이 됩니다.
@GetMapping({ "/guestbook/list"})
public void list(PageRequestDTO dto, Model model){
log.info("list............");
//서비스 메서드 호출
//result 의 dtoList 에 DTO 의 List 가 있고
//result 의 pageList 에 페이지 번호의 List 가 존재
model.addAttribute("result",
guestBookService.getList(dto));
}
}

 

⇒list.html 파일 수정


  
<!DOCTYPE html>
<html lang="en"
xmlns:th="http://www.thymeleaf.org">
<th:block th:replace="~{/layout/basic:: setContent(~{this::content})}">
<th:block th:fragment="content">
<h1>방명록</h1>
<table class="table table-striped">
<thead>
<tr>
<th scope="col">글번호</th>
<th scope="col">제목</th>
<th scope="col">작성자</th>
<th scope="col">작성일</th>
</tr>
</thead>
<tbody>
<tr th:each="dto : ${result.dtoList}">
<th scope="row">[[${dto.gno}]]</th>
<td>[[${dto.title}]]</td>
<td>[[${dto.writer}]]</td>
<td>[[${#temporals.format(dto.regDate, 'yyyy/MM/dd')}]]</td>
</tr>
</tbody>
</table>
<ul class="pagination h-100 justify-content-center align-items-center">
<li class="page-item " th:if="${result.prev}">
<a class="page-link"
th:href="@{/guestbook/list(page= ${result.start - 1})}"
tabindex="-1">이전</a>
</li>
<li th:class=" 'page-item' + ${result.page == page ? 'active':''} "
th:each = "page : ${result.pageList}">
<a class="page-link"
th:href="@{/guestbook/list(page=${page})}">
[[${page}]]
</a>
</li>
<li class="page-item " th:if="${result.next}">
<a class="page-link"
th:href="@{/guestbook/list(page= ${result.end + 1})}"
>다음</a>
</li>
</li>
</ul>
</th:block>
</th:block>

 

2)등록 구현

⇒등록 링크를 눌러서 데이터 입력 화면으로 이동으로 데이터 입력 화면에서 데이터를 입력한 후 실제 등록 링크를 눌러서 이동하는 것은 forwarding으로 처리하지만 삽입 작업을 수행하고 결과 페이지로 이동 할 때는 redirect로 이동

⇒Controller클래스에 등록을 위한 메서드 생성


  
//등록 요청을 GET 방식으로 처리하는 메서드 - 등록 페이지로 이동
@GetMapping("/guestbook/register")
public void register(){
log.info("register GET...");
}
//등록 요청을 POST 방식으로 처리하는 메서드 - 등록 수행
@PostMapping("/guestbook/register")
public String register(GuestBookDTO dto,
RedirectAttributes redirectAttributes){
log.info("register POST...");
//등록 요청 처리
Long gno = guestBookService.register(dto);
//데이터 저장
redirectAttributes.addFlashAttribute("msg",
gno + " 등록");
//목록 보기로 리다이렉트
return "redirect:/guestbook/list";
}

 

⇒list.html파일에 등록 링크를 추가하고 msg 출력할 영역을 생성


  
<span>
<a th:href="@{/guestbook/register}">
<button type="button" class="btn btn-outline-primary">
방명록 작성
</button>
</a>
</span>
<div th:if = "${msg != null}" th:text="${msg}"></div>

 

=>templates/guestbook 디렉토리에 register.html 파일을 만들고 작성


  
<!DOCTYPE html>
<html lang="en"
xmlns:th="http://www.thymeleaf.org">
<th:block th:replace="~{/layout/basic:: setContent(~{this::content})}">
<th:block th:fragment="content">
<h1 class="mt-4">방명록 등록</h1>
<form th:action="@{/guestbook/register}"
th:method="post">
<div class="form-group">
<label>제목</label>
<input type="text" class="form-control"
name="title" placeholder="제목 입력"/>
</div>
<div class="form-group">
<label>내용</label>
<textarea class="form-control" rows="5"
name="content"></textarea>
</div>
<div class="form-group">
<label>작성자</label>
<input type="text" class="form-control"
name="writer" placeholder="작성자 입력"/>
</div>
<button type="submit" class="btn btn-primary">
등록
</button>
</form>
</th:block>
</th:block>
728x90

댓글