검색기능 사용을 위한 기본편 이후 controller부에서 사용하기 위한 service로직 구현까지의 내용이다
@RepositoryRestResource // spring data rest 사용하기 위함
public interface ArticleRepository extends
JpaRepository<Article, Long>,
QuerydslPredicateExecutor<Article>, //해당 엔티티 안에있는 모든 검색기능을 추가해줌 // 완전 동일해야만 동작
QuerydslBinderCustomizer<QArticle> // 부분검색, 대소문자 구분 등을 위함
{
Page<Article> findByTitleContaining(String title, Pageable pageable); //제목검색
Page<Article> findByContentContaining(String content, Pageable pageable); //내용검색/ Containing-부분검색
// Page<Article> findByUserAccount_UserIdContaining(String userId, Pageable pageable); //회원id검색
// Page<Article> findByUserAccount_NicknameContaining(String nickName, Pageable pageable); //닉네임검색
Page<Article> findByHashtag(String hashtag, Pageable pageable); //제목검색
@Override
default void customize(QuerydslBindings bindings, QArticle root){
//Article에 대해 선택적인 필드에 대한 검색
bindings.excludeUnlistedProperties(true); //리스팅 하지 않은 프로퍼티 검색 제외
bindings.including(root.title, root.content, root.hashtag, root.createdAt, root.createdBy); //검색 컬럼
bindings.bind(root.title).first(StringExpression::containsIgnoreCase); // like '%${v}%'/ 부분검색
bindings.bind(root.content).first(StringExpression::containsIgnoreCase);
bindings.bind(root.hashtag).first(StringExpression::containsIgnoreCase);
bindings.bind(root.createdAt).first(DateTimeExpression::eq);//원하는 날짜검색 /시분초 동일하게 넣어야함
bindings.bind(root.createdBy).first(StringExpression::containsIgnoreCase);
}
}
* Repository
레파지토리 부분에 서비스 로직에 사용할 메서드를 추가하였다.
Containing은 부분검색을 가능하게 만들어준다.
@Slf4j
@RequiredArgsConstructor
@Transactional //import annotation 유의해서 가져오기
@Service
public class ArticleService {
private final ArticleRepository articleRepository;
//게시글 페이지형 검색
@Transactional(readOnly = true) // 변경하지 않기때문에 readonly
public Page<Article> searchArticles(SearchType searchType, String search_keyword, Pageable pageable) {
// 검색어 없이 검색하면 게시글 페이지를 반환.
if (search_keyword == null || search_keyword.isBlank()) {
return articleRepository.findAll(pageable);
}
// 항목에 따른 검색 - 조회
switch (searchType) { // return이 되는 switch ->와 함께 사용 java14이상부터 사용가능
case TITLE:
return articleRepository.findByTitleContaining(search_keyword, pageable);
case CONTENT:
return articleRepository.findByContentContaining(search_keyword, pageable);
// case ARTICLE_ID:
// return articleRepository.findByUserAccount_UserIdContaining(search_keyword, pageable);
// case NICKNAME:
// return articleRepository.findByUserAccount_NicknameContaining(search_keyword, pageable);
case HASHTAG:
return articleRepository.findByHashtag(search_keyword, pageable);
}
return null;
}
* Service
일치하는 검색어가 없으면 전체 게시글 페이지를 반환하고 있으면 해당하는 페이지만 반환하도록 로직을 구성하였다.
public enum SearchType {
TITLE, CONTENT, ARTICLE_ID, NICKNAME, HASHTAG
}
엔티티 검색을 하기위해 필요한 컬럼명 등록
@RequiredArgsConstructor
@Validated
@RequestMapping("/articles")
@RestController
public class ArticleController {
private final ArticleService articleService;
private final ArticleMapper articleMapper;
@GetMapping //부분검색 //http://localhost:8080/articles?searchType=CONTENT&searchValue=검색어
public ResponseEntity searchArticles(@RequestParam(required = false)SearchType searchType,//required = false- 선택적 파라미터
@RequestParam(required = false)String searchValue,
@PageableDefault(size = 10,sort = "createdAt", direction = Sort.Direction.DESC)Pageable pageable)
{
Page<Article> articlePages = articleService.searchArticles(searchType, searchValue, pageable);
List<Article> articles = articlePages.getContent();
List<ArticleDto.Response> response = articleMapper.articleToArticleListResponse(articles);
return new ResponseEntity(response, HttpStatus.OK);
}
* Controller
부분검색기능을 받는 컨트롤러에 Getmapping부
@PageableDefault를 통해 pageble을 입력해주지 않으면 디폴트값이 들어가게 된다.
content내용 검색을 위한 파라미터 입력 예) searchType=CONTENT&searchValue=검색내용
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface ArticleMapper {
Article articlePostToArticle(ArticleDto.Post articlePostDto);
Article articlePatchToArticle(ArticleDto.Patch articlePatchDto);
ArticleDto.Response articleToArticleResponse(Article article);
List<ArticleDto.Response> articleToArticleListResponse(List<Article> articles);
}
* Mapper
컨트롤러 부에서 엔티티를 ResponseDto로 변환할 매퍼 검색기능에서는 페이지를 반환하기 위해
List<response>가 사용된다.
@Getter
@ToString(callSuper = true) //callSuper = true - 안쪽까지 들어가서 ToString을 찍어냄
@Table(indexes = {
@Index(columnList = "title"),
@Index(columnList = "hashtag"),
@Index(columnList = "createdAt"),
@Index(columnList = "createdBy"),
})
@Entity
@NoArgsConstructor//(access = AccessLevel.PROTECTED) //기본생성자의 무분별한 생성을 막아서 의도치않는 엔티티 생성을 막음
public class Article extends AuditingFields {
/* @Setter을 클래스레벨로 설정하지 않는 이유 id, 시간 등 을 Setter 하지 않기 위함*/
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long articleId;
// @Setter @ManyToOne(optional = false)
//private UserAccount userAccount; //유저 정보(id) 매핑
@Setter @Column(nullable = false)
private String title; // 제목
@Setter @Column(nullable = false, length = 10000)
private String content; // 본문
@Setter
private String hashtag; // 해시태그
@OrderBy("createdAt DESC") // 생성 시간순 정렬
@OneToMany(mappedBy = "article", cascade = CascadeType.ALL)// 모든경우 외래키를 걸어줌/ 실무에선 양방향바인딩 지양
@ToString.Exclude //조회하는 과정에서 무한 순환참조 방지, 퍼포먼스 이슈로 인하여 설정 / 연결고리를 끊어줌
private final Set<ArticleComment> articleComments = new LinkedHashSet<>();
public Article(String title, String content, String hashtag) { //도메인과 관련있는 정보만 오픈
//this.userAccount = userAccount;
this.title = title;
this.content = content;
this.hashtag = hashtag;
}
/**
//new를 사용하지 않고 생성자 생성하기 위함 // of는 매개변수가 뭐가필요한지 가이드해줌
public static Article of(String title, String content, String hashtag) {
return new Article(title, content, hashtag);
} // @Mapper 사용 할때에는 쓸수없는 방법
**/
//equalse 해시코드 기능 생성(alt+insert에 있음) / id만 같으면 같은게시물
@Override //동일성 검사
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Article)) return false;
Article article = (Article) o;
return articleId!=null && articleId.equals(article.articleId); //데이터가 insert되기 전에 만들어진 id는 탈락
}
@Override
public int hashCode() { //동일성 검사를 마친 아이디에 대해서만 해시
return Objects.hash(articleId);
}
}
* Entity
검색 대상 엔티티
postman URI ex) http://localhost:8080/articles?searchType=CONTENT&searchValue=검색
- article엔티티내의 content컬럼에서 검색이라는 단어가 포함되어있는 항목들을 모두 반환한다.