Skip to content
Closed
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ dependencies {
implementation group: 'org.springdoc', name: 'springdoc-openapi-starter-webmvc-ui', version: '2.6.0'
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-actuator', version: '3.3.3'

//elastic-search
implementation 'org.springframework.boot:spring-boot-starter-data-elasticsearch'

//test
testImplementation 'org.springframework.boot:spring-boot-starter-test'
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/com/somemore/SomemoreApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableJpaAuditing
@EnableScheduling
public class SomemoreApplication {

public static void main(String[] args) {
SpringApplication.run(SomemoreApplication.class, args);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.somemore.community.dto.response.CommunityBoardDetailResponseDto;
import com.somemore.community.dto.response.CommunityBoardResponseDto;
import com.somemore.community.usecase.board.CommunityBoardDocumentUseCase;
import com.somemore.community.usecase.board.CommunityBoardQueryUseCase;
import com.somemore.global.common.response.ApiResponse;
import io.swagger.v3.oas.annotations.Operation;
Expand All @@ -20,6 +21,7 @@
public class CommunityBoardQueryApiController {

private final CommunityBoardQueryUseCase communityBoardQueryUseCase;
private final CommunityBoardDocumentUseCase communityBoardDocumentUseCase;

@GetMapping("/community-boards")
@Operation(summary = "전체 커뮤니티 게시글 조회", description = "전체 커뮤니티 게시글 목록을 조회합니다.")
Expand All @@ -46,6 +48,19 @@ public ApiResponse<Page<CommunityBoardResponseDto>> getByWriterId(
);
}

@GetMapping("/community-boards/search")
@Operation(summary = "커뮤니티 게시글 키워드 검색", description = "키워드로 포함한 커뮤니티 게시글 목록을 조회합니다.")
public ApiResponse<Page<CommunityBoardResponseDto>> getCommunityBoardsBySearch(
String keyword,
Pageable pageable
) {
return ApiResponse.ok(
200,
communityBoardDocumentUseCase.getCommunityBoardBySearch(keyword, pageable.getPageNumber()),
"커뮤니티 게시글 검색 리스트 조회 성공"
);
}

@GetMapping("/community-board/{id}")
@Operation(summary = "커뮤니티 게시글 상세 조회", description = "커뮤니티 게시글의 상세 정보를 조회합니다.")
public ApiResponse<CommunityBoardDetailResponseDto> getById(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.somemore.community.domain;

import jakarta.persistence.Id;
import lombok.Builder;
import lombok.Getter;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;

@Getter
@Document(indexName = "community_board")
public class CommunityBoardDocument {

@Id
private Long id;

@Field(type = FieldType.Text, analyzer = "nori_analyzer")
private String title;

@Field(type = FieldType.Text, analyzer = "nori_analyzer")
private String content;

@Builder
public CommunityBoardDocument(Long id, String title, String content) {
this.id = id;
this.title = title;
this.content = content;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.somemore.community.repository.board;

import com.somemore.community.domain.CommunityBoardDocument;
import org.springframework.data.elasticsearch.annotations.Query;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;

import java.util.List;

public interface CommunityBoardDocumentRepository extends ElasticsearchRepository<CommunityBoardDocument, Long> {
@Query("{\"multi_match\": {\"query\": \"?0\", \"fields\": [\"title\", \"content\"]}}")
List<CommunityBoardDocument> findIdsByTitleOrContentContaining(String keyword);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

import java.util.List;
import java.util.Optional;
import java.util.UUID;

Expand All @@ -18,4 +19,9 @@ default boolean doesNotExistById(Long id) {
return !existsById(id);
}
void deleteAllInBatch();

Page<CommunityBoardView> findByCommunityBoardsContaining(String keyword, Pageable pageable);
void saveDocuments(List<CommunityBoard> communityBoards);
List<CommunityBoard> findAll();
void deleteDocument(Long id);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;
import com.somemore.community.domain.CommunityBoard;
import com.somemore.community.domain.CommunityBoardDocument;
import com.somemore.community.repository.mapper.CommunityBoardView;
import com.somemore.community.domain.QCommunityBoard;
import com.somemore.volunteer.domain.QVolunteer;
Expand All @@ -14,6 +15,7 @@
import org.springframework.data.support.PageableExecutionUtils;
import org.springframework.stereotype.Repository;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
Expand All @@ -24,6 +26,7 @@ public class CommunityBoardRepositoryImpl implements CommunityBoardRepository {

private final JPAQueryFactory queryFactory;
private final CommunityBoardJpaRepository communityBoardJpaRepository;
private final CommunityBoardDocumentRepository documentRepository;

private static final QCommunityBoard communityBoard = QCommunityBoard.communityBoard;
private static final QVolunteer volunteer = QVolunteer.volunteer;
Expand Down Expand Up @@ -83,6 +86,51 @@ public boolean existsById(Long id) {
return communityBoardJpaRepository.existsByIdAndDeletedFalse(id);
}

@Override
public Page<CommunityBoardView> findByCommunityBoardsContaining(String keyword, Pageable pageable) {
List<CommunityBoardDocument> boardDocuments = documentRepository.findIdsByTitleOrContentContaining(keyword);

List<Long> boardIds = boardDocuments.stream()
.map(CommunityBoardDocument::getId)
.toList();

List<CommunityBoardView> content = getCommunityBoardsQuery()
.where(communityBoard.id.in(boardIds)
.and(isNotDeleted()))
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetch();

JPAQuery<Long> countQuery = queryFactory
.select(communityBoard.count())
.from(communityBoard)
.join(volunteer).on(communityBoard.writerId.eq(volunteer.id))
.where(communityBoard.id.in(boardIds));

return PageableExecutionUtils.getPage(content, pageable, countQuery::fetchOne);
}

@Override
public void saveDocuments(List<CommunityBoard> communityBoards) {
List<CommunityBoardDocument> communityBoardDocuments = convertEntityToDocuments(communityBoards);
documentRepository.saveAll(communityBoardDocuments);
}

@Override
public void deleteDocument(Long id) {
documentRepository.deleteById(id);
}

@Override
public List<CommunityBoard> findAll() {
return communityBoardJpaRepository.findAll();
}

@Override
public void deleteAllInBatch() {
communityBoardJpaRepository.deleteAllInBatch();
}

private JPAQuery<CommunityBoardView> getCommunityBoardsQuery() {
return queryFactory
.select(Projections.constructor(CommunityBoardView.class,
Expand All @@ -93,10 +141,18 @@ private JPAQuery<CommunityBoardView> getCommunityBoardsQuery() {
.orderBy(communityBoard.createdAt.desc());
}


@Override
public void deleteAllInBatch() {
communityBoardJpaRepository.deleteAllInBatch();
private List<CommunityBoardDocument> convertEntityToDocuments(List<CommunityBoard> communityBoards) {
List<CommunityBoardDocument> communityBoardDocuments = new ArrayList<>();

for (CommunityBoard communityboard : communityBoards) {
CommunityBoardDocument document = CommunityBoardDocument.builder()
.id(communityboard.getId())
.title(communityboard.getTitle())
.content(communityboard.getContent())
.build();
communityBoardDocuments.add(document);
}
return communityBoardDocuments;
}

private BooleanExpression isNotDeleted() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.somemore.community.scheduler;

import com.somemore.community.domain.CommunityBoard;
import com.somemore.community.usecase.board.CommunityBoardDocumentUseCase;
import com.somemore.community.usecase.board.CommunityBoardQueryUseCase;
import lombok.RequiredArgsConstructor;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
@RequiredArgsConstructor
public class CommunityScheduler {

private final CommunityBoardQueryUseCase communityBoardQueryUseCase;
private final CommunityBoardDocumentUseCase communityBoardDocumentUseCase;


@Scheduled(cron = "10 * * * * *")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

간격이 타이트 한 것 같습니다 제 생각엔 데이터 최신화를 위한 스케줄러 같은데 맞나요?
맞다면 getAll Community Boards라 부하가 클 거 같다는 생각이 듭니다
logstash로 저희 RDB를 체크하는게 어떨까요

public void updateCommunityBoardDocuments() {
List<CommunityBoard> communityBoards = communityBoardQueryUseCase.getAllCommunityBoards();
communityBoardDocumentUseCase.saveCommunityBoardDocuments(communityBoards);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.somemore.community.service.board;

import com.somemore.community.domain.CommunityBoard;
import com.somemore.community.dto.response.CommunityBoardResponseDto;
import com.somemore.community.repository.board.CommunityBoardRepository;
import com.somemore.community.repository.mapper.CommunityBoardView;
import com.somemore.community.usecase.board.CommunityBoardDocumentUseCase;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@RequiredArgsConstructor
@Service
public class CommunityBoardDocumentService implements CommunityBoardDocumentUseCase {

private final CommunityBoardRepository communityBoardRepository;
private static final int PAGE_SIZE = 10;

@Transactional(readOnly = true)
@Override
public Page<CommunityBoardResponseDto> getCommunityBoardBySearch(String keyword, int page) {
Pageable pageable = PageRequest.of(page, PAGE_SIZE);
Page<CommunityBoardView> boards = communityBoardRepository.findByCommunityBoardsContaining(keyword, pageable);
return boards.map(CommunityBoardResponseDto::from);
}

@Transactional
@Override
public void saveCommunityBoardDocuments(List<CommunityBoard> communityBoards) {
communityBoardRepository.saveDocuments(communityBoards);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.UUID;

import static com.somemore.global.exception.ExceptionMessage.NOT_EXISTS_COMMUNITY_BOARD;
Expand Down Expand Up @@ -46,4 +47,9 @@ public CommunityBoardDetailResponseDto getCommunityBoardDetail(Long id) {
.orElseThrow(() -> new BadRequestException(NOT_EXISTS_COMMUNITY_BOARD.getMessage()));
return CommunityBoardDetailResponseDto.from(board);
}

@Override
public List<CommunityBoard> getAllCommunityBoards() {
return communityBoardRepository.findAll();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.somemore.community.usecase.board;

import com.somemore.community.domain.CommunityBoard;
import com.somemore.community.dto.response.CommunityBoardResponseDto;
import org.springframework.data.domain.Page;

import java.util.List;

public interface CommunityBoardDocumentUseCase {
Page<CommunityBoardResponseDto> getCommunityBoardBySearch(String keyword, int page);
void saveCommunityBoardDocuments(List<CommunityBoard> communityBoards);
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package com.somemore.community.usecase.board;

import com.somemore.community.domain.CommunityBoard;
import com.somemore.community.dto.response.CommunityBoardDetailResponseDto;
import com.somemore.community.dto.response.CommunityBoardResponseDto;
import org.springframework.data.domain.Page;

import java.util.List;
import java.util.UUID;

public interface CommunityBoardQueryUseCase {
Page<CommunityBoardResponseDto> getCommunityBoards(int page);
Page<CommunityBoardResponseDto> getCommunityBoardsByWriterId(UUID writerId, int page);
CommunityBoardDetailResponseDto getCommunityBoardDetail(Long id);
List<CommunityBoard> getAllCommunityBoards();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.somemore.global.configure;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.elasticsearch.client.ClientConfiguration;
import org.springframework.data.elasticsearch.client.elc.ElasticsearchConfiguration;

@Configuration
public class ElasticsearchConfig extends ElasticsearchConfiguration {
@Value("${elastic.search.uris}")
private String uri;
@Value("${elastic.search.username}")
private String username;
@Value("${elastic.search.password}")
private String password;

@Override
public ClientConfiguration clientConfiguration() {
return ClientConfiguration.builder()
.connectedTo(uri)
.withBasicAuth(username, password)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.somemore.recruitboard.dto.response.RecruitBoardResponseDto;
import com.somemore.recruitboard.dto.response.RecruitBoardWithCenterResponseDto;
import com.somemore.recruitboard.dto.response.RecruitBoardWithLocationResponseDto;
import com.somemore.recruitboard.usecase.query.RecruitBoardDocumentUseCase;
import com.somemore.recruitboard.usecase.query.RecruitBoardQueryUseCase;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
Expand All @@ -32,6 +33,7 @@
public class RecruitBoardQueryApiController {

private final RecruitBoardQueryUseCase recruitBoardQueryUseCase;
private final RecruitBoardDocumentUseCase recruitBoardDocumentUseCase;

@GetMapping("/recruit-board/{id}")
@Operation(summary = "봉사 모집글 상세 조회", description = "특정 모집글의 상세 정보를 조회합니다.")
Expand Down Expand Up @@ -83,7 +85,7 @@ public ApiResponse<Page<RecruitBoardWithCenterResponseDto>> getAllBySearch(

return ApiResponse.ok(
200,
recruitBoardQueryUseCase.getAllWithCenter(condition),
recruitBoardDocumentUseCase.getRecruitBoardBySearch(keyword, condition),
"봉사 활동 모집글 검색 조회 성공"
);
}
Expand Down
Loading
Loading