1.16 (tiles-게시판 기본폼 호출, 글등록, 목록처리(+검색))
<복습>
프로필 사진은 db에 저장 그외는 경로에 저장
db 저장 시
1.
member.profile.js
바이너리 타입 전송 시
contentType, processData 둘다 false처리
2.
MemberAjaxController
3.
MemberServiceImpl
updateProfile 메서드 추가
4.
MemberMapper.java
@Update(sql문)
근데 db저장한거랑 화면에 파일 출력하는건 경로가 달라서 출력 시에는 조금 다르게 기재해야함
화면 출력 시
1.
header.jsp
<img src="${pageContext.request.contextPath}/member/photoView">
stream을 만들어서 전달 -> 컨트롤러에서 photoView 요청 받음
2.
MemberController
프로필 메서드 (1,2,3,4) 기재된 내용 처리
byte[] readbyte = FileUtil.getBytes(request.getServletContext().getRealPath("/image_bundle/face.png"));
절대 경로를 읽어서 byte배열의 stream생성 -> byte배열로 전달
//빈의 이름이 imageView인 ImageView 객체를 호출
return "imageView";
3.
쌤이 넣어준 ImageView로 이동
4.
nav_mypage에도 적용됨
<게시판 생성하기>
기본설정
board 테이블에 filename 컬럼 추가함
table.sql
drop table spboard;
create table spboard(
board_num number not null,
title varchar2(90) not null,
content clob not null,
hit number(8) default 0 not null,
reg_date date default sysdate not null,
modify_date date,
filename varchar2(100),
ip varchar2(40) not null,
mem_num number not null,
constraint spboard_pk primary key (board_num),
constraint spboard_fk foreign key (mem_num) references spmember (mem_num)
);
이건 다른건데 db저장 시에는 파일명만 저장하는 게 좋다함 (용량문제 상)
BoardVO 수정
private MultipartFile upload;
private String filename;
경로에 저장할거라 byte[]가 아닌 MultipartFile을 사용
views
template
header.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!-- 상단 시작 -->
<h2 class="align-center">SpringPage</h2>
<div class="align-right">
<a href="${pageContext.request.contextPath}/board/list">게시판</a>
<c:if test="${!empty user}">
<a href="${pageContext.request.contextPath}/member/myPage">MY페이지</a>
<img src="${pageContext.request.contextPath}/member/photoView" width="25" height="25" class="my-photo">
</c:if>
board/list 게시판 링크 추가
<게시판 기본 화면 보이기>
컨트롤러에 목록 출력 코드
BoardController
package kr.spring.board.controller;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
import kr.spring.board.service.BoardService;
import lombok.extern.slf4j.Slf4j;
@Controller
@Slf4j
public class BoardController {
@Autowired
private BoardService boardService;
/*===========================
* 게시판 글 목록
* ==========================*/
@RequestMapping("/board/list")
public ModelAndView process(@RequestParam(value="pageNum",defaultValue="1") int currentPage,
String keyfield, String keyword) {
Map<String,Object> map = new HashMap<String,Object>();
ModelAndView mav = new ModelAndView();
mav.setViewName("boardList");
return mav;
}
}
tiles view 생성
views
board (folder)
boardList.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!-- 내용 시작 -->
<div class="page-main">
<h2>게시판 목록</h2>
<div class="align-right">
<c:if test="${!empty user}">
<input type="button" value="글쓰기" onclick="location.href='write'">
</c:if>
</div>
</div>
<!-- 내용 끝 -->
mapping하기
tiles-def
board.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE tiles-definitions PUBLIC
"-//Apache Software Foundation//DTD Tiles Configuration 3.0//EN"
"http://tiles.apache.org/dtds/tiles-config_3_0.dtd">
<tiles-definitions>
<definition name="boardList" extends="main">
<put-attribute name="title" value="게시판 목록"/>
<put-attribute name="body" value="/WEB-INF/views/board/boardList.jsp"/>
</definition>
</tiles-definitions>
자바코드기반설정에 board.xml 등록
AppConfig
package kr.spring.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.tiles3.TilesConfigurer;
import org.springframework.web.servlet.view.tiles3.TilesView;
import org.springframework.web.servlet.view.tiles3.TilesViewResolver;
//자바코드 기반 설정 클래스
@Configuration
public class AppConfig implements WebMvcConfigurer {
@Bean
public TilesConfigurer tilesConfigurer() {
final TilesConfigurer configurer = new TilesConfigurer();
//tilesdef.xml의 경로와 파일명 지정
configurer.setDefinitions(new String[] {"/WEB-INF/tiles-def/main.xml",
"/WEB-INF/tiles-def/member.xml",
"/WEB-INF/tiles-def/board.xml"
});
configurer.setCheckRefresh(true);
return configurer;
}
@Bean
public TilesViewResolver tilesViewResolver() {
final TilesViewResolver tilesViewResolver = new TilesViewResolver();
tilesViewResolver.setViewClass(TilesView.class);
return tilesViewResolver;
}
}
=============================
실행하면 게시판 목록 보임 (로그인 시 글쓰기 버튼)
<게시판 글 등록>
자바빈 초기화 + 등록폼 호출
BoardController
/*===========================
* 게시판 글 등록
* ==========================*/
//자바빈(VO) 초기화
@ModelAttribute
public BoardVO initCommand() {
return new BoardVO();
}
//등록 폼 호출
@GetMapping("/board/write")
public String form() {
return "boardWrite"; //tiles 설정
}
등록폼 생성
views
board
boardWrite.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!-- 내용 시작 -->
<div class="page-main">
<h2>글쓰기</h2>
<form:form action="write" modelAttribute="boardVO" id="register_form" enctype="multipart/form-data">
<form:errors element="div" cssClass="error-color"/>
<ul>
<li>
<form:label path="title">제목</form:label>
<form:input path="title"/>
<form:errors path="title" cssClass="error-color"/>
</li>
<li>
<form:label path="content">내용</form:label>
<form:textarea path="content"/>
<form:errors path="content" cssClass="error-color"/>
</li>
<li>
<form:label path="upload">파일업로드</form:label>
<input type="file" name="upload" id="upload">
</li>
</ul>
<div class="align-center">
<form:button>전송</form:button>
<input type="button" value="목록" onclick="location.href='list'">
</div>
</form:form>
</div>
<!-- 내용 끝 -->
VO에서 명시했던 fileupload 파라미터 네임이 upload기 때문에 이름 맞춰줌
xml파일에 글쓰기 넣기
tiles-def
board.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE tiles-definitions PUBLIC
"-//Apache Software Foundation//DTD Tiles Configuration 3.0//EN"
"http://tiles.apache.org/dtds/tiles-config_3_0.dtd">
<tiles-definitions>
<definition name="boardList" extends="main">
<put-attribute name="title" value="게시판 목록"/>
<put-attribute name="body" value="/WEB-INF/views/board/boardList.jsp"/>
</definition>
<definition name="boardWrite" extends="main">
<put-attribute name="title" value="글쓰기"/>
<put-attribute name="body" value="/WEB-INF/views/board/boardWrite.jsp"/>
</definition>
</tiles-definitions>
=============================
저장 후 실행하면 폼 보임
제목 내용 파일업로드
<파일 업로드>
webapp
upload (folder) 생성
kr.spring.util (package)
FileUtil
package kr.spring.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.multipart.MultipartFile;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class FileUtil {
//업로드 상대 경로
private static final String UPLOAD_PATH = "/upload";
//파일 생성 시 호출하는 메서드
public static String createFile(HttpServletRequest request, MultipartFile file) throws IllegalStateException, IOException {
//절대 경로 구하기
String absolutePath = request.getServletContext().getRealPath(UPLOAD_PATH);
//파일명 생성 (난수 발생)
String filename = null;
if(!file.isEmpty()) { //파일이 있을 경우
filename = UUID.randomUUID()+"_"+file.getOriginalFilename();
//원하는 경로에 파일 저장
file.transferTo(new File(absolutePath+"/"+filename));
}
return filename; //전송된 파일이 없다면 null처리
}
//파일 삭제 시 호출하는 메서드
public static void removeFile(HttpServletRequest request, String filename) {
if(filename!=null) {
//업로드 절대 경로
String absolutePath = request.getServletContext().getRealPath(UPLOAD_PATH);
//파일 객체 생성
File file = new File(absolutePath+"/"+filename);
//파일이 존재한다면 삭제, 없으면 무반응
if(file.exists()) file.delete();
}
}
public static byte[] getBytes(String path) {
FileInputStream fis = null;
byte[] readbyte = null;
try {
fis = new FileInputStream(path);
readbyte = new byte[fis.available()];
fis.read(readbyte);
}catch(Exception e) {
log.error(e.toString());
}finally {
if(fis!=null)try {fis.close();}catch(IOException e) {}
}
return readbyte;
}
}
메서드들 >
blob 형태가 아니라 특정 경로에 넣으려고 함
파일명 반환
=============================
filename = UUID.randomUUID()+"_"+file.getOriginalFilename();
multipartfile은 파일 이름이 겹쳐도 다른 이름을 주는 기능이 없음
그래서 난수를 발생시켜서 이름이 중복되는 상황을 막아줌 // 난수 파일명 + "_" + 원본 파일명
그래서 sql문에 filename varchar2(200), 크기를 넉넉하게 200바이트로 줌 (난수 때문에)
=============================
File = > java.io import
=============================
public static String createFile(HttpServletRequest request, MultipartFile file) {
//절대 경로 구하기
String absolutePath = request.getServletContext().getRealPath(UPLOAD_PATH);
//파일명 생성 (난수 발생)
String filename = UUID.randomUUID()+"_"+file.getOriginalFilename();
//원하는 경로에 파일 저장
file.transferTo(new File(absolutePath+"/"+filename));
return filename;
}
다 명시하면 이제 try~catch 에러가 뜨는데 add throws declaration 넣으면 자동으로 throws~ 생김
컨트롤러
BoardController
//전송된 데이터 처리
@PostMapping("/board/write")
public String submit(@Valid BoardVO boardVO, BindingResult result,
HttpServletRequest request, HttpSession session, Model model) throws IllegalStateException, IOException {
log.debug("<<게시판 글 저장>> : " + boardVO);
//유효성 체크 결과 오류가 있으면 폼 호출
if(result.hasErrors()) {
return form();
}
//회원번호 세팅
MemberVO vo = (MemberVO)session.getAttribute("user");
boardVO.setMem_num(vo.getMem_num());
//ip 세팅
boardVO.setIp(request.getRemoteAddr());
//파일 업로드
boardVO.setFilename(FileUtil.createFile(request, boardVO.getUpload()));
//글쓰기
boardService.insertBoard(boardVO);
//View에 표시할 메시지
model.addAttribute("message", "글쓰기가 완료되었습니다.");
model.addAttribute("url", request.getContextPath()+"/board/list");
return "common/resultAlert";
}
HttpServletRequest request = context경로 구하기
HttpSession session = 작성자 구하기 (session.getAttribute("user")로 자바빈 다 가져온 후 setMem_num 넣음)
=============================
common/resultAlert
자바스크립트로 처리할 예정
=============================
얘도 add throws declaration 눌러서 illegal 등 추가
kr.spring.board.service
BoardServiceImpl
@Override
public void insertBoard(BoardVO board) {
boardMapper.insertBoard(board);
}
BoardMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="kr.spring.board.dao.BoardMapper">
<!-- 게시판 글 등록 -->
<insert id="insertBoard" parameterType="boardVO">
INSERT INTO spboard(
board_num,
title,
content,
filename,
ip,
mem_num)
VALUES (
spboard_seq.nextval,
#{title},
#{content},
#{filename,jdbcType=VARCHAR},
#{ip},
#{mem_num})
</insert>
마이바티스는 #{} 라고 넣어주면 반드시 값을 넣어줘야 함
하지만 filename은 not null이기 때문에 null인 경우에 에러가 나지 않게 하기 위해서
jdbcType=VARCHAR 를 추가로 기재해줌 (그럼 null 에러 안남)
화면 설정
views
common
resultAlert.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<script>
alert('${message}');
location.href='${url}';
</script>
이제 글쓰기 폼에 글 작성하고 전송 누르면 alert창으로 글쓰기가 완료되었습니다. 메시지 뜸 -> list로 이동
( 오라클에 데이터 들어가있어야 정상 작동 )
=============================
파일명에 난수를 둔 이유
1. 중복 회피
2. _ 뒤로 경로 뽑아내기 위해
=============================
정리
MemberVO
table (db)에 저장할 때 byte[] 사용
BoardVO
특정 경로에 저장할 때 MultipartFile 사용
resultView > 단일페이지
resultAlert > 자바스크립트 처리
<게시판 목록처리>
sql문 작성
BoardMapper.xml
<!-- 게시판 총 개수/검색 개수 -->
<select id="selectRowCount" parameterType="map" resultType="integer">
SELECT
COUNT(*)
FROM spboard JOIN spmember USING(mem_num)
</select>
<!-- 게시판 전체 목록/검색 목록 -->
<select id="selectList" parameterType="map" resultType="boardVO">
SELECT
*
FROM (SELECT
a.*,
rownum rnum
FROM (SELECT
*
FROM spboard JOIN spmember
USING(mem_num) ORDER BY board_num DESC)a)
<![CDATA[
WHERE rnum >= #{start} AND rnum <= #{end}
]]>
</select>
다이나믹 sql을 명시할거기 때문에 xml에 기재
=============================
전체목록
자바빈에 담아서 보내기 때문에 resultType="boardVO"
Service파일에 넣기
BoardServiceImpl
@Override
public List<BoardVO> selectList(Map<String, Object> map) {
return boardMapper.selectList(map);
}
@Override
public int selectRowCount(Map<String, Object> map) {
return boardMapper.selectRowCount(map);
}
컨트롤러 호출
BoardController
/*===========================
* 게시판 글 목록
* ==========================*/
@RequestMapping("/board/list")
public ModelAndView process(@RequestParam(value="pageNum",defaultValue="1") int currentPage,
String keyfield, String keyword) {
Map<String,Object> map = new HashMap<String,Object>();
map.put("keyfield", keyfield);
map.put("keyword", keyword);
//전체/검색 레코드 수
int count = boardService.selectRowCount(map);
log.debug("<<count>> : " + count);
//페이지 처리
PageUtil page = new PageUtil(keyfield, keyword, currentPage, count, 20, 10, "list");
List<BoardVO> list = null;
if(count > 0) {
map.put("start", page.getStartRow());
map.put("end", page.getEndRow());
list = boardService.selectList(map);
}
ModelAndView mav = new ModelAndView();
mav.setViewName("boardList");
mav.addObject("count", count);
mav.addObject("list", list);
mav.addObject("page", page.getPage());
return mav;
}
출력화면 보이기
boardList.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!-- 내용 시작 -->
<div class="page-main">
<h2>게시판 목록</h2>
<div class="align-right">
<c:if test="${!empty user}">
<input type="button" value="글쓰기" onclick="location.href='write'">
</c:if>
</div>
<c:if test="${count == 0}">
<div class="result-display">표시할 게시물이 없습니다.</div>
</c:if>
<c:if test="${count > 0}">
<table class="striped-table">
<tr>
<th>번호</th>
<th width="400">제목</th>
<th>작성자</th>
<th>작성일</th>
<th>조회수</th>
<th>좋아요수</th>
</tr>
<c:forEach var="board" items="${list}">
<tr>
<td class="align-center">${board.board_num}</td>
<td><a href="detail?board_num=${board.board_num}">${board.title}(${board.re_cnt})</a></td>
<td class="align-center">
<c:if test="${empty board.nick_name}">${board.id}</c:if>
<c:if test="${!empty board.nick_name}">${board.nick_name}</c:if>
</td>
<td class="align-center">${board.reg_date}</td>
<td class="align-center">${board.hit}</td>
<td class="align-center">${board.fav_cnt}</td>
</tr>
</c:forEach>
</table>
</c:if>
</div>
<!-- 내용 끝 -->
실행하면 게시판에 글 목록 보임
<검색처리>
list에 keyfield/keyword ui 넣기
BoardList.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!-- 내용 시작 -->
<div class="page-main">
<h2>게시판 목록</h2>
<form action="list" id="search_form" method="get">
<ul class="search">
<li>
<select name="keyfield" id="keyfield">
<option value="1" <c:if test="${param.keyfield == 1}">selected</c:if>>제목</option>
<option value="2" <c:if test="${param.keyfield == 2}">selected</c:if>>ID+별명</option>
<option value="3" <c:if test="${param.keyfield == 3}">selected</c:if>>내용</option>
<option value="4" <c:if test="${param.keyfield == 4}">selected</c:if>>제목+내용</option>
</select>
</li>
<li>
<input type="search" name="keyword" id="keyword" value="${param.keyword}">
</li>
<li>
<input type="submit" value="찾기">
<input type="button" value="목록" onclick="location.href='list'">
</li>
</ul>
</form>
<div class="align-right">
<c:if test="${!empty user}">
<input type="button" value="글쓰기" onclick="location.href='write'">
</c:if>
</div>
<c:if test="${count == 0}">
<div class="result-display">표시할 게시물이 없습니다.</div>
</c:if>
<c:if test="${count > 0}">
<table class="striped-table">
<tr>
<th>번호</th>
<th width="400">제목</th>
<th>작성자</th>
<th>작성일</th>
<th>조회수</th>
<th>좋아요수</th>
</tr>
<c:forEach var="board" items="${list}">
<tr>
<td class="align-center">${board.board_num}</td>
<td><a href="detail?board_num=${board.board_num}">${board.title}(${board.re_cnt})</a></td>
<td class="align-center">
<c:if test="${empty board.nick_name}">${board.id}</c:if>
<c:if test="${!empty board.nick_name}">${board.nick_name}</c:if>
</td>
<td class="align-center">${board.reg_date}</td>
<td class="align-center">${board.hit}</td>
<td class="align-center">${board.fav_cnt}</td>
</tr>
</c:forEach>
</table>
<div class="align-center">${page}</div>
</c:if>
</div>
<!-- 내용 끝 -->
sql문 작성
BoardMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="kr.spring.board.dao.BoardMapper">
<!-- 게시판 글 등록 -->
<insert id="insertBoard" parameterType="boardVO">
INSERT INTO spboard(
board_num,
title,
content,
filename,
ip,
mem_num)
VALUES (
spboard_seq.nextval,
#{title},
#{content},
#{filename,jdbcType=VARCHAR},
#{ip},
#{mem_num})
</insert>
<!-- 검색 기능 -->
<!-- sql 태그와 include 태그를 이용해 SQL문 재사용 -->
<sql id="boardSearch">
<where>
<if test="keyword != null and keyword != ''">
<if test="keyfield == 1">
title LIKE '%' || #{keyword} || '%'
</if>
<if test="keyfield == 2">
id LIKE '%' || #{keyword} || '%' OR
nick_name LIKE '%' || #{keyword} || '%'
</if>
<if test="keyfield == 3">
content LIKE '%' || #{keyword} || '%'
</if>
<if test="keyfield == 4">
title LIKE '%' || #{keyword} || '%' OR
content LIKE '%' || #{keyword} || '%'
</if>
</if>
</where>
</sql>
<!-- 정렬 sql태그 -->
<sql id="boardOrder">
<if test="order == 1">
ORDER BY board_num DESC
</if>
<if test="order == 2">
ORDER BY hit DESC
</if>
<if test="order == 3">
ORDER BY fav_cnt DESC
</if>
<if test="order == 4">
ORDER BY re_cnt DESC
</if>
</sql>
<!-- 게시판 총 개수/검색 개수 -->
<select id="selectRowCount" parameterType="map" resultType="integer">
SELECT
COUNT(*)
FROM spboard JOIN spmember USING(mem_num)
<include refid="boardSearch"></include>
</select>
<!-- 게시판 전체 목록/검색 목록 -->
<select id="selectList" parameterType="map" resultType="boardVO">
SELECT
*
FROM (SELECT
a.*,
rownum rnum
FROM (SELECT
board_num,
<![CDATA[
REPLACE(REPLACE(title,'<','<'),'>','>') title,
]]>
hit,
reg_date,
mem_num,
id,
nick_name
FROM spboard JOIN spmember
USING(mem_num)
<include refid="boardSearch"></include>
<include refid="boardOrder"></include>)a)
<![CDATA[
WHERE rnum >= #{start} AND rnum <= #{end}
]]>
</select>
</mapper>
반복하고 싶은 부분을 sql태그로 만듦
부분적인 sql문을 만들고 아이디로 include (재사용)을 하는 방식
(sub_sql문과 유사)
=============================
정렬하는 ORDER BY가 있다면 정렬 전에 검색해야함
정렬 형태도 sql태그를 사용해서 selectList에 넣어줌
실행 후 검색해보면 검색기능 정상 작동
정렬 기능 넣기
BoardController
/*===========================
* 게시판 글 목록
* ==========================*/
@RequestMapping("/board/list")
public ModelAndView process(@RequestParam(value="pageNum",defaultValue="1") int currentPage,
@RequestParam(value="order",defaultValue="1") int order,
String keyfield, String keyword) {
Map<String,Object> map = new HashMap<String,Object>();
map.put("keyfield", keyfield);
map.put("keyword", keyword);
//전체/검색 레코드 수
int count = boardService.selectRowCount(map);
log.debug("<<count>> : " + count);
//페이지 처리
PageUtil page = new PageUtil(keyfield, keyword, currentPage, count, 20, 10, "list");
List<BoardVO> list = null;
if(count > 0) {
map.put("order", order);
map.put("start", page.getStartRow());
map.put("end", page.getEndRow());
list = boardService.selectList(map);
}
ModelAndView mav = new ModelAndView();
mav.setViewName("boardList");
mav.addObject("count", count);
mav.addObject("list", list);
mav.addObject("page", page.getPage());
return mav;
}
page말고 order도 정의해줘야함
order도 get방식으로 전달