Java/SpringBoot

[SpringBoot] 게시글 검색, 삭제, 전시 순서 변경 API

김호록님 2025. 3. 5. 13:14

1. 게시글 검색

사진처럼 검색 조건이 카테고리, 기간(등록일시, 수정일시, 전시시작일시, 전시종료일시), 전시 여부(Y/N), 검색조건(등록자, 수정자, 내용, 제목) 까지 여러 개이기 때문에 mybatis의 동적 쿼리를 활용했다.

 

dto

@Getter
@NoArgsConstructor
@AllArgsConstructor
public class PostListDto {
    private int postId;
    private int displayOrder;
    private String type; // 구분(일반/공지)
    private String category1; // category 1d
    private String category2; // category 2d
    private String title;
    private int views;
    private String isDisplayed; // 전시 여부 Y/N
    private String createdAt;
    private String createdBy;
    private String updatedAt;
    private String updatedBy;
}

 

controller

@PostMapping("")
    public ResponseEntity<Map<String, Object>> getPosts(@RequestBody(required = false) PostParamDto postParamDto) {
        if (postParamDto == null || postParamDto.getPostParam() == null) {
            postParamDto = new PostParamDto(new PostParam());
        }

        PostParam postParam = postParamDto.getPostParam();

        return postService.getPosts(
                postParam.getCategory1(), postParam.getCategory2(), postParam.getPeriodType(),
                postParam.getStartDate(), postParam.getEndDate(), postParam.getIsDisplayed(),
                postParam.getSearchType(), postParam.getKeyword(),
                (postParam.getSize() != null) ? postParam.getSize() : 5,  // 기본 size=5
                (postParam.getPage() != null) ? postParam.getPage() : 1    // 기본 page=1
        );
    }

 

service

