게시글 검색

UlBaMe·2023년 2월 13일
0

게시판 만들기 10 - 게시글 검색(onethejay님)

맨날 다르게 하는데 링크를 다는 게 큰 의미가 있나 하는 생각을 하고 있다...
JPA에 QueryDSL을 쓴다는데, 지금 JPA를 안 쓰고 있으니까 MyBatis 상의 동적 쿼리를 써서 처리해줘야 할 것 같다.
검색 조건도 링크 예시는 셀렉트 박스에서 하나 골라서 검색하는 식인데 체크박스나 그와 비슷한 효과를 만들어서 쓰고 싶다.

  • QueryDSL - 정적 타입을 이용해서 SQL과 같은 쿼리를 생성할 수 있도록 해 주는 오픈소스 프레임워크

multiple이면서도 dropdown으로 잘 작동하는 이쁜 selectbox를 찾다가 vue multiselect 같은걸 쓰느니 vuetify를 쓰기로 했다...

vue-cli로 만들어진 프로젝트에 추가하는 거라서 공식 문서의 해당 부분을 참고했다.
다음에 뷰 프로젝트 만들면 vite를 써야하나...? 보니까 vue2 지원도 올해가 끝이던데.


위 줄과 지금 쓰는 줄 간에 며칠 간 간격이 있는데 여러 가지 일이 있었다. 그건 다른 글에서 얘기하기로 하고.

제목, 내용, 작성자에 대해 각각 검색 내용을 지정해서 sql을 날릴 수 있도록 작성했다. 내용 기입은 v-select에서 multiple 선택해서 선택한 내용만큼 v-text-field 생성되게 해서 기입할 수 있게 해놨다.

검색 내용에 따라 전체 게시물 갯수를 찾아오고 다시 페이지네이션도 하게 해놨고, 뭐 조금 css도 수정하고... 이것저것 했다.

백엔드도 MyBatis에서 여러 타입의 파라메터를 그냥 받을 수 있는지 고민했었는데, 많은 검색 결과가 그냥 map이나 vo를 만들어서 쓰라고 되어있었지만 그냥 @param으로 해서 보내도 인식이 된다. 심지어 찾아보니 우리가 아무 생각없이 파라메터 이름만 쓰곤 하는 #{} 문법 안에서 타입에 대한 지정이나 그런 것도 다 할 수 있다. 별로 필요가 없어서 그렇지...

그리고 하나 배운 게, 정말 아무 생각없이 쓰고 있던 CDATA 문법. 일반적으로는 where 절이나 특정 부분에서 <, > 등을 사용할 때 파싱에 문제가 생겨서 CDATA로 sql문을 감싸놓는데, 너무 당연하게도 이래놓으면 마크업을 써서 사용하는 동적 쿼리는 쓸 수가 없다. 아무 생각 없이 써놓고 왜 안 되지? 하고 엄청 고민하고 검색해봤는데 결론이 그거였다...
하나 배운 건 예문을 볼 때 정말 꼼꼼하게 봐야 한다는 것. 또 하나는 그래도 에러 메시지 열심히 봐서 <, > 등의 문자가 제대로 파싱이 안 되는 것 같아서 검색할 때 파싱으로 했더니 찾을 수 있었던 거.
현재는 동적 쿼리가 동작은 하는데 좀 애매한 부분들이 있는데 나중에 일종의 유지보수를 하면 좀 더 개선해서 써야겠다.

//SearchBar.vue
<template>
  <div class="searchBar">
    <span class="searchOptions">
      <v-select
        v-model="searchCondition.selectedArea"
        :items="searchedArea"
        multiple
        label="검색 영역"
        persistent-hint
        chips
      ></v-select>
    </span>
    <span class="searchParams">
      <div v-for="(params, index) in searchCondition.selectedArea" :key="index">
        <v-text-field
          class="searchParam"
          v-model="searchCondition.value[params]"
          :label="params"
          :rules="rules"
          hide-details="auto"
        ></v-text-field>
      </div>
      <!-- <v-text-field
        @keydown="sendSearchCondition"
        v-model="searchCondition.value"
        label="검색할 내용"
        :rules="rules"
        hide-details="auto"
      ></v-text-field> -->
    </span>
    <svg-icon
      type="mdi"
      :path="path"
      @click="sendSearchCondition"
      disabled
    ></svg-icon>
  </div>
