⚙️ Backend/스프링(Spring) Framework

스프링 - 검색 처리 구현, 동적 쿼리에 관하여..

코너(Corner) 2021. 5. 13.
반응형

 

스프링 - 검색 처리 구현, 동적 쿼리에 관하여..

검색 기능은 검색 조건과 키워드로 나눠 생각해볼 수 있다.

검색 조건은 일반적으로 select 태그나 checkbox를 이용한다. 최근에는 select를 일반 사용자들의 경우에, 관리자용이나 검색 기능이 강한 경우 checkbox를 이용하는 경우가 대부분이다.

 

1-1. 검색 기능과 SQL

제목 / 내용 / 작성자 : 단일 항목 검색

제목 or 내용 , 제목 or 작성자, 내용 or 작성자, 제목 or 내용 or 작성자 : 다중 항목

단일 항목은 인라인뷰 안쪽에 필요한 데이터를 가져올 때 검색 조건이 적용되어야 하기 때문에 where 문 뒤에 검색 조건이 추가되고 ROWNUM 조건이 뒤따르게 하면 문제없다.

1-1-1. MyBatis의 동적 SQL..

검색 조건이 변하면 SQL문 역시 변하므로 XML이나 어노테이션과 같이 고정된 문자열을 작성하는 방식으론 제대로 처리할 수 없다.

다행히 MyBatis는 동적 태그 기능을 이용해 SQL을 파라미터의 조건에 맞게 조정할 수 있는 기능을 제공한다.

1-1-2. MyBatis의 동적 태그들..

  • if
  • choose(when, otherwise)
  • trim(where, set)
  • foreach

 

  • 검색 조건이 'T'면 : 제목이 키워드인 항목을 검색
  • 검색 조건이 'C'면 : 내용이 키워드인 항목을 검색
  • 검색 조건이 'W'면 : 작성자가 키워드인 항목을 검색

| (파이프 연산자): DBMS에서는 컨케이트네이션(연결) 이라는 뜻이다.

