67일차

sql에 if문을 활용해서 동적 sql문을 활용하고 있엇다.
작성하는 쿼리가 다양하게 변경될 수 잇다.
파라미터로 받은값을 bind로 변경해서 작성하거나
쿼리의 일부분을 작성하거나 안하거나

if bind foreach
쿼리 앞뒤로 script태그필요하다.
외워서 쓰지말고 메뉴얼 보고 사용해라.

OGNL기반으로 사용한다.

11.5 Dynamic SQL- 5

SELECT옵션 element를 추가해서 제목 본문 작성자중 무엇으로 검색할 것인지 작성하자.
뭘하든 3티어 구조로 작성하니 모든 파일을 다 고쳐야한다.

11.5.1 view

view에 설렉트옵션으로 전체 검색할지 제목을 검색할지를 선택한다.
type이 all 일때는 OR연산자로 여러개 검색하고
type이 title일때는 제목만 검색한다.

SELECT * FROM Board 
WHERE title LIKE '%keyword%'
OR body LIKE '%keyword%'
OR writer LIKE '%keyword%';

<form action="/list" class="d-flex" role="search">
    <select name="type">
        <option value="all">전체</option>
        <option value="title">제목</option>
    </select>
    <input value="${param.search}" name="search" class="form-control me-2" type="search" placeholder="Search" aria-label="Search">
    <button class="btn btn-outline-success" type="submit">
        <i class="fa-solid fa-magnifying-glass"></i>
    </button>
</form>

11.5.2 컨트롤러

String type 타입파라미터를 추가해준다.
적어두면 type파라미터가 꼭 잇어야하기 때문에 없어도 됨을 명시해줄 수 있다.

@RequestParam의속성으로 required = false를 줌으로써
null로 두어도되면 이속성을 두고 null말고 기본값을 두고 싶다면 defaultValue를 사용해야한다.
required = false로 받으면 null이 들어올수잇고 all title이 들어올수 잇으니 sql에 다 반영해야한다.

11.5.3 서비스

서비스 메소드도 파라미터에 String type을 받아줘야한다.

11.5.4 mapper

type에 의해서 쿼리가 변경되도록 작성해준다.
where태그로 감싸면 조건에 따라 AND나 OR을 제거하고 WHERE 를 붙여준다.
조건을 넣어서 null일때는 아무것도 안들어간다.
title일 때는 where title만 writer일때는 where writer body일때는 where body만 각각붙는다.
all 일때는 title or writer or body가 되게 된다.

@Select("""
        <script>
        <bind name="pattern" value="'%' + search + '%'"/>
        SELECT id, title, writer, body, inserted 
        FROM Board
        <where>
        <if test="type eq 'all' or type eq 'title'">
        title LIKE #{pattern}
        </if>
        <if test="type eq 'all' or type eq 'writer'">
        OR writer LIKE #{pattern}
        </if>
        <if test="type eq 'all' or type eq 'body'">
        OR body LIKE #{pattern}
        </if>    
        </where>
        ORDER BY id DESC 
        LIMIT #{startIndex}, #{rowPerPage}
        </script>
        """)
List<Board> selectAllPaging(Integer startIndex, Integer rowPerPage, String search, String type);

11.5.5 나머지 처리

1.조회후 select가 유지되게하기
파라미터를 검색해서 각 조건에 따라서 selected로 변경하면된다.

<select name="type">
    <option value="all">전체</option>
    <option ${(param.type == "title")? "selected" : ""} value="title">제목</option>
    <option ${(param.type == "body")? "selected" : ""} value="body">본문</option>
    <option ${(param.type == "writer")? "selected" : ""} value="writer">작성자</option>
</select>

2.count(*)수정
검색 후 남아잇을 수 있게 갯수에 따라 값을 나오도록 검색조건을 넣어준다.

@Select("""
        <script>
        <bind name="pattern" value="'%' + search + '%'"/>
        SELECT COUNT(id) count 
        FROM Board
        <where>
        <if test="type eq 'all' or type eq 'title'">
        title LIKE #{pattern}
        </if>
        <if test="type eq 'all' or type eq 'writer'">
        OR writer LIKE #{pattern}
        </if>
        <if test="type eq 'all' or type eq 'body'">
        OR body LIKE #{pattern}
        </if>    
        </where>
        </script>
        """)
Integer countAll(String search, String type);

3.페이지가 넘어가도 값이 유지되게 하기
파라미터를 검사해서 유지되도록 해준다.

