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로 작성해주자.
다른 해결책은 태그의 시작부분이 아니라 텍스트의 일부분임을 알려주는 방법이 있다.
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 |