GuestBook 프로젝트 (1) (프로젝트 구조, gradle, application, querydsl 설정)
GuestBook 프로젝트 (1) (프로젝트 구조, gradle, application, querydsl 설정)
코드로 배우는 스프링 부트 웹 프로젝트 - 남가람북스 코드로 배우는 스프링 부트 웹 프로젝트 - 남가람북스 코드로 배우는 스프링부트 웹 프로젝트 - 남가람북스 https://www.namgarambooks.co.kr/27 11.
soohykeee.tistory.com
앞서 만들었던 GuestbookRepositoryTests파일에서 querydsl의 테스트진행을 하기위해 코드를 추가했다.
Querydsl의 사용하는 방법은
- BooleanBuilder를 생성
- 조건에 맞는 구문은 Querydsl에서 사용하는 Predicate 타입의 함수를 생성
- BooleanBuilder에 작성된 Predicate를 추가하고 실행
// querydsl을 이용한 검색 테스트 + 페이징
@Test
public void testQuerydsl1() {
Pageable pageable = PageRequest.of(0, 10, Sort.by("gno")); //페이징
String keyword = "1"; //검색할 단어
QGuestbook qGuestbook = QGuestbook.guestbook; //1
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);
});
}
위의 코드를 번호에 따라서 설명하자면,
- 가장 먼저 동적으로 처리하기 위해서 Q도메인 클래스를 얻어온다. (Q도메인 클래스를 이용하면 엔티티클래스에 선언된 title, content같은 필드들을 변수로 활용할 수 있다.)
- BooleanBuilder는 where문에 들어가는 조건들을 넣어주는 컨테이너라고 간주하면 된다. (BooleanBuilder에는 .and(), .or()를 사용하여 여러조건들을 계속해서 추가해줄 수 있다.)
- 원하는 조건은 필드값과 같이 결합해서 생성한다. BooleanBuilder안에 들어가는 값은 Predicate타입이어야한다. (여기서 Predicate 타입은 com.querydsl.core.types.Predicate를 말한다.)
- 만들어진 조건은 where문에 and나 or같은 키워드와 결합시킨다. 만약 title에서 뿐만 아니라 content나 writer에서도 검색을 하고 싶다면 and, or 과 같은 키워드와 결합하여 추가적으로 검색조건을 부여할 수 있다.
- BooleanBuilder는 GuestbookRepository에 추가된 QuerydslPredicateExcutor 인터페이스의 findAll()을 사용할 수 있다.
위와같은 테스트코드를 통해서, 페이지 처리와 동시에 검색처리가 가능하다.코드를 실행해보면, 이전에 쌓아두었던 데이터에서 페이지 처리를 gno 순으로 한 후, 앞서부터 있는 title에서 1이 들어간 데이터들을 보여준다.
DTO설정
앞서 작성한 방식처럼 엔티티 객체에 직접적으로 접근하여 사용하는 방식은 보안상 권하지 않는 방식이다. 따라서 DTO (Data Transfer Object)를 이용하는 방식이 권장된다.
DTO는 계층 간 데이터 교환을 하기 위해 사용하는 객체로, DTO는 로직을 가지지 않는 순수한 데이터 객체(getter & setter 만 가진 클래스)이다. 순수한 데이터를 담고 있다는 점에서 엔티티 객체와 유사하지만, 목적 자체가 데이터의 전달이므로 읽고 쓰는 것이 모두 허용되고, 일회성으로 사용되는 성격이 강하다.
개발하는 guestbook 프로젝트에서는 Service 계층을 생성하고 Service 계층에서는 DTO로 파라미터와 리턴타입을 처리하도록 구성할 것이다. DTO를 사용하면 엔티티 객체의 범위를 한정 지을 수 있기 때문에 안전한 코드를 작성할 수 있다.
dto 디렉토리를 생성한 후, GuestbookDTO를 생성해준다. 또한 service 디렉토리 또한 생성한 후 GuestbookService, Impl을 생성해준다.
package com.example.guestbookdemo.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public class GuestbookDTO {
private Long gno;
private String title;
private String content;
private String writer;
private LocalDateTime regDate, modDate;
}
* Java 8 버전 이상부터는 인터페이스의 실제 내용을 가지는 코드를 default라는 키워드로 생성이 가능하다. 기존에는 추상클래스를 통해서 전달해야 하는 실제 코드를 인터페이스에서 생성이 가능하다.
따라서 기존의 '인터페이스 -> 추상클래스 -> 구현클래스' 방식에서'인터페이스 -> 구현클래스'로 가능해졌다.
GuestbookService에 방명록을 등록하는 register 메소드를 생성하고 Impl에서 구현을 했다.
GuestbookService에는 인터페이스 내에 default 기능을 활용해서 구현클래스에서 동작할 수 있는 dtoToEntity()를 구성하고, GuestbookServiceImpl 클래스에서는 이를 활용해서 파라미터로 전달되는 GuestbookDTO를 변환했다.
package com.example.guestbookdemo.service;
import com.example.guestbookdemo.dto.GuestbookDTO;
import com.example.guestbookdemo.entity.Guestbook;
public interface GuestbookService {
Long register(GuestbookDTO guestbookDTO);
default Guestbook dtoToEntity(GuestbookDTO guestbookDTO) {
Guestbook entity = Guestbook.builder()
.gno(guestbookDTO.getGno())
.title(guestbookDTO.getTitle())
.content(guestbookDTO.getContent())
.writer(guestbookDTO.getWriter())
.build();
return entity;
}
}
------------------------------------------------
package com.example.guestbookdemo.service;
import com.example.guestbookdemo.dto.GuestbookDTO;
import com.example.guestbookdemo.entity.Guestbook;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Service;
@Service
@Log4j2
public class GuestbookServiceImpl implements GuestbookService{
@Override
public Long register(GuestbookDTO guestbookDTO) {
log.info("DTO-----------------");
log.info(guestbookDTO);
Guestbook entity = dtoToEntity(guestbookDTO);
log.info(entity);
return null;
}
}
Service 테스트
작성된 GuestbookService가 정상적으로 작동하는지는 테스트 작업을 통해 확인하고 Controller와 연결해주는 것이 좋다. 따라서 test 하위 디렉토리에 service 디렉토리를 추가하고, GuestbookServiceTests 클래스 파일을 추가했다.
package com.example.guestbookdemo.service;
import com.example.guestbookdemo.dto.GuestbookDTO;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class GuestbookServiceTests {
@Autowired
private GuestbookService guestbookService;
@Test
public void testRegister() {
GuestbookDTO guestbookDTO = GuestbookDTO.builder()
.title("Sample Title...")
.content("Sample Content...")
.writer("user0")
.build();
System.out.println(guestbookService.register(guestbookDTO));
}
}
위의 testRegister() 코드는 실제 DB에는 저장되지 않지만 콘솔을 통해 GuestbookDTO가 Entity로 변환된 결과를 확인할 수 있다.
위의 테스트 코드를 통해 성공적으로 변환이 되는것을 확인했으니, GuestbookServiceImpl에서 repository에 값을 저장해주도록 작성해주면 된다. repository에 저장이 되도록 수정한 후, 다시 테스트 코드를 실행하여 데이터베이스에 정상적으로 저장이 되는지 확인해주면 된다.
package com.example.guestbookdemo.service;
import com.example.guestbookdemo.dto.GuestbookDTO;
import com.example.guestbookdemo.entity.Guestbook;
import com.example.guestbookdemo.repository.GuestbookRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Service;
@Service
@Log4j2
@RequiredArgsConstructor //의존성 자동 주입
public class GuestbookServiceImpl implements GuestbookService {
//반드시 final로 선언해주어야 한다.
private final GuestbookRepository repository;
@Override
public Long register(GuestbookDTO guestbookDTO) {
Guestbook entity = dtoToEntity(guestbookDTO);
repository.save(entity); //repository에 저장
return entity.getGno();
}
}
정상적으로 데이터베이스에 저장이 된것을 확인할 수 있다.