<!-- 페이지네이션 -->
<c:forEach begin="${pageInfo.leftPageNumber}" end="${pageInfo.rightPageNumber}" var="pageNum">
    <c:url value="/list" var="pageLink">
        <c:param name="page" value="${pageNum}" />
        <c:if test="${not empty param.search }">
            <c:param name="search" value="${param.search}"/>
        </c:if>
        <c:if test="${not empty param.type }">
            <c:param name="type" value="${param.type}"/>
        </c:if>
    </c:url>
    <li class="page-item"><a class="page-link ${pageNum eq pageInfo.currentPageNumber ? 'active' : '' }" href="${pageLink}">${pageNum}</a></li>
</c:forEach>

11.5.6 반복 코드 줄이기

페이지네이션의 pageLink와 파라미터 부분이 반복되고 있으니
태그화해서 코드를 줄여보자.

pageNum이 링크마다 달라지니 밖에서 받아와야한다.
태그의 pageNum을 attribute로 받아와서 사용하면 값을 그때 마다 바꿀 수 있다.
a태그에 값을 넣는 것은 body에 값을 넣고
jsp:doBody태그를 사용하면 그자리에 바디 값을 넣어서 사용할 수 있다.

<%@ tag language="java" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="jakarta.tags.core"%>
<%@ attribute name ="pageNum" %>

<li class="page-item">
<c:url value="/list" var="pageLink">
    <c:param name="page" value="${pageNum}" />
    <c:if test="${not empty param.search }">
        <c:param name="search" value="${param.search}"/>
    </c:if>
    <c:if test="${not empty param.type }">
        <c:param name="type" value="${param.type}"/>
    </c:if>
</c:url>
    <a class="page-link ${pageNum eq pageInfo.currentPageNumber ? 'active' : '' }" href="${pageLink}">
        <jsp:doBody></jsp:doBody>
    </a>
</li>

<!-- 첫페이지 -->
<c:if test="${pageInfo.currentPageNumber gt 1}">
    <my:pageItem pageNum="1">
        <i class="fa-solid fa-angles-left"></i>
    </my:pageItem>
</c:if>

<!-- 이전버튼 -->
<c:if test="${pageInfo.currentPageNumber gt 1}">
    <my:pageItem pageNum="${pageInfo.prevPageNumber}">
        <i class="fa-solid fa-angle-left"></i>
    </my:pageItem>
</c:if>

<!-- 페이지네이션 -->
<c:forEach begin="${pageInfo.leftPageNumber}" end="${pageInfo.rightPageNumber}" var="pageNum">
    <my:pageItem pageNum="${pageNum}">
        ${pageNum}
    </my:pageItem>
</c:forEach>

<!-- 다음버튼 -->
<c:if test="${pageInfo.currentPageNumber lt pageInfo.lastPageNumber}">
    <%-- 페이지번호 ${pageInfo.nextPageNumber} --%>
    <my:pageItem pageNum="${pageInfo.nextPageNumber}">
        <i class="fa-solid fa-angle-right"></i>
    </my:pageItem>
</c:if>

<!-- 마지막페이지 -->
<c:if test="${pageInfo.currentPageNumber lt pageInfo.lastPageNumber}">
    <my:pageItem pageNum="${pageInfo.lastPageNumber}">
        <i class="fa-solid fa-angles-right"></i>
    </my:pageItem>
</c:if>

11.6 Dynamic SQL 주의할점

Dynamic SQL 주의할점이 있다.
mapper에서 파라미터를 받아 if문을 작성할때
<if test="id < 5">을 하면 태그안에 태그가 들어간다고 인식한다.
'<' 문자가 포함되지 않아야 한다며 에러가 발생한다.
키워드 연산자를 사용하자.

Dynamic SQL이 아닌 sql에서 <나 >를 사용하는 것은 상관이 없다.
<if>와 같은 태그 안에 작성할때는 꼭 키워드 연산자를 사용하자.

<script>태그 안에 작성하면 마이바티스가 XML에 작성된 것처럼 이해를 하는것이다.
태그가 시작되었다고 판단을 하게 된다.
HTML entity를 사용해야한다. &;
&lt; 나 lt로 작성해주자.

다른 해결책은 태그의 시작부분이 아니라 텍스트의 일부분임을 알려주는 방법이 있다.
XML의 cdata태그가 있다.
으로 시작하면된다.
그러나 는 if태그나 where태그등이 들어갈 수없으니 주의하자.

@Select("""
        SELECT COUNT(*)
        FROM Customers
        WHERE CustomerId < #{id}
        """)
public Integer sql2(Integer id);

@Select("""
        <script>
        SELECT COUNT(*)
        FROM Customers
        WHERE CustomerId lt #{id}
        </script>
        """)
public Integer sql3(Integer id);

@Select("""
        <script>
        <![CDATA[
        SELECT COUNT(*)
        FROM Customers
        WHERE CustomerId < #{id}
        ]]>
        </script>
        """)
