Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 15 additions & 6 deletions src/main/java/com/example/braveCoward/controller/DoController.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import java.util.List;

@Slf4j // Lombok을 사용한 로깅
@RestController
@RequiredArgsConstructor
Expand Down Expand Up @@ -133,17 +135,24 @@ public ResponseEntity<Page<DoResponse>> searchDoStartsWith(

@GetMapping("/searchFullText")
@ExecutionTimeLogger
public ResponseEntity<Page<DoResponse>> searchDoFullText(
public ResponseEntity<List<DoResponse>> searchDoFullText(
@RequestParam String keyword,
@RequestParam Long projectId,
@RequestParam(defaultValue = "1") @Min(1) int page,
@RequestParam(defaultValue = "10") @Min(1) int size
@RequestParam Long projectId
) {
PageDTO pageDTO = new PageDTO(page, size);
Page<DoResponse> responses = doService.searchDoFullText(keyword, projectId, pageDTO);
List<DoResponse> responses = doService.searchDoFullText(keyword, projectId);
return ResponseEntity.ok(responses);
}




@PostMapping("/optimizeFullTextIndex")
@ExecutionTimeLogger
public ResponseEntity<Void> optimizeFullTextIndex() {
doService.optimizeFullTextIndex();
return ResponseEntity.ok().build();
}

@PatchMapping("/complete/{doId}")
public ResponseEntity<Void> completeDo(
@PathVariable Long doId
Expand Down
22 changes: 13 additions & 9 deletions src/main/java/com/example/braveCoward/repository/DoRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
import java.util.List;
import java.util.Optional;

import jakarta.persistence.QueryHint;
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.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.jpa.repository.QueryHints;
import org.springframework.data.repository.query.Param;

import com.example.braveCoward.model.Do;
Expand Down Expand Up @@ -36,15 +38,17 @@ Page<Do> findAllByDescriptionStartsWithAndProjectId(@Param("keyword") String key
@Query(
value = "SELECT d.id as id, d.date as date, d.description as description, d.plan_id as planId " +
"FROM do d " +
"WHERE MATCH(d.description) AGAINST(:keyword IN BOOLEAN MODE) " +
// "WHERE MATCH(d.description) AGAINST(:keyword IN BOOLEAN MODE) " +
"WHERE MATCH(d.description) AGAINST(:keyword IN NATURAL LANGUAGE MODE) " +
"AND d.project_id = :projectId " +
"ORDER BY d.id DESC",
countQuery = "SELECT COUNT(*) " +
"FROM do d " +
"WHERE MATCH(d.description) AGAINST(:keyword IN BOOLEAN MODE) " +
"AND d.project_id = :projectId",
"LIMIT 10", // FULLTEXT 검색 후 바로 LIMIT 적용
nativeQuery = true)
Page<DoProjection> searchDoFullText(@Param("keyword") String keyword,
@Param("projectId") Long projectId,
Pageable pageable);
List<DoProjection> searchDoFullText(@Param("keyword") String keyword,
@Param("projectId") Long projectId);

@Modifying
@Query(value = "OPTIMIZE TABLE do", nativeQuery = true)
void optimizeTable();


}
59 changes: 47 additions & 12 deletions src/main/java/com/example/braveCoward/service/DoService.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
package com.example.braveCoward.service;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;

import com.example.braveCoward.repository.DoProjection;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import lombok.extern.slf4j.Slf4j;
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 org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.sql.DataSource;
import com.example.braveCoward.dto.Do.ChangeDoRequest;
import com.example.braveCoward.dto.Do.CreateDoRequest;
import com.example.braveCoward.dto.Do.CreateDoResponse;
Expand All @@ -30,9 +36,35 @@
@Transactional(readOnly = true)
public class DoService {

private final DataSource dataSource;


private final DoRepository doRepository;
private final PlanRepository planRepository;


public void optimizeFullTextIndex() {
try (Connection connection = dataSource.getConnection();
Statement statement = connection.createStatement()) {

log.info("🚀 MySQL `OPTIMIZE TABLE do` 실행 중...");

// 🔥 현재 세션이 read-only 모드일 경우 강제 해제
connection.setReadOnly(false);
connection.setAutoCommit(true);

statement.execute("OPTIMIZE TABLE do");

statement.execute("ANALYZE TABLE do");

log.info("✅ 인덱스 최적화 완료!");
} catch (SQLException e) {
log.error("❌ FULLTEXT 인덱스 최적화 실패", e);
throw new RuntimeException("❌ FULLTEXT 인덱스 최적화 실패", e);
}
}


@Transactional(readOnly = false)
public CreateDoResponse createDo(Long planId, CreateDoRequest request) {

Expand Down Expand Up @@ -125,24 +157,25 @@ public Page<DoResponse> searchDoStartsWith(String keyword, Long projectId, PageD
Pageable pageable = PageRequest.of(pageDTO.page() - 1, pageDTO.pageSize(), Sort.by(Sort.Direction.DESC, "id"));

// ✅ QueryDSL 기반 검색 적용
Page<Do> searchedDos = doRepository.searchByDescriptionStartsWith(projectId, keyword, pageable);
Page<Do> searchedDos = doRepository.findAllByDescriptionStartsWithAndProjectId(keyword, projectId, pageable);

return searchedDos.map(DoResponse::from);
}

@Transactional
public Page<DoResponse> searchDoFullText(String keyword, Long projectId, PageDTO pageDTO) {
Pageable pageable = PageRequest.of(pageDTO.page() - 1, pageDTO.pageSize(),
Sort.by(Sort.Direction.DESC, "id"));
Page<DoProjection> projections = doRepository.searchDoFullText(keyword, projectId, pageable);
return projections.map(proj -> new DoResponse(
proj.getId(),
proj.getDate(),
proj.getDescription(),
proj.getPlanId()
));
public List<DoResponse> searchDoFullText(String keyword, Long projectId) {
List<DoProjection> projections = doRepository.searchDoFullText(keyword, projectId);
return projections.stream()
.map(proj -> new DoResponse(
proj.getId(),
proj.getDate(),
proj.getDescription(),
proj.getPlanId()
))
.toList();
}


@Transactional
public void completeDo(Long doId) {
Do completeDo = doRepository.findById(doId)
Expand All @@ -158,4 +191,6 @@ public void completeDo(Long doId) {
plan.setStatus(Plan.Status.COMPLETED);
}
}


}
23 changes: 18 additions & 5 deletions src/main/java/com/example/braveCoward/swagger/DoApi.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.example.braveCoward.swagger;

import com.example.braveCoward.global.exectime.ExecutionTimeLogger;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import org.springframework.data.domain.Page;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
Expand All @@ -27,6 +28,8 @@
import jakarta.validation.Valid;
import jakarta.validation.constraints.Min;

import java.util.List;

@RequestMapping("/dos")
@Tag(name = "(Normal) Do", description = "Do 관련 API")
public interface DoApi {
Expand Down Expand Up @@ -155,20 +158,30 @@ ResponseEntity<Page<DoResponse>> searchDoStartsWith(
@ApiResponses(
value = {
@ApiResponse(responseCode = "200", description = "FullText 검색 결과",
content = @Content(schema = @Schema(implementation = DoResponse.class))),
content = @Content(array = @ArraySchema(schema = @Schema(implementation = DoResponse.class)))),
@ApiResponse(responseCode = "404", content = @Content(schema = @Schema(hidden = true)))
}
)
@Operation(summary = "Do 검색 (FullText)")
@ExecutionTimeLogger
@GetMapping("/searchFullText")
ResponseEntity<Page<DoResponse>> searchDoFullText(
ResponseEntity<List<DoResponse>> searchDoFullText(
@RequestParam String keyword,
@RequestParam Long projectId,
@RequestParam(defaultValue = "1") @Min(1) int page,
@RequestParam(defaultValue = "10") @Min(1) int size
@RequestParam Long projectId
);


@ApiResponses(
value = {
@ApiResponse(responseCode = "200", description = "FULLTEXT 인덱스 최적화 실행 성공"),
@ApiResponse(responseCode = "500", description = "서버 오류 발생",
content = @Content(schema = @Schema(hidden = true)))
}
)
@Operation(summary = "FULLTEXT 인덱스 최적화", description = "MySQL 테이블 인덱스 최적화 (OPTIMIZE TABLE do 실행)")
@PostMapping("/optimizeFullTextIndex")
ResponseEntity<Void> optimizeFullTextIndex();

@ApiResponses(
value = {
@ApiResponse(responseCode = "200"),
Expand Down