Spring 자료실게시판 만들기 프로젝트
SpringFileBoard 생성
게시판을 만들기를 진행하면서 정리하고자 한다.
환경설정 및 글목록보기
페이징과 검색까지 포함
pom.xml 작성
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.board</groupId>
<artifactId>SpringFileBoard</artifactId>
<packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>SpringFileBoard Maven Webapp</name>
<url>http://maven.apache.org</url>
<repositories>
<repository>
<id>oracle</id>
<name>ORACLE JDBC Repository</name>
<url>http://maven.jahia.org/maven2</url>
</repository>
</repositories>
<dependencies>
<!-- Spring and Transactions -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>4.3.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.3.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.7.3</version>
</dependency>
<!-- 파일업로드 -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3</version>
</dependency>
<!-- -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>4.3.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.10.RELEASE</version>
</dependency>
<!-- 유효성검사(스프링) 자료실 글쓰기(입력필수 확인) -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.1.0.Final</version>
</dependency>
<!-- el문법오류발생 ${객체명.멤버변수} 인식X -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.3.10.RELEASE</version>
</dependency>
<!-- 커넥션풀 -->
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>20030825.184428</version>
</dependency>
<dependency>
<groupId>commons-pool</groupId>
<artifactId>commons-pool</artifactId>
<version>20030825.183949</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>20040616</version>
</dependency>
<!-- 로그객체를 이용해서 출력(내부과정처리,매개변수전달)-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>transactions</artifactId>
<version>3.9.0.M1</version>
</dependency>
<!-- orm(Mybatis) -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>4.3.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.2.1</version>
</dependency>
<dependency>
<groupId>org.apache.tiles</groupId>
<artifactId>tiles-extras</artifactId>
<version>3.0.1</version>
</dependency>
<dependency>
<groupId>org.apache.tiles</groupId>
<artifactId>tiles-servlet</artifactId>
<version>3.0.1</version>
</dependency>
<dependency>
<groupId>org.apache.tiles</groupId>
<artifactId>tiles-jsp</artifactId>
<version>3.0.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.5</version>
</dependency>
<!-- jsp(서블릿으로 변환)(servlet-api.jar) -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc6</artifactId>
<version>12.1.0.1</version>
</dependency>
<!-- -->
</dependencies>
<!-- -->
<build>
<finalName>SpringFileBoard</finalName>
</build>
</project>
오라클에 테이블생성
-db오라클에 태아불을 생성하고 넣어준다.
테이블을 생성
create table springboard2(
seq number primary key,
writer varchar2(50) not null,
title varchar2(100) not null,
content clob not null,
pwd varchar2(10) not null,
hit number(5) not null,
regdate date not null,
filename varchar2(100)
);
create sequence board_seq;
insert into springboard2 values(1,'테스트','최종프로젝트','마지막까지 열심히',
1234,0,sysdate,null);
commit;
select * from springboard2;
menu.jsp 받아오기
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%
//요청하는 프로젝트명
String contextPath = request.getContextPath();
%>
<ul>
<li><a href="<%=contextPath%>/board/list.do">목록</a></li>
<li><a href="<%=contextPath%>/board/write.do">글쓰기</a></li>
</ul>
index.jsp 수정
<%
response.sendRedirect(request.getContextPath()+"/board/list.do");
%>
web.xml 수정
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
<display-name>SpringMVC</display-name>
<!-- 외부의 DB에 관련된 환경설정파일을 불러오는 경우(관련클래스,매개변수)
다른경로에 DB연동파일을 설정하고 불러오는 방법
접두어(classpath):경로포함해서 불러올 환경설정파일명
-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:config/applicationContext.xml</param-value>
</context-param>
<!--위의 경로를 메모리에 올려주는 클래스 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- (1)웹상에서 요청(컨트롤러이름을 지정) -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<!--웹상에서 어떻게 요청을 할때 요청을 받아들일것인가에 대한 설정
(요청명령어 등록) *.do -->
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
<!-- 메인페이지 지정 -->
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
config폴더 가져오고 그 안에 있는 것들 확인 및 수정
=> src/main/java에 넣는다
applicationContext.xml 내용추가
~생략~
<!-- Mybatis빈을 등록(SqlSessionFactoryBean)
1)configLocation->전체 테이블에 대한 xml파일을 불러올때 사용(접속하자마자 가져올 테이블 정보)
2)dataSource->DB연결정보를 가진 멤버변수
-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="configLocation" value="classpath:config/SqlMapConfig.xml" />
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 4.SqlSessionTemplate(sqlSession객체를 더 쉽게 얻어오게 설정) -->
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory" />
</bean>
jdbc.properties 수정
jdbc.driverClassName=oracle.jdbc.driver.OracleDriver
jdbc.url=jdbc:oracle:thin:@localhost:1521:orcl
jdbc.username=scott
jdbc.password=tiger
SqlMapConfig.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "HTTP://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- DTO클래스의 별칭을 부여 -->
<typeAliases>
<typeAlias alias="BoardCommand"
type="com.board.domain.BoardCommand"/>
</typeAliases>
<mappers>
<mapper resource="com/board/dao/BoardMapper.xml"/>
</mappers>
</configuration>
BoardCommand.java생성 => DTO
package com.board.domain;
import java.sql.Date;//테이블의 필드로써 날짜자료형
//import java.util.Date; 보편적인 날짜
import org.springframework.web.multipart.MultipartFile;
//테이블의 필드와 연관이 있는 클래스(DTO or VO)
public class BoardCommand {
private int seq;
private String writer,title,content,pwd;//작성자,제목,내용,암호
private int hit;//조회수
private Date regdate;//작성날짜
//추가
private MultipartFile upload;//업로드할때 필요로하는 객체
private String filename;//업로드한 파일명
public int getSeq() {
return seq;
}
public void setSeq(int seq) {
this.seq = seq;
}
public String getWriter() {
return writer;
}
public void setWriter(String writer) {
this.writer = writer;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
public int getHit() {
return hit;
}
public void setHit(int hit) {
this.hit = hit;
}
public Date getRegdate() {
return regdate;
}
public void setRegdate(Date regdate) {
this.regdate = regdate;
}
public MultipartFile getUpload() {
return upload;
}
public void setUpload(MultipartFile upload) {
this.upload = upload;
}
public String getFilename() {
return filename;
}
public void setFilename(String filename) {
this.filename = filename;
}
}
BoardMapper.xml 생성
1)springboard2 테이블에서 SQL작업(1.자료실 글목록보기)
~생략~
<!-- 1.springboard2 테이블에서 SQL작업(1.자료실 글목록보기) -->
<select id="selectList" parameterType="map" resultType="BoardCommand">
select seq,writer,title,content,pwd,hit,regdate,filename
from (select a.*,rownum rnum
from (select * from springboard2
<where>
<if test="keyWord!=null and keyField=='title'">
title like '%' || #{keyWord} || '%'
</if>
<if test="keyWord!=null and keyField=='writer'">
writer like '%' || #{keyWord} || '%'
</if>
<if test="keyWord!=null and keyField=='content'">
content like '%' || #{keyWord} || '%'
</if>
<if test="keyWord!=null and keyField=='all'">
title like '%' || #{keyWord} || '%' or
writer like '%' || #{keyWord} || '%' or
content like '%' || #{keyWord} || '%'
</if>
</where>
order by seq desc) a)
<![CDATA[
where rnum >=#{start} AND rnum <=#{end}
]]>
</select>
2)검색어에 해당하는 총레코드수 구하기
~생략~
<!-- 2.검색어에 해당하는 총레코드수 구하기(java.lang.Ingeter->Ingeter->int) -->
<select id="selectCount" parameterType="map" resultType="Integer">
select count(*) from springboard2
<where>
<if test="keyWord!=null and keyField=='title'">
title like '%' || #{keyWord} || '%'
</if>
<if test="keyWord!=null and keyField=='writer'">
writer like '%' || #{keyWord} || '%'
</if>
<if test="keyWord!=null and keyField=='content'">
content like '%' || #{keyWord} || '%'
</if>
<if test="keyWord!=null and keyField=='all'">
title like '%' || #{keyWord} || '%' or
writer like '%' || #{keyWord} || '%' or
content like '%' || #{keyWord} || '%'
</if>
</where>
</select>
종합해여 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="Board">
<!-- 1.springboard2 테이블에서 SQL작업(1.자료실 글목록보기) -->
<select id="selectList" parameterType="map" resultType="BoardCommand">
select seq,writer,title,content,pwd,hit,regdate,filename
from (select a.*,rownum rnum
from (select * from springboard2
<where>
<if test="keyWord!=null and keyField=='title'">
title like '%' || #{keyWord} || '%'
</if>
<if test="keyWord!=null and keyField=='writer'">
writer like '%' || #{keyWord} || '%'
</if>
<if test="keyWord!=null and keyField=='content'">
content like '%' || #{keyWord} || '%'
</if>
<if test="keyWord!=null and keyField=='all'">
title like '%' || #{keyWord} || '%' or
writer like '%' || #{keyWord} || '%' or
content like '%' || #{keyWord} || '%'
</if>
</where>
order by seq desc) a)
<![CDATA[
where rnum >=#{start} AND rnum <=#{end}
]]>
</select>
<!-- 2.검색어에 해당하는 총레코드수 구하기(java.lang.Ingeter->Ingeter->int) -->
<select id="selectCount" parameterType="map" resultType="Integer">
select count(*) from springboard2
<where>
<if test="keyWord!=null and keyField=='title'">
title like '%' || #{keyWord} || '%'
</if>
<if test="keyWord!=null and keyField=='writer'">
writer like '%' || #{keyWord} || '%'
</if>
<if test="keyWord!=null and keyField=='content'">
content like '%' || #{keyWord} || '%'
</if>
<if test="keyWord!=null and keyField=='all'">
title like '%' || #{keyWord} || '%' or
writer like '%' || #{keyWord} || '%' or
content like '%' || #{keyWord} || '%'
</if>
</where>
</select>
</mapper>
BoardDao.java 생성 => BoardMapper와 연결되는 dao인터페이스다.
package com.board.dao;
//List(레코드 여러개 담을 객체),Map(검색분야,검색어)
import java.util.List;
import java.util.Map;
import com.board.domain.BoardCommand;
public interface BoardDao {
//1.자료실의 글목록보기
public List<BoardCommand> list(Map<String,Object>map);
//2.총레코드수(검색어에 맞는 레코드수까지 포함)
public int getRowCount(Map<String,Object>map);
}
BoardDaoImp.java 생성
package com.board.dao;
import java.util.List;
import java.util.Map;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import com.board.domain.BoardCommand;
//SqlSessionDaoSupport 상속받는 이유->SqlSession객체(getSqlSession())
public class BoardDaoImp extends SqlSessionDaoSupport implements BoardDao {
//검색분야에 따른 검색어까지 조회(페이징 처리)
public List<BoardCommand> list(Map<String, Object> map) {
// TODO Auto-generated method stub
List<BoardCommand> list=getSqlSession().selectList("selectList", map);
return list;
}
public int getRowCount(Map<String, Object> map) {
// TODO Auto-generated method stub
return getSqlSession().selectOne("selectCount", map);
}
}
dispatcher-servlet.xml 내용추가
~생략~
<!-- 이걸 만들어야 메서드호출 -->
<bean class="com.board.dao.BoardDaoImp" />
<bean class="com.board.controller.ListController" />
~생략~
<!-- 예외페이지 작성(개발자가 따로 에러를 처리해주는 페이지)
SimpleMappingExceptionResolve 등록
<props>
<prop key="발생한 예외처리클래스명">예외처리페이지명</prop>
</props>
-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="java.lang.Exception">pageError</prop>
</props>
</property>
</bean>
PagingUtil.java, StringUtil.java를 가져와서 com.board.util 패키지를 생성한 후 넣는다
=> 만들어져있는거 가져와서 넣으려한다
StringUtil.java
package com.board.util;
public class StringUtil {
//글내용을 불러올때 사용->한줄이상->enter(<br>)
//<pre></pre> 요즘은 이걸 사용하여 replace를 잘안씀 pre태그는 자동줄바꿈
public static String parseBr(String msg){
if(msg == null) return null;
//replace(변경전단어,변경후단어)
return msg.replace("\r\n", "<br>")
.replace("\n", "<br>");
}
}
---------------------------------------------------------------------
PagingUtil.java
package com.board.util;
//페이징 처리해주는 클래스
public class PagingUtil {
private int startCount; // 한 페이지에서 보여줄 게시글의 시작 번호
private int endCount; // 한 페이지에서 보여줄 게시글의 끝 번호
private StringBuffer pagingHtml;// 페이징 생성자(링크문자열)
/**
* currentPage : 현재페이지
* totalCount : 전체 게시물 수
* blockCount : 한 페이지의 게시물의 수
* blockPage : 한 화면에 보여줄 페이지 수
* pageUrl : 호출 페이지 url
* addKey : 부가적인 key 없을 때는 null 처리 (&num=23형식으로 전달할 것)
* */
public PagingUtil(int currentPage, int totalCount, int blockCount,
int blockPage, String pageUrl) {
this(null,null,currentPage,totalCount,blockCount,blockPage,pageUrl,null);
}
public PagingUtil(int currentPage, int totalCount, int blockCount,
int blockPage, String pageUrl, String addKey) {
this(null,null,currentPage,totalCount,blockCount,blockPage,pageUrl,addKey);
}
public PagingUtil(String keyField, String keyWord, int currentPage, int totalCount, int blockCount,
int blockPage,String pageUrl) {
this(null,null,currentPage,totalCount,blockCount,blockPage,pageUrl,null);
}
public PagingUtil(String keyField, String keyWord, int currentPage, int totalCount, int blockCount,
int blockPage,String pageUrl,String addKey) {
if(addKey == null) addKey = ""; //부가키가 null 일때 ""처리
// 전체 페이지 수 ex)122/10=12.2=13.2=13
int totalPage = (int) Math.ceil((double) totalCount / blockCount);
if (totalPage == 0) {
totalPage = 1;
}
// 현재 페이지가 전체 페이지 수보다 크면 전체 페이지 수로 설정
if (currentPage > totalPage) {
currentPage = totalPage;
}
// 현재 페이지의 처음과 마지막 글의 번호 가져오기.
startCount = (currentPage - 1) * blockCount + 1;
endCount = currentPage * blockCount;
// 시작 페이지와 마지막 페이지 값 구하기.
int startPage = (int) ((currentPage - 1) / blockPage) * blockPage + 1;
int endPage = startPage + blockPage - 1;
// 마지막 페이지가 전체 페이지 수보다 크면 전체 페이지 수로 설정
if (endPage > totalPage) {
endPage = totalPage;
}
// 이전 block 페이지
pagingHtml = new StringBuffer();
if (currentPage > blockPage) {
if(keyWord==null){//검색 미사용시
pagingHtml.append("<a href="+pageUrl+"?pageNum="+ (startPage - 1) + addKey +">");
}else{
pagingHtml.append("<a href="+pageUrl+"?keyField="+keyField+"&keyWord="+keyWord+"&pageNum="+ (startPage - 1) + addKey +">");
}
pagingHtml.append("이전");
pagingHtml.append("</a>");
}
pagingHtml.append(" | ");
//페이지 번호.현재 페이지는 빨간색으로 강조하고 링크를 제거.
for (int i = startPage; i <= endPage; i++) {
if (i > totalPage) {
break;
}
if (i == currentPage) {
pagingHtml.append(" <b> <font color='red'>");
pagingHtml.append(i);
pagingHtml.append("</font></b>");
} else {
if(keyWord==null){//검색 미사용시
pagingHtml.append(" <a href='"+pageUrl+"?pageNum=");
}else{
pagingHtml.append(" <a href='"+pageUrl+"?keyField="+keyField+"&keyWord="+keyWord+"&pageNum=");
}
pagingHtml.append(i);
pagingHtml.append(addKey+"'>");
pagingHtml.append(i);
pagingHtml.append("</a>");
}
pagingHtml.append(" ");
}
pagingHtml.append(" | ");
// 다음 block 페이지
if (totalPage - startPage >= blockPage) {
if(keyWord==null){//검색 미사용시
pagingHtml.append("<a href="+pageUrl+"?pageNum="+ (endPage + 1) + addKey +">");
}else{
pagingHtml.append("<a href="+pageUrl+"?keyField="+keyField+"&keyWord="+keyWord+"&pageNum="+ (endPage + 1) + addKey +">");
}
pagingHtml.append("다음");
pagingHtml.append("</a>");
}
}
public StringBuffer getPagingHtml() {
return pagingHtml;
}
public int getStartCount() {
return startCount;
}
public int getEndCount() {
return endCount;
}
}
tilesdef.xml 내용추가
=> css와 js도 지정해줄수있다
<?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" template="/WEB-INF/tiles-view/template/layout.jsp">
<put-attribute name="title" value="메인 페이지"/>
<put-attribute name="css" value="/WEB-INF/tiles-view/script/boardDefaultCss.jsp"/>
<put-attribute name="js" value="/WEB-INF/tiles-view/script/boardSearchJs.jsp"/>
<put-attribute name="menu" value="/WEB-INF/tiles-view/template/menu.jsp"/>
<put-attribute name="header" value="/WEB-INF/tiles-view/template/header.jsp"/>
<put-attribute name="body" value="/WEB-INF/tiles-view/boardList.jsp"/>
<put-attribute name="footer" value="/WEB-INF/tiles-view/template/footer.jsp"/>
</definition>
<!-- error 페이지 처리 -->
<definition name="pageError" extends="boardList">
<put-attribute name="title" value="에러 페이지"/>
<put-attribute name="js" value="" />
<put-attribute name="body" value="/WEB-INF/tiles-view/pageError.jsp"/>
</definition>
</tiles-definitions>
layout.jsp 내용추가
=> <tiles:~ 태그를 사용하여 불러올 속성값을 넣어준다
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="tiles" uri="http://tiles.apache.org/tags-tiles"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title><tiles:getAsString name="title" /></title>
<tiles:insertAttribute name="css" />
<tiles:insertAttribute name="js" />
</head>
<body>
<table border="0" cellpadding="0" cellspacing="1" bgcolor="#a0a0a0" width="100%">
<tr height="100" valign="middle" bgcolor="#ffffff">
<td colspan="2"><tiles:insertAttribute name="header" /></td>
</tr>
<tr height="600" bgcolor="#ffffff">
<td width="15%" valign="top"><tiles:insertAttribute name="menu" /></td>
<td width="85%" valign="top"><tiles:insertAttribute name="body" /></td>
</tr>
<tr bgcolor="#ffffff">
<td colspan="2"><tiles:insertAttribute name="footer" /></td>
</tr>
</table>
</body>
</html>
ListController.java (컨트롤러) 생성
package com.board.controller;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.log4j.Logger;//로그객체를 불러오겠다는 선언문
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 com.board.dao.BoardDao;
import com.board.domain.BoardCommand;
import com.board.util.PagingUtil;
@Controller
public class ListController {
//로그객체 생성문(로그를 처리할 클래스명.class)
//private Logger log=Logger.getLogger(ListController.class);
private Logger log=Logger.getLogger(getClass());//현재클래스를 자동으로 불러와서 넣어줌
@Autowired
private BoardDao boardDao; //Setter Method을 작성X
@RequestMapping("/board/list.do") // 요청을 받아서 처리해주는 어노테이션 => 페이지번호, 검색분야, 검색어를 요청받아 불러옴
public ModelAndView process( // value가 없으면 디폴트로 "1"을 준다, 정상적으로 받으면 currentPage로 반환받는다
@RequestParam(value="pageNum",defaultValue="1") int currentPage,//페이지번호
@RequestParam(value="keyField" ,defaultValue="") String keyField,//검색분야
@RequestParam(value="keyWord" ,defaultValue="") String keyWord//검색어
) {
if(log.isDebugEnabled()) {//로그객체가 디버깅모드상태라면
System.out.println("/board/list.do 요청중");//?을 출력X
log.debug("currentPage:"+currentPage);//? 출력 O
log.debug("keyField:"+keyField);
log.debug("keyWord:"+keyWord);
}
//검색분야,검색어->parameterType="map" 2.Map객체
Map<String,Object> map=new HashMap<String,Object>();
map.put("keyField",keyField);//<->map.get("keyField")
map.put("keyWord",keyWord);
//총레코드수 또는 검색된 글의 총레코드수
int count=boardDao.getRowCount(map);
System.out.println("ListController클래스의 count="+count);
//페이징 처리(1.현재페이지 2.총레코드수 3.페이지당 게시물수 4.블럭당 페이지수 5.요청명령어)
PagingUtil page=new PagingUtil(currentPage,count,3,3,"list.do");
//start=>페이지당 맨 첫번째 나오는 게시물번호, end=>마지막 게시물번호
map.put("start", page.getStartCount());
//<->map.get("start")=>#{start}
map.put("end", page.getEndCount());
List<BoardCommand> list=null;
if(count > 0) {
list=boardDao.list(map);//keyField,keyWord,start,end
}else {
list=Collections.EMPTY_LIST;//0 적용 =>아무것도 들어있지않다
}
ModelAndView mav=new ModelAndView("boardList");
mav.addObject("count",count);//총레코드수 ${count}
mav.addObject("list",list); //${list}
mav.addObject("pagingHtml",page.getPagingHtml());
// <a href="~>이전</a>~ ${pagingHtml}
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"%>
<form action="list.do" name="search" method="get" onsubmit="return searchCheck()">
<table align="center" width="200" border="0" cellspacing="0" cellpagging="2">
<tr>
<td align="center">
<select name="keyField">
<option value="title">제목</option>
<option value="writer">이름</option>
<option value="content">내용</option>
<option value="all">전체</option>
</select>
</td>
<td>
<input type="text" size="16" name="keyWord">
</td>
<td>
<input type="submit" value="찾기">
</td>
</tr>
</table>
</form>
<table width="100%" border="0" cellspacing="0" cellpadding="2">
<tr>
<td align="right" colspan="5" >
<input type = "button" value="글쓰기" onclick="location.href='write.do'">
</td>
</tr>
<tr bgcolor="#F3F3F3">
<td width="50">번호</td>
<td>제목</td>
<td width="70">글쓴이</td>
<td width="100">날짜</td>
<td width="70">조회수</td>
</tr>
<!-- 레코드가 없다면 -->
<c:if test="${count==0}">
<tr>
<td colspan="5" align="center">
등록된 게시물이 없습니다.
</td>
</tr>
</c:if>
<c:forEach var="article" items="${list}">
<tr>
<td align="center">${article.seq}</td>
<td><a href="detail.do?seq=${article.seq}">${article.title}</a></td>
<td>${article.writer}</td>
<td>${article.regdate}</td>
<td>${article.hit}</td>
</tr>
</c:forEach>
<tr>
<td align="center" colspan="5">${pagingHtml }</td>
</tr>
</table>
index.jsp로 실행해보고 잘 나오면 이어서 글쓰기를 해보려고한다
2022-09-01
자료실 글쓰기 및 업로드
게시판의 글쓰기 부분을 spring으로 하고자 하는데 기존과는 다르게 파일 첨부도 하고자 한다.
messages 패키지생성 후 label.properties, validation.properties 가져와서 넣기
=> 필수입력을 위한 자료
=> 사실 JQuery를 사용하는것이 더 좋은데 spring 정리니깐 감안하고 해보려고 한다.
write.form.title=\uc81c\ubaa9
write.form.writer=\uc791\uc131\uc790
write.form.pwd=\ube44\ubc00\ubc88\ud638
write.form.content=\ub0b4\uc6a9
write.form.upload=\ud30c\uc77c\uc5c5\ub85c\ub4dc
write.form.submit=\uc804\uc1a1
list.content.title=\ubaa9\ub85d
invalidPassword=\ube44\ubc00\ubc88\ud638\uac00 \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4.
required=\ud544\uc218 \ud56d\ubaa9 \uc785\ub2c8\ub2e4.
required.pwd=\ube44\ubc00\ubc88\ud638\ub294 \ud544\uc218 \ud56d\ubaa9 \uc785\ub2c8\ub2e4.
required.writer=\uc774\ub984\uc740 \ud544\uc218 \ud56d\ubaa9 \uc785\ub2c8\ub2e4.
required.title=\uc81c\ubaa9\uc740 \ud544\uc218 \ud56d\ubaa9 \uc785\ub2c8\ub2e4.
required.content=\ub0b4\uc6a9\uc740 \ud544\uc218 \ud56d\ubaa9 \uc785\ub2c8\ub2e4.
dispatcher-servlet.xml 내용추가
리소그 번들 설정부분 내용추가 (다국어 처리)
=> 웹상에 공통으로 출력할 문자열이나 에러메세지를 등록해서 필요로했을때 출력시켜주는 파일(다국어 처리)
파일업로드부분 내용 추가
~생략~
<!-- 리소스 번들 설정 -> 웹상에 공통으로 출력할 문자열이나 에러메세지를 등록해서 필요로했을때 출력시켜주는 파일(다국어 처리)
messages.label->패키지명.불러올파일명.properties(확장자생략)
messages.validation->validation.properties파일 불러오기
-->
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basenames">
<list>
<value>messages.label</value>
<value>messages.validation</value>
</list>
</property>
<!-- <property name="defaultEncoding" value="utf-8" /> -->
</bean>
~생략~
<!-- 파일업로드 52428800->byte단위
50MB->51,200kb->52,428,800byte
CommonsMultipartResolver->업로드 관여하는 클래스 빈즈로등록
maxUploadSize(최대업로드 크기를 지정)
defaultEncoding(한글처리 부분설정)
-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="52428800" />
<property name="defaultEncoding" value="utf-8" />
</bean>
FileUtil.java 생성
=> 파일업로드시 업로드할 경로지정 및 파일의 새이름을 부여
하는이유: 업로드 위치->파일명이 노출->파일이름을 임의의 파일명으로 변경(보안)
package com.board.util;//util => 프로젝트에서 공통으로 사용되는 기능만 모아놓은 클래스(=공통솬심 클래스) AOP
import java.io.File;
//파일업로드시 업로드할 경로지정 및 파일의 새이름을 부여(공통모듈)
public class FileUtil {
//업로드 및 다운로드 경로->정적상수
public static final String UPLOAD_PATH="C:\\webtest\\4.jsp\\sou\\SpringFileBoard\\src\\main\\webapp\\upload";
//1.탐색기의 원본파일명만 받아서 처리해주는 메서드
public static String rename(String filename) throws Exception{
if(filename==null) return null;//업로드 X ->이름변경X
//새이름 변경=>시스템날짜+랜덤숫자(0~49)->조합
String newName=Long.toString(System.currentTimeMillis()+(int)(Math.random()*50));
System.out.println("newName(난수)=>"+newName);
return rename(filename,newName);
}
//2.실제로 새로운 파일명을 변경->확장자구분=>변경된 이름만 구함
public static String rename(String filename,String newName) throws Exception{
if(filename==null) return null;
//확장자 구하기(ex) testdddd.txt 뒤에서부터 찾는게 좋음 => lastIndexOf
int idx=filename.lastIndexOf(".");//못찾으면 -1을 리턴
String extention="";//확장자 저장
String newFileName="";//새파일명을 저장
if(idx!=-1) {//찾았다면
extention=filename.substring(idx);//idx이후 파일 끝까지 substring=>
System.out.println("extention=>"+extention);
}
//새파일명
int newIdx=newName.lastIndexOf(".");//확장자를 포함한 변견된 파일명
if(newIdx!=-1) {
//0~newIdx바로 앞번호까지 구함(파일명)
newName=newName.substring(0,newIdx);
System.out.println("newName(변경파일)=>"+newName);
}
//확장자(대)->소문자로 변환해서 붙여라
newFileName=newName+extention.toLowerCase();
return newFileName;
}
//3.글수정->업로드된 파일도 수정->기존 업로드된 파일삭제
//파일삭제->수정목적
public static void removeFile(String filename) {
File file=new File(UPLOAD_PATH,filename);//1.경로,2.파일명
if(file.exists()) file.delete();// 이 경로에 파일이 존재하면 삭제
}
}
BoardDao.java (인터페이스) 내용추가
~생략~
//3.글쓰기(최대글번호)
public int getNewSeq();
//4.자료실의 글쓰기
public void insert(BoardCommand board);
BoardDaoImp 내용추가
~생략~
public int getNewSeq() {
// TODO Auto-generated method stub
int newseq=(Integer)getSqlSession().selectOne("getNewSeq");
System.out.println("getNewSeq의 new seq=>"+newseq);
return newseq;
}
public void insert(BoardCommand board) {
// TODO Auto-generated method stub
getSqlSession().insert("insertBoard",board);
}
BoardMapper.xml 내용추가 (sql구문)
<!-- 3.최대값 구하기 -->
<select id="getNewSeq" resultType="int">
select max(seq) from springboard2
</select>
<!-- 4.자료실의 글쓰기
업로드하는 파일명만,jdbcType=VARCHAR(문자열) 추가
-->
<insert id="insertBoard" parameterType="BoardCommand">
insert into springboard2(seq,writer,title,content,pwd,hit,regdate,filename)
values(#{seq},#{writer},#{title},#{content},#{pwd},0,sysdate,#{filename,jdbcType=VARCHAR})
</insert>
tilesdef.xml에 내용추가 =>boardWrite.jsp화면에 출력
~생략~
<!--글쓰기-->
<definition name="boardWrite" extends="boardList">
<put-attribute name="title" value="글쓰기"/>
<put-attribute name="js" value="" />
<put-attribute name="body" value="/WEB-INF/tiles-view/boardWrite.jsp"/>
</definition>
~생략~
유효성검사
BoardValidator.java생성 (Validator인터페이스를 상속)
package com.board.validator;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import com.board.domain.BoardCommand;
//유효성검사를 하는 방법=>Validator인터페이스를 상속
public class BoardValidator implements Validator {
//1.유효성검사를 할 클래스명을 지정해주는 메서드
public boolean supports(Class<?> clazz) {
// TODO Auto-generated method stub
//형식) return DTO클래스명.class.isAssignableFrom(clazz)
return BoardCommand.class.isAssignableFrom(clazz);
}
//2.유효성검사를 해주는 메서드(1.입력받을 대상자(DTO객체),2.에러정보를 저장할 에러객체명
public void validate(Object target, Errors errors) {
// TODO Auto-generated method stub
//입력하지 않았거나 공백을 체크해주는 메서드->에러정보를 저장(에러객체)
//1.에러객체명 2.적용시킬 필드명 3.적용시킬 에러코드명=>required.pwd => validation.properties에서 꺼내온다
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "pwd", "required");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "writer", "required");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "title", "required");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "content", "required");
}
}
WriteController를 작성 => 엄청 복잡함 코드에 주석들 잘 읽어보기
package com.board.controller;
import java.io.File;
import java.io.IOException;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.board.dao.BoardDao;
import com.board.domain.BoardCommand;
import com.board.util.FileUtil;
import com.board.validator.BoardValidator;
@Controller
public class WriteController {
//로그객체생성문
private Logger log=Logger.getLogger(getClass());
@Autowired
private BoardDao boardDao;
/*
* 하나의 요청명령어->하나의 컨트롤러만 사용X
* 하나의 컨트롤러->여러개의 요청명령어를 등록해서 사용이 가능 O
* 같은 요청명령어를 GET or POST방식으로 전송할지 결정하는 속성
* writeForm.do write.do
*
* write.do(페이지이동) write.do
* method=RequestMethod.GET or POST
*/
//1.글쓰기폼으로 이동
@RequestMapping(value="/board/write.do", method=RequestMethod.GET)
public String form() {
System.out.println("다시 처음부터 값을 입력받기위해서 form()호출");
return "boardWrite";//return "이동할 페이지명"
}
//2.에러메세지 출력->다시 입력을 받을수 있도록 초기화
@ModelAttribute("command")
public BoardCommand forBacking() {
//반환형(DTO형 or VO형) 임의의 메서드명
System.out.println("forBacking() 호출됨");
return new BoardCommand();//return new UserVO();
}
//3.입력해서 유효성 검사->에러발생
//BindingResult->유효성 검사때문에 필요=>에러정보객체를 저장
@RequestMapping(value="/board/write.do", method=RequestMethod.POST)
public String submit(@ModelAttribute("command") BoardCommand com, BindingResult result) {
if(log.isDebugEnabled()) {
System.out.println("/board/write.do 요청중(post)");
log.debug("BoardCommand:"+com);//com.toString()
}
//유효성검사->validate()호출
new BoardValidator().validate(com, result);
//에러정보가 있으면
if(result.hasErrors()) {
return form();//"boardWrite";->boardWrite.jsp로 가라
}
//글쓰기 및 업로드=>입출력=>예외처리
try {
String newName="";//업로드한 파일의 변경된 파일명을 저장
//업로드되어있다면
if(!com.getUpload().isEmpty()) {
//탐색기에서 선택한파일->aaa.txt->12344556.txt로 파일이름 변경
newName=FileUtil.rename(com.getUpload().getOriginalFilename());
System.out.println("newName=>"+newName);
//DTO에 변경=>테이블에서도 변경해서 저장
com.setFilename(newName);
}
//최대값+1
int newSeq=boardDao.getNewSeq()+1;
System.out.println("newSeq=>"+newSeq);
//자료실번호=>계산해서 저장
com.setSeq(newSeq);
//자료실 글쓰기호출
boardDao.insert(com);//DB상에 반영
if(!com.getUpload().isEmpty()) {
File file=new File(FileUtil.UPLOAD_PATH+"\\"+newName);
//물리적으로 데이터전송(파일전송)
com.getUpload().transferTo(file);
}
}catch(IOException e) {
e.printStackTrace();
}catch(Exception e2) {
e2.printStackTrace();
}
//return "redirect:/요청명령어" -> return "이동할페이지"
return "redirect:/board/list.do";
}
}
BoardCommand.java (DTO)에 내용추가
- 오버라이드 추가
~생략~
@Override
public String toString() {
// TODO Auto-generated method stub
return "BoardCommand[seq="+seq+",writer="+writer+",title="
+title+",content="+content+",pwd="+pwd+",hit="
+hit+",regdate="+regdate+",upload="+upload
+",filename="+filename+"]";
BoardWrite.jsp 가져와서 내용추가
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<!-- enctype->파일 첨부를 하기위해서 필요
spring:message 액션태그->code="불러올 키값"
form:errors 액션태그=>path="적용필드명(DTO중 입력받은 필드명)"
spring:hasBindErrors name="커맨드객체별칭" ->에러발생시 처리
form:errors path="커맨드객체별칭"-> 폼안에서의 입력받을때 에러처리 지정
-->
<spring:hasBindErrors name="command" />
<form:errors path="command" />
<form action="write.do" enctype="multipart/form-data" method="post">
<spring:message code="write.form.title"/>
<input type="text" name="title" value="${command.title}">
<form:errors path="command.title"/><br>
<spring:message code="write.form.writer" />
<input type="text" name="writer" value="${command.writer }">
<form:errors path="command.writer"/><br>
<spring:message code="write.form.pwd" />
<input type="password" name="pwd">
<form:errors path="command.pwd"/><br>
<spring:message code="write.form.content" />
<textarea rows="10" cols="50" name="content"></textarea>
<form:errors path="command.content"/><br>
<spring:message code="write.form.upload"/>
<input type="file" name="upload">
<p>
<input type="submit" value="<spring:message code="write.form.submit" />">
<input type="button" value="<spring:message code="list.content.title" />"
>
</form>
index.jsp에서 실행해보고 글써본뒤 잘써지면 글쓰기는 끝
2022-09-02
글 상세보기 및 다운로드
게시물 상세보기 및 게시된 파일 다운로드를 하고자 한다.
글 상세보기
dispatcher-servlet.xml에 내용추가 등록
~생략~
<!-- 이걸 만들어야 메서드호출 -->
<bean class="com.board.dao.BoardDaoImp" />
<bean class="com.board.controller.ListController" />
<bean class="com.board.controller.WriteController" />
새로 추가
<bean class="com.board.controller.DetailController" />
~생략~
<!-- 파일 다운로드뷰를 위한 전용 viewResolver를 빈즈로 등록
동일한 기능이 2개존재하면 반드시 우선순위를 정해준다. order(멤버변수)
-->
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver" p:order="0" />
~생략~
BoardMapper.xml sql내용추가
=> 조회수증가, 상세보기 부분 sql구문을 추가해준다
~생략~
<!-- 5.조회수 증가 --> <!-- java.lang.Integer, Integer, int 셋중 하나 사용 -->
<update id="updateHit" parameterType="Integer">
update springboard2 set hit=hit+1 where seq=#{seq}
</update>
<!-- 6.자료실번호에 따른 레코드 한개 상세보기 -->
<select id="selectBoard" parameterType="Integer" resultType="BoardCommand">
select * from springboard2 where seq=#{seq}
</select>
BoardDao.java (인터페이스) 2개의 메서드 선언
~생략~
//5.글상세보기
public BoardCommand selectBoard(Integer seq);//~(int seq)
//6.자료실번호에 해당하는 조회수증가
public void updateHit(Integer seq);
BoardDaoImpl -> 2개의 메서드를 호출 코딩 추가
~생략~
//DetailController에서 호출
public BoardCommand selectBoard(Integer seq) {
// TODO Auto-generated method stub
return (BoardCommand)getSqlSession().selectOne("selectBoard", seq);
}
//조회수 증가
public void updateHit(Integer seq) {
// TODO Auto-generated method stub
getSqlSession().update("updateHit",seq);
}
DetailController.java 생성
=>위에 2개 메서드 호출(글상세보기, 조회수증가)
package com.board.controller;
import java.io.File;
import org.apache.log4j.Logger;//로그객체에 관련된 클래스 불러오기
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 com.board.dao.BoardDao;
import com.board.domain.BoardCommand;
import com.board.util.FileUtil;
import com.board.util.StringUtil;
//글상세보기
//글상세보기,글수정하기,글삭제,글조회하기
@Controller
public class DetailController {
private Logger log=Logger.getLogger(getClass());
//@Inject와 같다
@Autowired
private BoardDao boardDao;//결합도를 낮추기위해서 인터페이스로 상속 (byType)
// /board/detail.do?seq=4->boardView.jsp
//@RequestParam("searchName") String searchName =>검색일때
//@RequestParam("searchValue") String searchValue
@RequestMapping("/board/detail.do")
public ModelAndView process(@RequestParam("seq") int seq) {
//=> int seq=Integer.parseInt(request.getParameter("seq"));
if(log.isDebugEnabled()) {//로그객체가 작동중이라면(디버깅상태)
log.debug("seq->"+seq);
}
//1.조회수 증가
boardDao.updateHit(seq);
//2.출력레코드
BoardCommand board=boardDao.selectBoard(seq);
//3.글내용-> \r\n aaaa \r\n-> <br> <pre></pre>
board.setContent(StringUtil.parseBr(board.getContent())); //지금은 사용X 옛날방식
/*(1)
ModelAndView mav=new ModelAndView("boardView");
mav.addObject("board",board);//${board}
return mav;*/
//1.이동할 페이지명 2.전달할키명 3.전달할값 => 위에꺼를 이렇게 할수도있다
return new ModelAndView("boardView","board",board);
}
}
tilesdef.xml 내용추가
~생략~
<!-- 상세 페이지 -->
<definition name="boardView" extends="boardList">
<put-attribute name="title" value="글상세보기"/>
<put-attribute name="js" value="" />
<put-attribute name="body" value="/WEB-INF/tiles-view/boardView.jsp"/>
</definition>
boardView.jsp 가져와서 작성
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<table width="600" border="0" cellspacing="0" cellpagging="0" align="center">
<tr>
<td colspan="2"><h3>스프링 자료실 </h3></td>
</tr>
<tr>
<td>번호</td>
<td>${board.seq}</td>
</tr>
<tr>
<td>글쓴이</td>
<td>${board.writer}</td>
</tr>
<tr>
<td>제목</td>
<td>${board.title}</td>
</tr>
<tr>
<td colspan="2">
<hr size="1" width="100%" noshade>
</td>
</tr>
<tr>
<td>내용</td>
<td>${board.content}</td>
</tr>
<tr>
<td colspan="2">
<hr size="1" width="100%" noshade>
</td>
</tr>
<tr>
<td>조회수</td>
<td>${board.hit}</td>
</tr>
<tr>
<td>등록날짜</td>
<td>${board.regdate}</td>
</tr>
<tr>
<td>첨부파일</td>
<td><a href="file.do?filename=${board.filename}">${board.filename }</a></td>
</tr>
<tr>
<td align="right" colspan="2">
<input type="button" value="수정"
onclick="location.href='update.do?seq=${board.seq}'">
<input type="button" value="삭제"
onclick="location.href='delete.do?seq=${board.seq}'">
<input type="button" value="목록" onclick="location.href='list.do'">
</td>
</tr>
</table>
index.jsp에서 실행해보고 잘되면 글 상세보기 완료
다운로드
DetailController.java에 다운로드를 추가로 작성
~생략~
//글상세보기와 연관(다운로드)
@RequestMapping("/board/file.do")
public ModelAndView download(@RequestParam("filename") String filename) {
//1.다운로드 받을 파일의 위치와 이름을 알아야 된다.
File downloadFile=new File(FileUtil.UPLOAD_PATH+"\\"+filename);
//2.스프링에서 다운로드 받는 뷰를 작성->AbstractView를 상속 O
// 1.이동할 페이지명(X) 2.전달할키명 3.전달할값 (X)
// 1.다운로드 받을 뷰객체 2.모델객체명(키명) 3.전달할값(다운로드받을 파일)
//DownloadView객체의 id명
return new ModelAndView("downloadView","downloadFile",downloadFile);
}
DownloadView.java 생성 (AbstractView 클래스를 상속받는다)
package com.board.view;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.servlet.view.AbstractView;
//다운로드를 따로 처리해줄 수 있는 전용뷰클래스 작성하기위해 AbstractView상속
public class DownloadView extends AbstractView {
//contentType="application/down"
public DownloadView() {
//다운로드 받는 화면으로 자동으로 전환
setContentType("application.download");//text/html대신
}
/*
return new ModelAndView("downloadView","downloadFile",downloadFile);*/
//모델값(화면에 출력할 대상자)을 매개변수로 전달받아서 처리해주는 메서드
@Override
protected void renderMergedOutputModel
(Map<String, Object> model,
HttpServletRequest request,
HttpServletResponse response) throws Exception {
// TODO Auto-generated method stub
//1.다운로드 받을 파일의 정보 얻어오기
File file=(File)model.get("downloadFile");//Object
System.out.println("file=>"+file);
//2.다운받을 파일의 타입,크기를 지정
response.setContentType(getContentType());//"application/download"
response.setContentLength((int)file.length());//다운로드받을 파일의 길이지정
//브라우저별로 한글처리(서버의 로그객체)분석
//User-Agent(브라우저 정보가 저장된 매개변수명(head)값)
String userAgent=request.getHeader("User-Agent");
System.out.println("userAgent=>"+userAgent);//MSIE => 인터넷익스플로어라면
boolean ie=userAgent.indexOf("MSIE") > -1;//못찾으면 -1
String fileName=null;
if(ie) {//파일명(한글)->utf-8로 한글처리 O
fileName=URLEncoder.encode(file.getName(),"utf-8");
}else {// 영문
fileName=new String(file.getName().getBytes("utf-8"),"iso8859-1");//iso8859-1은 영문
}
//대화상자에서 원하는 다운로드 대화상자에서 지정(위치,파일명)
//Content-Disposition=>다운로드 받는 위치
//attachment;filename=다운로드 받을 파일명
//exe,bat=>이잔파일도 다운=>Content-Transfer-Encoding을 binary
response.setHeader("Content-Disposition","attachment;filename=\""+fileName+"\";");
response.setHeader("Content-Transfer-Encoding", "binary");
//입출력객체를 만들어서 전송
OutputStream out=response.getOutputStream();//서버
FileInputStream fis=null;//pc컴퓨터
try {
fis=new FileInputStream(file);//다운로드 받을 파일정보
//서버로부터 파일을 읽어들여서 다운로드 받음->복사(스프링에서 제공)
FileCopyUtils.copy(fis, out);//1.다운로드 받는쪽 입력객체명 2.서버의 출력객체명
}catch(IOException e) {
e.printStackTrace();
}finally {//예외처리와 상관없이 항상 처리해야할 구문->메모리 해제
if(fis!=null)
try {fis.close();}catch(IOException e) {}
}
out.flush();//입출력->양이 될때까지 그대로 버퍼에 보관X
//실시간으로 바로전송
}
}
dispatcher-sevlet.xml 빈즈추가
~생략~
<!-- downloadView id값을 가진 빈즈클래스를 등록 -->
<bean id="downloadView" class="com.board.view.DownloadView" />
~생략~
index.jsp로 실행후 첨부파일 올려져있는거 다운이되면 성공
로그객체
환경설정
pom.xml 로그객체 추가
<!-- 로그객체를 이용해서 출력(내부과정처리,매개변수전달)-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
log4j.xml 가져오기
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j='http://jakarta.apache.org/log4j/'>
<!-- 내부처리과정,에러메세지를 출력할때 출력양식을 지정 -->
<appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="[%p:%C{1}.%M()] %m%n" />
</layout>
</appender>
<!-- 컨트롤러뿐만아니라 모든 클래스의 에러메세지,디버깅을 알고싶다.
com.board이하의 모든 로그객체 반영클래스에서 내부처리 확인가능
-->
<category name="com.board" additivity="false">
<priority value="debug" />
<appender-ref ref="STDOUT" />
</category>
<!-- 에러발생했을때 처리 -->
<root>
<priority value="error" />
<appender-ref ref="STDOUT" />
</root>
</log4j:configuration>
설정후 다시 시작해본 뒤 콘솔창에 DEBUG가 찍히면 성공
2022-09-05
게시물 수정, 삭제
게시물 글수정,삭제를 같이 진행하고자 한다.
예제를 진행하며정리해보자
dispatcher-servlet.xml 요청명령어 등록
< bean class="com.board.controller.UpdateController" />
BoardDao.java (인터페이스) 내용추가
//7.글수정하기
public void update(BoardCommand board);
//8.글삭제하기
public void delete(Integer seq);
BoardDaoImp.java 내용추가
package com.board.dao;
import java.util.List;
import java.util.Map;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import org.springframework.stereotype.Service;
import com.board.domain.BoardCommand;
//SqlSessionDaoSupport 상속받는 이유->SqlSession객체(getSqlSession())
public class BoardDaoImp extends SqlSessionDaoSupport implements BoardDao {
//검색분야에 따른 검색어까지 조회(페이징 처리)
public List<BoardCommand> list(Map<String, Object> map) {
// TODO Auto-generated method stub
List<BoardCommand> list=getSqlSession().selectList("selectList", map);
return list;
}
public int getRowCount(Map<String, Object> map) {
// TODO Auto-generated method stub
return getSqlSession().selectOne("selectCount", map);
}
public int getNewSeq() {
// TODO Auto-generated method stub
int newseq=(Integer)getSqlSession().selectOne("getNewSeq");
System.out.println("getNewSeq의 new seq=>"+newseq);
return newseq;
}
public void insert(BoardCommand board) {
// TODO Auto-generated method stub
getSqlSession().insert("insertBoard",board);
}
//DetailController에서 호출
public BoardCommand selectBoard(Integer seq) {
// TODO Auto-generated method stub
return (BoardCommand)getSqlSession().selectOne("selectBoard", seq);
}
//조회수 증가
public void updateHit(Integer seq) {
// TODO Auto-generated method stub
getSqlSession().update("updateHit",seq);
}
//글수정하기
public void update(BoardCommand board) {
// TODO Auto-generated method stub
getSqlSession().update("updateBoard",board);
}
//글삭제하기
public void delete(Integer seq) {
// TODO Auto-generated method stub => getSeq()호출
getSqlSession().delete("deleteBoard",seq);//#{seq}
}
}
BoardMapper.xml sql 추가
~생략~
<!-- 7.수정하기 -->
<update id="updateBoard" parameterType="BoardCommand">
update springboard2 set writer=#{writer},title=#{title},content=#{content},filename=#{filename:VARCHAR}
where seq=#{seq}
</update>
<!-- 8.삭제하기 -->
<delete id="deleteBoard" parameterType="Integer">
delete from springboard2 where seq=#{seq}
</delete>
UpdateController.java 생성 (글수정 컨트롤러)
package com.board.controller;
import java.io.File;
import java.io.IOException;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
import com.board.dao.BoardDao;
import com.board.domain.BoardCommand;
import com.board.util.FileUtil;
import com.board.validator.BoardValidator;
@Controller
public class UpdateController {
//로그객체생성문
private Logger log=Logger.getLogger(getClass());
@Autowired
private BoardDao boardDao;
/*
* 하나의 요청명령어->하나의 컨트롤러만 사용X
* 하나의 컨트롤러->여러개의 요청명령어를 등록해서 사용이 가능 O
* 같은 요청명령어를 GET or POST방식으로 전송할지 결정하는 속성
* updateForm.do update.do
*
* update.do(페이지이동) update.do
* method=RequestMethod.GET or POST
*/
//1.글수정폼으로 이동(Get방식)->반환값(단순히 페이지 이동->String)
//페이지 이동=>데이터출력->ModelAndView
@RequestMapping(value="/board/update.do", method=RequestMethod.GET)
public ModelAndView form(@RequestParam("seq") int seq) {
System.out.println("다시 처음부터 값을 입력받기위해서 form()호출");
BoardCommand boardCommand=boardDao.selectBoard(seq);
//1.이동할 페이지명(확장자생략) 2.키명 3.전달할값
return new ModelAndView("boardModify","command",boardCommand);
}
//2.입력해서 유효성 검사->에러발생
//BindingResult->유효성 검사때문에 필요=>에러정보객체를 저장
@RequestMapping(value="/board/update.do", method=RequestMethod.POST)
public String submit(@ModelAttribute("command") BoardCommand com, BindingResult result) {
if(log.isDebugEnabled()) {
System.out.println("/board/update.do 요청중(post)");
log.debug("BoardCommand:"+com);//com.toString()
}
//유효성검사->validate()호출
new BoardValidator().validate(com, result);
//에러정보가 있으면
if(result.hasErrors()) {
return "boardModify";//그대로 다시 이동해서 입력을 받기(편집)
}
//변경전의 데이터를 불러오기 -> board(비밀번호)==웹상의 비밀번호
BoardCommand board=null;
String oldFileName="";//변경전 파일명
board=boardDao.selectBoard(com.getSeq());
//비밀번호체크(DB암호!=웹암호)
if(!board.getPwd().contentEquals(com.getPwd())) {
//1.젹용필드 2.에로코드명
result.rejectValue("pwd", "invalidPassword");
return "boardModify";
}else {//비밀번호가 맞다면
/*
*기본파일명->업로드된 파일이 존재->기존파일삭제->새오운 파일로 세팅 업로드돼야한다.
*
*/
oldFileName=board.getFilename();//DB상의 원래기존파일명
//업로드되어있다면
if(!com.getUpload().isEmpty()) {
try {//웹상에서 새로 바꾼 업로드파일명을 DB에 저장
com.setFilename(FileUtil.rename(com.getUpload().getOriginalFilename()));
}catch(Exception e) {e.printStackTrace();}
}else {//새로운 파일로 업로드하지 않은경우(기존파일은 덮어쓰기)
com.setFilename(oldFileName);
}
//자료실 글수정호출
boardDao.update(com);//DB상에 반영
//실제로 업로드됐다면
if(!com.getUpload().isEmpty()) {
try {
File file=new File(FileUtil.UPLOAD_PATH+"\\"+com.getFilename());
//물리적으로 이미 변경된 데이터전송(파일전송)
com.getUpload().transferTo(file);
}catch(IOException e) {
e.printStackTrace();
}catch(Exception e2) {
e2.printStackTrace();
}
//기존파일은 삭제하는 구문이 필요
if(oldFileName!=null) {
FileUtil.removeFile(oldFileName);
}
}//if(!com.getUpload().isEmpty())
}//else암호가 맞다면
return "redirect:/board/list.do";
}
}
tilesdef.xml 내용추가
~생략~
<!-- 글 삭제 페이지 -->
<definition name="boardDelete" extends="boardList">
<put-attribute name="title" value="글삭제하기"/>
<put-attribute name="js" value="" />
<put-attribute name="body" value="/WEB-INF/tiles-view/boardDelete.jsp"/>
</definition>
<!-- 글 수정 페이지 -->
<definition name="boardModify" extends="boardList">
<put-attribute name="title" value="글수정하기"/>
<put-attribute name="js" value="" />
<put-attribute name="body" value="/WEB-INF/tiles-view/boardModify.jsp"/>
</definition>
boardModify.jsp 작성
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!--자료실의 글쓰기와 동일하다. -->
<spring:hasBindErrors name="command"/>
<form:errors path="command"/>
<form action="update.do"
enctype="multipart/form-data" method="post">
<input type="hidden" name="seq"
value="${command.seq }">
<!--
message액션태그 code="불러올 키명"
errors액션태그 path="DTO의 필드명(=Vo)
-->
<spring:message code="write.form.title"/>
<input type="text" name="title" value="${command.title }">
<form:errors path="command.title"/><br>
<spring:message code="write.form.writer"/>
<input type="text" name="writer" value="${command.writer }">
<form:errors path="command.writer"/><br>
<spring:message code="write.form.pwd"/>
<input type="password" name="pwd" >
<form:errors path="command.pwd"/><br>
<spring:message code="write.form.content"/>
<textarea rows="10" cols="50" name="content">${command.content }</textarea>
<form:errors path="command.content"/><br>
<spring:message code="write.form.upload"/>
<input type="file" name="upload">
<!--전에 이미 업로드된 파일이 있는 경우에만 수행 -->
<c:if test="${!empty command.filename }">
(${command.filename }) 파일이 등록되어있습니다.<br>
</c:if>
<input type="submit"
value="<spring:message code="write.form.submit"/>">
<input type="button"
value="<spring:message code="list.content.title"/>"
>
</form>
index.jsp에서 실행해보고 글수정이 되면 성공
다시 삭제로 돌아와서 이어하려한다.
dispatcher-servlet.xml 내용추가
<bean class="com.board.controller.DeleteController" />
다른건 아까 미리했으니까 바로 컨트롤러 생성한다.
DeleteController.java 생성
package com.board.controller;
import java.io.File;
import java.io.IOException;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.board.dao.BoardDao;
import com.board.domain.BoardCommand;
import com.board.util.FileUtil;
import com.board.validator.BoardDeleteValidator;
@Controller
public class DeleteController {
//로그객체생성문
private Logger log=Logger.getLogger(getClass());
@Autowired
private BoardDao boardDao;
//1.글삭제폼으로 이동(Get방식)->반환값(단순히 페이지 이동->String)
//페이지 이동=>데이터출력->ModelAndView
@RequestMapping(value="/board/delete.do", method=RequestMethod.GET)
public String form() {
return "boardDelete";
}
//2.에러메세지 출력->다시 초기화->입력받음(재입력) WriteController과 비슷
@ModelAttribute("command")
public BoardCommand forBacking() {
return new BoardCommand();
}
//3.입력해서 유효성 검사->에러발생
//BindingResult->유효성 검사때문에 필요=>에러정보객체를 저장
@RequestMapping(value="/board/delete.do", method=RequestMethod.POST)
public String submit(@ModelAttribute("command") BoardCommand com, BindingResult result) {
if(log.isDebugEnabled()) {
System.out.println("/board/delete.do 요청중(post)");
log.debug("BoardCommand:"+com);//com.toString()
}
//유효성검사->validate()호출
//BoardDeleteValidator
new BoardDeleteValidator().validate(com, result);
//에러정보가 있으면
if(result.hasErrors()) {
return form();//"boardDelete"->boardDelete.jsp이동
}
//변경전의 데이터를 불러오기 -> board(비밀번호)==웹상의 비밀번호
BoardCommand board=null;
board=boardDao.selectBoard(com.getSeq());
//비밀번호체크(DB암호!=웹암호)
if(!board.getPwd().contentEquals(com.getPwd())) {
//1.젹용필드 2.에로코드명
result.rejectValue("pwd", "invalidPassword");
return form();
}else {//비밀번호가 맞다면
//자료실 글삭제호출
boardDao.delete(com.getSeq());//DB상에 반영
//업로드한 파일까지 삭제하는 경우 코딩
if(board.getFilename()!=null) {
FileUtil.removeFile(board.getFilename());
}
}//else암호가 맞다면
return "redirect:/board/list.do";
}
}
BoardDeleteValidator 가져오기
package com.board.validator;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import com.board.domain.BoardCommand;
//유효성 검사를 하는 방법=>Validator인터페이스를 상속받아라
public class BoardDeleteValidator implements Validator{
//1.유효성 검사를 할 클래스명을 지정해주는 메서드
public boolean supports(Class<?> clazz) {
// TODO Auto-generated method stub
//형식) return DTO클래스명.class.isAssignableFrom(clazz);
return BoardCommand.class.isAssignableFrom(clazz);
}
//2.유효성검사를 해주는 메서드->에러메세지를 작성->저장->웹에 출력
public void validate(Object target, Errors errors) {
// TODO Auto-generated method stub
//입력하지 않았거나 공백을 체크해주는 메서드->에러정보를 저장(에러객체)
//1.에러객체명 2.적용시킬 필드명 3.적용시킬 에러코드명
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "pwd", "required");
}
}
boardDelete.jsp 생성
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<%-- form액션태그 commandName=이름(DTO객체이름) 별칭명
<form:errors element="div"/> div태그형태로 에러메세지출력하겠다(설정)
<form:password path="name이름(pwd)"/>은
<input type="password" name="pwd" />와 같다
--%>
<form:form commandName="command">
<form:errors element="div"/>
<spring:message code="write.form.pwd"/>
<form:password path="pwd"/>
<form:errors path="pwd"/><br>
<input type="submit" value="<spring:message code="write.form.submit"/>">
<input type="button" value="<spring:message code="list.content.title"/>"
>
</form:form>
index.jsp로 실행해보고 삭제가 되면 성공
어노테이션 적용
빈즈 등록을 일일히 등록하면 나중에 많아진다면 매우 복잡해질수있다 이럴때 어노테이션을 사용하여 간단히 사용할수있다.
dispatcher-servlet.xml 의 호출 빈즈들 대신 @Component을 상용한다.
~생략~
<context:component-scan base-package="com.board" />
<!--
<context:component-scan base-package="com.board.dao" />
->com.board.doa패키지에 @Component가 적용된 클래스만 빈즈로 자동으로 등록
<context:component-scan base-package="com.board.controller" />
->com.board.controller패키지에 @Component가 적용된 클래스만 빈즈로 자동으로 등록
-->
<!--
<bean class="com.board.dao.BoardDaoImp" />
<bean class="com.board.controller.ListController" />
<bean class="com.board.controller.WriteController" />
<bean class="com.board.controller.DetailController" />
<bean class="com.board.controller.UpdateController" />
<bean class="com.board.controller.DeleteController" />
-->
BoardDaoImp.java 에 @Repository 어노테이션 적용
- @Repository =>DAO역할을 하는 클래스에게 부여하는 어노테이션(빈등록)
- @Component를 지우고 대신 @Repository 를 적용
BoardDaoImp.java 에 @Service 어노테이션 적용
이번에는@Component또는 @Service 대신 @Service를 적용해보자
- @Service =>컨트롤러와 DB(DAO)사이의 중간역할을 하는 클래스에게 부여(빈등록)
- @Service("빈즈로 등록할 객체의 id구분자를 지정") 가능하다 안해도된다
<br>
위에 2개는 단순히 위에 각각 @Repository 와 @Service만 달면 되는거기때문에 예시는 생략한다.
여기까지가 이번 자료실게시판 프로젝트 끝이다!
실행 후 잘 되는지 확인
2022-09-06