게시판 검색 기능 구현하기

1. 게시판 검색

 

2.

MyBatis의 동적 쿼리(1) - <sql>과 <include>

공통 부분을 <sql>로 정의하고 <include>로 포함시켜 재사용

 

 

MyBatis의 동적 쿼리(2) - <if>

if문으로 조건에 맞는 쿼리문을 실행함

그러나 단 한가지 조건이 아닌, 다른 조건도 true라면 실행됨 (if문만 쓰였기 때문에)

 

MyBatis의 동적 쿼리(3) - <choose> <when>

반대로 <choose><when>은 조건이 true면 그 해당되는 쿼리문만 실행함

그 뒤에 있는 조건문은 보지 않음.

 

* mySQL 의 와일드카드 %, _

%는 여러글자 (0~n)

_는 한 글자 (1)

 

ex)

'title%'은 title도 OK, title1도 OK

'title_'은 title은 X, title1은 OK

 

와일드카드를 사용할 땐 등호 사용 X, LIKE를 사용해야 함

 

MyBatis의 동적 쿼리(4) - <foreach>

 

where bno = 1     /   (bno가 1인 경우만)

where bno in (1,2,3)  / (bno가 1,2,3 인 경우)

 

배열인 array에 담겨있는 값을 괄호 안에 구분자( , ) 를 넣어서 쿼리문을 만듦

ex) 배열이 {1,2,3} 이면 WHERE bno IN (1,2,3)

 

 

package com.fastcampus.ch4.domain;

import org.springframework.web.util.UriComponentsBuilder;

public class SearchCondition {
    private Integer page = 1;
    private Integer pageSize = 10;
//    private Integer offset = 0;
    private String keyword = "";
    private String option = "";

    public SearchCondition () {}
    public SearchCondition(Integer page, Integer pageSize, String keyword, String option) {
        this.page = page;
        this.pageSize = pageSize;
        this.keyword = keyword;
        this.option = option;
    }

    public String getQueryString(Integer page) {
        // ?page=1&pageSize=10&option=T&keyword="title"
        return UriComponentsBuilder.newInstance()
                .queryParam("page", page)
                .queryParam("pageSize", pageSize)
                .queryParam("option", option)
                .queryParam("keyword", keyword)
                .build().toString();
    }

    public String getQueryString() {
        return getQueryString(page);
    }

    public Integer getPage() {
        return page;
    }

    public void setPage(Integer page) {
        this.page = page;
    }

    public Integer getPageSize() {
        return pageSize;
    }

    public void setPageSize(Integer pageSize) {
        this.pageSize = pageSize;
    }

    public Integer getOffset() {
        return (page-1) * pageSize;
    }

    public String getKeyword() {
        return keyword;
    }

    public void setKeyword(String keyword) {
        this.keyword = keyword;
    }

    public String getOption() {
        return option;
    }

    public void setOption(String option) {
        this.option = option;
    }

    @Override
    public String toString() {
        return "SearchCondition{" +
                "page=" + page +
                ", pageSize=" + pageSize +
                ", offset=" + getOffset() +
                ", keyword='" + keyword + '\'' +
                ", option='" + option + '\'' +
                '}';
    }
}

먼저 검색에 필요한 조건을 담은 SearchCondition 클래스를 생성함

 

 

    @Override
    public int searchResultCnt(SearchCondition sc) throws Exception {
        return session.selectOne(namespace+"searchResultCnt", sc);
} // T selectOne(String statement, Object parameter)

    @Override
    public List<BoardDto> searchSelectPage(SearchCondition sc) throws Exception {
        return session.selectList(namespace+"searchSelectPage", sc);
    } // List<E> selectList(String statement, Object parameter)

BoardDaoImpl에 다음과 같이 쿼리를 실행할 메소드를 추가

 

@Override
public int getSearchResultCnt(SearchCondition sc) throws Exception {
    return boardDao.searchResultCnt(sc);
} // 검색하여 얻은 게시물 수

@Override
public List<BoardDto> getSearchResultPage(SearchCondition sc) throws Exception {
    return boardDao.searchSelectPage(sc);
} // 검색하여 얻은 게시물을 list에 담음