public Integer sql4(Integer id);

11.7 Dynamic SQL - 6

foreach 태그를 사용해보자.
반복할 값을 collection attribute에 넣어주고 이 값들이 들어갈 값을 item에 넣어주면된다.
JSTL의 items / var처럼 사용하면된다.
꺼내서 사용할때는 #{item}에 넣어서 사용한다.

그런데 그냥 넣으면 구분자가 들어가지 않는다.
각 값 사이사이에 구분자를 넣기 위해서 separator 속성을 사용하면된다.

이외에도
open close로 열고 닫을 것을 정할 수 있다.
만약 index가필요하다면 idnex속성을 사용하면된다.
nullable로 null이 가능한지 안한지를 지정할 수도 있다.

@Select("""
        <script>
        SELECT COUNT(*)
        FROM Customers 
        WHERE country IN (
        <foreach collection="elems" item="elem" separator=",">
            #{elem}
        </foreach>
        )
        </script>
        """)
public Integer sql1(List<String> elems);

11.8 Dynamic SQL forEach -2

forEach 예제 만들기
Suppliers의 나라

@GetMapping("link2")
public void method2() {
    List<Supplier> list1 = mapper.sql2(List.of("UK", "USA", "Japan"));
    list1.forEach(System.out::println);

    List<Supplier> list2 = mapper.sql2(List.of("Brazil", "Germany"));
    list2.forEach(System.out::println);
}

@Select("""
        <script>
        SELECT *
        FROM Suppliers
        WHERE country IN
        <foreach collection="countries" item="country" 
        open="("
        separator=", "
        close=")">
        #{country}
        </foreach>
        </script>
        """)
public List<Supplier> sql2(List<String> countries);

11.9 Dynamic SQL forEach -3

WHERE에 들어갈 값을 하나도 선택하지 않앗을경우
country requestparam이 필수인데 존재하지 않는다는 오류가 발생한다.
@RequestParam어노테이션에 required = false 속성을 넣어주면된다.

@GetMapping("link4")
public void method4(@RequestParam(value = "country", required = false) List<String> countries) {
    List<Supplier> list = mapper.sql2(countries);
    list.forEach(System.out::println);
}

@Select("""
        <script>
        SELECT *
        FROM Suppliers
        <if test="countries neq null">
        WHERE country IN
        <foreach collection="countries" item="country" 
        open="("
        separator=", "
        close=")">
        #{country}
        </foreach>
        </if>
        </script>
        """)
public List<Supplier> sql2(List<String> countries);

12. file upload

파일의 내용은 길기때문에 get방식으로 보낼수가 없다.
post방식으로 보내야한다.
파일은 MultipartFile객체로 받아와야한다.

file을 받으려면 input 박스의 타입이 file이어야한다.
기본 form박스의 인코딩 타입은 application/x-www-form-urlencoded인데 이렇게하면 문자열밖에 넘기지 못한다.
바이너리 데이터를 넘기기 위해 인코딩타입을 multipart/form-data로 설정해줘야한다.

// 경로 : /sub29/link1?name=
@GetMapping("link1")
public void method1() {

}

// 경로 : /sub29/link2
@PostMapping("link2")
public void method2(@RequestParam("myfile") MultipartFile file) {
    System.out.println(file.getOriginalFilename());
    System.out.println(file.getSize());
}

<h1>파일 전송 예시</h1>
<form action="/sub29/link2" method="post" enctype="multipart/form-data">
    <input type="file" name="file"/>
    <input type="submit" value="전송" />
</form>

12.2 file upload - 2

연습하기

// 경로 : /sub29/link3
@GetMapping("link3")
public void method3() {
    // link3으로 포워드
}

@PostMapping("link4")
public void method4(@RequestParam("files") MultipartFile file) {
    System.out.println(file.getOriginalFilename());
    System.out.println(file.getSize());
}

<h1>파일 전송 예시 2</h1>
<form action="/sub29/link4" method="post" enctype="multipart/form-data">
    <input type="file" name="files"/>
    <input type="submit" value="전송" />
</form>

12.3 다중 file

여러 파일을 보내는 경우는 어떻게 처리해야하나
체크박스를 여러개 만들때 값을 보내듯이 배열을 통해서 post되게 된다.
컨트롤러에서는 for문으로 값을 받아와서 읽으면된다.

@PostMapping("link4")
<h1>파일 업로드</h1>
<form action="/sub29/link4" method="post" enctype="multipart/form-data">
    <input type="file" name="files"/> <br />
    <input type="file" name="files"/> <br />
    <input type="submit" value="전송" />
</form>

public void method4(@RequestParam("files") MultipartFile[] files) {
    for (MultipartFile file : files) {
        System.out.println(file.getOriginalFilename());
        System.out.println(file.getSize());
    }
}

