재밌고 어려운 IT를 이해해보자~!

QUERY를 이용한 검색 필터링 [TEAM PROJECT] 본문

코리아IT핀테크과정

QUERY를 이용한 검색 필터링 [TEAM PROJECT]

언제나즐거운IT 2024. 2. 2. 23:55

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 메서드에 필터링된 쿼리문이 적용되어
최종적으로 자신이 원하는 데이터를 추출할 수 있다.

Comments