BoardServiceImpl에도 다음과 같이 메소드를 추가

 

 

<sql id="searchCondition">
    <choose>
        <when test='option=="T"'>
            AND title LIKE concat('%', #{keyword}, '%')
        </when>
        <when test='option=="W"'>
            AND writer LIKE concat('%', #{keyword}, '%')
        </when>
        <otherwise>
            AND (title LIKE concat('%', #{keyword}, '%')
            OR content LIKE concat('%', #{keyword}, '%'))
        </otherwise>
    </choose>
</sql>

<select id="searchSelectPage" parameterType="SearchCondition" resultType="BoardDto">
    <include refid="selectFromBoard"/>
    WHERE true
        <include refid="searchCondition"/>
        ORDER BY reg_date DESC, bno DESC
        LIMIT #{offset}, #{pageSize}
</select>

<select id="searchResultCnt" parameterType="SearchCondition" resultType="int">
    SELECT count(*)
    FROM board
    WHERE true
    <include refid="searchCondition"/>
</select>

다음과 같은 쿼리문을 boardMapper.xml에 추가하였다.

 

중복되는 쿼리문은 따로 만들어서 간편하게 호출하여 사용할 수 있다.

 

@GetMapping("/list")
    public String list(SearchCondition sc, Model m, HttpServletRequest request) {
        if(!loginCheck(request))
            return "redirect:/login/login?toURL="+request.getRequestURL();  // 로그인을 안했으면 로그인 화면으로 이동

//        if(page==null) page=1;
//        if(pageSize==null) pageSize=10;
        
        try {
            int totalCnt = boardService.getSearchResultCnt(sc);
            m.addAttribute("totalCnt", totalCnt);
    
            PageHandler pageHandler = new PageHandler(totalCnt, sc);

            List<BoardDto> list = boardService.getSearchResultPage(sc);
            m.addAttribute("list", list);
            m.addAttribute("ph", pageHandler);

            Instant startOfToday = LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toInstant();
            m.addAttribute("startOfToday", startOfToday.toEpochMilli());
        } catch (Exception e) {
            e.printStackTrace();
            m.addAttribute("msg", "LIST_ERR");
            m.addAttribute("totalCnt", 0);
        }

        return "boardList"; // 로그인을 한 상태이면, 게시판 화면으로 이동
    }

list 메소드와 pageHandler 클래스를 SearchCondition 객체를 사용하도록 수정한다.

 

SearchCondition 객체를 톰캣에서 자동으로 만들어준다(기본 생성자로)

 

 

 

REST API와 Ajax

1. JSON이란?

Java Script Object Notation - 자바 스크립트 객체 표기법

 

{ 속성명1: 속성값1, 속성명2: 속성값2, ... }

 

[{속성명: 속성값, ...}, { 속성명: 속성값, ...}, ...] // 객체 배열

{키1:{속성명: 속성값,...}, 키2: {속성명 : 속성값, ...}, ... } // Map

 

2. stringify()와 parse()

 

JS객체를 서버로 전송하려면, 직렬화(문자열로 변환)가 필요

서버가 보낸 데이터(JSON 문자열)를 JS객체로 변환할 때, 역직렬화가 필요

 

JSON.striingify() - 객체를 JSON 문자열로 변환(직렬화, JS객체->문자열)

JSON.parse() - JSON 문자열을 객체로 변환(역직렬화, 문자열->JS객체)

 

ex)

 

 

3. Ajax란?

 

Asynchronous javascript and XML - 요즘은 JSON을 주로 사용

비동기 통신으로 데이터를 주고 받기 위한 기술

웹페이지 전체(data+UI)가 아닌 일부(data)만 업데이트 가능

 

동기와 비동기

동기는 보낸 요청이 응답으로 돌아올 때까지 기다려야함

비동기는 요청이 끝나지 않아도 다른 요청을 할 수 있음(콜백으로 응답이 온 것을 확인함)

 

 

4. jQuery를 이용한 Ajax

%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
    <script src="https://code.jquery.com/jquery-1.11.3.js"></script>
</head>
<body>
<h2>{name:"abc", age:10}</h2>
<button id="sendBtn" type="button">SEND</button>
<h2>Data From Server :</h2>
<div id="data"></div>
<script>
    $(document).ready(function(){
        let person = {name:"abc", age:10};
        let person2 = {};
        $("#sendBtn").click(function(){
            $.ajax({
                type:'POST',       // 요청 메서드
                url: '/ch4/send',  // 요청 URI
                headers : { "content-type": "application/json"}, // 요청 헤더
                dataType : 'text', // 전송받을 데이터의 타입
                data : JSON.stringify(person),  // 서버로 전송할 데이터. stringify()로 직렬화 필요.
                success : function(result){
                    person2 = JSON.parse(result);    // 서버로부터 응답이 도착하면 호출될 함수
                    alert("received="+result);       // result는 서버가 전송한 데이터
                    $("#data").html("name="+person2.name+", age="+person2.age);
                },
                error   : function(){ alert("error") } // 에러가 발생했을 때, 호출될 함수
            }); // $.ajax()
            alert("the request is sent")
        });
    });
</script>
</body>
</html>
package com.fastcampus.ch4.controller;

import com.fastcampus.ch4.domain.*;
import org.springframework.stereotype.*;
import org.springframework.web.bind.annotation.*;

@Controller
public class SimpleRestController {
    @GetMapping("/ajax")
    public String ajax() {
        return "ajax";
    }

    @PostMapping("/send")
    @ResponseBody
    public Person test(@RequestBody Person p) {
        System.out.println("p = " + p);
        p.setName("ABC");
        p.setAge(p.getAge() + 10);

        return p;
    }
}

 

5. Ajax요청과 응답 과정

JS객체를 문자열로 변환하여 POST 메소드로 요청을 보냄

jackson-databind가 문자열로 받은 데이터를 Java 객체로 만들어서 매개변수로 넘겨줌

작업을 하고 나서 객체를 반환하면 다시 jackson-databind가 문자열로 변환하여 응답을 함

 

@RequestBody 는 request(요청)의 body(내용)을 읽어와서 객체로 변환해주고

@ResponseBody는 response(응답)으로  jackson-databind에 의해 문자열로 변환하여 body로 보낸다.

 

 

6. @RestController 

 

@ResponseBody 대신, 클래스에 @RestController 사용 가능

클래스 내부의 모든 메소드에 @ResponseBody가 붙게 된다.

 

package com.fastcampus.ch4.controller;

import com.fastcampus.ch4.domain.*;
import org.springframework.stereotype.*;
import org.springframework.web.bind.annotation.*;

@RestController
public class SimpleRestController {
//    @GetMapping("/ajax")
//    public String ajax() {
//        return "ajax";
//    }

    @PostMapping("/send")
//    @ResponseBody
    public Person test(@RequestBody Person p) {
        System.out.println("p = " + p);
        p.setName("ABC");
        p.setAge(p.getAge() + 10);

        return p;
    }

    @PostMapping("/send")
//    @ResponseBody
    public Person test2(@RequestBody Person p) {
        System.out.println("p = " + p);
        p.setName("ABC");
        p.setAge(p.getAge() + 10);

        return p;
    }
}

 

7. REST란?

Roy Fielding이 제안한 웹서비스 디자인 아키텍쳐 접근 방식

프로토콜에 독립적이며, 주로 HTTP를 사용해서 구현

리소스 중심의 API 디자인 - HTTP메소드로 수행할 작업을 정의

 

PUT : 파일 update

DELETE : 파일 삭제

PATCH : 일부 수정

 

 

8. REST API란?

 

Representational State Transfer API - REST규약을 준수하는 API

 

REST is a set of architectural constraints, not a protocol or a standard.

API developers can implement REST in a variety of ways.

 

API(Application Programming Interface)

 

 

 

9. RESTful API 설계

 


출처 : 스프링의 정석 : 남궁성과 끝까지 간다

 

+ Recent posts