12.4 다중 file - 2

input박스를 파일 수만큼 만들어야하는 것은 아니다.
하나의 input element로 여러 파일을 받을 수 있다.
input elemet에 multiple attribute를 넣어주면 여러 파일을 받을 수 있다.

<h1>하나의 인풋으로 여러 파일 업로드</h1>
<form action="/sub29/link6" method="post" enctype="multipart/form-data">
    <input type="file" name="files" multiple/> <br />
    <input type="submit" value="전송" />
</form>

@PostMapping("link6")
public void method6(@RequestParam("files") MultipartFile[] files) {
    for (MultipartFile file : files) {
        System.out.println(file.getOriginalFilename());
        System.out.println(file.getSize());
    }
}

12.5 다중 file - 3

파일만 보내는 것이 아니라 파일업로드시 다른 input데이터도 보내줘야한다.
다른 값들은 평소에 보내듯이 보내면된다.

<h1>파일과 다른 데이터 전송</h1>
<form action="/sub29/link8" method="post" enctype="multipart/form-data">
    <input type="text" name="name" />
    <br />
    <input type="number" name="age" />
    <br />
    <input type="file" name="files" multiple />
    <br />
    <input type="submit" value="전송" />
</form>

@PostMapping("link8")
public void method8(@RequestParam("files") MultipartFile[] files,
        String name, Integer age) {

    System.out.println(name);
    System.out.println(age);
    for (MultipartFile file : files) {
        System.out.println(file.getOriginalFilename());
        System.out.println(file.getSize());
    }
}

@ModelAttribute를 사용하면 requestParam을 dto의 맞는 property에 매핑해주는 성질을 사용해서
java bean으로 값을 받아올 수도 있다.

@Data
static class Sub29Dto{
    private String name;
    private Integer age;
}

@PostMapping("link8")
public void method8(@RequestParam("files") MultipartFile[] files,
        String name, Integer age, Sub29Dto dto) {

    System.out.println(dto);
    System.out.println(name);
    System.out.println(age);
    for (MultipartFile file : files) {
        System.out.println(file.getOriginalFilename());
        System.out.println(file.getSize());
    }
}

12.6 file 저장

파일을 받아오면 이름만 출력하는게 아니라 저장을 해야한다.

file의 inputStream을 얻어야한다.
그리고 우리 하드디스크에 저장해야하기 때문에 outputStream도 필요하다.

그냥 저장하면 우리 프로젝트 경로로 다운로드된다.

@PostMapping("link10")
public void method10(@RequestParam("file1") MultipartFile file) {

    try (InputStream fis = file.getInputStream();
            BufferedInputStream bis = new BufferedInputStream(fis)) {

        String targetFileName = "copy_" + file.getOriginalFilename();

        try (FileOutputStream fos = new FileOutputStream(targetFileName);
                BufferedOutputStream bos = new BufferedOutputStream(fos)) {
            byte[] data = new byte[1024];
            while (true) {
                int len = bis.read(data);
                if (len == -1) {
                    break;
                }
                bos.write(data, 0, len);
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

12.7 file 저장 - 2

inputStream에서 ouputStream을 얻어서 사용할 수 있지만
배열을 만들고 배열을 복사하는 등 코드량이 많다.
더 쉬운방법을 사용할 수 있다.
file의 transferTo메소드를 사용하면 inputStream에서 outputStream으로 파일을 바로 복사 할 수 있다.

파일경로를 파라미터로 받는다.

@PostMapping("link11")
public void method11(@RequestParam("file1") MultipartFile file) {

    try {
        File target = new File("F:/study/copy11_" + file.getOriginalFilename());
        file.transferTo(target);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

requestParameter에 MultipartFile 객체로 파일을 받아서 하드디스크에 저장을 할 수 있다.
실행하는 프로그램 기준으로 어디론가 쓸수 있다라는 것을 알 수 있다.
만든 어플리케이션을 공개하면 하드디스크에 저장된 파일을 다시 서비스하기가 어렵다.
그래서 aws 서버에 파일을 다시 저장해야한다. 하드디스크에 저장한걸 누구나 볼 수 잇도로고 서버에 저장을 해야한다.

2023.05.01

파일을 저장하는 방법에 대해서 배웠다.

'국비 > Project - 1 게시판' 카테고리의 다른 글

2023.05.03 69일차 Project  (0) 2023.05.03
2023.05.02 68일차 Project  (0) 2023.05.02
2023.04.28 66일차 Project  (0) 2023.05.01
2023.04.27 65일차 Project  (0) 2023.04.27
2023.04.26 64일차 Project  (0) 2023.04.26

+ Recent posts