public ResponseEntity<Map<String, Object>> getPosts(String category1, String category2, String periodType, String startDate,
                                                                   String endDate, String isDisplayed, String searchType, String keyword,
                                                                   int size, int page) {
        int offset = (page - 1) * size;

        List<PostListDto> posts = postMapper.getPosts(safeParseInt(category1), safeParseInt(category2), periodType, startDate, endDate, isDisplayed, searchType, keyword, size, offset);
        int totalCount = postMapper.getPostsCount(safeParseInt(category1), safeParseInt(category2), periodType, startDate, endDate, isDisplayed, searchType, keyword, size, offset);

        Map<String, Object> response = new HashMap<>();

        Map<String, String> totalCountMap = new HashMap<>();
        totalCountMap.put("totalCount", String.valueOf(totalCount));

        response.put("postList", posts);
        response.put("totalCount", totalCountMap);

        return ResponseEntity.ok(response);

 

mapper

List<PostListDto> getPosts(@Param("category1") String category1,
                           @Param("category2") String category2,
                           @Param("periodType") String periodType,
                           @Param("startDate") String startDate,
                           @Param("endDate") String endDate,
                           @Param("isDisplayed") String isDisplayed,
                           @Param("searchType") String searchType,
                           @Param("keyword") String keyword,
                           @Param("size") int size,
                           @Param("offset") int offset);

 

mapper.xml

<select id="getPosts" resultType="com.example.websquareproject.post.dto.PostListDto">
        SELECT
        p.post_id AS postId,
        p.display_order AS displayOrder,
        CASE
        WHEN p.type = 0 THEN '공지'
        WHEN p.type = 1 THEN '일반'
        END AS type,
        -- 카테고리 1d: category_id가 부모일 경우 그대로 사용, 자식일 경우 부모 이름 가져오기
        COALESCE(parent_category.category_name, child_category.category_name) AS category1,
        -- 카테고리 2d: category_id가 자식일 때만 가져오기
        CASE
        WHEN child_category.parent_id IS NOT NULL THEN child_category.category_name
        ELSE NULL
        END AS category2,
        p.title,
        p.views,
        p.is_displayed AS isDisplayed,
        p.updated_at AS updatedAt,
        p.updated_by AS updatedBy,
        p.created_at AS createdAt,
        p.created_by AS createdBy
        FROM POST p
        LEFT JOIN CATEGORY child_category ON child_category.category_id = p.category_id
        LEFT JOIN CATEGORY parent_category ON parent_category.category_id = child_category.parent_id
        WHERE 1=1

            <if test="category1 != null and category1 != ''">
                AND (parent_category.category_id = #{category1} OR child_category.category_id = #{category1})
            </if>

            <if test="category2 != null and category2 != ''">
                AND child_category.category_id = #{category2}
            </if>

            <!-- 기간 검색 -->
            <if test="periodType != null and periodType != '' and startDate != null and endDate != null">
                <choose>
                    <when test="'전시시작일시'.equals(periodType)">
                        AND p.display_start BETWEEN STR_TO_DATE(#{startDate}, '%Y%m%d%H%i%s')
                        AND STR_TO_DATE(#{endDate}, '%Y%m%d%H%i%s')
                    </when>
                    <when test="'전시종료일시'.equals(periodType)">
                        AND p.display_end BETWEEN STR_TO_DATE(#{startDate}, '%Y%m%d%H%i%s')
                        AND STR_TO_DATE(#{endDate}, '%Y%m%d%H%i%s')
                    </when>
                    <when test="'수정일시'.equals(periodType)">
                        AND p.display_end BETWEEN STR_TO_DATE(#{startDate}, '%Y%m%d%H%i%s')
                        AND STR_TO_DATE(#{endDate}, '%Y%m%d%H%i%s')
                    </when>
                    <when test="'등록일시'.equals(periodType)">
                        AND p.display_end BETWEEN STR_TO_DATE(#{startDate}, '%Y%m%d%H%i%s')
                        AND STR_TO_DATE(#{endDate}, '%Y%m%d%H%i%s')
                    </when>
                </choose>
            </if>

            <!-- 전시 여부 -->
            <if test="isDisplayed != null and isDisplayed != '' and isDisplayed != '전체'">
                AND p.is_displayed = #{isDisplayed}
            </if>

            <!-- 검색 조건 -->
            <if test="keyword != null and keyword != ''">
                <choose>
                    <when test="'등록자'.equals(searchType)">
                        AND p.created_by LIKE CONCAT('%', #{keyword}, '%')
                    </when>
                    <when test="'수정자'.equals(searchType)">
                        AND p.updated_by LIKE CONCAT('%', #{keyword}, '%')
                    </when>
                    <when test="'내용'.equals(searchType)">
                        AND p.content LIKE CONCAT('%', #{keyword}, '%')
                    </when>
                    <when test="'제목'.equals(searchType)">
                        AND p.title LIKE CONCAT('%', #{keyword}, '%')
                    </when>
                    <otherwise>
                        AND (p.title LIKE CONCAT('%', #{keyword}, '%')
                        OR p.content LIKE CONCAT('%', #{keyword}, '%')
                        OR p.created_by LIKE CONCAT('%', #{keyword}, '%')
                        OR p.updated_by LIKE CONCAT('%', #{keyword}, '%'))
                    </otherwise>
                </choose>
            </if>

            ORDER BY
                p.display_order IS NULL ASC,  -- NULL이면 맨 뒤로 정렬
                p.display_order,  -- display_order가 있는 경우 오름차순 정렬
                p.created_at DESC      -- 같은 순서일 경우 최신 등록일 순으로 정렬
            LIMIT #{size} OFFSET #{offset}
    </select>

 

COALESCE(A, B ..)

 

COALESCE 함수는 왼쪽 값부터 체크하며 가장 먼저 NULL이 아닌 값을 반환한다.

parent_category가 있을 경우 category1에 부모 카테고리를 가져오고, 없다면 스스로가 부모 카테고리이므로 child_category 이름을 가져온다.

STR_TO_DATE(string, '%Y%m%d%H%i%s)

20240505235959 형태로 받기 때문에 이를 날짜 형식으로 변환하는 함수이다.

 

2. 게시글 삭제

dto

@Getter
@NoArgsConstructor
@AllArgsConstructor
public class PostDeleteDto {
    private List<Integer> postIdList;
}

 

controller

@DeleteMapping("")
    public ResponseEntity<String> deletePosts(@RequestBody PostDeleteDto postDeleteDto) {
        postService.deletePosts(postDeleteDto.getPostIdList());
        return ResponseEntity.ok("Success");
    }

 

service

@Transactional
    public void deletePosts(List<Integer> postIdList) {
        postMapper.deletePosts(postIdList);
    }

 

mapper

void deletePosts(List<Integer> postIds);

 

mapper.xml

<delete id="deletePosts">
        DELETE FROM POST WHERE post_id IN
               <foreach collection="postIds" item="postId" open="(" separator="," close=")">
                   #{postId}
               </foreach>
    </delete>

postId 여러 개를 list로 받으므로 foreach문을 사용했다.

postIds 리스트 안의 item을 postId라고 지칭하고, 구문의 시작과 끝에 각각 (과 )를 붙이고 ,를 구분자로 사용한다.

{
    "postIdList: [1,7]
}
DELETE FROM POST WHERE post_id IN (1, 7);

위 sql문 형태로 실행되게 된다.

3. 게시글 전시 순서 변경

dto

@Getter
@NoArgsConstructor
@AllArgsConstructor
public class PostUpdateListDto {
    List<PostListDto> postList;
}

 

controller

@PutMapping("")
    public ResponseEntity<String> updatePosts(@RequestBody PostUpdateListDto postUpdateListDto) {
        postService.updatePosts(postUpdateListDto.getPostList());
        return ResponseEntity.ok("Success");
    }

 

service

@Transactional
    public void updatePosts(List<PostListDto> postListDto) {
        postMapper.updatePosts(postListDto);
    }

 

mapper

void updatePosts(List<PostListDto> postListDto);

 

mapper.xml

<update id="updateDisplayOrder">
    <foreach collection="postListDto" item="post" separator=";">
        UPDATE POST SET display_order = #{post.displayOrder}
        WHERE post_id = #{post.postId}
    </foreach>
</update>

 

초기 쿼리문이다. 그러나 이렇게 작성하고 실행했을 경우 오류가 발생했다.

검색해보니 mysql에서는 한 번에 여러 개의 쿼리문을 실행시킬 수 없다고 한다.

UPDATE ...; UPDATE ...; 처럼 여러 개의 쿼리문이 한꺼번에 실행되기 때문에 해당 쿼리문을 수정해야 한다.

<update id="updatePosts">
        UPDATE POST
        SET display_order =
        CASE post_id
        <foreach collection="postListDto" item="post">
            WHEN #{post.postId} THEN #{post.displayOrder}
        </foreach>
        END
        WHERE post_id IN
        <foreach collection="postListDto" item="post" open="(" separator="," close=")">
            #{post.postId}
        </foreach>
    </update>

이렇게 수정했을 경우

{
    "postIdList": [35, 6]
}
UPDATE POST
SET display_order =
    CASE post_id
        WHEN 1 THEN 3
        WHEN 2 THEN 1
        WHEN 3 THEN 2
    END
WHERE post_id IN (1, 2, 3);

이런 형태로 실행되게 된다.