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);
이런 형태로 실행되게 된다.
'Java > SpringBoot' 카테고리의 다른 글
[SpringBoot] 파일 업로드 시 서버에 해당 파일이 즉시 적용되지 않는 문제 (정적 리소스 경로 설정) (0) | 2025.03.20 |
---|---|
[SpringBoot] 엑셀 다운로드 기능 구현 (0) | 2025.03.14 |
[SpringBoot] 게시판 DB 설계 (2) | 2025.02.27 |
[SpringBoot] Swagger 기본 설정 (0) | 2025.02.14 |
[SpringBoot]Cannot resolve symbol 'servlet' 오류 해결 (0) | 2023.04.11 |