Back-End/Spring 2019. 7. 3. 11:02
- 페이지 나누기 구현 -
- 페이지당 게시물수 : 10개
- 전체 게시물수 : 991개
- 몇 페이지? : 100
991 / 10 => 99.1 올림 => 100
(99.1에서 올림을 한 이유는 한 페이지에 글이 1개만 들어갈 수도 있기 때문.. (맨 오래전에 쓴 맨 첫글같은 경우))
- 페이지의 시작번호, 끝번호 계산
where rn between 1 and 10
1페이지 => 1 ~ 10
2페이지 => 11 ~ 20
.....
11페이지 => 101 ~ 110
57페이지 => 561 ~ 570
99페이지 => 981 ~ 990
100페이지 => 991 ~ 1000
시작번호 = (현재페이지 - 1) * 페이지당 게시물수 + 1
1페이지 => (1-1) * 10 + 1 => 1
2페이지 => (2-1) * 10 + 1 => 11
7페이지 => (7-1) * 10 + 1 => 61
끝번호 = 시작번호 + 페이지당 게시물수 - 1
1페이지 => 1 + 10 - 1 => 10
2페이지 => 11 + 10 -1 => 20
* 전체 페이지 블록수 = 전체 페이지 갯수 / 10
(페이지가 한꺼번에 다 나오면 알아보기가 힘들기때문에 10페이지씩 1블록을 만들어 저장)
91 / 10 => 9.1 => 10개
예시 )
1 2 3 4 5 6 7 8 9 10 [다음]
[이전] 11 12 13 14 15 16 17 18 19 20 [다음]
[이전] 21 22
22페이지가 끝일 경우에는 그 다음페이지는 표시되지 않아야 하고, [다음] 버튼도 출력되면 안된다.
현재 페이지가 속한 블록 = (현재 페이지 - 1) / 페이지 블록 단위 + 1
1페이지 => 몇번째 블록? 1
(1-1) / 10 + 1 => 1
9페이지 => 1블록
(9-1) / 10 + 1 => 1
11페이지 => 2블록
(11-1) / 10 + 1 => 2
57페이지
(57-1) / 10 + 1 => 6
* 페이지 블록의 시작번호 = (현재블록 - 1) * 블록단위 + 1
1블록 => (1-1) * 10 + 1 => 1
2블록 => (2-1) * 10 + 1 => 11
6블록 => (6-1) * 10 + 1 => 51
* 페이지 블록의 끝번호 = 블록 시작번호 + 블록단위 - 1
1블록 => 1 + 10 - 1 => 10
2블록 => 11 + 10 - 1 => 20
6블록 => 51 + 10 - 1 => 60
board테이블과 member테이블을 join을 해야 작성자의 이름을 알 수 있다.
board 테이블
member 테이블
(연습) board테이블과 member테이블을 join해서 작성자가 작성한 게시글의 정보를 조회하는 쿼리
select bno, title, writer, name, regdate, viewcnt // 글번호, 제목, 작성자, 이름, 날짜, 조회수를 검색
from board b, member m // board 테이블과 member테이블로 부터 (b와 m은 약자로 쓴다는 의미)
where b.writer= m.userid // board 테이블의 작성자id와 member 테이블의 회원id가 같은 것을 검색
order by bno desc; // 글번호의 내림차순으로 검색
cs
boardMapper.xml에서 코드를 수정 (위에서 실행해본 SQL 구문을 listAll에 삽입한다)
< ! -- 다른 mapper와 중복되지 않도록 네임스페이스 기재 -->
< mapper namespace= "board" >
< select id= "listAll"
resultType= "com.example.spring02.model.board.dto.BoardDTO" >
select bno, title, writer, name, regdate, viewcnt
from board b, member m
where b.writer = m.userid
order by bno desc;
< / select >
cs
list.jsp 중 일부 코드 수정 (게시판에서 작성자 이름이 출력될 수 있도록....)
이렇게 하면 name에 join된 이름이 출력되게 된다.
<c:forEach var = "row" items = "${map.list}" >
<tr >
<td >${row.bno}</td >
<td >${row.title}</td >
<td >${row.name}</td > //write를 name로 변경
<td ><fmt:formatDate value = "${row.regdate}"
pattern = "yyyy-MM-dd HH:mm:ss" / ></td >
<td >${row.viewcnt}</td >
</tr >
</c:forEach >
</table >
</body >
</html >
cs
게시물 1000개를 입력하기 위해 데이터베이스에서 반복문을 만들어서 실행한다.
declare
i number := 1 ; -- declare는 선언문, i의 값을 1로 대입하는것을 선언,
begin
while i < = 1000 loop
insert into board (bno,title,content,writer) values
((select nvl(max(bno)+ 1 ,1 )from board),'제목' | | i, '내용' | | i, 'park' );
-- 시퀀스를 만들긴 했지만 번호가 깔끔하게 나오지 않기때문에 서브쿼리를 사용해서 번호를 1씩 증가시킨다.
-- ||는 숫자와 문자열을 더할때 사용한다 이 쿼리에서는 제목1, 제목2 이런식으로 출력이 되게 된다.
-- id는 member 테이블에 있는 id만 사용해야 한다. 그렇게 해야 join이 되기 때문에
i := i+ 1 ;
--한 바퀴 돌때마다 i를 1씩 증가시킨다는 의미 (1000바퀴 돌때까지)
end loop;
end;
/
commit; //게시물 1000개를 집어넣은 후 commit를 실시해서 작업을 완료함.
cs
게시물을 한페이지에 10개씩 볼 수 있게하는 쿼리 (연습)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
-- rownum : 출력순서
-- 아래sql 문에서는 원래 from 문이 먼저 실행되고,
-- where문이 다음으로 실행되고,
-- 그 다음 맨위쪽 문장이 실행된다.
-- 마지막에는 order by문이 실행된다.
select *
from (
select rownum as rn, A.* --rownum을 rn이란 문자로 줄여쓴다. 밑에 from문을 A로 줄여쓴다.
from ( --이 from문이 먼저 실행된 다음에 번호를 붙였기 때문에 일련번호가 다시 새로 매겨졌다.
--이 안쪽의 쿼리가 가장 중요함
select rownum, bno, title, regdate, viewcnt, name --3번째로 실행
from board b, member m --1번째로 실행
where b.writer= m.userid --2번째로 실행
order by bno desc --4번째로 실행
) A
) where rn between 1 and 10 ; -- 새로매겨진 일련번호 1~10번 글까지 1페이지
-- 11~20번 글까지 2페이지 ..
cs
페이지를 나누기 위한 클래스 작성
Pager.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
package com.example.spring02.service.board;
public class Pager {
public static final int PAGE_SCALE= 10 ; //페이지당 게시물수
public static final int BLOCK_SCALE= 10 ; //화면당 페이지수
private int curPage; //현재 페이지
private int prevPage; //이전 페이지
private int nextPage; //다음 페이지
private int totPage; //전체 페이지 갯수
private int totBlock; //전체 페이지블록 갯수
private int curBlock; //현재 블록
private int prevBlock; //이전 블록
private int nextBlock; //다음 블록
private int pageBegin; // #{start} 변수에 전달될 값
private int pageEnd; // #{end} 변수에 전달될 값
private int blockBegin; //블록의 시작페이지 번호
private int blockEnd; //블록의 끝페이지 번호
//생성자
// Pager(레코드갯수, 출력할페이지번호)
public Pager(int count, int curPage) {
curBlock = 1 ; //현재블록 번호
this .curPage = curPage; //현재 페이지 번호
setTotPage(count); //전체 페이지 갯수 계산
setPageRange(); // #{start}, #{end} 값 계산하는 메소드
setTotBlock(); // 전체 블록 갯수 계산
setBlockRange(); //블록의 시작,끝 번호 계산
}
public void setBlockRange() {
//원하는 페이지가 몇번째 블록에 속하는지 계산
curBlock= (curPage- 1 )/ BLOCK_SCALE + 1 ;
//블록의 시작페이지,끝페이지 번호 계산
blockBegin= (curBlock- 1 )* BLOCK_SCALE+ 1 ;
blockEnd= blockBegin+ BLOCK_SCALE- 1 ;
//마지막 블록 번호가 범위를 초과하지 않도록 처리
if (blockEnd > totPage) {
blockEnd = totPage;
}
//[이전][다음]을 눌렀을 때 이동할 페이지 번호
prevPage= (curBlock= = 1 ) ? 1 : (curBlock- 1 )* BLOCK_SCALE;
nextPage= curBlock> totBlock ? (curBlock* BLOCK_SCALE)
: (curBlock* BLOCK_SCALE)+ 1 ;
//마지막 페이지가 범위를 초과하지 않도록 처리
if (nextPage > = totPage) {
nextPage= totPage;
}
}
//페이지블록의 총 갯수 계산 (총 100페이지라면 10개의 블록이다)
public void setTotBlock() {
totBlock = (int )Math.ceil(totPage* 1. 0 / BLOCK_SCALE);
}
// where rn between #{start} and #{end}에 입력될 값
public void setPageRange() {
// 시작번호=(현재페이지-1)x페이지당 게시물수 + 1
// 끝번호=시작번호 + 페이지당 게시물수 - 1
pageBegin = (curPage- 1 ) * PAGE_SCALE + 1 ;
pageEnd = pageBegin + PAGE_SCALE - 1 ;
}
public int getCurPage() {
return curPage;
}
public void setCurPage(int curPage) {
this .curPage = curPage;
}
public int getPrevPage() {
return prevPage;
}
public void setPrevPage(int prevPage) {
this .prevPage = prevPage;
}
public int getNextPage() {
return nextPage;
}
public void setNextPage(int nextPage) {
this .nextPage = nextPage;
}
public int getTotPage() {
return totPage;
}
//전체 페이지 갯수 계산
public void setTotPage(int count) {
// Math.ceil() 올림
totPage = (int )Math.ceil(count* 1. 0 / PAGE_SCALE);
}
public int getTotBlock() {
return totBlock;
}
public void setTotBlock(int totBlock) {
this .totBlock = totBlock;
}
public int getCurBlock() {
return curBlock;
}
public void setCurBlock(int curBlock) {
this .curBlock = curBlock;
}
public int getPrevBlock() {
return prevBlock;
}
public void setPrevBlock(int prevBlock) {
this .prevBlock = prevBlock;
}
public int getNextBlock() {
return nextBlock;
}
public void setNextBlock(int nextBlock) {
this .nextBlock = nextBlock;
}
public int getPageBegin() {
return pageBegin;
}
public void setPageBegin(int pageBegin) {
this .pageBegin = pageBegin;
}
public int getPageEnd() {
return pageEnd;
}
public void setPageEnd(int pageEnd) {
this .pageEnd = pageEnd;
}
public int getBlockBegin() {
return blockBegin;
}
public void setBlockBegin(int blockBegin) {
this .blockBegin = blockBegin;
}
public int getBlockEnd() {
return blockEnd;
}
public void setBlockEnd(int blockEnd) {
this .blockEnd = blockEnd;
}
}
cs
BoardController.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
package com.example.spring02.controller.board;
import java.util.HashMap;
import java.util.List;
import javax.inject.Inject;
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import com.example.spring02.model.board.dto.BoardDTO;
import com.example.spring02.service.board.BoardService;
import com.example.spring02.service.board.Pager;
@Controller //controller bean
@RequestMapping("board/*" ) //공통적인 url pattern
public class BoardController {
@Inject
BoardService boardService;
//첨부파일 목록을 리턴
//ArrayList를 json 배열로 변환하여 리턴
@RequestMapping("getAttach/{bno}" )
@ResponseBody // view가 아닌 데이터 자체를 리턴
public List< String > getAttach(@PathVariable int bno){
return boardService.getAttach(bno);
}
@RequestMapping("delete.do" )
public String delete(int bno) throws Exception {
boardService.delete(bno); //삭제 처리
return "redirect:/board/list.do" ; //목록으로 이동
}
//게시물 내용 수정
@RequestMapping("update.do" )
public String update(BoardDTO dto) throws Exception {
System .out .println ("dto:" + dto);
if (dto ! = null ) {
boardService.update(dto); //레코드 수정
}
// 수정 완료 후 목록으로 이동
//return "redirect:/board/list.do";
// 상세 화면으로 되돌아감
return "redirect:/board/view.do?bno=" + dto.getBno();
}
@RequestMapping("list.do" ) //세부적인 url pattern
public ModelAndView list(//RequestParam으로 옵션, 키워드, 페이지의 기본값을 각각 설정해준다.
@RequestParam(defaultValue= "1" ) int curPage,
@RequestParam(defaultValue= "all" ) String search_option,
@RequestParam(defaultValue= "" ) String keyword)
//defaultValue를 설정하지 않으면 null point 에러가 발생할수 있기 때문에 기본값을 설정해주어야 한다.
throws Exception{
//레코드 갯수 계산
int count = 1000 ;
//페이지 관련 설정, 시작번호와 끝번호를 구해서 각각 변수에 저장함
Pager pager= new Pager(count, curPage); //레코드 번호와 원하는 페이지의 번호를 주게 되면
int start= pager.getPageBegin();
int end= pager.getPageEnd();
//게시물 목록을 출력하기 위해 <BoardDTO>타입에 list변수에 게시물 목록관련 값들을 저장함.
//넣어야될 값들이 여러개 있으므로 haspmap.put 메소드를 사용해서 값들을 넣어서 list에 저자
List< BoardDTO> list= boardService.listAll(search_option,keyword,start,end); //게시물 목록
ModelAndView mav= new ModelAndView(); //자료를 보낼 페이지를 지정해야하고, 자료를 지정해야 하기 때문에
//ModelAndView 객체를 생성한다.
HashMap< String ,Object> map= new HashMap< > (); //여러개의 값들을 저장해야하기 때문에 hashmap() 객체를 생성
map.put("list" , list); //map에 자료 저장
map.put("count" , count);
map.put("pager" , pager); //페이지 네비게이션을 위한 변수
map.put("search_option" , search_option);
map.put("keyword" ,keyword);
mav.setViewName("board/list" ); //포워딩할 뷰의 이름
mav.addObject("map" , map); //ModelAndView에 map을 저장
return mav; // board/list.jsp로 이동
}
@RequestMapping("view.do" )
public ModelAndView view(int bno, HttpSession session)
throws Exception {
//조회수 증가 처리
boardService.increaseViewcnt(bno, session);
ModelAndView mav= new ModelAndView();
mav.setViewName("board/view" ); //포워딩할 뷰의 이름
mav.addObject("dto" , boardService.read(bno)); //자료 저장
return mav; // views/board/view.jsp로 넘어가서 출력됨
}
@RequestMapping("write.do" )
public String write() {
// 글쓰기 폼 페이지로 이동
return "board/write" ;
}
// write.jsp에서 입력한 내용들이 BoardDTO에 저장됨
@RequestMapping("insert.do" )
public String insert(@ModelAttribute BoardDTO dto
, HttpSession session) //세션은 아이디를 확인하기위해서 필요하므로 세션을 가져온다.
throws Exception {
// 로그인한 사용자의 아이디
String writer= (String )session.getAttribute("userid" );
dto.setWriter(writer); //로그인한 아이디를 dto에 저장
//레코드 저장
boardService.create(dto); //서비스와 dao를 통해서 레코드에 추가가된다.
//게시물 목록으로 이동
return "redirect:/board/list.do" ;
}
}
cs
BoardDAOImpl.java 중 일부 (게시물 목록을 리턴하는 메소드를 수정함)
//게시물 목록 리턴
@Override
public List< BoardDTO> listAll(
String search_option, String keyword,int start, int end)
throws Exception {
Map< String ,Object> map= new HashMap< > ();
map.put("search_option" , search_option);
map.put("keyword" , "%" + keyword+ "%" );
map.put("start" , start); //맵에 자료 저장
map.put("end" , end);
// mapper에는 2개 이상의 값을 전달할 수 없음(dto 또는 map 사용)
return sqlSession.selectList("board.listAll" ,map);
}
cs
boardMapper.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<?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와 중복되지 않도록 네임스페이스 기재 -->
<mapper namespace ="board" >
<select id ="listAll"
resultType ="com.example.spring02.model.board.dto.BoardDTO" >
<!-- 결과는 boardDTO타입이 된 -->
<include refid ="paging_header" / >
<!-- ref는 다른테이블을 의미한다. -->
<!-- 번호, 제목, 작성자, 이름, 날짜, 조회수 , 그리고 댓글의 갯수를 검색 -->
<!-- board 테이블과 member 테이블로 부터 검색 -->
select bno,title,writer,name,regdate,viewcnt,show
,(select count(*) from reply where bno=b.bno) cnt
from board b, member m
<!-- bno의 내림차순으로 검색 -->
<include refid ="search" / >
order by bno desc
<include refid ="paging_footer" / >
</select >
<sql id ="paging_header" >
<!-- 게시물을 한페이지에 10개씩 볼 수 있게하는 쿼리 윗부분-->
select *
from (
select rownum as rn, A.*
from (
</sql >
<sql id ="paging_footer" >
<!-- 게시물을 한페이지에 10개씩 볼 수 있게하는 쿼리 아랫 부분-->
<!-- 새로 매겨진 일련번호 1~10번 글까지 1페이지 -->
<!-- 11~20번 글까지 2페이지.. -->
) A
) where rn between #{start} and #{end}
</sql >
<insert id ="insert" >
insert into board (bno,title,content,writer) values
( seq_board.nextval, #{title}, #{content}, #{writer} )
</insert >
</mapper >
cs
list.jsp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
<% @ page language= "java" contentType= "text/html; charset=UTF-8"
pageEncoding= "UTF-8" %>
<!DOCTYPE html >
<html >
<head >
<meta http-equiv ="Content-Type" content ="text/html; charset=UTF-8" >
<title >Insert title here</title >
<% @ include file= "../include/header.jsp" %>
<script >
$(function (){ //아이디가 btnWrite인 버튼을 누르게 되면 write.do 컨트롤러로 맵핑
$("#btnWrite" ).click(function (){
location .href= "${path}/board/write.do" ;
});
});
function list(page){ //현재 페이지의 조건을 넘겨준다. +뒤에있는 것들은 검색
location .href= "${path}/board/list.do?curPage=" + page
+ "$search_option=${map.search_option}"
+ "$keyword=${map.keyword}" ;
}
</script >
</head >
<body >
<% @ include file= "../include/menu.jsp" %>
<h2 >게시판</h2 >
<button type ="button" id ="btnWrite" >글쓰기</button >
${map.count}개의 게시물이 있습니다.<!-- map에 저장된 개시물의 갯수를 불러옴 -->
<table border ="1" width ="600px" >
<tr >
<th >번호</th >
<th >제목</th >
<th >이름</th >
<th >날짜</th >
<th >조회수</th >
</tr >
<!-- forEach var="개별데이터" items="집합데이터" -->
<c:forEach var ="row" items ="${map.list}" > <!-- 컨트롤러에서 map안에 list를 넣었기 때문에 이렇게 받는다. -->
<tr >
<td >${row.bno}</td >
<td >
<a href ="${path}/board/view.do?bno=${row.bno}" >
${row.title}
</a >
<c:if test ="${row.cnt > 0}" >
<span style ="color:red;" >( ${row.cnt} )</span >
</c:if >
</td >
<td >${row.name}</td >
<td ><fmt:formatDate value ="${row.regdate}"
pattern ="yyyy-MM-dd HH:mm:ss" / > </td >
<td >${row.viewcnt}</td >
</tr >
</c:forEach >
<!-- 페이지 네비게이션 출력 -->
<tr >
<td colspan ="5" align ="center" >
<c: forEach var = "num" begin = "1" end = "${map.pager.totPage}" >
<a href = "javascript : list ('${num}')" >${num}</a >
</c:forEach >
</td >
</tr >
</table >
</body >
</html >
cs
위에서 페이지 나누기 기능을 추가하기 위해서 페이지 네비게이션 부분에 페이지 나누기 코드를 추가
list.jsp 중 일부
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<!-- 페이지 네비게이션 출력 -->
<tr >
<td colspan ="5" align ="center" >
<c:if test ="${map.pager.curBlock > 1}" >
<a href ="#" onclick ="list('1')" >[처음]</a >
</c:if > <!-- 현재 블록이 1블록보다 크면 (뒤쪽에 있기때문에) 처음으로 갈 수 있도록 링크를 추가 -->
<c:if test ="${map.pager.curBlock > 1}" >
<a href ="#" onclick ="list('${map.pager.prevPage}')" >
[이전]</a >
</c:if > <!-- 현재 블록이 1블록보다 크면 이전 블록으로 이동할 수 있도록 링크 추가 -->
<c:forEach var ="num"
begin ="${map.pager.blockBegin}"
end ="${map.pager.blockEnd}" >
<c:choose >
<c:when test ="${num == map.pager.curPage}" >
<!-- 현재 페이지인 경우 하이퍼링크 제거 -->
<!-- 현재 페이지인 경우에는 링크를 빼고 빨간색으로 처리를 한다. -->
<span style ="color:red;" >${num}</span >
</c:when >
<c:otherwise >
<a href ="#" onclick ="list('${num}')" >${num}</a >
</c:otherwise >
</c:choose >
</c:forEach >
<c:if test ="${map.pager.curBlock <= map.pager.totBlock}" >
<a href ="#"
onclick ="list('${map.pager.nextPage}')" >[다음]</a >
</c:if > <!-- 현재 페이지블록이 총 페이지블록보다 작으면 다음으로 갈 수있도록 링크를 추가 -->
<c:if test ="${map.pager.curPage <= map.pager.totPage}" >
<a href ="#"
onclick ="list('${map.pager.totPage}')" >[끝]</a >
</c:if > <!-- 현재 페이지블록이 총 페이지블록보다 작거나 같으면 끝으로 갈 수 있도록 링크를 추가함-->
</td >
</tr >
</table >
</body >
</html >
cs
컨트롤러에서 게시물 갯수를 확인하는 count 변수의 값을 boardService에서 얻어 올 수 있게 변경
BoardController.java 중 일부
@RequestMapping("list.do" ) //세부적인 url pattern
public ModelAndView list(//RequestParam으로 옵션, 키워드, 페이지의 기본값을 각각 설정해준다.
@RequestParam(defaultValue= "1" ) int curPage,
@RequestParam(defaultValue= "all" ) String search_option,
@RequestParam(defaultValue= "" ) String keyword)
//defaultValue를 설정하지 않으면 null point 에러가 발생할수 있기 때문에 기본값을 설정해주어야 한다.
throws Exception{
//레코드 갯수 계산
int count = boardService.countArticle(search_option, keyword); //검색옵션과 키워드를 고려
//페이지 관련 설정, 시작번호와 끝번호를 구해서 각각 변수에 저장함
Pager pager= new Pager(count, curPage); //레코드 번호와 원하는 페이지의 번호를 주게 되면
int start= pager.getPageBegin();
int end= pager.getPageEnd();
cs
BoardServiceImpl.java 중 일부
@Override
public int countArticle(
String search_option, String keyword) throws Exception {
return boardDao.countArticle(search_option,keyword);
//boardDao를 호출해서 리턴
}
cs
BoardDAO.Impl.java 중 일부
//레코드 갯수 계산
//검색옵션과 키워드를 같이 전달해야하기 때문에 haspmap로 묶어서 같이 전달한다.
@Override
public int countArticle(
String search_option, String keyword) throws Exception {
Map< String ,String > map= new HashMap< > ();
map.put("search_option" , search_option);
map.put("keyword" , "%" + keyword+ "%" );
return sqlSession.selectOne("board.countArticle" ,map );
}
cs
boardMapper.xml 중 일부
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!-- 다른 mapper와 중복되지 않도록 네임스페이스 기재 -->
<mapper namespace ="board" >
<!-- 레코드 갯수 계산 -->
<select id ="countArticle" resultType ="int" >
select count(*)
from board b,member m
<include refid ="search" / >
</select >
<sql id ="search" >
<choose >
<!--검색옵션을 설정 글내용과, 작성자, 제목으로 검색할 수있도록 설정)
<!--검색 옵션에 따라 실행되는 구문이 틀려짐--!>
<when test ="search_option == 'all' " >
where b.writer=m.userid
and (name like '%'||#{keyword}||'%'
or content like '%' || #{keyword}||'%'
or title like '%'||#{keyword}||'%' ) //검색 옵션이 all이면 실행
</when >
<otherwise >
where b.writer=m.userid
and ${search_option} like '%'||#{keyword}||'%' //검색 옵션이 all이 아니면 실행되는 구문
</otherwise>
</choose >
</sql >
cs
Back-End/Spring 2019. 7. 2. 18:18
-게시글 쓰기 구현-
1. list.jsp 중 일부
글쓰기 링크 버튼과 스크립트 태그를 추가
<script >
$(function (){ //아이디가 btnWrite인 버튼을 누르게 되면 write.do 컨트롤러로 맵핑
$("#btnWrite" ).click(function (){
location .href= "${path}/board/write.do" ;
});
});
</script >
=========================================================================
<button type = "button" id = "btnWrite" >글쓰기</button >
cs
2. BoardController.java 중 일부
list.jsp 페이지에서 맵핑된 write.do가 맵핑된 메소드를 만듦
@RequestMapping("write.do" )
public String write() {
// 글쓰기 폼 페이지로 이동
return "board/write" ; //write.jsp 페이지로 이동
}
// write.jsp에서 입력한 내용들이 BoardDTO에 저장됨
cs
3. write.jsp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<% @ page language= "java" contentType= "text/html; charset=UTF-8"
pageEncoding= "UTF-8" %>
<!DOCTYPE html >
<html >
<head >
<meta http-equiv ="Content-Type" content ="text/html; charset=UTF-8" >
<title >Insert title here</title >
<% @ include file= "../include/header.jsp" %>
<script src ="${path}/include/js/common.js" ></script >
<!-- ckeditor의 라이브러리 -->
<script src ="${path}/ckeditor/ckeditor.js" ></script >
<script >
$(function (){ //id가 btnSave인 버튼을 클릭하면 실행되는 구문.
//post 방식으로 자료를 insert.do (컨트롤러)로 보낸다.
$("#btnSave" ).click(function (){
document .form1.submit();
});
});
</script >
</head >
<body >
<% @ include file= "../include/menu.jsp" %>
<h2 >글쓰기</h2 >
< form id = "form1" name = "form1" method = "post"
action = "${path}/board/insert.do" >
<div >제목 <input name ="title" id ="title" size ="80"
placeholder ="제목을 입력하세요" > <!-- 제목 입력 -->
</div >
<div style ="width:800px;" >
내용 <textarea id ="content" name ="content"
rows ="3" cols ="80" placeholder ="내용을 입력하세요" ></textarea ><!-- 내용 입력 -->
</div >
<div > 첨부파일을 등록하세요
<div class ="fileDrop" ></div >
<div id ="uploadedList" ></div >
</div >
<div style ="width:700px; text-align:center;" >
< button type = "button" id = "btnSave" >확인</ button >
</div >
</form >
</body >
</html >
cs
write.jsp 에서 입력한 내용들이 dto를 거쳐서 BoardController.java 페이지로 이동함
4. BoardController.java 중 일부
// write.jsp에서 입력한 내용들이 BoardDTO에 저장됨
@RequestMapping("insert.do" )
public String insert(@ModelAttribute BoardDTO dto
, HttpSession session ) //세션은 아이디를 확인하기위해서 필요하므로 세션을 가져온다.
throws Exception {
// 로그인한 사용자의 아이디
String writer= (String )session.getAttribute("userid" );
dto.setWriter(writer); //로그인한 아이디를 dto에 저장
//레코드 저장
boardService.create(dto); //서비스와 dao를 통해서 레코드에 추가가된다.
//게시물 목록으로 이동
return "redirect:/board/list.do" ;
}
cs
5. BoardServiceImpl.java 중 일부
@Override
public void create(BoardDTO dto) throws Exception {
boardDao.create(dto); //dto를 매개값으로 dao를 호출
}
cs
6. BoardDAOImpl.java 중 일부
@Override
public void create(BoardDTO dto) throws Exception {
sqlSession.insert("board .insert " , dto);
}
cs
7. boardMapper.xml 중 일부 (namespace와 id를 맵핑)
<!--다른 mapper과 중복되지 않도록 namespace를 기재--!>
<mapper namespace = "board">
<insert id="insert">
//게시글번호는 시퀀스로 하고 (생성할때마다 숫자가 1씩 증가하게함)
//제목
//글 내용
//작성자를 삽입함
insert into board (bno,title,content,writer) values
( seq_board.nextval, #{title}, #{content}, #{writer} )
</insert>
cs
8. 글번호를 시퀀스로 만들기 위해서 board 테이블을 삭제함
9. 게시물 번호 계산용 시퀀스를 생성
create sequence seq_board
start with 1
increment by 1;
cs
10. 게시글이 정상적으로 삽입되는지 확인해보기위해서 게시글을 삽입
insert into board (bno, title, content, writer) values
(seq_board.nextval, '제목' , '내용' , 'park' );
cs
11. 생성이 끝난후에는 작업완료를 하기위해 commit을 실시함.
-추가-
로그인 되어있지 않은 상태에서 게시글을 작성하려고 하면 에러가 발생하므로 servlet-context에 있는
인터셉터에 write.do와 insert.do를 호출할때 인터셉터를 거치도록 코드를 수정
인터셉터를 거치도록 하면 만약 로그인이 안된 상태에서 게시글을 작성을 하려고 하면 로그인을 먼저 하라고 로그인 창으로
이동시켜 준다.
(왜냐하면 인터셉터에 id값이 null일때 처리할 수 있는 코드가 작성되어 있기 때문에 이곳을 거쳐야 null 처리를 할 수 있기 때문에)
servlet-context 중 일부
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<beans:bean id ="loginInterceptor" class ="com.example.spring02.interceptor.LoginInterceptor" / >
==============================================================================================
<!-- 인터셉터 호출을 위한 url mapping -->
<!-- write.do나 insert.do를 호출하면 loginInterceptor 한번 거쳐 가라는 뜻--!>
<interceptors>
<interceptor>
<mapping path="/shop/**" />
<beans:ref bean="sampleInterceptor" />
</interceptor>
<interceptor>
<mapping path="/board/write.do" />
<mapping path="/board/insert.do" />
<mapping path="/board/update.do" />
<mapping path="/board/delete.do" />
<mapping path="/shop/cart/list.do"/>
<mapping path="/shop/cart/insert.do"/>
<beans:ref bean="loginInterceptor"/>
</interceptor>
cs
LoginInterceptor.java 중 일부
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//메인 액션이 실행되기 전
@Override
public boolean preHandle(HttpServletRequest request
, HttpServletResponse response, Object handler)
throws Exception {
//세션 객체 생성
HttpSession session= request.getSession();
//세션이 없으면(로그인되지 않은 상태)
if (session.getAttribute("userid" ) = = null ) {
//login 페이지로 이동
response.sendRedirect(request.getContextPath()
+ "/member/login.do?message=nologin" );
return false ; //메인 액션으로 가지 않음
}else {
return true ; //메인 액션으로 이동
}
}
cs
Back-End/Spring 2019. 7. 2. 16:20
- 게시글 목록 보기 구현 -
1. menu.jsp에 게시글 목록을 볼수있는 링크를 추가
<a href="${path}/board/list.do">게시판</a> |
cs
2. BoardDTO.java 클래스 추가
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
package com.example.spring02.model.board.dto;
import java.util.Arrays;
import java.util.Date;
//게시글을 저장할 dto 클래스
public class BoardDTO {
private int bno; //게시물 번호
private String title; //제목
private String content; //내용
private String writer; //작성자 아이디
private Date regdate; //날짜
private int viewcnt; //조회수
private String name; //작성자 이름 (member 테이블과 조인할것을 고려해서 만들었음)
private int cnt; //댓글 갯수
private String show; //화면 표시 여부
private String [] files; //첨부파일 배열
//getter,setter,toString()
public int getBno() {
return bno;
}
public void setBno(int bno) {
this .bno = bno;
}
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 getWriter() {
return writer;
}
public void setWriter(String writer) {
this .writer = writer;
}
public Date getRegdate() {
return regdate;
}
public void setRegdate(Date regdate) {
this .regdate = regdate;
}
public int getViewcnt() {
return viewcnt;
}
public void setViewcnt(int viewcnt) {
this .viewcnt = viewcnt;
}
public String getName() {
return name;
}
public void setName(String name) {
this .name = name;
}
public int getCnt() {
return cnt;
}
public void setCnt(int cnt) {
this .cnt = cnt;
}
public String getShow() {
return show;
}
public void setShow(String show) {
this .show = show;
}
public String [] getFiles() {
return files;
}
public void setFiles(String [] files) {
this .files = files;
}
@Override
public String toString () {
return "BoardDTO [bno=" + bno + ", title=" + title + ", content=" + content + ", writer=" + writer + ", regdate="
+ regdate + ", viewcnt=" + viewcnt + ", name=" + name + ", cnt=" + cnt + ", show=" + show + ", files="
+ Arrays.toString (files) + "]" ;
}
}
cs
3. BoardDAO.java 만들기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.example.spring02.model.board.dao;
import java.util.List;
import com.example.spring02.model.board.dto.BoardDTO;
public interface BoardDAO {
public void deleteFile(String fullName); //첨부파일 삭제
public List< String > getAttach(int bno); //첨부파일 정보
public void addAttach(String fullName); //첨부파일 저장
public void updateAttach(String fullName, int bno); //첨부파일 수정
public void create(BoardDTO dto) throws Exception; //글쓰기
public void update(BoardDTO dto) throws Exception; //글수정
public void delete(int bno) throws Exception; //글삭제
//목록 (페이지 나누기, 검색 기능 포함)
//매개변수는 시작 레코드번호, 끝번호, 옵션과 키워드가 들어간다)
public List < BoardDTO > listAll(
String search_option, String keyword, int start, int end)
throws Exception;
//조회수 증가 처리
public void increateViewcnt(int bno) throws Exception;
//레코드 갯수 계산
public int countArticle(
String search_option, String keyword) throws Exception;
//레코드 조회
public BoardDTO read(int bno) throws Exception;
}
cs
4. boardDAOImpl.java (인터페이스 구현 클래스) 중 일부
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.example.spring02.model.board.dao;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import org.apache.ibatis.session.SqlSession;
import org.springframework.stereotype.Repository;
import com.example.spring02.model.board.dto.BoardDTO;
@Repository // dao bean
public class BoardDAOImpl implements BoardDAO {
@Inject //의존관계 주입(Dependency Injection, DI)
SqlSession sqlSession;
//게시물 목록 리턴
@Override
public List< BoardDTO> listAll(
//매개변수는 시작 레코드번호, 끝번호, 옵션과 키워드가 들어간다)
String search_option, String keyword,int start, int end)
throws Exception {
return sqlSession.selectList("board.listAll" );
}
cs
5. boardMapper.xml 만들기
<!-- 다른 mapper와 중복되지 않도록 네임스페이스 기재 -->
<mapper namespace="board">
< select id = " listAll "
resultType ="com.example.spring02.model.board.dto.BoardDTO" >
<!-- 결과는 boardDTO타입이 된다. -->
select bno,title,writer,name,regdate,viewcnt
from board
order by bno desc
</select >
cs
6. boardService.java 만들기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.example.spring02.service.board;
import java.util.List;
import javax.servlet.http.HttpSession;
import com.example.spring02.model.board.dto.BoardDTO;
public interface BoardService {
public void deleteFile(String fullName); //첨부파일 삭제
public List< String > getAttach(int bno); //첨부파일 목록
public void create(BoardDTO dto) throws Exception; //글쓰기
public BoardDTO read(int bno) throws Exception; //글읽기
public void update(BoardDTO dto) throws Exception; //글 수정
public void delete(int bno) throws Exception; //글 삭제
//목록 (페이지 나누기, 검색 기능 포함)
//매개변수는 시작 레코드번호, 끝번호, 옵션과 키워드가 들어간다)
public List< BoardDTO> listAll(
String search_option, String keyword,int start, int end)
throws Exception;
//조회수 증가 처리
public void increaseViewcnt(int bno, HttpSession session) throws Exception;
//레코드 갯수 계산
public int countArticle( String search_option, String keyword) throws Exception;
}
cs
7. boardServiceImpl.java 만들기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.example.spring02.service.board;
import java.util.List;
import javax.inject.Inject;
import javax.servlet.http.HttpSession;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.example.spring02.model.board.dao.BoardDAO;
import com.example.spring02.model.board.dto.BoardDTO;
@Service // service bean
public class BoardServiceImpl implements BoardService {
@Inject //dao를 호출하기 때문에 의존성을 주입
BoardDAO boardDao;
@Override
public List< BoardDTO> listAll(
String search_option, String keyword,int start, int end)
throws Exception {
return boardDao.listAll(search_option,keyword,start,end);
}
cs
8. BoardController.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package com.example.spring02.controller.board;
import java.util.HashMap;
import java.util.List;
import javax.inject.Inject;
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import com.example.spring02.model.board.dto.BoardDTO;
import com.example.spring02.service.board.BoardService;
import com.example.spring02.service.board.Pager;
@Controller //controller bean
@RequestMapping("board/*" ) //공통적인 url pattern
public class BoardController {
@Inject
BoardService boardService; //서비스를 사용하기 위해 의존성을 주입
@RequestMapping("list.do" ) //세부적인 url pattern
public ModelAndView list() throws Exception{
List< BoardDTO> list=
boardService.listAll(search_option,keyword,start,end); //게시물 목록
ModelAndView mav= new ModelAndView();
Map< String ,Object> map= new HashMap< > (); //데이터가 많기 때문에 hashmap<>에 저장한다 .
map.put( "list" , list); //map에 list(게시글 목록)을 list라는 이름의 변수로 자료 저장
mav.addObject("map" , map); //ModelAndView에 map을 저장
return mav; // board/list.jsp로 이동
}
cs
9. list.jsp (view 파일)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<% @ page language= "java" contentType= "text/html; charset=UTF-8"
pageEncoding= "UTF-8" %>
<!DOCTYPE html >
<html >
<head >
<meta http-equiv ="Content-Type" content ="text/html; charset=UTF-8" >
<title >Insert title here</title >
<% @ include file= "../include/header.jsp" %>
</head >
<body >
<% @ include file= "../include/menu.jsp" %>
<h2 >게시판</h2 >
<table border ="1" width ="600px" >
<tr >
<th >번호</th >
<th >제목</th >
<th >이름</th >
<th >날짜</th >
<th >조회수</th >
</tr >
<!-- forEach var="개별데이터" items="집합데이터" -->
<c:forEach var ="row" items = "${map.list}" > <!-- 컨트롤러에서 map안에 list를 넣었기 때문에 이렇게 받는다. -->
<tr >
<td >${row.bno}</td > //글 번호
<td >${row.title}</td > //글 제목
<td >${row.writer}</td > // 작성자 이름
<td ><fmt:formatDate value = "{row.regdate}"
pattern ="yyyy-MM-dd HH:mm:ss" / ></td > //날짜의 출력형식을 변경
<td >${row.viewcnt}</td > //조회수
</tr >
</c:forEach >
</table >
</body >
</html >
cs
Back-End/Spring 2019. 7. 2. 12:24
-게시판의 주요 기능-
1. 기본 기능
ㄱ. 로그인 후 게시물 등록, 수정이 가능하도록 처리
ㄴ. 글쓰기 (스마트 에디터 적용, 태그 문자 처리, 공백 처리, 줄바꿈 처리)
ㄷ. 게시물 상세정보, 조회수 증가 처리
ㄹ. 게시물 수정
ㅁ. 게시물 삭제 (delete 방식)
ㅂ. 게시물 삭제 (update 방식)
ㅅ. 검색 기능
ㅇ. 페이지 나누기
2. 파일업로드 (ajax)
ㄱ. 게시판에 파일 첨부
ㄴ. 첨부파일 목록, 다운로드, 삭제
ㄷ. 수정화면에서 새로운 파일 올리기
3. 댓글 기능
ㄱ. 일반적인 방식으로 댓글쓰기 (RestController, REST 방식)
$.ajax( ) 함수 호출하여 insert
컨트롤러에서 뷰로 포워딩한 responseText를 html 태그에 출력
ㄴ. 컨트롤러에서 ArrayList json 형식으로 리턴받아 댓글 목록 출력 (list_json.do)
ㄷ. 목록에 댓글 갯수 표시
ㄹ. 댓글 페이지 나누기 (댓글 목록은 일반적인 방식으로 처리)
ㅁ. 비밀댓글 쓰기, 표시
ㅂ. REST 방식으로 댓글 쓰기 (insert_rest.do)
크롬 확장 프로그램을 이용한 입력 처리
{ "replytext" : "댓글", "replyer" : "kim", "bno" : "1", "secret_reply" : "n" }
ㅅ. REST 방식으로 댓글 목록 출력
ㅇ. REST 방식으로 댓글 상세 화면, 수정, 삭제 기능 구현
4. 페이지 나누기
- 페이지당 게시물수 : 10개
- 전체 게시물수 : 991개
- 몇 페이지? : 100
991 / 10 => 99.1 올림 => 100
- 페이지의 시작번호, 끝번호 계산
where rn between 1 and 10
1페이지 => 1 ~ 10
2페이지 => 11 ~ 20
.....
11페이지 => 101 ~ 110
57페이지 => 561 ~ 570
99페이지 => 981 ~ 990
100페이지 => 991 ~ 1000
시작번호 = (현재페이지 - 1) * 페이지당 게시물수 + 1
1페이지 => (1-1) * 10 + 1 => 1
2페이지 => (2-1) * 10 + 1 => 11
7페이지 => (7-1) * 10 + 1 => 61
끝번호 = 시작번호 + 페이지당 게시물수 - 1
1페이지 => 1 + 10 - 1 => 10
2페이지 => 11 + 10 -1 => 20
* 전체 페이지 블록수
전체 페이지 갯수 / 10
91 / 10 => 9.1 => 10개
예시 )
1 2 3 4 5 6 7 8 9 10 [다음]
[이전] 11 12 13 14 15 16 17 18 19 20 [다음]
현재 페이지가 속한 블록
(현재 페이지 - 1) / 페이지 블록 단위 + 1
1페이지 => 몇번째 블록? 1
(1-1) / 10 + 1 => 1
9페이지 => 1블록
(9-1) / 10 + 1 => 1
11페이지 => 2블록
(11-1) / 10 + 1 => 2
57페이지
(57-1) / 10 + 1 => 6
* 페이지 블록의 시작번호
(현재블록 - 1) * 블록단위 + 1
1블록 => (1-1) * 10 + 1 => 1
2블록 => (2-1) * 10 + 1 => 11
6블록 => (6-1) * 10 + 1 => 51
* 페이지 블록의 끝번호
블록 시작번호 + 블록단위 - 1;
1블록 => 1 + 10 - 1 => 10
2블록 => 11 + 10 - 1 => 20
6블록 => 51 + 10 - 1 => 60
-데이터베이스 게시판 테이블 만들기-
drop table board cascade constraints; // 기존의 테이블과 중복되지 않도록 제약조건까지 모두 삭제
cs
create table board ( //테이블 생성
bno number not null , // 게시물 번호
title varchar2(200 ) not null , // 제목
content varchar2(4000 ), // 내용
writer varchar2(50 ) not null , // 작성자 아이디 (member테이블과 join할 예정)
regdate date default sysdate, // 작성 날짜 (기본값으로 현재 시간이 들어가게됨)
viewcnt number default 0 , // 조회수 (기본값이 0이기 때문에 0부터 시작)
primary key (bno) //기본키는 게시물 번호로 설정함
);
cs
insert into board (bno,title,content,writer) values
(( select nvl(max(bno) + 1 , 1 ) from board) ,'제목' ,'내용' ,'pard' );
//게시물의 번호를 시퀀스를 이용해도 되지만 객체를 따로 관리해야 하기때문에
//서브쿼리를 작성하여서 관리
기본적으로 board에서 가장 큰 게시글 번호에서 1을 더하거나, 값이 없을때 (초기값) 은 1을 준다는 쿼리이다.
cs
Back-End/Spring 2019. 7. 2. 11:29
Smart Editor
WYSIWYG Editor (위지윅 에디터, whar you see is what you ger)
- 사용자가 현재 화면에서 보고 있는 내용과 동일한 html code를 생성하는 에디터
- 네이버, 다음 에디터, CKEditor, SummerNote 등
CKEditor과 SummerNote의 차이점 CKEditor : 이미지 파일은 디렉토리에 저장, 테이블에는 링크만 저장 Summernote : 이미지를 테이블에 저장 (이미지를 테이블에 저장하면 DB가 과부하가 걸릴 수 있다.)
가. CKEditor
- http://ckeditor.com
- 자바스크립트 기반 위지윅 웹에디터, 쉬운 이식, 웹사이트 구축언어가 asp든, php든 관계없이 도입하기가 용이하다.
- 현재 CKEditor5 버전이 최신 버전이지만 여기서는 안정화된 버전인 CKEditor 4.11.2 버전으로 실습함
- 이미지 업로드를 위해서는 별도의 작업이 필요함 (컨트롤러를 미리 호출하는 등의 작업이 필요..)
-다운로드 방법-
1. https://ckeditor.com/ckeditor-4/download/ 접속
2. Standard Package 다운로드
3. 다운로드 후 압축을 풀고 ckeditor 디렉토리를 views에 붙인다.
- ckeditor 실습 예제 -
view 하위의 디렉토리들은 기본적으로 selvlet으로 인식이 된다. 그렇기 때문에 리소스로 등록을 하지 않으면 링크가 되지 않고 404에러가 발생을 하게 된다. 그렇기 때문에 servlet-context.xml에 리소스 매핑을 해주어야 한다.
1. servlet-context.xml에 리소스 매핑을 추가
<resources location="/WEB-INF/views/ckeditor/" mapping="/ckeditor/**"></resources>
cs
2. ckeditor.js 참조 (ckeditor을 붙이고 싶은 파일로 이동해서 태그를 넣으면 된다)
<script src = "${path}/ckeditor/ckeditor.js"></script>
cs
3. ckeditor를 적용할 태그에 작성
(WEB-INF/views/shop/product_write.jsp 와 product_edit.jsp 에 적용해보기)
1
2
3
4
5
6
7
8
9
10
11
12
13
<div style = "width:800px;" >
내용
<textarea id = "description" name = "description" rows = "5" cols = "80"
placeholder = "상품설명을 입력하세요" ></textarea > //이런 textarea가 있을때 주로 스마트에디터를 많이 사용
<!--textarea를 스마트에디터로 변경함--!>
<script>
//CKEDITOR.replace("description"); //태그의 id
//이미지 업로드를 할 경우
CKEDITOR.replace("description",{
// CKEDITOR.replace와 id("description")를 잘 적어주면 그 태그가 smart editor 스타일로 바뀌게 된다.
filebrowserUploadUrl : "${path}/imageUpload.do"
//파일을 업로드 해야하기 때문에 filebrowserUploadUrl을 사용하고, 서버쪽에 코드를 완성해주어야 한다.
});
</script>
</div>
cs
4. product_write.jsp 에 ckeditor을 적용하기 위한 코드를 추가
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<% @ page language= "java" contentType= "text/html; charset=UTF-8"
pageEncoding= "UTF-8" %>
<!-- views/shop/product_write.jsp -->
<!DOCTYPE html >
<html >
<head >
<meta http-equiv ="Content-Type" content ="text/html; charset=UTF-8" >
<title >Insert title here</title >
<% @ include file= "../include/header.jsp" %>
<!-- ckeditor 사용을 위해 js 파일 연결 -->
< script src = "${path}/ckeditor/ckeditor.js" > < / script >
</head >
<body >
<% @ include file= "../include/admin_menu.jsp" %>
============중간생략==========================
<tr >
<td >상품설명</td >
<td ><textarea rows ="5" cols ="60"
name ="description" id ="description" ></textarea >
< script >
//id가 description인 태그에 ckeditor를 적용시킴
//이미지 업로드 안됨
CKEDITOR.replace( "description" ,{
filebrowserUploadUrl : "${path}/imageUpload.do"
}); //이미지 업로드 기능을 추가하기위한 코드
< / script >
</td >
</tr >
<tr >
<td >상품이미지</td >
<td >
<input type ="file" name ="file1" id ="file1" >
</td >
</tr >
cs
5. ImageUploadController.java (컨트롤러)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package com.example.spring02.controller.upload;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;
@Controller // 컨트롤러 어노테이션
public class ImageUploadController {
// 컨트롤러클래스의 로그를 출력
private static final Logger logger = LoggerFactory.getLogger(ImageUploadController.class );
// 이미지 업로드
// product_edit페이지에서 맵핑되는 메소드
@RequestMapping("imageUpload.do" )
// 이미지를 저장하고, 불러오고, 업로드하기위해 매개변수를 선언
public void imageUpload(HttpServletRequest request, HttpServletResponse response, @RequestParam MultipartFile upload )
//MultipartFile 타입은 ckedit에서 upload란 이름으로 저장하게 된다
throws Exception {
// 한글깨짐을 방지하기위해 문자셋 설정
response.setCharacterEncoding("utf-8" );
// 마찬가지로 파라미터로 전달되는 response 객체의 한글 설정
response.setContentType("text/html; charset=utf-8" );
// 업로드한 파일 이름
String fileName = upload.getOriginalFilename();
// 파일을 바이트 배열로 변환
byte [] bytes = upload.getBytes();
// 이미지를 업로드할 디렉토리(배포 디렉토리로 설정)
String uploadPath = "D:\\work\\.metadata\\.plugins\\org.eclipse.wst.server.core\\tmp1\\wtpwebapps\\spring02\\WEB-INF\\views\\images\\" ;
프로젝트는 개발 디렉토리에 저장이 되는데 이미지를 업로드할 디렉토리를 개발 디렉토리로 설정하면 일일이 새로고침을 해주어야되서
불편하기 때문에 이미지를 업로드할 디렉토리를 배포 디렉토리로 설정한다.
OutputStream out = new FileOutputStream(new File(uploadPath + fileName));
// 서버로 업로드
// write메소드의 매개값으로 파일의 총 바이트를 매개값으로 준다.
// 지정된 바이트를 출력 스트립에 쓴다 (출력하기 위해서)
out .write(bytes);
// 클라이언트에 결과 표시
String callback = request.getParameter("CKEditorFuncNum" );
// 서버=>클라이언트로 텍스트 전송(자바스크립트 실행)
PrintWriter printWriter = response.getWriter();
String fileUrl = request.getContextPath() + "/images/" + fileName;
printWriter.println ("<script>window.parent.CKEDITOR.tools.callFunction(" + callback + ",'" + fileUrl
+ "','이미지가 업로드되었습니다.')" + "</script>" );
printWriter.flush();
}
}
cs
만약에 에러가 발생할 경우
ckeditor / config.js 파일을 열고 마지막 라인에 추가
config.filebrowserUploadMethod = 'form';
ckeditor 적용 화면
나. SummerNote
- http://summernote.org (우리나라 개발자들이 만듦)
- 이미지를 텍스트 형식으로 저장함
- 적용 예시
1. http://summernote.org 접속해서 SummerNote 다운로드
2. 압축을 풀고 dist 디렉토리를 views에 붙이고 이름을 summernote로 변경한다.
3. servlet-context.xml에 리소스 매핑 추가
(location이 실제 경로이고, mapping가 url 상의 경로를 의미한다)
<resources location="/WEB-INF/views/summernote/" mapping="/summernote/**"></resources>
cs
4. product_write.jsp (상품정보 쓰기 페이지)에 summernote를 적용
4-1 적용시킬 코드를 찾기 위해 summernote 홈페이지로 이동하고, Getting started를 클릭
4-2 스크롤바를 밑으로 내려서 Include js/css 안에 있는 코드를 복사한다. ( summernote 라이브러리를 사용하기 위한 코드)
5. product_write.jsp (상품정보 쓰기 페이지)에 summernote를 적용 시키는 코드를 추가
(복사한 코드를 붙여넣기)
product_write.jsp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
<%@ page language ="java" contentType ="text/html; charset=UTF-8"
pageEncoding ="UTF-8" % >
<!-- views/shop/product_write.jsp -->
<!DOCTYPE html >
<html >
<head >
<meta http-equiv ="Content-Type" content ="text/html; charset=UTF-8" >
<title >Insert title here</title >
<%@ include file ="../include/header.jsp" % >
<!-- include libraries(jQuery, bootstrap) -->
<!-- summernote홈페이지에서 받은 summernote를 사용하기 위한 코드를 추가 -->
< link href = "http://netdna.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.css" rel = "stylesheet" >
< script src = "http://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.js" > < / script >
< script src = "http://netdna.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.js" > < / script >
<!-- include summernote css/js -->
<!-- 이 css와 js는 로컬에 있는 것들을 링크시킨 것이다. -->
< link href = "http://cdnjs.cloudflare.com/ajax/libs/summernote/0.8.12/summernote.css" rel = "stylesheet" >
< script src = "http://cdnjs.cloudflare.com/ajax/libs/summernote/0.8.12/summernote.js" > < / script >
< %-- < !-- ckeditor 사용을 위해 js 파일 연결 -- >
같은 페이지에 스마트에디터를 동시에 2개를 사용할 수 없으므로 ckeditor은 주석처리 한다.
< script src = "${path}/ckeditor/ckeditor.js" > < / script > --%>
</head >
<body >
<%@ include file ="../include/admin_menu.jsp" % >
< script >
//id가 description인 것을 summernote 방식으로 적용하라는 의미이다.
//높이와 넓이를 설정하지 않으면 화면이 작게 나오기때문에 설정해주어야 한다.
$( function (){
$( "#description" ).summernote({
height : 300 ,
width : 800
});
});
function product_write(){
// 태그를 name으로 조회할 경우
//var product_name=document.form1.product_name.value;
// 태그를 id로 조회할 경우
var product_name= $("#product_name" ).val();
var price= $("#price" ).val();
var description= $("#description" ).val();
if (product_name= = "" ){ //빈값이면
alert ("상품이름을 입력하세요" );
$("#product_name" ).focus(); //입력포커스 이동
return ; //함수 종료, 폼 데이터를 제출하지 않음
}
if (price= = "" ){
alert ("가격을 입력하세요" );
$("#price" ).focus();
return ;
}
/* if(description==""){
alert("상품 설명을 입력하세요");
$("#description").focus();
return;
} */
//폼 데이터를 받을 주소
document .form1.action= "${path}/shop/product/insert.do" ;
//폼 데이터를 서버에 전송
document .form1.submit();
}
</script >
<h2 >상품 등록</h2 >
<form name ="form1" method ="post"
enctype ="multipart/form-data" >
<table >
<tr >
<td >상품명</td >
<td ><input name ="product_name" id ="product_name" ></td >
</tr >
<tr >
<td >가격</td >
<td ><input name ="price" id ="price" ></td >
</tr >
<tr >
<td >상품설명</td >
<td ><textarea rows ="5" cols ="60"
name ="description" id ="description" ></textarea >
< script >
//위쪽과 마찬가지로 같은페이지에서 스마트에디터를 동시에 2개를 사용할 수 없으므로 주석처리 한다.
//id가 description인 태그에 ckeditor를 적용시킴
//CKEDITOR.replace("description"); //이미지 업로드 안됨
/* CKEDITOR.replace("description",{
filebrowserUploadUrl : "${path}/imageUpload.do"
}); */
< / script >
</td >
</tr >
<tr >
<td >상품이미지</td >
<td >
<input type ="file" name ="file1" id ="file1" >
</td >
</tr >
<tr >
<td colspan ="2" align ="center" >
<input type ="button" value ="등록"
onclick ="javascript:product_write()" >
<input type ="button" value ="목록"
onclick ="location.href='${path}/shop/product/list.do'" >
</td >
</tr >
</table >
</form >
</body >
</html >
cs
summernote 적용 화면
아래 책은 제가 공부할때 활용했던 책으로 추천드리는 책이니 한번씩 읽어보시는것을 추천드립니다!! ㅎㅎ
이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.
Back-End/Spring 2019. 7. 1. 15:54
-이메일 서비스 구축 방법-
이메일 발송을 위해서는 메일 서버가 필요
* 서버 구축 방법 * - 자체 구축 - 포털사이트에서 제공하는 smtp(Simple Mail Transfer Protocol) server 활용 - gmail의 경우 : 내 계정 - 로그인 및 보안 - 기기 활동 및 보안관련 활동 - 보안 수준이 낮은 앱 허용 옵션을 사용으로 설정해야 함 - 여기에서는 gmail을 활용할 예정
-실습 예제-
1) pom.xml에 라이브러리 추가
빌드가 잘 안될 경우 스프링의 버전을 조정할 필요가 있음
비슷한 이름의 라이브러리가 있으므로 artifactld (버전 정보를 생략한 jar파일의 이름)를 정확히 확인하고 추가해야 함
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!-- 이메일 발송 관련 라이브러리 -->
<dependency >
<groupId >javax.mail </groupId >
<artifactId >mail</artifactId >
<version >1.4.7</version >
</dependency >
<dependency >
<groupId >org.springframework</groupId >
<artifactId >spring-context-support</artifactId >
<version >${org.springframework-version}</version >
</dependency >vax.mail</groupId >
<artifactId >mail</artifactId >
<version >1.4.7</version >
</dependency >
<dependency >
<groupId >org.springframework</groupId >
<artifactId >spring-context-support</artifactId >
<version >${org.springframework-version}</version >
</dependency >
cs
2) root-context.xml 에 메일 발송을 위한 bean을 등록
(계정이 등록되어 있어야 하기 때문)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- 이메일 발송 bean 설정 -->
<bean id ="mailSender"
class ="org.springframework.mail.javamail.JavaMailSenderImpl" >
<property name ="host" value ="smtp.gmail.com" / > <!-- gmail을 사용 -->
<property name ="port" value ="587" / >
< property name = "username" value = "이메일주소" / >
< property name = "password" value = "비밀번호" / >
<property name ="javaMailProperties" >
<props >
<prop key ="mail.transport.protocol" >smtp</prop >
<prop key ="mail.smtp.auth" >true</prop >
<prop key ="mail.smtp.starttls.enable" >true</prop >
<prop key ="mail.debug" >true</prop >
</props >
</property >
</bean >
</beans >
cs
3) EmailDTO.java 만들기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.example.spring02.model.email;
public class EmailDTO {
private String senderName; //발신자 이름
private String senderMail; //발신자 이메일 주소
private String receiveMail; //수신자 이메일 주소
private String subject; //제목
private String message; //본문
//getter,setter,toString
public String getSenderName() {
return senderName;
}
public void setSenderName(String senderName) {
this .senderName = senderName;
}
public String getSenderMail() {
return senderMail;
}
public void setSenderMail(String senderMail) {
this .senderMail = senderMail;
}
public String getReceiveMail() {
return receiveMail;
}
public void setReceiveMail(String receiveMail) {
this .receiveMail = receiveMail;
}
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this .subject = subject;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this .message = message;
}
@Override
public String toString () {
return "EmailDTO [senderName=" + senderName + ", senderMail=" + senderMail + ", receiveMail=" + receiveMail
+ ", subject=" + subject + ", message=" + message + "]" ;
}
}
cs
4) EmailService.java 만들기
package com.example.spring02.service.email;
import com.example.spring02.model.email.EmailDTO;
public interface EmailService {
public void sendMail(EmailDTO dto);
}
cs
5)EmailServiceImpl.java 만들기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package com.example.spring02.service.email;
import javax.inject.Inject;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMessage.RecipientType;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;
import com.example.spring02.model.email.EmailDTO;
@Service // 서비스 빈으로 등록
public class EmailServiceImpl implements EmailService {
@Inject
JavaMailSender mailSender; // root-context.xml에 설정한 bean, 의존성을 주입
@Override
public void sendMail(EmailDTO dto) {
try {
// 이메일 객체
MimeMessage msg = mailSender.createMimeMessage();
// 받는 사람을 설정 (수신자, 받는사람의 이메일 주소 객체를 생성해서 수신자 이메일주소를 담음)
msg.addRecipient(RecipientType.TO, new InternetAddress(dto.getReceiveMail()));
/*
* createMimeMessage() : MimeMessage객체를 생성시킴 (이것을 이용해서 메시지를 구성한 뒤 메일 발송)
* addRecipient() : 메시지의 발신자를 설정 InternetAddress() : 이메일 주소 getReceiveMail() :
* 수신자 이메일 주소
*/
// 보내는 사람(이메일주소+이름)
// (발신자, 보내는 사람의 이메일 주소와 이름을 담음)
// 이메일 발신자
msg.addFrom(new InternetAddress[] { new InternetAddress(dto.getSenderMail(), dto.getSenderName()) });
// 이메일 제목 (인코딩을 해야 한글이 깨지지 않음)
msg.setSubject(dto.getSubject(), "utf-8" );
// 이메일 본문 (인코딩을 해야 한글이 깨지지 않음)
msg.setText(dto.getMessage(), "utf-8" );
// html로 보낼 경우
// MimeMessage message = mailSender.createMimeMessage();
// MimeMessageHelper helper
// = new MimeMessageHelper(message, true);
// helper.setTo("test@host.com");
// helper.setText("<html><body><img src='cid:identifier1234'></body></html>", true);
// 이메일 보내기
mailSender.send(msg);
} catch (Exception e) {
e.printStackTrace();
}
}
}
cs
6) admin_menu.jsp 에 이메일 발송 하이퍼링크를 추가
<a href="${path}/email/write.do">이메일 발송</a> |
cs
7) EmailController.java 만들기 (컨트롤러)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.example.spring02.controller.email;
import java.util.List;
import javax.inject.Inject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import com.example.spring02.model.email.EmailDTO;
import com.example.spring02.service.email.EmailService;
@Controller // 컨트롤러 어노테이션 선언
@RequestMapping("email/*" ) // 공통적인 매핑 주소
public class EmailController {
@Inject
EmailService emailService; // 서비스를 호출하기위한 의존성 주입
@RequestMapping("write.do" ) // 이메일 쓰기를 누르면 이 메소드로 맵핑되어서
public String write() {
return "/email/write" ; // 다시 write jsp 페이지로 이동하고 jsp페이지에서 내용을 다 채운 뒤에 확인 버튼을 누르면 send.do로 넘어감
}
@RequestMapping("send.do" ) // 확인 (메일발송) 버튼을 누르면 맵핑되는 메소드
public String send(@ModelAttribute EmailDTO dto, Model model) {
try {
emailService.sendMail(dto); // dto (메일관련 정보)를 sendMail에 저장함
model.addAttribute("message" , "이메일이 발송되었습니다." ); // 이메일이 발송되었다는 메시지를 출력시킨다.
} catch (Exception e) {
e.printStackTrace();
model.addAttribute("message" , "이메일 발송 실패..." ); // 이메일 발송이 실패되었다는 메시지를 출력
}
return "/email/write" ; // 실패했으므로 다시 write jsp 페이지로 이동함
}
}
cs
8) write.jsp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<% @ page language= "java" contentType= "text/html; charset=UTF-8"
pageEncoding= "UTF-8" %>
<!DOCTYPE html >
<html >
<head >
<meta http-equiv ="Content-Type" content ="text/html; charset=UTF-8" >
<title >Insert title here</title >
<% @ include file= "../include/header.jsp" %>
</head >
<body >
<% @ include file= "../include/admin_menu.jsp" %>
<h2 >이메일 보내기</h2 >
<form method ="post" action ="${path}/email/send.do" >
<!-- post방식으로 자료를 컨트롤러로 보냄 -->
발신자 이름 : <input name ="senderName" ><br >
발신자 이메일 : <input name ="senderMail" ><br >
수신자 이메일 : <input name ="receiveMail" ><br >
제목 : <input name ="subject" ><br >
내용 : <textarea rows ="5" cols ="80" name ="message" ></textarea >
<br >
<input type ="submit" value ="전송" >
</form >
<span style ="color:red;" >${message}</span >
</body >
</html >
cs
아래 책은 제가 공부할때 활용했던 책으로 추천드리는 책이니 한번씩 읽어보시는것을 추천드립니다!! ㅎㅎ
이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.
Back-End/API 2019. 7. 1. 15:26
javax.mail 패키지의 주요 클래스는
Session, Message, Address, Authenticator, Transport Store, Folder 등이 있다.
이들은 메시지를 만들고 전송하고 또 메일 서버에 있는 메시지를 꺼내오는데 개념적으로 중요한 역할을 한다. 이러한 클래스들의 주요 기능과 역할은 다음과 같다.
Session
메일 처리를 시작하는데 필요한 클래스로서 메일 처리 환경을 설정하고, 처리에 필요한 객체들을 생성해내는 역할을 한다. 메일 작업을 시작하려면 우선 Session 객체가 필요하다.환경 설정을 위하여 java.util.Properties 를 사용하여 파라미터를 사용하며, 새로 객체를 생성하는 constructor는 private이기 때문에 사용할 수 없고, 다음과 같이 getDefaultInstance 메소드를 통하여 객체를 얻을 수 있다.
Properties props = new Properties(); // props 에 적절한 파라미터를 채움
Session session = Session.getInstance(props, null);
getDefaultInstance의 두번째 파라미터는 Authenticator인데 이는 차 후에 다루도록 하겠다.
Message
메일의 메시지를 의미하는 클래스로서 수신자/송신자/유형 등 헤더 부분과 본문 부분등 을 처리할 수 있도록 만들어진 클래스이다. 메시지의 기초 부분을 의미하는 Part 인터페이스를implement 하고 있으며 Part는 메시지가 갖추어야한 기본적 요소를 정의하고 있다. Message는 기본적으로 추상 클래스이므로 실제 이를 상속받아 구체적 인터넷 메일 메시지를 구현한 클래스를 사용하기 위해서는 javax.mail.internet.MimeMessage를 주로 사용한다.
다음은 간단한 메시지를 생성해내는 예제이다.
MimeMessage message = new MimeMessage(session); message.setSubject("테스트 제목"); message.setText("테스트 메일 입니다.");
Address
메시지를 송/수신하는 메일 주소를 추상한 클래스이다. 이 클래스도 추상 클래스이므로인테넷 메일을 주소 클래스를 사용하려면 javax.mail.internet.InternetAddress 를 사용한다.
다음은 " xxx@javanuri.com "이라는 인터넷 주소를 InternetAddress클래스를 사용하한 객체로 만들어 Message의 setFrom 메소드를 이용하여 송신자를 표시하는 예제이다.
Address address = new InternetAddress(" xxx@javanuri.com ", "홍길동"); message.setFrom(address);
메일 메시지의 수신자를 지정하는데는 Message 클래스의 addRecipient 메소드를 사용하는데 이 메소드 역시 Address 객체를 파라미터로 받아들인다. 송신자의 구분은 Message 객체에 RecipientType 이라는 상수로 정의되어 있다. 다음은 송신자, 참조자, 숨김 참조자를 메시지에 지정하는 예제이다.
Address toAddr = new InternetAddress(" xxx@javanuri.com "); Address ccAddr = new InternetAddress(" yyy@javanuri.com "); Address bccAddr = new InternetAddress(" zzz@javanuri.com ");
message.addRecipient(Message.RecipientType.TO, toAddr); message.addRecipient(Message.RecipientType.CC, ccAddr); message.addRecipient(Message.RecipientType.BCC, bccAddr);
Authenticator
주로 네트웍이나 서버에 연결할 때 사용자 아이디 및 암호 확인에 사용되는 추상 클래스이다. 사용자 인증을 위하여 Authenticator 클래스를 사용하려면 이 클래스를 상속을 받아 자신만의 클래스를 만들어 인증 방법을 구현해야 한다. 주로 사용자 인증에 관한 자신만의 고유 방법이 있거나 사용자 아이디와 암호를 묻는 화면을 만들때 사용한다. 상속하는 클래스를 만들 때는Authoticator에 있는 getXXX() 메소드를 override 하면 된다.
get 메소드 중에서 getPasswordAuthentication()은 사용자 아이디와 암호가 있는 PasswordAuthentication 객체를 리턴하도록 되어 있는데 PasswordAuthentication 객체는 화면 또는 적절한 방법에 의하여 아이디와 암호를 얻은 후에 생성하여 리턴하면 된다.
Session 생성시에 Authenticator를 파라미터로 주면 session이 필요한 시점에 사용자 아이디와 암호를 묻을 수 있도록 응용프로그램을 작성할 수 있다. 다음은 MyAuthenicator라는 새로운 클래스를 Authenticator로부터 상속받아 만들었다고 가정하고 session을 생성하는 예제이다.
Properties props = new Properties(); Authenticator auth = new MyAuthenticator(); Session session = Session.getDefaultInstance(props, auth);
Transport
메세지를 최종적으로 전달하는 클래스이다. 이 클래스도 추상 클래스로서 구체적 구현은 프로토콜 마다 다르게 할 수 있도록 구성되어 있다. 하지만 인터넷 메일을 일반적으로 SMTP 프로토콜에 의하여 전달된다. 메시지를 기본 SMTP 프로토콜을 이용하여 전달하려면 static 메소드인 Transport.send (Message msg)를 이용하면 된다. 프로토콜에 의하여 메시지를 전달하려면 다음 예제와 같이 Session 객체의 getTransport 메소드를 이용한다.
Transport trans = session.getTransport("smtp"); trans.connect(" www.javanuri.com ", "xxxx", "yyyy"); trans.sendMessage(message, message.getAllRecipients()); trans.close();
Store
메시지를 저장하고 있는 장소를 추상화한 클래스인데, 메시지 저장은 메일 서버에서 하게된다. Store는 메일 서버와 연결이 되어 있어 저장된 메시지를 Store 객체를 통하여 가져올 수 있다. 다음은 Session 객체에서 POP3 프로토콜을 이용하여 Store 객체를 가져와 서버와 연결하는 예제이다.
Store store = session.getStore("pop3"); store.connect(host, username, password);
Folder
메시지를 분류해 놓은 장소를 추상화한 클래스이다.
서버에 따라 메시지는 수신함 "INBOX", 발신함 "OUTBOX" 등 여러가지 폴더를 갖을 수 있다.
하지만 POP3 프로토콜을 "INBOX" 폴더만을 지원한다.
폴더는 메시지를 저장하고 있는 Store 객체 안에서 찾을 수 있다.
다음은 Store 객체로부터 "INBOX" 폴더를 가져와 그 안에 있는 메시지를 가져오는 예제이다.
Folder folder = store.getFolder("INBOX"); folder.open(Folder.READ_ONLY); Message message[] = folder.getMessages();
Part
Message 와 BodyPart등의 메일을 이루는 부분들을 공통적 기반이되는 interface로서 내용과 그 내용에 대한 속성들로 이루어져 있다. 하나의 메일에는 여러 부분의 내용이 서로 형식을 달리하여 있을 수 있고 또 첨부 화일이나 자료가 여러가지가 있을 수 있다. 이러한 하나 하나의 구성요소가 Part라 볼 수 있고, 여러가지 형태가 복합되어 있는 것을 multipart라 한다. 이러한 메일 메시지의 기반을 이루고 있는 것이 바로 Part interface이다.
위에서 다룬 javax.mail 패키지들의 모두 추상 클래스이거나 인터페이스 이다.
이를 상속 받아 인터넷 메일 쪽으로 구현한 패키지는 javax.mail.internet 패키지이다.
한지만 javax.mail.internet 패키지에서도 구체적 MIME 형태의 메시지인 MimeMessage를 주로 다룬다. 즉 메시지 내용을 어떻게 구성하는지에 관한 클래스들이다. 메일에서 중요한 전송과 수신 즉 Transport와 Store에 관한 구현클래스는 없다. 기본적으로 Transport와 Store는 제 3의 제공자 (Provider)가 해당 프로토콜에 맞도록 구현하여 제공하도록 되어 있다. JavaMail 패키지에는 SUN사가 Provider가 되어 Transport와 Store 클래스를 상속받아 만든 com.sun.mail 패키지가 클래스 형태로 포함되어 있고 디폴트로 이 클래스를 사용하도록 설정되어 있다. 어떤 Provider가 제공하는 클래스들을 사용할 것인가는 Session 객체가 설정된 환경 및 properties를 가지고 결정을 한다. Session 객체는 다음 이름의 파일들을 읽어들여 Provider를 결정하고 제공하는 클래스들을 사용한다.
javamail.providers javamail.default.providers
위의 파일들의 위치를 찾는 순서는 우선 JRE의 홈디렉토리, 다음의 Jar 파일의 META-INF 등의 순서로 다음과 같이 찾는다.
1. java.home/lib/javamail.X 2. META-INF/javamail.X 3. META-INF/javamail.default.X
다음의 JavaMail 패키지의 mail.jar안에 있는 META-INF/javamail.default.providers의 내용이다. 이러한 내용이 기본적으로 설정되어 있기 때문에 Session 객체는 com.sun.mail 패키지의 클래스들을 사용하는 것이다. 만일 제3의 Vendor가 제공하는 클래스를 사용하려면 다음 내용을 바꾸어주면 된다.
javamail.default.providers ====================================================
# JavaMail IMAP provider Sun Microsystems, Inc protocol=imap; type=store; class=com.sun.mail.imap.IMAPStore; vendor=Sun Microsystems, Inc; # JavaMail SMTP provider Sun Microsystems, Inc protocol=smtp; type=transport; class=com.sun.mail.smtp.SMTPTransport; vendor=Sun Microsystems, Inc; # JavaMail POP3 provider Sun Microsystems, Inc protocol=pop3; type=store; class=com.sun.mail.pop3.POP3Store; vendor=Sun Microsystems, Inc
출처: https://jmoonyoung.tistory.com/entry/펌자바메일javamail-관련 [무녕이 스토리지]
Back-End/Spring 2019. 7. 1. 15:16
일반적인 bean 선언
<bean id = "testBean" class = "com.ljw.TestBean" />
id : spring container 에서 유일하게 식별할 수 있는 이름
class : 해당하는 bean의 full path (경로)
id대신 name 속성을 사용할 수 있음
<bean name = "testBean" class = "com.ljw.TestBean" />
facotry 클래스의 getInstace 를 통한 bean을 설정
<bean id = "testFactory" class = "com.ljw.TestFactory"
factory-method = "getInstance" />
factory-method : 해당 클래스의 객체를 반환해주는 메소드 (singleton 에서)
생성자를 통한 bean 설정
<bean id="testService" class="com.ljw.Service"> <constructor-arg> <ref bean="testDao"/> </constructor-arg> </bean> <bean id="testDao" class="com.ljw.TestDao"/> ref : reference, 즉 testDao id(혹은 name)를 갖는 bean을 생성자의 인자로 넘겨주겠다는 의미 <bean id="testService" class="com.ljw.Service"> <constructor-arg ref="testDao"/> </bean>
이것 역시 위와 같은 의미 생성자에 특정 값을 넣어줄 때 <bean id="testService" class="com.ljw.Service"> <constructor-arg> <value> 10 </value> </constructor-arg> </bean> 혹은 다음과 같이 작성가능 <bean id="testService" class="com.ljw.Service"> <constructor-arg value="10"/> </bean> <value type="long"> 3000 </value> 같이 value 값의 type을 지정해줄수도 있다(기본적으로 string으로 인식) 즉 생성자가 여러가지 유형이 있을경우 위와같이 type을 설정해주지 않으면 기본적으로 string type의 생성자를 먼저 고려하게됨 property bean 설정 방식(setXXX 함수 를 이용하여 bean 설정) <bean id="testServcie" class="con.ljw.TestService" > <property name="testDao"> <ref bean="implTestDao"/> </property> </bean> <bean id="implTestDao" class="con.ljw.ImplTestDao" /> property : TestService class의 setTestDao method를 의미 즉 위의 설정은 TestService class의 setTestDao 메소드를 호출하면서 인자 값으로 ImplTestDao 객체를 넘겨준다는 의미 <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="mappings"> <props> <prop key="/login/login.mw">loginController</prop> </props> </property> </bean> props : java.util.properties 클래스로 key, value를 string type으로만 갖는다. 즉 위의 예는 SimpleUrlHandlerMapping 클스의 setMappings 메소드를통해 properties 객체를 생성하고 해당 properties 객체는 key="/login/login.mw", value = loginController를 갖고 있게된다. Bean 객체 범위 <bean name="testDao" class="com.ljw.TestDao"/> TestDao testDao1 = (TestDao)applicationContext.getBean("testDao"); TestDao testDao2 = (TestDao)applicationContext.getBean("testDao"); 위 코드를 통해 생성된 testDao는 동일한 객체이다(스프링 컨테이너 내에서 빈 객체는 싱글턴) bean scope를 명시하여 서로다른 객체로 생성이 가능한데 다음과 같다. scope="singleon" : 기본값이며 스프링 컨테이너당 하나의 빈 객체 생성 scope="prototype" : 빈은 사용할때마다 새로운 객체 생성 scope="request" : http 요청마다 새로운 객체 생성(WebApplicationContext에서 사용) scope="session" : 세션마다 새로운 객체 생성(WebApplicationContext에서 사용) <bean name="testDao" class="com.ljw.TestDao" scope="protytype" /> --> bean의 scope 속성값에 설정 하여 사용 list, map 등의 컬렉션 에대한 xml 태크가 있긴하지만 위에 정리된 부분만 봐도 spring xml에 대해 이해가 될거라 판단됨.
출처
http://jwlee1728.egloos.com/v/1805102