<if test="type=='T'.toString()">
    (title like '%' || #{keyword} || '%')
</if>
<if test="type=='C'.toString()">
    (content like '%' || #{keyword} || '%')
</if>
<if test="type=='W'.toString()">
    (writer like '%' || #{keyword} || '%')
</if>

if 안에 들어가는 표현식은 OGNL 표현식이다.

 

<choose>
<when test="type=='T'.toString()">
    (title like '%' || #{keyword} || '%')
</when>
<when test="type=='T'.toString()">
    (title like '%' || #{keyword} || '%')
</when>
<when test="type=='T'.toString()">
    (title like '%' || #{keyword} || '%')
</when>
<otherwise>
    (title like '%' || #{keyword} || '%' OR content like '%' || #{keyword} || '%')
</otherwise>
</choose>

 

태그 안쪽에서 SQL이 생성되면 WHERE 구문이 붙고, 그렇지 않은 경우 생성되지 않는다.

SELECT * FROM TBL_BOARD 
<where>
    <if test="bno != null">
      bno = #{bno}
  </if>
</where>

은 태그의 내용을 앞의 내용과 관련되어 원하는 접두/접미를 처리할 수 있다.

SELECT * FROM TBL_BOARD 
<where>
    <if test="bno != null">
      bno = #{bno}
  </if>
  <trim prefixOverrides = "and">
      rownum = 1
  </trim>
</where>
  • bno가 null 일 때
  • ​ SELECT * FROM TBL_BOARD WHERE ROWNUM = 1
  • bno가 null이 아닐 때
  • ​ SELECT * FROM TBL_BOARD WHERE BNO = #{bno} AND ROWNUM = 1

trim은 prefix, suffix, prefixOverrides, suffixOverrides 속성 지정 가능.

Overrides 는 '기각하다.' 의미를 가지고 있다.

prefixOverrides 는 앞에 조건식이 있는지, 없는지에 따라 판단한다. 조건식이 if문에 들어가있으면 조건식이 있을때 추가하고 없을 때 생략하고 한다. 하지만, 만약에 무조건 앞에 조건식이 있으면 prefixOverrides가 추가된다.

 

List, 배열, 맵 등을 이용해 루프 처리 가능. 주로 IN 조건에서 많이 사용한다.

예를 들어 제목은 'PS5'로, 내용을 '판매' 값으로 이용한다면

Map<String, String> map = new HashMap<>();
map.put("T", "PS5");
map.put("C", "판매");

작성된 Map을 파라미터로 전달하고 foreach를 이용한다.

SELECT * FROM TBL_BOARD
    <trim prefix="where (" suffix=")" prefixOverrides="OR">
<!-- 이 전달받은 MAP(맵)의 index="key"(인덱스)는 어떤 값이며, 이 값을 어떤 변수(item)에 담을 것인가. -->
    <foreach item="val" index="key" collection="map">

      <trim prefix="OR">
          <if test="key == 'C'.toString()">
            title = #{val}
        </if>
        <if test="key == 'T'.toString()">
            content = #{val}
        </if>
        <if test = "key == 'W'.toString()">
            writer = #{val}
        </if>
      </trim>

        </foreach>
    </trim>

foreach를 배열이나 List를 이용해야 하는 경우 item 속성만을 이용하면 되고 MAP의 형태로 key와 value를 이용해야 하면 index와 item 속성을 둘 다 이용한다.

prefixOverrides 속성은 만약, 맨 앞에 있는 조건식에 OR이 있다면 제거하는 속성이다.

결과 *

SELECT * FROM TBL_TABLE WHERE(TITLE = '건담' OR CONTENT = '판매')

1-2. 검색 조건 처리를 위한 Criteria의 변화

검색 조건을 처리하기 위해 검색 조건과 검색에 사용하는 키워드가 필요하므로 기존의 Criteria를 확장할 필요가 있다.

package com.koreait.domain;

import lombok.AllArgsConstructor;
import lombok.Data;

//Criteria : 검색의 기준
@Data
@AllArgsConstructor
public class Criteria {
   private int pageNum;
   private int amount;

   //[1] 
   //외부에서 사용자가 선택한 카테고리를 대표하는 문자열을 type으로 전달받는다(페이지에서 제목 또는 내용 선택 시 "TC" 전달됨)
   //사용자가 검색하고 싶은 키워드를 keyword로 전달받는다.
   private String type;
   private String keyword;

   public Criteria() {
      this(1, 10);
   }

   public Criteria(int pageNum, int amount) {
      this.pageNum = pageNum;
      this.amount = amount;
   }

   //[2]
   //사용자가 다중 항목을 선택했을 경우 각 항목을 분리해야 하기 때문에,
   //단일 항목들을 문자열 타입으로 리턴해준다.
   //MyBatis에서는 getter를 찾아서 실행하므로, typeArr변수 선언 없이 getter만 선언한다.
   public String[] getTypeArr() {
      return type == null ? new String[] {} : type.split("");
   }

}

type과 keyword 변수 추가. getTypeArr은 검색 조건이 T, W, C로 구성되어 있을 때 검색 조건을 배열로 만들어 한 번에 처리하기 위함이다.

1-2-1. BoardMapper.xml에서 Criteria 처리

getListWithPaging()

    <select id="getListWithPaging" resultType="com.koreait.domain.BoardVO">
            SELECT BNO, TITLE, CONTENT, WRITER, REGDATE, UPDATEDATE 
            FROM 
                (SELECT /*+ INDEX_DESC(TBL_BOARD PK_BOARD) */ ROWNUM RN, BNO, TITLE, CONTENT, WRITER, REGDATE, UPDATEDATE
                FROM TBL_BOARD
                WHERE 
      <if test="type != null and keyword != null">
        <trim prefix="(" suffix=") AND" prefixOverrides="OR">
          <foreach item="type" collection="typeArr">
            <trim prefix="OR">
              <choose>
                <when test="type=='T'.toString()">
                  (TITLE LIKE '%'||#{keyword}||'%')
                </when>
                <when test="type=='C'.toString()">
                  (CONTENT LIKE '%'||#{keyword}||'%')
                </when>
                <when test="type=='W'.toString()">
                  (WRITER LIKE '%'||#{keyword}||'%')
                </when>
              </choose>
            </trim>
          </foreach>
        </trim>
      </if>
        <![CDATA[        
                ROWNUM <= #{pageNum} * #{amount})
            WHERE RN > (#{pageNum} - 1) * #{amount}
        ]]>
    </select>

    <select id="getTotalCount" resultType="int">
        SELECT COUNT(*) FROM TBL_BOARD WHERE bno > 0
    </select>

검색 조건이 총 3가지이므로 6가지 조합이 가능하지만 각 문자열을 이용해 검색 조건을 결합하는 형태로 하면 3개의 동적 SQL 구문만으로 처리할 수 있다.

foreach를 이용해 검색 조건을 처리하는데 typeArr 속성을 이용한다.

MyBatis는 원하는 속성을 찾을 때 getTypeArr와 같이 이름에 기반을 두어 검색하므로 Criteria에서 만들어 둔 getTypeArr 결과인 문자열 배열 foreach 대상이 된다.

<choose> 안쪽의 동적 SQL은 OR title .... OR content ... OR writer 와 같은 구문을 만들어 내게 된다. 따라서 바깥족에서는 <trim>을 이용해 맨 앞에 생성되는 OR를 없애준다.

BoardMapperTests 수정

    @Test
    public void testSearch() {
        Criteria cri = new Criteria();
        cri.setKeyword("제목");
        cri.setType("TCW");
        List<BoardVO> list = mapper.getListWithPaging(cri);
        list.forEach(board -> log.info(board));
    }

<sql><include> 와 검색 데이터 개수 처리

동적 SQL을 이용해 검색 조건을 처리하는 부분은 해당 데이터의 개수를 처리하는 부분에서도 동일하게 적용되어야 한다.

MyBatis는 <sql>태그로 SQL 일부를 별도 보관하고 필요한 경우 include 시키는 형태로 사용할 수 있다.


        <sql id="criteria">
        <!-- [3] -->
        <!-- Mapper 인터페이스로부터 전달받은 Criteria객체 안의 type과 keyword 둘 다 null이 아니라면 -->
        <if test="type != null and keyword != null">
            <!-- [7] -->
            <!-- trim 태그 안에 있는 쿼리문을 기준으로 가장 앞에 있는 OR를 없애준다. -->
            <!-- trim 태그 안에 있는 쿼리문 뒤에는 페이징 처리 조건식이 한 개 더 있기 때문에 마지막에(suffix) AND를 붙여준다. -->
            <trim prefix="(" suffix=") AND" prefixOverrides="OR">
                <!-- [4] -->
                <!-- Criteria에 선언된 getTypeArr()메소드를 호출하여 단일항목이 들어 있는 배열의 길이만큼 반복해준다. -->
                <!-- 각각의 단일 항목들이 item속성에 있는 type변수에 들어가게 된다. -->
                <!-- 예 : "TW"는 2칸 문자열 배열이며, 2번 반복된다. [2]참고 -->
                <foreach item="type" collection="typeArr">
                    <!-- [6] -->
                    <!-- 작성된 쿼리문마다 맨 앞에(prefix) OR를 붙여준다. -->
                    <trim prefix="OR">
                        <!-- [5] -->
                        <!-- 단일 항목 중 조건식이 참이라면 알맞는 쿼리문이 작성된다. -->
                        <choose>
                            <when test="type=='T'.toString()">
                                (TITLE LIKE '%'||#{keyword}||'%')
                            </when>
                            <when test="type=='C'.toString()">
                                (CONTENT LIKE '%'||#{keyword}||'%')
                            </when>
                            <when test="type=='W'.toString()">
                                (WRITER LIKE '%'||#{keyword}||'%')
                            </when>
                        </choose>
                    </trim>
                </foreach>
            </trim>
        </if>
    </sql>
    <select id="getListWithPaging" resultType="com.koreait.domain.BoardVO">
            SELECT BNO, TITLE, CONTENT, WRITER, REGDATE, UPDATEDATE 
            FROM 
                (SELECT /*+ INDEX_DESC(TBL_BOARD PK_BOARD) */ ROWNUM RN, BNO, TITLE, CONTENT, WRITER, REGDATE, UPDATEDATE
                FROM TBL_BOARD
                WHERE 
                <include refid="criteria"/>
        <![CDATA[        
                ROWNUM <= #{pageNum} * #{amount})
            WHERE RN > (#{pageNum} - 1) * #{amount}
        ]]>
    </select>

    <select id="getTotalCount" resultType="int">
        SELECT COUNT(*) FROM TBL_BOARD WHERE 
        <include refid="criteria"></include>
        <include refid="criteria"></include> bno > 0
    </select>

1-3. 화면에서 검색 조건 처리

  • 페이지 번호가 파라미터로 유지되었던 것처럼 검색 조건과 키워드 역시 화면 이동 시 같이 전송되어야 한다.
  • 화면에서 검색 버튼을 클릭하면 새로 검색을 한다는 의미이므로 1페이지로 이동
  • 한글의 경우 GET 방식을 쓰면 문제가 생길 수 있다.

1-3-1. 목록 화면에서 검색 처리

list.jsp 수정

<form action="/board/list" id="searchForm">
  <div class="fields">
    <div class="field">
      <div style="text-align:center">
        <select name="type">
          <option value="">검색 기준</option>
          <option value="T">제목</option>
          <option value="C">내용</option>
          <option value="W">작성자</option>
          <option value="TC">제목 또는 내용</option>
          <option value="TW">제목 또는 작성자</option>
          <option value="TCW">전체</option>
        </select>
        <input id="keyword" type="text" name="keyword">
        <a href="javascript:void(0)" class="search button primary icon solid fa-search">검색</a>
      </div>
    </div>
  </div>
</form>

HTML을 보면 페이징 처리를 위해 만들어둔 form 태그에 select와 input 태그가 추가 된 것을 볼 수 있다.

form 내 button 기본 동작은 submit 이므로 별도의 처리 없이 검색이 되는지 확인하다.

검색 바튼의 이벤트 처리

list.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
    <head>
        <title>Board</title>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
        <meta name="description" content="" />
        <meta name="keywords" content="" />
        <link rel="stylesheet" href="/resources/assets/css/main.css" />
        <style>
            .big-width{display:block;}
            .small-width{display:none;}
            .table-wrapper {overflow-x:hidden !important;}
            select{width: 25%;display: inline;}
            input[name='keyword']{width: 54%; display: inline;}
            .search{width: 20%;}

            @media (max-width: 918px){
                .writer {display:none;}
                .regDate {display:none;}
                .updateDate {display:none;}
                .big-width{display:none;}
                .small-width{display:block;}
            }
        </style>
    </head>
    <body class="is-preload">
        <!-- Main -->
            <div id="main">
                <div class="wrapper">
                    <div class="inner">

                        <!-- Elements -->
                            <header class="major">
                                <h1>Board</h1>
                                <p>게시판 목록</p>
                            </header>
                                    <!-- Table -->
                                        <h3><a href="/board/register" class="button small">글 등록</a></h3>
                                        <div class="table-wrapper">
                                            <table>
                                                <thead>
                                                    <tr class="tHead">
                                                        <th class="bno">번호</th>
                                                        <th class="title">제목</th>
                                                        <th class="writer">작성자</th>
                                                        <th class="regDate">작성일</th>
                                                        <th class="updateDate">수정일</th>
                                                    </tr>
                                                </thead>
                                                <tbody>
                                                    <c:forEach var="board" items="${list}">
                                                        <tr class="tBody">
                                                            <td class="bno">${board.bno}</td>
                                                            <td class="title"><a href="/board/get?bno=${board.bno}&pageNum=${pageMaker.cri.pageNum}&amount=${pageMaker.cri.amount}">${board.title}</a></td>
                                                            <td class="writer">${board.writer}</td>
                                                            <td class="regDate">${board.regDate}</td>
                                                            <td class="updateDate">${board.updateDate}</td>
                                                        </tr>
                                                    </c:forEach>
                                                </tbody>
                                                <tfoot>
                                                </tfoot>
                                            </table>
                                            <!-- A -->
                                            <form action="/board/list" id="searchForm">
                                                <div class="fields">
                                                    <div class="field">
                                                        <div style="text-align:center">
                                                            <select name="type">
                                                                <option value="">검색 기준</option>
                                                                <option value="T">제목</option>
                                                                <option value="C">내용</option>
                                                                <option value="W">작성자</option>
                                                                <option value="TC">제목 또는 내용</option>
                                                                <option value="TW">제목 또는 작성자</option>
                                                                <option value="TCW">전체</option>
                                                            </select>
                                                            <input id="keyword" type="text" name="keyword">
                                                            <a href="javascript:void(0)" class="search button primary icon solid fa-search">검색</a>
                                                        </div>
                                                    </div>
                                                </div>
                                            </form>
                                            <div class="big-width" style="text-align:center;">
                                                <c:if test="${pageMaker.prev}">
                                                    <a class="changePage" href="${1}"><code>&lt;&lt;</code></a>
                                                    <a class="changePage" href="${pageMaker.startPage - 1}"><code>&lt;</code></a>
                                                </c:if>
                                                <c:forEach var="num" begin="${pageMaker.startPage}" end="${pageMaker.endPage}">
                                                    <c:choose>
                                                        <c:when test="${num eq pageMaker.cri.pageNum}">
                                                            <code>${num}</code>
                                                        </c:when>
                                                        <c:otherwise>
                                                            <a class="changePage" href="${num}"><code>${num}</code></a>
                                                        </c:otherwise>
                                                    </c:choose>
                                                </c:forEach>
                                                <c:if test="${pageMaker.next}">
                                                    <a class="changePage" href="${pageMaker.endPage + 1}"><code>&gt;</code></a>
                                                    <a class="changePage" href="${pageMaker.realEnd}"><code>&gt;&gt;</code></a>
                                                </c:if>
                                            </div>

                                            <div class="small-width" style="text-align:center;">
                                                <c:if test="${pageMaker.cri.pageNum > 1}">
                                                    <a class="changePage" href="${1}"><code>&lt;&lt;</code></a>
                                                    <a class="changePage" href="${pageMaker.cri.pageNum - 1}"><code>&lt;</code></a>
                                                </c:if>
                                                <code>${pageMaker.cri.pageNum}</code>
                                                <c:if test="${pageMaker.cri.pageNum < pageMaker.realEnd}">
                                                    <a class="changePage" href="${pageMaker.cri.pageNum + 1}"><code>&gt;</code></a>
                                                    <a class="changePage" href="${pageMaker.realEnd}"><code>&gt;&gt;</code></a>
                                                </c:if>
                                            </div>

                                            <form id="actionForm" action="/board/list">
                                                <input type="hidden" name="pageNum" value="${pageMaker.cri.pageNum}">
                                                <input type="hidden" name="amount" value="${pageMaker.cri.amount}">
                                            </form>
                                            <!-- B -->
                                        </div>
                                </div>
                            </div>
                        </div> 
        <!-- Scripts -->
            <script src="/resources/assets/js/jquery.min.js"></script>
            <script src="/resources/assets/js/jquery.dropotron.min.js"></script>
            <script src="/resources/assets/js/browser.min.js"></script>
            <script src="/resources/assets/js/breakpoints.min.js"></script>
            <script src="/resources/assets/js/util.js"></script>
            <script src="/resources/assets/js/main.js"></script>
    </body>
    <script>
        $("a.search").on("click", function(e){
            e.preventDefault();
            var searchForm = $("#searchForm");

            if(!searchForm.find("option:selected").val()){
                alert("검색 종류를 선택하세요.");
                return false;
            }
            if(!searchForm.find("input[name='keyword']").val()){
                alert("키워드를 입력하세요.");
                return false;
            }
            searchForm.submit();
        })

        $(".changePage").on("click", function(e){
            e.preventDefault();
            var actionForm = $("#actionForm");
            var pageNum = $(this).attr("href");
            actionForm.find("input[name='pageNum']").val(pageNum);
            actionForm.submit();
        })

        //alert("${result}");
        var result = "${result}";
        $(document).ready(function(){
            if(result == '' || isNaN(result)){
                return;
            }
            alert("게시글 " + result + "번이 등록되었습니다.")
        })
    </script>
</html>

브라우저에서 검색 버튼을 클릭하면 form 태그 전송을 막고 페이지 번호가 1이 되도록 처리.

검색 부분과 form 부분 두 개, javascript 부분 수정.


1-3-2. 조회 페이지에서 검색 처리

조회 페이지로의 이동은 이미 form 태그로 처리했으므로 별도의 처리가 필요친 않는다.

다만 Cirteria의 type과 keyword를 처리해야 함.

get.jsp

<li>
  <input type="button" class="button" value="수정" onclick="location.href='/board/modify?bno=${board.bno}&pageNum=${cri.pageNum}&amount=${cri.amount}'"/>
  <input type="submit" class="button" value="삭제"/>
  <input type="hidden" name="pageNum" value="${cri.pageNum}">
  <input type="hidden" name="amount" value="${cri.amount}">
</li>

1-3-3. 수정 / 삭제 페이지에서 검색 처리

조회 페이지에서 수정 / 삭제 페이지로 이동은 GET 방식으로 이동하고, 이동 방식 역시 form 태그를 이용하는 방식이므로 기존 form 태그에 추가적인 조건만 추가.

modify.jsp

<input type="hidden" name="pageNum" value="${cri.pageNum}">
<input type="hidden" name="amount" value="${cri.amount}">

수정 / 삭제 처리는 BoardController에서 Redirect 방식이므로 type과 keyword 조건도 리다이렉트에 포함해야 한다.

BoardController


    // 수정과 삭제는 성공 시 result에 success를 담아서 view에 전달하기.
    // 수정 처리와 테스트 구현
    @PostMapping("/modify")
    public String modify(BoardVO board, Criteria cri, RedirectAttributes rttr) {
        log.info("modify : " + board);

        if(service.modify(board)) {
            rttr.addFlashAttribute("result", "success");
        }

        /*
         * 항상 컨트롤러에 있는 클래스 타입의 매개변수는 생성자를 통해서 파라미터 값으로 초기화 한다.
         * 만약 전달받은 파라미터 값에 매핑되는 생성자가 없다면 값을 전달받을 수 없다.
            rttr.addAttribute("cri", cri); 이렇게 쓰면 안된다. 
         */

        // POST 방식으로 진행되는 수정과 삭제 처리는 보드컨트롤러에서 각각의 메서드 형태로 구현되었으므로 
//        페이지 관련 파라미터 처리를 위해 변형해야 한다.
        /*
         * Flash는 세션의 남용을 방지하고자 1개의 파라미터만 전달할 수 있다.
         * 따라서 여러 개를 전달할 수 있는 컬렉션에 담아서 넘기거나
         * URL에 붙여서 전달하는 addAttribute() 방식을 사용해야 한다.
         */

//        따라서 반드시 해당 객체의 생성자에 전달할 필드명과 일치하도록 설정해주어야 한다.
        rttr.addAttribute("pageNum", cri.getPageNum());
        rttr.addAttribute("amount", cri.getAmount());
        rttr.addAttribute("type", cri.getType());
        rttr.addAttribute("keyword", cri.getKeyword());
        return "redirect:/board/list";
    }

    // 삭제 처리와 테스트 구현
    @GetMapping("/remove")
    public String remove(@RequestParam("bno") Long bno, Criteria cri, RedirectAttributes rttr) {
        log.info("remove : " + bno);
        if(service.remove(bno)) {
            rttr.addFlashAttribute("result", "success");
        }
        rttr.addAttribute("pageNum", cri.getPageNum());
        rttr.addAttribute("amount", cri.getAmount());
        rttr.addAttribute("type", cri.getType());
        rttr.addAttribute("keyword", cri.getKeyword());
        return "redirect:/board/list";
    }

리다이렉트 GET 방식으로 이루어지므로 추가 파라미터를 처리해야 한다.

<script type="text/javascript">
    $(document).ready(function() {    
        var formObj = $("form");

        $('button').on("click", function(e){
            e.preventDefault();
            var operation = $(this).data("oper");

            console.log(operation);

            if(operation === 'remove') {
                 formObj.attr("action", "/board/remove");
             } else if (operation === 'list'){
                //move to list
                formObj.attr("action", "/board/list").attr("method","get");
                var pageNumTag = $("input[name='pageNum']").clone();
                var amountTag = $("input[name='amount']").clone();
                var keywordTag = $("input[name='keyword']").clone();
                var typeTag = $("input[name='type']").clone();
                formObj.empty();
                formObj.append(pageNumTag);
                formObj.append(amountTag);
                formObj.append(keywordTag);
                formObj.append(typeTag);
            }
            formObj.submit();
        });
    });
</script>
<%@include file="../includes/footer.jsp"%>

UriComponentBuilder를 이용하는 링크 생성

웹 페이지에서 매번 파라미터를 유지하는 일이 번거롭고 힘들 때 유용하다.

org.springframework.web.util.UriComponentsBuilders는 여러 개의 파라미터들을 연결해서 URL의 형태로 만들어주는 기능을 한다.

Criteria 클래스 추가

public String getListLink() {
        UriComponentsBuilder builder = UriComponentsBuilder.fromPath("")
                .queryParam("pageNum", this.pageNum)
                .queryParam("amount", this.getAmount())
                .queryParam("type", this.getType())
                .queryParam("keyword", this.getKeyword());
        return builder.toUriString();
    }

UriComponentBuilder는 queryParam 메서드로 필요 파라미터를 손쉽게 추가할 수 있다.

getListLink로 BoardController의 modify와 remove를 다음처럼 간단하게 정리할 수 있다.

    @PostMapping("/modify")
    public String modify(BoardVO board, @ModelAttribute("cri") Criteria cri, RedirectAttributes rttr) {
        log.info("modify: " + board);
        if(service.modify(board)) {
            rttr.addFlashAttribute("result", "sucess");
        }
        return "redirect:/board/list" + cri.getListLink();
    }

    @PostMapping("/remove")
    public String remove(@RequestParam("bno") Long bno, @ModelAttribute("cri") Criteria cri, RedirectAttributes rttr) {
        log.info("remove..." + bno);;
        if(service.remove(bno))
        {
            rttr.addFlashAttribute("result", "success");
        }
        return "redirect:/board/list" + cri.getListLink();
    }

 

반응형

댓글