개인프로젝트 - 기술 요소
1. Tiles
tiles란, 홈페이지에서 상하단, 사이드, 메인을 설정 상태로 include하는 구조를 나타낸다.
이를 적용시키기 위해서는, pom.xml에 라이브러리를 추가하고 include 식으로 추가해야한다.
1) pom.xml
<!-- tiles -->
<dependency>
<groupId>org.apache.tiles</groupId>
<artifactId>tiles-jsp</artifactId>
<version>2.2.2</version>
</dependency>
<!-- tiles-core -->
<dependency>
<groupId>org.apache.tiles</groupId>
<artifactId>tiles-core</artifactId>
<version>2.2.2</version>
</dependency>
2) tiles-def.xml로 해당 타일즈를 어떻게 적용시킬지를 나타내기
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE tiles-definitions PUBLIC
"-//Apache Software Foundation//DTD Tiles Configuration 2.1//EN"
"http://tiles.apache.org/dtds/tiles-config_2_1.dtd">
<tiles-definitions>
<definition name=".main.layout" template="/WEB-INF/views/layout.jsp">
<put-attribute name="header" value="/WEB-INF/views/header.jsp"/>
<put-attribute name="side" value="/WEB-INF/views/side.jsp"/>
<put-attribute name="content" value="list.do"/>
<put-attribute name="footer" value="/WEB-INF/views/footer.jsp"/>
</definition>
<definition name=".main.*.*" extends=".main.layout"> <!-- 상속받아서(jsp는 매번 바뀌기 때문) -->
<put-attribute name="content" value="/WEB-INF/views/{1}/{2}.jsp"/> <!-- 1: 폴더 2: jsp이름 -->
</definition>
</tiles-definitions>
3) servlet-context.xml 설정하기
<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
<resources mapping="/resources/**" location="/resources/" />
<beans:bean class="org.springframework.web.servlet.view.tiles2.TilesConfigurer">
<beans:property name="definitions">
<beans:list>
<beans:value>/WEB-INF/tiles-def.xml</beans:value>
</beans:list>
</beans:property>
</beans:bean>
<beans:bean class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<beans:property name="viewClass" value="org.springframework.web.servlet.view.tiles2.TilesView"/>
<beans:property name="viewNames" value=".*"/>
<beans:property name="order" value="1"/>
</beans:bean>
여기서 <beans:property name="order" value="1"/> 이 부분은, 만약 bean 등록에 대해
여러가지가 있을 경우, 제일 먼저 실행하도록 1을 주고 우선순위를 부여한 것이다.
4) 타일즈에 나타낼 view 꾸미기
5) 실제로 사용하기(Controller에서 사용)
//공지사항 리스트 보이기
@RequestMapping("noticeList.do")
public String noticeList() {
return ".main.board.nblist";
}
return에다가 .main.board.nblist 이런식으로, 위에서 name으로 설정한 .main.*.* 가 적용될 수 있도록 한다.
.main.board(해당 jsp가 있는 폴더 이름).nblist(jsp 파일 이름)
2. 답글 및 댓글 리스트에 나타내기
ref : 해당 글의 그룹(게시글을 작성하면 그룹의 순서가 알아서 만들어짐)
re_step : 해당 답변의 순서(이것이 첫번째 답변인지, 두번째 답변인지, 두번째의 재답변인지를 나타냄)
re_level : 해당 게시글에 대한 답변의 순서를 나타냄(게시글이 있고 답변을 달게되면 해당 답변은 몇 번째 임을 나타내는 것이 됨) 그래서 한 게시물에 순차적으로 값이 매겨짐(1,2,3,4,5,6,7..이런식으로!)
해당 코드
3. MultipartFile
3-1) Multipart
웹 클라이언트가 요청을 보낼 때, http 프로토콜의 바디 부분에 데이터를 여러 부분으로 나눠서 보내는 것.
보통 파일을 전송할 때 사용한다.
3-2) multipart File 사용 경험
기존에는 img table을 만들어서
이런식으로 해당 이름과 사진과 연결된 글의 번호를 연결하고 사진의 size만을 가져와서 자신의 컴퓨터에 직접 사진을 저장하게 되어 나중에 배포를 하게 될 경우에 대해서는 따로 그 웹사이트에서 파일을 내리거나 올릴 수 있도록 코드를 추가해야 하는 경우가 생긴다.
이번에 프로젝트에 적용한 방법은 그것과는 별개로 스프링 내 저장소에 저장하여 나중에 가상에서도 적용할 경우, 해당 저장소를 만들고 파일을 저장시키는 형식으로 이용한다. 이는 나중에 클라우드 서비스를 이용할 경우에도 사용하기에 편리하다 (**같은 원리이기 때문**)
해당 코드를 소개하자면,
1. pom.xml에 파일 관련 라이브러리를 등록한다.
<!-- file upload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
2. root-context.xml
<!-- file upload -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="268435456"/>
</bean>
추가한다!
3. web.xml (지금과는 별개인 것 같은데 미리 해놓기!!)
<!-- 인코딩 UTF-8 -->
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
인코딩을 먼저하자(이것은 전체와 관련되어있기 때문에 먼저 해야 함!!)
3. 파일이 저장되는 원리를 가진 코드를 저장한다.
원리 다시 설명하자면,
서버에 가상 폴더 안에 url을 가진 파일을 저장하려 한다. 이는 환경이 바뀌더라도 그 안에서(여기서는 spring 프로그램 안에) 꺼내 쓸 수 있다.
1.
private String CLASSPATH = "C:\\work_sts_ys\\ysmb\\src\\main\\webapp"; //서버 파일이 저장될 위치
private String PATHNAME = "/resources/storage/"; //저장소 위치
2.
public Map<String,String> Save(MultipartFile file){
String rs = RandomString(40);
long date = System.currentTimeMillis();
String originalFileName = file.getOriginalFilename();
String ext = originalFileName.split("[.]")[1];
String safe_pathname = (CLASSPATH+PATHNAME +date+rs+"."+ext).trim(); //실질적 저장 위치
String save_pathname = (PATHNAME+date + rs+"."+ext).trim(); //db저장 시 경로이름
try {
file.transferTo(new File(safe_pathname));
Map<String,String> value = new HashMap<String, String>();
value.put("ABSOLUTE_PATH",safe_pathname);//원본 서버의 경로
value.put("PATH",save_pathname);//db에 저장할 url(파일이름을 안쓸 때는 이 url만!)
value.put("FILENAME",date+ rs+"."+ext);//원본 파일 이름의 url
//이는 db에 저장하기 위해 url을 저정하는 것
return value;
}catch (Exception e) {
e.printStackTrace();
}
return null;
}
3. 실제 컨트롤러에서 사용하는 예시(일반, 파일을 저장하는 예시)
//파일 저장하기
@RequestMapping(value="file.do", method=RequestMethod.POST)
public void file(@RequestParam("fileup") MultipartFile file) {
Fileupload fileupload = new Fileupload();
Map<String, String> save = fileupload.Save(file);
String imgurl = save.get("PATH"); //이걸 blob대신에 db에 저장하기(db에는 varchar처럼! string형태로 저장이 되는 것)
//jsp에 저장할때는 미리 localhost:8081을 먼저 붙여준 다음 controller로 넘겨서 저장시킬 것!
}
3-1) 실제로 첨부했을 때는, 그림만 저장이 되는 것이 아니라 해당 글과 저장이 되어야 한다.
그래서, 글과 그림을 담을 그릇(DTO)이 필요하다. 그래서 NFBoardDTO를 만들었다.
(이는 실제로 mybatis-config.xml에 포함되지는 않고 임의로 사용할 것이라 잠깐 사용되는 용도로 만들었다.)
아래는 파일 저장만이 아니라 더 나아가서, 썸네일로 사용할 목적으로 만들어진 코드다.
//그림 저장하기
@RequestMapping(value="flPro.do",method=RequestMethod.POST)
public String flPro(@ModelAttribute NFBoardDTO nboard, HttpSession session, Model model) {
NBoardDTO nBoardDTO = new NBoardDTO(nboard);
if(session.getAttribute("btype")=="nboard")
{nBoardDTO.setDocument(0);}
else {nBoardDTO.setDocument(1);}
String name = (String)session.getAttribute("name");
nBoardDTO.setNwriter(name);
sqlSession.insert("nboard.ninsertDao",nBoardDTO);
List<MultipartFile> attachFiles = nboard.getAttachFiles();
Fileupload fileupload = new Fileupload();
ImgDTO images = new ImgDTO();
if(attachFiles != null)
{
for(int i = 0 ; i < attachFiles.size() ; i++) {
Map<String, String> save = fileupload.Save(attachFiles.get(i));
images.setBno(nBoardDTO.getNo());
images.setUrl(save.get("PATH"));
images.setThumb(null);
if(session.getAttribute("btype")=="nboard")
{images.setDocument(0);}
else {images.setDocument(1);}
sqlSession.insert("imgs.saveImg",images);}
//번호를 넣어서 찾고 첫번째꺼 가져와야 함
List<ImgDTO> slist = sqlSession.selectList("imgs.selectImg",images.getBno());
int cno = sqlSession.selectOne("imgs.selectCount",images.getBno());
int totalcount = sqlSession.selectOne("imgs.selectAllCount");
int num=totalcount-cno+1;
images.setNo(num);
images.setThumb(slist.get(0).getUrl());
System.out.println("현재 글개수"+cno);
System.out.println("썸네일을 저장할 번호"+num);
sqlSession.update("imgs.updateImg",images);
}
//썸네일을 저장할 때, 제일 첫 사진을 썸네일로 저장해야 함
//데이터를 쌓은 다음 나중에 첫번째꺼만 골라내서 저장하기
model.addAttribute("str",session.getAttribute("str"));
model.addAttribute("pageNum",1);
return "redirect:/list.do";
}
* 여기서 NFBoardDTO에 대해 알아보면,
public class NFBoardDTO {
private int no;
private String title;
private String content;
private Date nwdate;
private int document;
private List<MultipartFile> attachFiles;
getter & setter 자리
}
이후에 파일에 대한 내용을 불러올 때!
쿼리를 만들어야 한다.(원하는 때에 원하는 데이터를 얻어오기 위해서는 join을 써야 한다.)
이를 sts에서는 result map이라는 것으로 사용이 가능하다. 실제로 NFBoardDTO를 적용시키기 위한 DTO를 myBatis에도 적용시켜주는 것이 좋은데 이를 다 포함하는 것이 ApplDTO라는 것이다. 얘를 등록함으로써 모든 데이터를 자유롭게 입출력할 수가 있어졌다.
* 여기서는 ApplDTO를 알아보자,
public class ApplDTO {
private int no;
private String title;
private String ncontent;
private String nwriter;
private Date nwdate;
private List<ImgDTO> imgs;
private Integer document;
getter & setter 부분
}
얘를 myBatis-config.xml에도 등록해줘야 한다.
<configuration>
<typeAliases>
<typeAlias alias="member77" type="co.kr.ysmb.dto.MemberDTO"/>
<typeAlias alias="Board77" type="co.kr.ysmb.dto.BoardDTO"/>
<typeAlias alias="NBoard" type="co.kr.ysmb.dto.NBoardDTO"/>
<typeAlias alias="Comment" type="co.kr.ysmb.dto.CommentDTO"/>
<typeAlias alias="Imgs" type="co.kr.ysmb.dto.ImgDTO"/>
<typeAlias alias="Appl" type="co.kr.ysmb.dto.ApplDTO"/>
<typeAlias alias="Cmt" type="co.kr.ysmb.dto.CmtDTO"/>
</typeAliases>
</configuration>
실제로 resultmap을 쓰게 된다면,
<resultMap type="Appl" id="getApplList">
<result property="no" column="no"></result>
<result property="title" column="title"></result>
<result property="content" column="content"></result>
<result property="nwdate" column="nwdate"></result>
<result property="document" column="document"></result>
<collection property="imgs" column="{no=no}" javaType="java.util.ArrayList" ofType="Imgs" select="getImgList"></collection>
</resultMap>
<select id="ImgList" parameterType="map" resultMap="getApplList">
SELECT no,title,ncontent,nwdate,nwriter FROM nboard where document=#{document} order by nwdate desc limit #{start}, #{cnt}
</select>
<select id="findImgListCondition" parameterType="Appl" resultMap="getApplList">
SELECT no,title,ncontent,nwdate FROM nboard where document=#{document} and no=#{no}
</select>
<select id="findImgList" parameterType="int" resultMap="getApplList">
SELECT no,title,ncontent,nwdate FROM nboard where document=#{document}
</select>
<select id="getImgList" resultType="java.util.Map">
SELECT no,bno,url,thumb FROM imgs where bno=#{no}
</select>
위와 같이, 필요에 따라 parameterType(입력 타입), resultMap(resultmap 타입), resultType(출력 타입)을 정해서 조건에 맞춰서 데이터를 가져올 수가 있다.
ResultMap 관련 내용
1) resultMap type: 실제로 결과로 나올 데이터 형태를 나타냄
2) id: 해당 데이터를 가져오기 위해 사용되는 요청명 비슷한 거
3) collection의 property는 실제 조인시킬 타입에 대한 것을 놓는다.
4) column: collection의 칼럼은 조인 시키는 외래키와 기본키를 연결시키는 것
5) javaType: 실제 조인이 적용될 때 나타나는 결과의 형태
6) ofType: 조인시키는 DTO 이름(여기서는 별명을 썼다.)
7) select: 조인시키는 id를 가져온다.