재밌고 어려운 IT를 이해해보자~!
QUERY를 이용한 검색 필터링 [TEAM PROJECT] 본문
filterSearch.js
/**
* 페이징처리 JS 파일
*/
// 최소 가격 range input 값 표시를 담당하는 변수 및 이벤트 핸들러 등록
const minPriceInput = document.getElementById('minPrice');
const minPriceOutput = document.getElementById('minPriceOutput');
// 최대 가격 range input 값 표시를 담당하는 변수 및 이벤트 핸들러 등록
const maxPriceInput = document.getElementById('maxPrice');
const maxPriceOutput = document.getElementById('maxPriceOutput');
var isFiltered = false;
const category = dataContainer.getAttribute('data-category');
// 필터 및 정렬에 사용되는 변수들 초기화
var minPrice;
var maxPrice;
var selectedCompanies, selectedProductCategories, selectedstates;
var searchField;
var searchInput;
var column;
// 초기 상태 정렬 방향
const defaultOrderColumnDirection = {
boardNum: 'DESC',
title: 'ASC',
writer: 'ASC',
boardDate: 'DESC',
recommendCNT: 'DESC',
views: 'DESC',
price: 'DESC'
};
// 현재 정렬 방향
var orderColumnDirection = $.extend(true, {}, defaultOrderColumnDirection);
// 선택된 헤더 1개와 방향을 저장하는 객체
var selectedOrderDirection;
// 최소 가격과 최대 가격 초기화 및 이벤트 핸들러 등록
$("#minPrice").val($("#minPrice").attr("min"));
$("#maxPrice").val($("#maxPrice").attr("min"));
if (minPriceInput) {
minPriceInput.addEventListener('input', function() {
// 최소 가격이 최대 가격을 초과하면 최대 가격으로 설정
if (parseInt(minPriceInput.value) > parseInt(maxPriceInput.value)) {
minPriceInput.value = maxPriceInput.value;
}
minPriceOutput.textContent = minPriceInput.value;
});
}
if (maxPriceInput) {
maxPriceInput.addEventListener('input', function() {
// 최대 가격이 최소 가격보다 작으면 최소 가격으로 설정
if (parseInt(maxPriceInput.value) < parseInt(minPriceInput.value)) {
maxPriceInput.value = minPriceInput.value;
}
maxPriceOutput.textContent = maxPriceInput.value;
});
}
// 필터링 및 정렬에 사용되는 변수들 업데이트
function updateVariables() {
minPrice = $("#minPrice").val();
maxPrice = $("#maxPrice").val();
searchField = $("#searchField").val();
searchInput = $("#searchInput").val();
// 선택된 체크박스들의 값을 배열로 저장
selectedCompanies = $('input[type=checkbox][name=company]:checked').map(function() {
return this.value;
}).get();
selectedProductCategories = $('input[type=checkbox][name=productcategory]:checked').map(function() {
return this.value;
}).get();
selectedstates = $('input[type=checkbox][name=state]:checked').map(function() {
return this.value;
}).get();
}
// 검색 버튼 클릭 이벤트
$("#searchButton").on("click", function() {
updateVariables();
performAjaxRequest();
});
// 가격 입력값 변경 이벤트
$("#minPrice, #maxPrice").on("mouseup", function() {
updateVariables();
performAjaxRequest();
});
// 체크박스 변경 이벤트
$('input[type=checkbox]').change(function() {
updateVariables();
performAjaxRequest();
});
// 테이블 헤더 정렬 클릭 이벤트
$('th.sortable').on('click', function() {
column = $(this).data('column');
// 동일 헤더를 클릭하면 정렬 방향을 토글
if (column === orderColumnDirection.column) {
orderColumnDirection[column] = orderColumnDirection[column] === 'ASC' ? 'DESC' : 'ASC';
} else { // 다른 헤더를 클릭하면 초기 상태의 정렬 방향을 사용
orderColumnDirection = $.extend(true, {}, defaultOrderColumnDirection);
orderColumnDirection[column] = orderColumnDirection[column];
}
orderColumnDirection.column = column;
// 선택된 컬럼과 그 방향만 보내는 객체 생성
selectedOrderDirection = {};
selectedOrderDirection[column] = orderColumnDirection[column];
// 테이블 헤더 정렬의 하이라이트 상태 초기화 및 적용
$('th.sortable').removeClass('highlight').each(function() {
$(this).html($(this).html().split(" ")[0]); // 화살표 제거
});
$(this).addClass('highlight');
// 화살표 방향 변경 (활성화된 헤더에만 적용)
if(orderColumnDirection[column] === 'ASC') {
$(this).html($(this).html() + " ↑");
} else {
$(this).html($(this).html() + " ↓");
}
updateVariables();
performAjaxRequest();
});
// Ajax 요청을 수행하는 함수
function performAjaxRequest() {
$.ajax({
type: "GET",
url: "filterSearch.do",
data: {
'minPrice': minPrice,
'maxPrice': maxPrice,
'company': selectedCompanies,
'productcategory': selectedProductCategories,
'state': selectedstates,
'category': category, //게시판에 맞는 게시글 출력을 위한 변수
'searchField': searchField,
'searchInput': searchInput,
'id': id,
'jsonOrderColumnDirection': JSON.stringify(selectedOrderDirection)
},
traditional: true,
dataType: 'json',
success: function(jsonFilterBoardDatas) {
if (jsonFilterBoardDatas != null) { // filterDatas가 존재하는 경우
window.jsonFilteredBoardDatas = jsonFilterBoardDatas; // 서버에서 받은 데이터를 변수에 할당
console.log(jsonFilteredBoardDatas); // 데이터 확인
isFiltered = true; // 데이터가 존재하므로 isFiltered를 true로 설정
loadReviewData(1);
}
},
error: function(error) {
console.log('에러');
console.log('에러종류: ' + JSON.stringify(error));
alert("fail");
}
});
}
window.onload = function() {
var headers = document.querySelectorAll("th.sortable");
headers.forEach(function(header) {
header.addEventListener("click", function() {
headers.forEach(function(header) {
header.classList.remove("highlight");
});
this.classList.add("highlight");
});
});
};
필터검색은 다중값 선택이 가능함으로 유저로 부터 피터조건이 선택될 때 마다 어떤 필터들이 선택되었는지
변수들을 업데이트해준다.
그 이후 서버에게 비동기로 요청
filterSearchAction.java(비동기)
package controller.async;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.google.gson.Gson;
import model.board.BoardDTO;
import model.board.SearchDAO;
import model.board.SearchDTO;
/**
* FilterSearchAction: 비동기적으로 필터링된 검색을 처리하는 서블릿
*/
@WebServlet("/filterSearch.do")
public class FilterSearchAction extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* 기본 생성자
*/
public FilterSearchAction() {
super();
}
/**
* GET 요청 처리
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
System.out.println("[로그] doPost 50번째 라인 진입");
// 파라미터로부터 필터링에 사용될 값들을 받아옴
String company[] = request.getParameterValues("company");
String productcategory[] = request.getParameterValues("productcategory");
String state[] = request.getParameterValues("state");
String category = request.getParameter("category");
String searchField = request.getParameter("searchField");
String searchInput = request.getParameter("searchInput");
String jsonOrderColumnDirection = request.getParameter("jsonOrderColumnDirection");
String priceSort = request.getParameter("priceSort");
String id = request.getParameter("id");
System.out.println("[로그] 59번 라인" + jsonOrderColumnDirection);
// Map을 사용하여 정렬기준과 방향 정보를 담음
Map<String, String> orderMap = new HashMap<>();
ArrayList<String> cList = new ArrayList<String>();
ArrayList<String> pList = new ArrayList<String>();
ArrayList<String> sList = new ArrayList<String>();
SearchDTO searchDTO = new SearchDTO();
SearchDAO searchDAO = new SearchDAO();
//마이보드, 멤버보드 정렬을 위한 set
searchDTO.setCategory(category);
searchDTO.setId(id);
searchDTO.setPriceSort(priceSort);
if (jsonOrderColumnDirection != null) {
// 중괄호를 제거하고 문자열을 키-값 쌍으로 나눔
String[] keyValuePairs = jsonOrderColumnDirection.replace("{", "").replace("}", "").split(",");
// 키-값 쌍을 반복
for (String pair : keyValuePairs) {
String[] entry = pair.split(":");
// 키와 값에서 따옴표와 공백을 제거
String key = entry[0].trim().replace("\"", "");
String value = entry[1].trim().replace("\"", "");
// 키-값 쌍을 맵에 추가
orderMap.put(key, value);
}
System.out.println("[로그] 받아온 정렬 파라미터 값 :" + orderMap);
searchDTO.setOrderColumnDirection(orderMap);
}
if (searchField != null && !searchInput.equals("")) {
System.out.println("[로그] 받아온 검색 파라미터 값 :" + searchField + "," + searchInput);
searchDTO.setSearchField(searchField);
searchDTO.setSearchInput(searchInput);
}
if (request.getParameter("minPrice") != null && request.getParameter("maxPrice") != null) {
int minPrice = Integer.parseInt(request.getParameter("minPrice"));
int maxPrice = Integer.parseInt(request.getParameter("maxPrice"));
System.out.println("[로그] 받아온 가격 파라미터 값 :" + minPrice + "," + maxPrice);
searchDTO.setMinPrice(minPrice);
searchDTO.setMaxPrice(maxPrice);
}
if (company != null) {
for (int i = 0; i < company.length; i++) {
cList.add(company[i]);
System.out.println("[로그] 받아온 회사 파라미터 값 : " + company[i]);
}
searchDTO.setCompanyList(cList);
}
if (productcategory != null) {
for (int i = 0; i < productcategory.length; i++) {
pList.add(productcategory[i]);
System.out.println("[로그] 받아온 제품 카테고리 파라미터 값 : " + productcategory[i]);
}
searchDTO.setProductcategoryList(pList);
}
if (state != null) {
for (int i = 0; i < state.length; i++) {
sList.add(state[i]);
System.out.println("[로그] 받아온 상태 파라미터 값 : " + state[i]);
}
searchDTO.setStateList(sList);
}
// 검색 DAO를 통해 필터링된 데이터를 가져옴
ArrayList<BoardDTO> filteredBoardDatas = searchDAO.selectAll(searchDTO);
System.out.println("[로그] 필터된 데이터 : " + filteredBoardDatas);
// 검색 결과를 JSON 형식으로 응답
if (filteredBoardDatas != null) {
Gson gson = new Gson();
String jsonFilterBoardDatasStr = gson.toJson(filteredBoardDatas);
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.getWriter().write(jsonFilterBoardDatasStr);
}
}
/**
* POST 요청 처리
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
서버 (서블릿) 에서는 전달받은 데이터들을 searchDTO에 세팅해주기 위해 알맞는 데이터 타입으로 전환 후
searchDAO를 통해 selectOne 메서드를 진행하여 쿼리문을 통해 원하는 게시글들을 찾아낸다.
SearchDAO.java
package model.board;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import model.common.JDBCUtil;
public class SearchDAO {
private Connection conn;
private PreparedStatement pstmt;
String PRICESQL = "";
String COMPANYSQL = "";
String PRODUCTCATEGORYSQL = "";
String STATESQL = "";
String USERSEARCHSQL = "";
String ORDERSQL = "";
String CATEGORYSQL = "";
String SQL_SELECTALL = "";
public ArrayList<BoardDTO> selectAll(SearchDTO searchDTO) {
ArrayList<BoardDTO> datas = new ArrayList<BoardDTO>();
BoardDTO data = null;
conn = JDBCUtil.connect();
try {
CATEGORYSQL = searchDTO.getCategory();
System.out.println(CATEGORYSQL);
System.out.println(searchDTO);
if (searchDTO.getSearchField() != null && searchDTO.getSearchInput() != null) {
// 사용자의 검색 입력에서 띄어쓰기 제거 및 소문자 변환
String searchInput = searchDTO.getSearchInput().replace(" ", "").toLowerCase();
if (searchDTO.getSearchField().equals("title")) {
USERSEARCHSQL = "AND REPLACE(LOWER(TITLE), ' ', '') LIKE '%'||'" + searchInput + "'||'%'";
}
if (searchDTO.getSearchField().equals("contents")) {
USERSEARCHSQL = "AND REPLACE(LOWER(CONTENTS), ' ', '') LIKE '%'||'" + searchInput + "'||'%'";
}
if (searchDTO.getSearchField().equals("writer")) {
USERSEARCHSQL = "AND REPLACE(LOWER(NICKNAME), ' ', '') LIKE '%'||'" + searchInput + "'||'%'";
}
if (searchDTO.getSearchField().equals("titleAndContents")) {
USERSEARCHSQL = "AND (REPLACE(LOWER(TITLE), ' ', '') LIKE '%'||'" + searchInput
+ "'||'%' OR REPLACE(LOWER(CONTENTS), ' ', '') LIKE '%'||'" + searchInput + "'||'%')";
}
}
if (searchDTO.getMaxPrice() != 0) {
PRICESQL = "AND PRICE BETWEEN " + searchDTO.getMinPrice() + " AND " + searchDTO.getMaxPrice();
}
if (searchDTO.getCompanyList().size() > 0) {
StringBuilder bCompanySb = new StringBuilder();
ArrayList<String> companyDatas = searchDTO.getCompanyList();
for (int i = 0; i < searchDTO.getCompanyList().size(); i++) {
bCompanySb.append("\'" + companyDatas.get(i) + "\'");
if (i + 1 < searchDTO.getCompanyList().size()) {
bCompanySb.append(",");
}
COMPANYSQL = "AND COMPANY IN (" + bCompanySb.toString() + ")";
}
}
if (searchDTO.getProductcategoryList().size() > 0) {
StringBuilder bProductcategorySb = new StringBuilder();
ArrayList<String> productcategoryDatas = searchDTO.getProductcategoryList();
for (int i = 0; i < searchDTO.getProductcategoryList().size(); i++) {
String productCategory = productcategoryDatas.get(i);
// 대소문자 구분 없이 검색하기 위해, 대문자와 소문자 둘 다 쿼리문에 추가
bProductcategorySb.append("\'" + productCategory.toUpperCase() + "\',");
bProductcategorySb.append("\'" + productCategory.toLowerCase() + "\'");
if (i + 1 < searchDTO.getProductcategoryList().size()) {
bProductcategorySb.append(",");
}
}
PRODUCTCATEGORYSQL = "AND PRODUCTCATEGORY IN (" + bProductcategorySb.toString() + ")";
}
if (searchDTO.getStateList().size() > 0) {
StringBuilder bStateSb = new StringBuilder();
ArrayList<String> stateDatas = searchDTO.getStateList();
for (int i = 0; i < searchDTO.getStateList().size(); i++) {
bStateSb.append("\'" + stateDatas.get(i) + "\'");
if (i + 1 < searchDTO.getStateList().size()) {
bStateSb.append(",");
}
STATESQL = "AND STATE IN (" + bStateSb.toString() + ")";
}
}
String element ="";
if (!searchDTO.getOrderColumnDirection().isEmpty()) {
if (searchDTO.getOrderColumnDirection().containsKey("boardNum")) {
String direction = searchDTO.getOrderColumnDirection().get("boardNum");
element = "SORT_DATA.BOARDNUM " + direction;
}
if (searchDTO.getOrderColumnDirection().containsKey("title")) {
String direction = searchDTO.getOrderColumnDirection().get("title");
element = "TITLE " + direction;
}
if (searchDTO.getOrderColumnDirection().containsKey("writer")) {
String direction = searchDTO.getOrderColumnDirection().get("writer");
element = "NICKNAME " + direction;
}
if (searchDTO.getOrderColumnDirection().containsKey("boardDate")) {
String direction = searchDTO.getOrderColumnDirection().get("boardDate");
element = "SORT_DATA.BOARDNUM " + direction;
}
if (searchDTO.getOrderColumnDirection().containsKey("views")) {
String direction = searchDTO.getOrderColumnDirection().get("views");
element = "VIEWCOUNT " + direction;
}
if (searchDTO.getOrderColumnDirection().containsKey("recommendCNT")) {
String direction = searchDTO.getOrderColumnDirection().get("recommendCNT");
element = "RECOMMENDCNT " + direction;
}
if (searchDTO.getOrderColumnDirection().containsKey("price")) {
String direction = searchDTO.getOrderColumnDirection().get("price");
element = "PRICE " + direction;
}
ORDERSQL = "ORDER BY " + element;
System.out.println("[로그] element값 : " + element);
}
if (!CATEGORYSQL.equals("")) {
System.out.println("[로그] SELECTALL 진입!");
SQL_SELECTALL = "SELECT " + "SORT_DATA.*, " + "MEMBER.NICKNAME, " + "MEMBER.ID " + "FROM (" + "SELECT "
+ "FILTER_DATA.*, " + "COALESCE(RECOMMEND_COUNT.RECOMMENDCNT, 0) AS RECOMMENDCNT FROM ( " + "SELECT ROWNUM, ROWNUM_DATA.* FROM ("
+ "SELECT "
+ "BOARDNUM, ID, CATEGORY, TITLE, CONTENTS, TO_CHAR(BOARDDATE, 'YYYY-MM-DD') AS BOARDDATE, "
+ "PRICE, PRODUCTCATEGORY, COMPANY, STATE, VIEWCOUNT " + "FROM BOARD " + "WHERE CATEGORY = '"
+ CATEGORYSQL + "' " + PRICESQL
+ " " + COMPANYSQL + " " + PRODUCTCATEGORYSQL + " " + STATESQL + "ORDER BY BOARDNUM ASC) "
+ "ROWNUM_DATA) FILTER_DATA " + "LEFT JOIN (" + "SELECT " + "BOARDNUM, COUNT(BOARDNUM) AS RECOMMENDCNT "
+ "FROM RECOMMEND " + "GROUP BY BOARDNUM"
+ ") RECOMMEND_COUNT ON FILTER_DATA.BOARDNUM = RECOMMEND_COUNT.BOARDNUM ORDER BY ROWNUM DESC"
+ ") SORT_DATA " + "JOIN MEMBER ON MEMBER.ID = SORT_DATA.ID " + USERSEARCHSQL +" " + ORDERSQL;
System.out.println(SQL_SELECTALL);
pstmt = conn.prepareStatement(SQL_SELECTALL);
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
data = new BoardDTO();// 새로운 BoardDTO 객체 생성
// ResultSet에서 읽은 각 열의 값을 해당 객체에 담음
data.setBoardNum(rs.getInt("BOARDNUM"));
data.setRownum(rs.getInt("ROWNUM"));
data.setTitle(rs.getString("TITLE"));
data.setNickname(rs.getString("NICKNAME"));
data.setBoardDate(rs.getString("BOARDDATE"));
data.setRecommendCNT(rs.getInt("RECOMMENDCNT"));
data.setViewCount(rs.getInt("VIEWCOUNT"));
data.setPrice(rs.getInt("PRICE"));
data.setState(rs.getString("STATE"));
data.setCategory(rs.getString("CATEGORY"));
datas.add(data); // 게시글의 정보를 담은 'data'를'datas'에 담아줌
}
rs.close(); // ResultSet을 닫음으로써 자원을 해제
} else {
SQL_SELECTALL =
"SELECT " +
"SORT_DATA.*, " +
"MEMBER.NICKNAME " +
"FROM (" +
" SELECT " +
" FILTER_DATA.*, " +
" COALESCE(RECOMMEND_COUNT.RECOMMENDCNT, 0) AS RECOMMENDCNT " +
" FROM (SELECT ROWNUM, ROWNUM_DATA.* " +
" FROM (" +
" SELECT " +
" BOARDNUM, ID, TITLE, CONTENTS, TO_CHAR(BOARDDATE, 'YYYY-MM-DD') AS BOARDDATE, VIEWCOUNT, STATE, CATEGORY, PRICE " +
" FROM BOARD "+ "WHERE ID='" + searchDTO.getId() + "' " +
" ORDER BY BOARDNUM ASC" +
" ) ROWNUM_DATA) FILTER_DATA " +
" LEFT JOIN (" +
" SELECT " +
" BOARDNUM, COUNT(BOARDNUM) AS RECOMMENDCNT " +
" FROM RECOMMEND " +
" GROUP BY BOARDNUM" +
" ) RECOMMEND_COUNT ON FILTER_DATA.BOARDNUM = RECOMMEND_COUNT.BOARDNUM " +
" ORDER BY ROWNUM DESC" +
") SORT_DATA " +
"JOIN MEMBER ON MEMBER.ID = SORT_DATA.ID " +
USERSEARCHSQL + " " +
ORDERSQL;
System.out.println(SQL_SELECTALL);
pstmt = conn.prepareStatement(SQL_SELECTALL);
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
data = new BoardDTO();// 새로운 BoardDTO 객체 생성
// ResultSet에서 읽은 각 열의 값을 해당 객체에 담음
data.setBoardNum(rs.getInt("BOARDNUM"));
data.setRownum(rs.getInt("ROWNUM"));
data.setTitle(rs.getString("TITLE"));
data.setNickname(rs.getString("NICKNAME"));
data.setId(rs.getString("ID"));
data.setBoardDate(rs.getString("BOARDDATE"));
data.setRecommendCNT(rs.getInt("RECOMMENDCNT"));
data.setViewCount(rs.getInt("VIEWCOUNT"));
data.setState(rs.getString("STATE"));
datas.add(data); // 게시글의 정보를 담은 'data'를'datas'에 담아줌
}
rs.close(); // ResultSet을 닫음으로써 자원을 해제
}
} catch (SQLException e) { // SQLException 예외가 발생할 경우 해당 예외를 출력
e.printStackTrace();
} finally {
JDBCUtil.disconnect(pstmt, conn); // 데이터베이스 연결 해제
}
return datas;
}
}
searchDAO에서는 필터 값이 들어올 떄 마다 변경되는 공백의 SQL구문을 담을 변수를 선언해두고유저가 선택한 필터 조건에 따라서 그에 알맞는 쿼리문을 생성해준다.
그 이후 데이터를 전체선택하는 selecALL 메서드에 필터링된 쿼리문이 적용되어
최종적으로 자신이 원하는 데이터를 추출할 수 있다.
'코리아IT핀테크과정' 카테고리의 다른 글
모달창 [TEAM PROJECT] (0) | 2024.02.19 |
---|---|
Pagination [TEAM PROJECT] (0) | 2024.02.15 |
검색 필터링 [TEAM PROJECT] (2) | 2024.02.02 |
VIEW에 JSTL, EL, CustomTag 사용하기 [TEAM PROJECT] (0) | 2024.01.23 |
Image Upload Update [TEAM PROJECT] (0) | 2024.01.20 |
Comments