</template>

<script>
import SvgIcon from '@jamescoyle/vue-icon';
import { mdiMagnify } from '@mdi/js';
import _ from 'lodash';

export default {
  components: {
    SvgIcon,
  },
  data() {
    return {
      path: mdiMagnify,
      searchedArea: ['title', 'contents', 'creatorId'],
      searchCondition: {
        selectedArea: ['title'],
        value: { title: '', contents: '', creatorId: '' },
      },
      rules: [
        value => !!value || '값을 입력해 주세요.',
        value => (value && value.length >= 2) || '2글자 이상 입력해 주세요.',
      ],
    };
  },
  methods: {
    sendSearchCondition(event) {
      if (event.key !== 'Enter' && event.type !== 'click') {
        return;
      }
      if (
        _.isEmpty(this.searchCondition.selectedArea) ||
        _.isEmpty(this.searchCondition.value)
      ) {
        alert('검색조건을 설정해 주세요.');
        return;
      }
      this.$emit('sendSearchCondition', this.searchCondition);
    },
  },
};
</script>

<style scoped>
.searchBar {
  margin: 10px auto;
  justify-content: flex-end;
}
.searchBar .searchOptions {
  display: inline-block;
  width: 300px;
  padding: 0px 10px;
}
.searchBar .searchParams {
  display: inline-block;
  min-width: 220px;
}
</style>

css 부분은 좀 허접한 것 같다. 퍼블리셔나 디자이너는 아니라도 기본은 알아야 하는데.

//검색 옵션이 있든 없든 내용 따라서 get 요청 보내는 함수
function fetchBoard(postIndex, searchCondition) {
  const { firstPostIndex, lastPostIndex } = postIndex;
  const { title, contents, creatorId } = searchCondition.value;
  const params = { firstPostIndex, lastPostIndex, title, contents, creatorId };
  return postAxios.get(null, { params });
}

내가 생각해도 너무 지저분하게 구현되어 있어서... 이전에 뚝딱뚝딱 한 건 없애버렸다.

// mapper.xml에 연동된 java class의 메서드
List<BoardDto> selectBoardList(
  @Param("firstPostIndex") int firstPostIndex,
  @Param("lastPostIndex") int lastPostIndex,
  @Param ("title") String title,
  @Param ("contents") String contents,
  @Param ("creatorId") String creatorId
) throws Exception;

그냥 Map 써도 상관은 없는데... 해보고 싶었다.

<!-- mapper.xml의 일부 -->
WHERE
  deleted_yn = 'N'
  <trim prefix="AND (" prefixOverrides="OR" suffix=")">
    <if test='!"".equals(title) and title!=null'>
      title LIKE CONCAT("%",#{title},"%")
    </if>
    <if test='!"".equals(contents) and contents!=null'>
      OR contents LIKE CONCAT("%",#{contents},"%")
    </if>
    <if test='!"".equals(creatorId) and creatorId!=null'>
      OR creator_id LIKE CONCAT("%",#{creatorId},"%")
    </if>
  </trim>
  ) t_table_filtered
  WHERE
  ROWNUM BETWEEN #{firstPostIndex} and #{lastPostIndex}
</select>

검색 조건을 OR로 해서 넣고 싶은데, 1=1이나 <where> 포함해서 어떻게 짜면 될거 같기도 한데 나중에~
-> trim 써서 정리. 대충 쓰는 법 알겠다 이젠.

0개의 댓글