- Controller 클래스 SampleController.java 에서 "sample2" 요청 부분만
@RequestMapping("sample2")
public String sample2(Model model) {
Member member = new Member(1,"test","1234","홍길동", new Timestamp(System.currentTimeMillis()));
model.addAttribute("member", member);
return "sample2";
}
- DTO Member 객체 member 를 생성하면서 값을 설정했다
- 그 객체 member 를 Model 객체에 저장해서 templates 폴더 아래의 sample2.html View 파일로 이동
- 즉 객체가 Model 에 저장되서 전달되는 경우 어떻게 출력하는지 보자
- View 파일 sample2.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<!-- th:text : DTO 객체 출력 -->
<h1 th:text="${member}">Thymeleaf Test page</h1>
<!-- th:text : 문자열(HTML태그) 출력 -->
<div th:text='${"<h3>"+member.no+"</h3>"}'></div>
<!-- th:utext : 데이터 출력 -->
<div th:utext='${"<h3>"+member.no+"</h3>"}'></div>
<div th:utext='${"<h3>"+member.id+"</h3>"}'></div>
<div th:utext='${"<h3>"+member.pw+"</h3>"}'></div>
<div th:utext='${"<h3>"+member.name+"</h3>"}'></div>
<div th:utext='${"<h3>"+member.regdate+"</h3>"}'></div>
</body>
</html>
- 타임리프 태그들은 모두 네임스페이스(xmls) 으로 th 를 써야한다, 즉 태그들은 th: 로 시작한다
- 넘어온 DTO 객체 member 를 th:text 태그로 출력시 해당 DTO 객체의 주소값이 출력된다
- 넘어온 DTO 객체 member 의 번호 member.no 를 th:text 태그로 출력시, member.getNo() 의 값이 출력된다
- 즉 Model 객체로 DTO가 전달되었을땐 넘어온 객체의 Model 네임값.객체 필드명 으로 그 값을 출력
th:text vs th:utext
th.text
- th:text 를 사용하면 <h3> 태그가 문자로 인식되어 <h3>1</h3> 로 출력된다
<!-- th:text : 문자열(HTML태그) 출력 -->
<div th:text='${"<h3>"+member.no+"</h3>"}'></div>
th.utext
- th:utext 를 사용하면 태그를 문자가 아닌 HTML 태그로 인식해서 1 을 출력
<!-- th:utext : 데이터 출력 -->
<div th:utext='${"<h3>"+member.no+"</h3>"}'></div>
프로젝트 Thymeleaf : 코드 설명 3
- index.jsp 에서 "sample3" 으로 요청 (리스트를 타임리프로 출력하는 예제)
- Controller 클래스 SampleController.java 에서 "sample3" 요청 부분만
@RequestMapping("sample3")
public String sample3(Model model) {
List<Member> list = new ArrayList<Member>();
for(int i=0; i<10; i++) {
Member member = new Member(1,"test"+i,"1234","홍길동"+i, new Timestamp(System.currentTimeMillis()));
list.add(member);
}
model.addAttribute("list", list);
return "sample3";
}
- 리스트 list 를 Model 객체에 저장해서 templates 폴더 하위의 sample3.html 파일로 이동한다
- Controller 클래스 SampleController.java 에서 "sample4" 요청 부분만
@RequestMapping("sample4")
public String sample4(Model model) {
List<Member> list = new ArrayList<Member>();
for(int i=0; i<10; i++) {
Member member = new Member(i,"u000"+i %3, // 3으로 나눈 나머지 id
"p000"+i %3, // 3으로 나눈 나머지 pw
"홍길동"+i,
new Timestamp(System.currentTimeMillis()));
list.add(member);
}
model.addAttribute("list", list);
return "sample4";
}
- List 객체 생성 후 값을 입력해서 그 리스트를 Model 객체에 저장하고 sample4.html 로 이동
- Controller 클래스 SampleController.java 에서 "sample5" 요청 부분만
@RequestMapping("sample5")
public String sample5(Model model) {
String result = "SUCCESS";
model.addAttribute("result", result);
return "sample5";
}
- View 페이지 sample5.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<!-- javascript안에서 Thymeleaf 사용하기 -->
<!-- script태그에 th:inline="javascript"를 추가하면 script안에서 Thymeleaf를 사용할 수 있다. -->
<script th:inline="javascript">
var result = [[${result}]]; // result = "SUCCESS" 할당됨
document.write(result); // SUCCESS 출력
</script>
<script>
var result = [[${result}]]; // result변수에 값이 할당 안됨
document.write(result); // 아무것도 출력 안됨
</script>
</body>
</html>
자바스크립트안에서 타임리프 사용하는 방법
- script 태그 안에 th:inline="javascript" 코드를 작성해야한다
- 자바스크립트안에서 타임리프를 사용해서 ${result} 를 가져온다, 가져와서 자바스크립트 변수 result 에 저장후 출력
+ 아래쪽은 th:inline="javascript" 코드가 없으므로 출력되지 않는다, 아래 사진의 에러는 아래쪽 코드때문
프로젝트 Thymeleaf : 코드 설명 6
- index.jsp 에서 "sample6" 으로 요청 (여러 자료형의 값을 포맷 함수 사용해서 형식 바꿔서 출력 )
- Controller 클래스 SampleController.java 에서 "sample6" 요청 부분만
@RequestMapping("sample6")
public String sample6(Model model) {
model.addAttribute("now", new Date());
model.addAttribute("price", 123456789);
model.addAttribute("title", "This is a just sample");
model.addAttribute("options", Arrays.asList("AAA","BBB","CCC","DDD"));
return "sample6";
}
- Date 객체, int 형 데이터, String 형 데이터, 리스트를 만들어서 Model 객체에 저장해서 sample6.html 로 이동
+ 리스트를 만드는 방법 : new 연산자로 만들기, 또는 값이 정해져있는 데이터가 있을때는 Arrays.asList() 로 리스트 생성가능
- View 페이지 sample6.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1 th:text="${now}"></h1>
<h1 th:text="${price}"></h1>
<h1 th:text="${title}"></h1>
<h1 th:text="${options}"></h1>
<!--1.날짜 관련 : #dates -->
<h2 th:text="${#dates.format(now, 'yyyy-MM-dd HH:mm:ss')}"></h2>
<!--2.숫자 관련 : #numbers -->
<h2 th:text="${#numbers.formatInteger(price, 3, 'COMMA')}"></h2>
<!--3.문자 관련 : #strings -->
<h1 th:text="${title}"></h1>
<!-- replace()함수를 이용해서 s를 진하게 출력 -->
<span th:utext="${#strings.replace(title,'s','<b>s</b>')}"></span>
<!-- listSplit()함수를 이용해서 title변수의 값을 리스트로 처리하고 출력 -->
<ul>
<li th:each="str:${#strings.listSplit(title,' ')}">[[${str}]]</li>
</ul>
</body>
</html>
- th:text 가 h1 태그에 들어가있다, 앞에 넘어온 자료형이 모두 다른 데이터인 now, price, title, options 를 th:text 로 출력
- 다음으로는 해당 값들을 형식을 바꿔서 출력
+ JSTL 에서 국제화 라이브러리로 처리했던 내용을 타임리프에서는 # 포맷함수들 사용
1. 날짜 관련 형식 변경하는 방법: #dates (sample6.html 부분)
<!--1.날짜 관련 : #dates -->
<h2 th:text="${#dates.format(now, 'yyyy-MM-dd HH:mm:ss')}"></h2>
- #dates 포맷 함수로 패턴을 지정
- th:text 태그를 사용하고, "${#dates.format(날짜데이터 네임값, '패턴지정')}" 형식을 써서 날짜를 원하는 형식으로 출력
2. 숫자 관련 형식 변경하는 방법: #numbers (sample6.html 부분)
<!--2.숫자 관련 : #numbers -->
<h2 th:text="${#numbers.formatInteger(price, 3, 'COMMA')}"></h2>
- #numbers 포맷 함수로 패턴을 지정
- th:text 태그를 사용하고 ${#numbers.formatInteger(숫자데이터 네임값, 몇자리씩 끊어서 출력할지,'끊을 문자')}" 형식을 써서 숫자를 원하는 형식으로 출력
- 여기선 price 에 저장된 숫자값을 3자리 씩 콤마(,) 로 끊어서 출력하라는 의미
3. 문자 관련 형식 변경하는 방법 : #strings (sample6.html 부분)
<!--3.문자 관련 : #strings -->
<h1 th:text="${title}"></h1>
<!-- replace()함수를 이용해서 s를 진하게 출력 -->
<span th:utext="${#strings.replace(title,'s','<b>s</b>')}"></span>
<!-- listSplit()함수를 이용해서 title변수의 값을 리스트로 처리하고 출력 -->
<ul>
<li th:each="str:${#strings.listSplit(title,' ')}">[[${str}]]</li>
</ul>
- web.xml, servlet-context.xml, root-context.xml 이 사용되지 않는다
- MyBatis 환경설정 파일인 Configuration.xml 도 사용되지 않는다, 여기서 alias 가 설정되지 않음
- 새로 application.properties 환경 설정 파일이 생성된다
+ 새로 DB 관련 환경 설정 관련 클래스 (DataAccessConfig.java) 하나가 생성된다
Spring Boot 환경설정 파일 정리
1. pom.xml
2. Mapper 파일
3. application.properties
4. DataAccess 설정 파일 클래스
Spring Boot - MyBatis 연동 예제 1 : 프로젝트 MyBatis01
프로젝트 MyBatis01 : 프로젝트 생성
- java 폴더 하위에 Package 에 적힌 패키지가 생성된다
프로젝트 MyBatis01 : 프로젝트 실행 확인
오른쪽 마우스 - Run As - Run on Server 로 프로젝트를 실행
- 이 화면이 출력되면 실행 성공이다
프로젝트 MyBatis01 : 환경설정 pom.xml 파일 수정
<!-- jsp 파일을 사용하기 위한 의존 라이브러리 -->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>
<!-- jstl -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
- pom.xml 에 JSP 파일 사용하기 위한 의존라이브러리와 JSTL 의존 라이브러리 추가
- JSTL 을 사용하기 위해 필요한 라이브러리이다
+ ojdbc8 을 쓸 것이므로 ojdbc8 을 남기고, ojdbc6 와 ojdbc6 다운 위한 repository 인 codelds 는 주석으로 막기
프로젝트 MyBatis01 :테이블 생성하기
- webapp 폴더 하위에 sql 폴더를 생성하고, sql 폴더 하위에 myboard.sql 파일 생성
- Connection profile 을 설정하고, 테이블 boardtest, 시퀀스 boardtest_seq 생성
- myboard.sql
select * from tab;
select * from seq;
create table boardtest(
no number primary key,
name varchar2(20),
subject varchar2(50),
content varchar2(1000),
register date
);
create sequence boardtest_seq;
DB 연동 준비
top-level 패키지 하위에 JAVA 파일이 저장될 폴더 생성하기
- src/main/java/com/example/demo 폴더 하위에 config, controller, service, dao, model 폴더 생성
- config 폴더엔 DB 연동에 필요한 클래스가 들어갈 것
DataAccess 설정 파일 생성 (간략히만 설명)
- src/main/java/com/example/demo/config – DataAccessConfig.java 생성
- DataAccessConfig.java
package com.example.demo.config;
import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
@Configuration
@PropertySource("classpath:/application.properties")
public class DataAccessConfig {
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception{
SqlSessionFactoryBean factoryBean=new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
factoryBean.setMapperLocations(
new PathMatchingResourcePatternResolver().getResources("classpath:/mapper/*.xml")
);
factoryBean.setTypeAliasesPackage("com.example.demo.model"); // DTO Alias 설정
return factoryBean.getObject();
}
@Bean
public SqlSessionTemplate sessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
- root-context.xml 에서 설정했던 내용을 이 클래스 DataAccessConfig 와 application.properties 파일에서 설정
- 프로젝트 생성 시 Spring Web 을 선택했으므로 spring-boot-starter-web, spring-boot-starter-tomcat 라이브러리 등이 들어가있다
Spring 에는 있지만 Spring Boot 에는 없어진 환경설정 파일
- web.xml : webapp 폴더 가 비어있다, 즉 WEB-INF 폴더도 없고, 그 안에 있어야할 web.xml 파일도 없다
- web.xml 에서 불러왔던 spring 환경설정 파일 2개인 servlet-context.xml, root-context.xml 파일도 없다
- Spring Boot 프로젝트에서는 web.xml, servlet-context.xml, root-context.xml 파일이 없다
- Spring 의 6개 환경설정 파일 중에서 pom.xml, Mapper 파일(.xml) 만 Spring Boot 에 있다, Configuration.xml 파일도 필요없다
Spring 에는 없지만 Spring Boot 에서 새로 생긴 환경설정 파일 : application.properties - application.properties 파일에 직접 환경설정을 해야한다 - 없어진 3개의 파일의 환경설정 내용을 여기에 작성해야함 - 프로젝트 생성 직후 application.properties 에는 아무 내용없다
Spring 에는 없지만 Spring Boot 에서 새로 생긴 폴더 설명
resources폴더 하위 static 폴더
- CSS, JS, 이미지(images) 디자인 관련 파일들을 저장함
- 공유가 되는 폴더, 이 폴더에 저장되면 쉽게 CSS, JS, 이미지들을 불러올 수 있다
- 이 폴더에 있는 파일은 다른 페이지에서 쉽게 불러올 수 있다
+ Python 의 장고 프로젝트가 Spring Boot 프로젝트와 비슷한 구조, 장고에도 static 폴더가 있다
resources 폴더 하위 templates 폴더
- EL, JSTL 대신하는게 타임리프, EL, JSTL 대신 타임리프 지원 태그들을 사용
- 타임리프를 쓸때 여기에 View 파일인 HTML 파일들을 저장해야한다 - Spring Boot 에서는 JSTL 을 사용하든지 타임리프를 사용하든지 선택 가능 - 타임리프 사용시 View 파일을 JSP 가 아닌 HTML 파일을 사용해야함
- 그 HTML 인 VIew 페이지들이 여기 저장되어야함 + HTML 파일을 쓰므로 EL, JSTL 을 사용 못하게되는 것이다
Controller를 추가해서 Hello World 출력 - src/main/java/com/example/demo/controller – SampleController.java 생성 - index 파일에서 값을 요청했을때 여기서 처리해보자 - top-level 패키지 하위인 com.example.demo 아래에 controller 폴더 생성 후 SampleController.ajva 파일 생성
- 그냥 일반 클래스로 만듬
- SampleController.java
package com.example.demo.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class SampleController {
@RequestMapping("/")
public String hello() {
return "Hello World~!!";
}
}
+ @RestController = @Controller + @ResponseBody ,
- 현재 프로젝트 Run As -> Run on Server 로 실행 한 후 웹브라우저에 http://localhost:8080 요청한다.
- Hello World ~!! 가 브라우저 나타나면 잘 실행된 것
- web.xml 에서 설정하던 Dispatcher Servlet 매핑을 하지 않았으므로, 그냥 여기 Controller 클래스의 @RequestMapping 만 맞으면 바로 찾아간다
- 현재는 / 이므로 아무거나 요청해도 다받음
- 이후 프로젝트를 Run As - Run on Server 로 실행
- View 로 가서 출력하는 대신 @ResponseBody 에 의해 return 만 하면 바로 브라우저에 출력됨
- @RestController 를 썼으므로 @ResponseBody 에 의해 return 시 요청한 브라우저로 바로 찾아가서 돌려준다, 즉 브라우저에 바로 출력된다
port 번호 설정
- Spring boot 에 내장된 tomcat 은 기본 port 가 8080, 현재 8080 은 오라클에서 쓰고 있는 포트이다
- 포트충돌 가능성, 오류 생긴다면 내장 tomcat port 번호를 80 으로 바꿔줘야한다
- tomcat port 번호를 바꾸기 위해 application.properties 에 server.port = 80 한줄을 추가해준다
- application.properties
- 이후 다시 실행시 실행 됨
Spring Boot 예제 : 프로젝트 boot01
실습 준비
- 클라우드의 프로젝트 boot01 을 다운받아 압축 해제 후 import
Spring Boot 프로젝트 import 하는 방법
- Spring 과 같은 방법으로 import 하면 된다
- boot01 프로젝트 import
- 프로젝트 오류 발생, 현재 설정된 JAVA 가 11 버전으로 되어있고, 우리가 설치한 JAVA 는 8 점대 버전이라서 오류 발생
- Controller 클래스에서 넘어온 값이 부모글에 대한 5가지 정보를 hidden 으로 "insert.do" 로 재전달한다
- 댓글 작성 시에는 부모글의 num, ref, re_level, re_step, pageNum 이 넘어온다
+ 원문은 가져가는 num, ref, re_level, re_step 이 모두 0 으로 초기화되서 전달됨
- 댓글 작성 후 "확인" 버튼을 누르면 "insert.do" 로 요청
댓글 작성
- 댓글 작성폼에서 댓글 작성 후 "확인" 버튼을 누르면 "insert.do" 로 요청
- Controller 클래스에서 "insert.do" 요청 부분만
@RequestMapping("insert.do") // 글 작성
public String insert(Board board, Model model, HttpServletRequest request) {
int num = board.getNum();
int number = bs.getMaxNum();
if (num != 0) { // 답변글
bs.updateRe(board);
board.setRe_level(board.getRe_level() + 1);
board.setRe_step(board.getRe_step() + 1);
} else // 원문
board.setRef(number); // else 문 끝
board.setNum(number);
String ip = request.getRemoteAddr();
board.setIp(ip);
int result = bs.insert(board);
model.addAttribute("result", result);
return "insert";
}
- 여기서 원문 작성도 하고 댓글 작성도 한다, ref, re_level, re_step 값을 다르게 설정해서 insert 하면 각각 원문, 댓글이 됨
- 댓글이므로 부모글의 num, ref, re_level, re_step, pageNum 가 hidden 으로 넘어온다
+ 원문은 num, ref, re_level, re_step 이 모두 0이고 pageNum 이 null 이다
- 이걸 @ModelAttribute (생략) 로 DTO Board 객체 board 로 바로 받음
흐름 설명
1. 넘어온 num 값을 가져옴, 그 num 값은 부모의 num 값이다
2. 작성할 댓글의 num 컬럼을 입력해야하므로, Service 클래스의 getMaxNum() 메소드를 사용해서 최대 num 값 + 1 된 값을 구해서 변수 number 에 저장 *아래에서 설명
3. 댓글 작성이므로 num != 0 이다
4. Service 의 updateRe() 메소드를 사용해서 부모글과 ref 가 같으면서 부모글보다 step 값이 큰 글들의 step 을 1 증가
* 아래에서 설명
5. 현재 board 의 ref, re_lvel, re_step 은 부모의 값이므로, ref 는 그대로, re_level 은 1 증가, re_step 은 1 증가시켜서 board 에 대시 세팅
6. if-else 문 뒤의 코드들을 실행, 변수 number 를 board 의 프로퍼티 num 으로 세팅한다
7. 글 작성한 사람의 ip 주소를 request.getRemoteAddr() 메소드로 구해와서 객체 board 의 프로퍼티 ip에 세팅
8. 객체 board 를 매개변수로 insert() 를 호출해서 댓글을 작성함
* 아래에서 설명
- 이때 board 의 num 은 새로 작성할 글번호, ref 는 부모와 같음, re_level, re_step 은 부모글보다 1 보다 증가된 값
<돌아온 후>
- insert() 의 결과를 변수 result 에 받아서 Model 객체에 저장해서 insert.jsp 로 이동
댓글 작성시 필요한 DB작업 2가지
1. getMaxNum() 메소드 : DB에서 최대 num 값 + 1을 가져옴, 컬럼 num 값을 넣기 위해 필요
2. updateRe() 메소드 : 부모글과 ref 가 같으면서 부모글보다 step 값이 큰 글들의 step 값을 1 증가
3. insert() 메소드 : 댓글 등록 (insert)
- Service, DAO 생략하고 Mapper 파일만 보기
- Mapper 파일 Board.xml 에서 id 가 "getMaxNum" 인 SQL문 부분만
<!-- num 번호중 최대값 구하기 : 첫번째 글은 1번으로 설정 -->
<select id="getMaxNum" resultType="int">
select nvl(max(num),0) + 1 from board
</select>
- DB에 아무 글도 없을때만 max(num) 이 null 이므로 nvl() 함수는 글을 처음으로 작성할때만 사용된다
- 여기서 이미 1 을 더한 뒤 돌아오므로, 이 돌아온 값이 새로 작성할 글의 num 이 될 것
- Mapper 파일 Board.xml 에서 id 가 "updateRe" 인 SQL문 부분만
<update id="updateRe" parameterType="board">
update board set re_step = re_step + 1
where ref=#{ref} and re_step > #{re_step}
</update>
- 모글과 ref 가 같으면서 부모글보다 step 값이 큰 글들의 step 을 1 증가시킴
- form 태그의 name 값이 frm 이므로 frm.passwd.value 와 frm.passwd2.value 를 비교
- frm.passwd.value 는 넘어온 DB의 비밀번호, frm.passwd2.value 는 사용자가 수정 폼에 입력한 비밀번호
+ hidden 으로 넘어가는 값도 입력양식과 마찬가지로 name 값으로 값을 구할 수 있음
+ form 객체 하위객체는 name 값이다, name 값을 .(점) 으로 연결한다
글 수정
- 수정 폼에서 입력하고 "확인" 버튼 클릭시 "update.do" 로 요청
- Controller 클래스에서 "update.do" 요청 부분만
@RequestMapping("update.do") // 수정
public String update(Board board, String pageNum, Model model) {
int result = bs.update(board);
model.addAttribute("result", result);
model.addAttribute("pageNum", pageNum);
return "update";
}
- 비번 일치한 경우에만 여기로 넘어온다
- 수정폼에선 hidden 으로 넘어온 글 번호, 글 비밀번호, 사용자가 수정폼에서 입력한 값들을 Board 객체 board 로 받음
+ DTO 프로퍼티명과 일치한 num 이 passwd 인 DB에 저장된 비밀번호가 넘어오게 되지만, DB의 비번과 사용자가 입력한 비번이 일치한 경우만 여기로 넘어오므로 괜찮다
- 수정폼에서 hidden 으로 넘어온 페이지 번호 pageNum 은 DTO Board 프로퍼티에 없으므로 따로 받아줌
- update() 메소드를 호출하면서 매개변수로 board 를 전달한다, 그렇게 수정을 완료
- 수정 결과 result 와 페이지 번호 pageNum 을 Model 객체에 저장해서 전달
- update.jsp 에서 수정 성공 / 실패 처리를 한 후 목록 페이지로 이동할때 페이지 번호가 필요하므로 여기서 페이지 번호를 전달
- Service, DAO 생략
- Mapper 파일 Board.xml 에서 id 가 "update" 인 SQL문 부분만
<update id="update" parameterType="board">
update board set writer=#{writer},subject=#{subject},
content=#{content},email=#{email} where num=#{num}
</update>
- 자식 테이블에 관련된 DTO 인 ReplyBoard DTO 에 대한 alais 가 "rb"
- 테이블 개수가 늘어나면 DAO, DTO, Service, Controller, Alias 모두 늘어난다
테이블 생성
- SQL파일이 2개 있다
- board1.sql 에서 board 테이블 생성 코드 있다, 이전 프로젝트에서 생성 했으므로 생성할 필요 없음
- sboard.sql 에서 replyBoard 테이블을 생성하자
테이블 2개
1. 부모 테이블은 board1 으로서 이전 프로젝트 board1 에서 이미 만들었던 테이블 board을 그대로 활용
- 새로 만들 필요 없다
2. 자식 테이블은replyBoard 이다, 댓글을 저장하는 테이블이다
- 부모 테이블 안의 글 마다 댓글을 따로 달 것이므로 자식 테이블이 필요함
- sboard.sql
select * from tab;
select * from board;
select * from REPLYBOARD;
-- 댓글 게시판
drop table replyBoard;
create table replyBoard (
rno number primary key, -- 댓글 번호
bno number not null references board(num), -- 부모키 번호
-- on delete cascade,
replytext varchar2(500) not null, -- 댓글 내용
replyer varchar2(50) not null, -- 댓글 작성자
regdate date not null, -- 댓글 작성일
updatedate date not null -- 댓글 수정일
);
select * from REPLYBOARD;
select * from board order by num desc;
insert into REPLYBOARD values(10,262,'11','나',sysdate,sysdate);
- 테이블 replyBoard 를 생성하자
- 이 테이블 관련 DTO는 이 컬럼명과 같은 이름의 프로퍼티를 만들어아햔다
+ 테이블 board 는 생성하지 않고 이전 프로젝트의 테이블 그대로 쓰기
테이블 replyBoard 컬럼 설명
- rno : 댓글 번호, primary key
- 여기서도 sequence 를 쓰지 않고 있으므로 최대 댓글 번호 rno 를 구해서 1 증가시킨 값을 새 글의 rno 로 넣을것
- bno : 부모 글의 번호, foreign key 제약 조건이 설정되어있다
- replytext : 댓글 내용
- replyer : 댓글 단 사람 이름
- regdate : 댓글 작성된 날짜
- updatedate : 댓글 수정된 날짜
bno 컬럼 설명
- 부모 글의 번호, foreign key 제약 조건이 설정되어있다
-부모 글이 같은 댓글 끼리는 bno 값이 같다
- 컬럼 bno 는 참조하는 부모 테이블 board 에서 num 컬럼을 부모키로 설정
+ 부모 키의 조건은 primary 또는 unique, board 의 num 은 primary key 이므로 부모키가 될 수 있다
+ bno 에 on delete cascade 옵션을 붙이면 부모 글을 삭제할때 달린 댓글(참조하는 자식)들도 모두 삭제됨
bno number not null references board(num) on delete cascade, -- 부모키 번호
- bno에 on delete cascade 옵션이 없으면, 참조하는 자식이 있는 경우 부모 글이 삭제되지 않음!
+ 단 여기서는 on delete cascade 가 필요 없다, 실제 부모글을 삭제하는 코드는 없기때문에, update 로 상태값만 바꿔줌
흐름 설명
- 프로젝트 실행
- 이전에 달았던 댓글(실제론 글) 이 아니라 한 부모글에 대한 실제 댓글을 달 수 있다!
+ 동기적으로 처리할떄는 form 의 action 값으로 값을 전달함, action 값이 있어야함
- 아래의 id 가 "slist" 인 div 태그는 댓글 목록이 출력될 자리
- 처리한 댓글 목록을 콜백함수로 받아서 아래의 div 태그에 뿌릴 것
- textarea 에 내용을 입력하고 "확인" 버튼 클릭시 onClick 이벤트 발생, 그걸 jQuery 에서 처리 * 아래에서 설명
- hidden 객체로 부모글 작성자명(원래는 댓글 작성자명을 써야함) ${board.writer} 값을 변수 "replyer" 에 저장
- hidden 객체로 부모글 번호 ${board.num} 값을 변수 "bno" 에 저장
- 사용자가 입력한 댓글 내용은 변수 "replytex" 에 저장
- 댓글을 달면 replyBoard 테이블에 저장된다
create table replyBoard (
rno number primary key, -- 댓글 번호
bno number not null references board(num),
-- on delete cascade, -- 부모키 번호
replytext varchar2(500) not null, -- 댓글 내용
replyer varchar2(50) not null, -- 댓글 작성자
regdate date not null, -- 댓글 작성일
updatedate date not null -- 댓글 수정일
);
- 컬럼별로 어떤 값을 어디로 넣어야할지 보자
- rno 컬럼 : 댓글 번호는 최대 rno 컬럼을 구해서 1 더한 값을 넣을 것
- bno 컬럼 : 현재 view.jsp 에는 부모글 정보를 저장한 board 객체가 넘어오므로 ${board.number} 로 가져오면된다
- replytext 컬럼 : 댓글 내용, 사용자가 view.jsp 댓글 작성 부분에서 입력한 글을 의미
- replyer 컬럼 : 댓글 작성자를 저장함, 댓글 작성자를 구해와야한다
댓글 작성자 구하는 2가지 방법
1. 작성자명을 직접 입력하게 함
2. 로그인 해야만 댓글을 입력할 수 있게 하고, 세션에서 id 값을 가져와서, 그걸로 DB의 회원 이름을 구해옴
- 현재는 둘 다 하고 있지 않고 그냥 부모글 작성자의 이름이 댓글 작성자 이름 replyer 로 들어가게 된다
- regdate 컬럼 : sysdate
- updatedate 컬럼 : 처음엔 regdate 로 같은 값 삽입, 나중에 수정시엔 이 값 수정
- id 가 slist 인 태그를 불러옴, 즉 댓글 목록을 보여줄 공간인 div 태그를 가져왔다
- 그 div 태그에 load() 함수를 사용해서 "slist" 로 요청하고 부모글 번호 ${board.num} 를 전달한다
- 부모글 번호 를 전달해야하는 이유 : 댓글 중 bno 가 부모글 번호와 같은 댓글들을 리스트로 가져와서 출력해야하므로
- 그럼 그 요청으로 인해 브라우저에 출력되는 값이 돌아와서 div 태그 안에 보여지는 것이다, 출력되는 값은 가져와진 댓글 이므로, 댓글 목록이 div 태그 안에 보여짐,
- 이 작업은 이 페이지 view.jsp 로 들어오자마자 자동으로 처리됨
+ load('${path}/slist/num/${board.num}') 는 load('slist?num=${board.num}') 와 같은 기능
- Controller 클래스 ReplyBoardController.java 에서 "slist" 요청 부분만
// 댓글 목록 구하기
@RequestMapping("/slist/num/{num}")
public String slist(@PathVariable int num, Model model) {
Board board = bs.select(num); // 부모 테이블의 상세 정보 구하기
List<ReplyBoard> slist = rbs.list(num); // 댓글 목록
model.addAttribute("slist", slist);
model.addAttribute("board", board);
return "slist";
}
- 넘어오는 값은 부모글의 글 번호 num, 그걸 @PathVariable 로 옆의 변수 num 에 저장한다
- 넘어온 부모 글 번호 num 으로 BoardService 의 메소드 select() 로 부모 글의 상세 정보를 구한 후 객체 board 로 받아서 저장한다
- ReplyBoardService 의 메소드 list() 로 댓글 목록을 구한다, 이때, 넘어온 부모글 번호 num 을 전달한다 이 값으로 댓글 중 bno 가 num 인 댓글 목록을 가져올 것
- 여러개 데이터를 가져오므로 List 로 반환받는다
<돌아온 후>
- 구해온 해당 부모글의 댓글 목록 slist 와 부모 글 정보 객체 board 를 Model 객체에 저장해서 slist.jsp 로 전달
- 댓글 목록 slist 를 slist.jsp 에서 댓글 목록 출력을 하면, 그 브라우저에 출력된 내용이 load() 함수로 돌아가서 view.jsp 의 div 태그에 출력함
- Service 클래스 ReplyBoardServiceImpl.java 에서 list() 메소드 부분만
public List<ReplyBoard> list(int num) {
return rbd.list(num);
}
- DAO 클래스ReplyBoardDaoImpl.java 에서 list() 메소드 부분만
public List<ReplyBoard> list(int bno) {
return sst.selectList("rbns.list", bno);
}
- 넘어온 부모글 번호를 매개변수 bno 로 받는다
- Mapper 파일 ReplyBoard.xml 에서 id 가 "list" 인 SQL문 부분만
<select id="list" parameterType="int" resultMap="rbResult">
select * from replyBoard where bno=#{bno} order by rno
</select>
- 넘어온 값 #{bno} 는 부모글 번호이다
- 댓글들을 저장한 replyBoard 테이블에서 bno 컬럼의 값이 부모글 번호인 모든 댓글들을 검색해서 리스트로 돌려줌
+ bno 값이 같은 댓글들은 같은 부모 아래의 댓글들
- View 페이지 slist.jsp
- 이 페이지에 출력되는 내용을 view.jsp 의 load() 함수가 불러서, view.jsp 의 div 태그 안에 출력된다!
- class 가 edit1 인 태그를 $('.edit1') 으로 불러온다, 모든 수정버튼은 class 값이 edit1 으로 고정
- $(this) : 이벤트를 발생시킨 태그를 구함, 여기선 클릭된 수정 버튼 의 태그 input 태그를 구해서 id 속성 값을 가져옴
- 구해온 id 는 ${rb.rno} 로 변하는 값, 즉 각 댓글의 댓글 번호값을 구해서 변수 id 에 저장
2. 각 댓글의 내용이 출력되는 td 태그 구하기
- 그 댓글 번호인 id 를 사용해서 ${'td_'+id) 로 두번째 td 태그, 즉 내용이 출력되는 태그를 구한다
- $('td_'+id) 는 두번째 td 태그, .text() 로 그 댓글 내용을 구한 뒤 변수 txt 에 저장
3. 두번째 td 태그에서 출력되던 댓글 내용을 textarea 로 만들기
- $('#td_'+id) 로 구해온 두번째 td 태그에 .html() 함수를 사용해서 textarea 태그를 만들고 안의 내용으로 txt 를 넣음
- textarea 의 id 값은 'tt_' + id 값이다, 아래에서 textarea 에 입력한 값을 가져올때 사용할 것
4."수정", "삭제" 버튼을 "확인", "취소" 버튼으로 바꾸기
- "수정", "삭제" 버튼은 4번째 td 태그에 있다, 그 4번째 태그의 id 는 "btn_${rb.rno}" 이므로 ${'#btn_'+id) 로 구해온 뒤 html() 함수를 사용해서 "확인" "취소" 버튼을 만든다
5 "확인" 버튼 클릭시 onclick 을 통해 up() 메소드 호출, 이때 각 댓글의 글 번호값인 변수 id 를 넘겨줌
+ 이 "확인" 버튼을 누르면 실제 댓글 내용 update 가 수행됨
jQuery 부분 2 (slist.jsp 부분)
- "확인" 버튼 클릭시 up()함수 호출하며 DB 와 연동해서 댓글 내용을 수정
- 이때, 비동기적으로 처리한다
function up(id) { // '확인' 버튼을 클릭해서 댓글 내용을 수정
var replytext = $('#tt_'+id).val();
var formData = "rno="+id+'&replytext='+replytext
+"&bno=${board.num}";
$.post('${path}/repUpdate',formData, function(data) {
$('#slist').html(data);
});
}
- 위에서 변경된 textarea 의 내용을 구해와야하므로 textarea 태그를 $('#tt_'+id) 를 써서 구해온다, val() 로 내용을 구해서 변수 replytext 에 저장
- "수정" 버튼을 눌렀을때만 rno, replytext 값이 존재하므로 serialize() 함수 사용 불가능, rno, replytext, bno(부모글번호) 를 일일히 써서 묶은 후 formData 에 저장
- post() 함수로 전달되는 값을 여러개 쓸때는 변수=값&변수=값&변수=값 형태로 쓴다
+ 앞에 ? 는 파일명이 있는 경우만 파일명?변수=값&변수=값&변수=값 형태로 쓰는 것이다
- post 방식 요청이지만 형태만 마치 get 방식으로 값이 넘어가는 것 처럼 쓰는 것
- $.post() 함수를 사용해서 "repUpdate" 요청, formData 전달
- Controller 클래스 ReplyBoardController.java 에서 "repUpdate" 요청 부분만
// 댓글 내용 수정
@RequestMapping("/repUpdate")
public String repUpdate(ReplyBoard rb, Model model) {
rbs.update(rb); // 댓글 내용 수정
return "redirect:slist/num/" + rb.getBno();
}
- 앞에서 넘어온 formData 안의 rno, replytext, bno(부모 글번호) 값을 ReplyBoard 객체 rb 로 받아서 저장
- update() 메소드를 사용해서 댓글 내용 수정, 객체 rb 전달
<돌아온 후>
- 댓글 내용 수정 후 다시 댓글 목록을 "slist" 로 요청하고 있다
- 그럼 slist.jsp 에 삭제된 댓글은 제외된 나머지 댓글 목록들이 출력된다, 이때 브라우저에 출력된 댓글 목록이 slist.jsp 에 있는 id 가 slist 인 div 태그 아래에 나타남
- Service, DAO 생략
- Mapper 파일 ReplyBoard.xml 에서 id 가 "update" 인 SQL문 부분만
<update id="update" parameterType="rb">
update replyBoard set replytext=#{replytext},
updatedate=sysdate where rno=#{rno}
</update>
- 수정할 날짜를 현재 날짜로 바꿈
- 전달된 3가지 값 중 댓글 번호 rno 를 where 절에 넣어서 해당 내용 수정, replytext 로 댓글 내용 수정
댓글 삭제
jQuery 부분 3 (slist.jsp 부분)
- slist.jsp 에서 삭제 버튼을 클릭했을때 click 이벤트 발생하면서 아래의 del() 함수 실행됨
- 가져온 상세정보 객체 board 와 페이지 번호 page 를 Model 객체에 저장해서 각각의 View 페이지로 이동
- 글 번호는 객체 board 안에 있다
<조회수 증가>
- Service 클래스 BoardServiceImpl.java 에서 hit() 메소드 부분만
/* 조회수 증가 */
public void hit(int board_num) throws Exception {
boardDao.boardHit(board_num); // 조회수 증가
}
- DAO 클래스 BoardDaoImpl.java 에서 boardHit() 메소드 부분만
/* 게시판 조회수 증가 */
public void boardHit(int board_num) throws Exception {
sqlSession.update("Test.board_hit", board_num);
}
- Mapper 파일 board.xml 에서 id 가 "board_hit" 인 SQL문 부분만
<!-- 게시판 조회수 증가 -->
<update id="board_hit" parameterType="int">
update board53 set
board_readcount=board_readcount+1
where board_num=#{board_num}
</update>
<상세 정보 구하기>
- Service 클래스 BoardServiceImpl.java 에서 board_cont() 메소드 부분만
- 답변달기 폼에서 사용자가 입력한 값들과 hidden 으로 넘겨준 부모글의 정보는 BoardBean 객체 b 에 저장
- 페이지 번호는 DTO 프로퍼티안에 저장할 수 있는 곳이 없으므로 따로 받아서 저장
<돌아온 후>
- 댓글을 단 후 reply_ok() 에서 돌아오면 View 에 출력하는 대신 바로 목록 페이지로 이동하기 위해 "redirect:board_list.do" 요청하고 페이지 번호를 전달한다
- Service 클래스 BoardServiceImpl.java 에서 reply_ok() 메소드 부분만
/* 게시판 댓글 달기 */
public void reply_ok(BoardBean b) throws Exception {
boardDao.refEdit(b); // 기존 댓글 board_re_seq값 1증가
b.setBoard_re_lev(b.getBoard_re_lev() + 1); // 부모보다 1증가된 값을 저장함
b.setBoard_re_seq(b.getBoard_re_seq() + 1);
boardDao.boardReplyOk(b);
}
댓글을 달때 수행하는 SQL문 2가지
1. Update SQL문으로 기존 댓글들의 board_re_seq 값을 1 증가
- 이후 부모글보다 1 증가한 board_re_lev, 부모글보다 1 증가한 board_re_seq 를 저장함
2. Insert SQL문으로 댓글 삽입
- DAO 클래스 BoardDaoImpl.java 에서 refEdit() 메소드 부분만
/* 답변글 레벨 증가 */
public void refEdit(BoardBean b) throws Exception {
sqlSession.update("Test.board_Level", b);
}
- Mapper 파일 board.xml 에서 id 가 "board_Level" 인 SQL문 부분만
<!-- 답변글 레벨 증가 -->
<update id="board_Level" parameterType="board">
update board53 set
board_re_seq=board_re_seq+1
where board_re_ref=#{board_re_ref} and
board_re_seq > #{board_re_seq}
</update>
- 부모글과 ref 값이 같고, 부모글보다 seq 값이 큰 글들만 board_re_seq 값을 1 증가시킴
<Service 클래스에서 돌아온 후>
- Service 클래스로 넘어온 DTO 객체 b는 작성할 댓글의 제목, 내용들을 저장하고 있지만, board_re_seq, board_re_ref, board_re_lev, board_num 은 부모 글의 값이다
- DTO 객체 b의 board_re_lev 컬럼의 값을 부모글의 board_re_lev 값보다 1 증가시킨 값을 넣는다
- DTO 객체 b의 board_re_seq 컬럼의 값을 부모글의 board_re_seq 값보다 1 증가시킨 값을 넣는다
- DAO 클래스 BoardDaoImpl.java 에서 boardReplyOkay() 메소드 부분만
/* 답변글 저장 */
public void boardReplyOk(BoardBean b) throws Exception {
sqlSession.insert("Test.board_reply", b);
}
- Mapper 파일 board.xml 에서 id 가 "board_reply" 인 SQL문 부분만
<!-- 답변글 저장 -->
<insert id="board_reply" parameterType="board">
insert into board53
(board_num,board_name,board_subject,board_content,
board_pass,board_re_ref,board_re_lev,board_re_seq,board_readcount,board_date)
values(board53_num_seq.nextval,#{board_name},#{board_subject},#{board_content},
#{board_pass},#{board_re_ref},#{board_re_lev},#{board_re_seq},0,SYSDATE)
</insert>
- board_num 은 원문 글이든 댓글이든 모두 시퀀스로 입력받음
- 댓글의 ref 값은 부모글의 ref 값과 같아야하므로 그대로 #{board_re_ref} 값을 저장한다
- resouces 폴더 안에 jdbc.properties 파일이 있으므로 classpath: 를 붙이기
- jdbc.properties 파일안의 변수값들을 불러서 사용
테이블 생성
- board1.sql 에서 spring 계정으로 연결 후 테이블 생성
- board1.sql
-- 게시판
select * from tab;
select * from board;
create table board (
num number primary key, -- key
writer varchar2(20) not null, -- 작성자
subject varchar2(50) not null, -- 제목
content varchar2(500) not null, -- 본문
email varchar2(30) , -- 이메일
readcount number default 0, -- 읽은 횟수
passwd varchar2(12) not null, -- 암호
ref number not null, -- 답변글끼리 그룹
re_step number not null, -- ref내의 순서
re_level number not null, -- 들여쓰기
ip varchar2(20) not null, -- 작성자 ip
reg_date date not null, -- 작성일
del char(1)
);
update board set readcount = 51 where num = 250;
- 테이블 board 생성
- 원문을 작성하는 양식과 댓글을 작성하는 양식이 같을때는 Sequence 를 사용하지 못한다!
- 그래서 컬럼 num 에는 시퀀스로 값을 입력 가능하지만, ref 에 시퀀스로 값을 입력할 수 없음, 즉 같이 시퀀스를 사용 불가
- 컬럼 num 에는 그룹함수 max 를 사용해서 num 컬럼에 들어가있는 값 중 최대값을 구한 후 새로운 데이터를 insert 시킬땐 그 최대값에 1 을 증가한 값을 넣음
- 처음 글을 작성할떄는 컬럼 num 에 1 입력
테이블 board 컬럼 설명
- ip : 작성자 ip 를 저장할 것
- del : 글을 삭제하면 컬럼 del 에 "y" 란 글로 상태값을 바꿀 것
- 글을 삭제하더라도 실제로 delete SQL문으로 삭제시키지 않고, update SQL문으로 컬럼 del 을 "y" 로 수정, 그러면 목록페이지에서 글 제목에 링크가 걸리지 않게 됨
페이징 처리를 위해서 필요한 값
- 전체 목록을 구할때는 총 데이터 개수 ( 전체 게시물 개수 ) 가 필요
- 검색된 데이터 목록에는 페이징 처리를 위해 검색된 데이터 총 개수가 필요함
흐름 보기
- 프로젝트 실행
- 글을 작성해보자
- 검색 기능을 보자
- 내가 검색한 단어를 포함한 글만 목록에 출력해준다
- 위의 select-option 에서 어떤 컬럼을 기준으로 검색할건지 선택 가능하다
- 제목 + 내용은 OR 로 처리함
A가 포함된 단어를 검색하는 SQL문
select * from emp where ename like '%A%'
- 고정된 값이 아니라서 동적 SQL문이라고 한다
- 제목으로 검색한다면 제목을 가진 컬럼명이 ename 자리에 들어간다
- 내요응로 검색한다면 내용을 가진 컬럼명이 ename 자리에 들어간다
- 검색어가 A 자리에 들어간다, 사용자가 입력양식에 입력한 값을 A 자리에 넣음
- select-option 으로 이 양식을 만드는데, select 는 변수명이 되고 option 의 value 속성은 해당 컬럼(제목, 내용 등) 이 됨
- 입력양식에서 4개의 값을, hideen 객체로 5개의 값(부모의 num, ref, re_step, re_level 과 pageNum) 을 "insert.do" 로 요청하며 전달
- 현재는 원문글을 작성하므로 부모가 없다, num, ref, re_step, re_level 은 0 이고 pageNum 은 null 이다
- Controller 클래스에서 "insert.do" 요청 부분만
@RequestMapping("insert.do") // 글 작성
public String insert(Board board, Model model, HttpServletRequest request) {
int num = board.getNum();
int number = bs.getMaxNum();
if (num != 0) { // 답변글
bs.updateRe(board);
board.setRe_level(board.getRe_level() + 1);
board.setRe_step(board.getRe_step() + 1);
} else // 원문
board.setRef(number); // else 문 끝
board.setNum(number);
String ip = request.getRemoteAddr();
board.setIp(ip);
int result = bs.insert(board);
model.addAttribute("result", result);
return "insert";
}
- 넘어온 값들을 DTO Board 객체 board 로 받아서 저장한다
- 원문인 경우 board 객체의 num 값은 0 이다, 댓글인 경우 board 객체의 num, ref, re_level, re_step 값은 부모의 값이다
- 즉 num 이 0 이면 원문, num 이 0 이 아니면 댓글이다
<공통적으로 적용>
시퀀스를 쓰지 않고 컬럼 num 에 값 넣기
- 먼저 Service 클래스의 getMaxNum() 을 호출해서, 컬럼 num 중 최대값을 구한 뒤 1을 증가시켜, 변수 number 에 돌려줌
ex) 현재 DB의 데이터들 중 가장 큰 num 값을 구해와서 1 을 더함, 이게 새로 입력할 글의 num 값이 된다
* getMaxNum() 메소드는 아래에서 설명
<댓글인 경우>
- 댓글인 경우 ref 값은 부모의 ref 값과 같아야하므로, updateRe() 메소드를 호출해서 부모의 ref 와 같은 ref 이면서 부모의 re_step 보다 큰 re_step 을 가진 글들의 step 값을 1 증가시킴
- 이후 객체 board 에는 작성할 글의 정보가 들어가야하므로 부모의 re_level, re_step 에서 1 증가한 값을 Setter 메소드로 DTO 객체 board 에 저장
<원문인 경우>
- 원문인 경우 num 과 ref 값이 같은 값이 들어가야하므로 DTO 의 Setter 메소드로 글의 ref 컬럼의 값을 number 로 설정
+ else 문에 괄호가 없으므로 board.setRef(number) 한줄만 적용됨
+ 원문일때 객체 board 안의 seq, level 값이 0 이므로 그대로 둔다
<공통적으로 적용>
- 컬럼 num 의 값을 최대값보다 1 증가된 값인 number 로 설정
- 글을 작성한 사람의 IP 주소를 구하기 위해 request.getRemoteAddr() 메소드 사용하고 Setter 메소드로 객체 board 에 세팅
- Servic 클래스의 insert() 메소드로 실제 글 작성(삽입)
* insert() 메소드 아래에서 설명
- Model 객체에 받은 result 저장 후 insert.jsp 로 이동
- Service 클래스 BoardServiceImpl.java 에서 getMaxNum() 메소드 부분만
public int getMaxNum() {
return bd.getMaxNum();
}
- DAO 클래스 BoardDaoImpl.java 에서 getMaxNum() 메소드 부분만
public int getMaxNum() {
return sst.selectOne("boardns.getMaxNum");
}
- 그룹함수 max 는 결과가 1개이므로 selectOne() 메소드 사용
- Mapper 파일 Board.xml 에서 id 가 "getMaxNum" 인 SQL문 부분만
<!-- num 번호중 최대값 구하기 : 첫번째 글은 1번으로 설정 -->
<select id="getMaxNum" resultType="int">
select nvl(max(num),0) + 1 from board
</select>
- 테이블 board 에서 가장 큰 num 값을 구해온다
- 처음으로 글을 작성할땐 max(num) 은 아무 데이터 없이 null 이 나온다
- nvl() 함수를 사용해서 null 값인 경우 0 으로 바꿔준다
- 그 후 구한 컬럼 num 의 최대값에서 1 을 더해서 int 형으로 돌려준다
- Service 클래스 BoardServiceImpl.java 에서 insert() 메소드 부분만
public int insert(Board board) {
return bd.insert(board);
}
- DAO 클래스 BoardDaoImpl.java 에서 insert() 메소드 부분만
public int insert(Board board) {
return sst.insert("boardns.insert",board);
}
- 입력 성공시 목록 페이지로 이동, 이 프로젝트 전체 목록을 구할때는 페이지값을 넘겨주지 않아도 된다
- 즉, 글 작성 / 댓글 작성 성공시 "list.do" 로 요청한다
목록 페이지
- 글 작성 / 댓글 작성 성공시 "list.do" 로 요청한다
- 검색 목록을 요청할때도 "확인" 버튼 클릭시 다시 "list.do" 로 요청한다
- 이때는 hidden 객체로 pageNum 에 1 을 저장해서 넘어옴
- 그러므로 전체 목록인지 검색 목록인지를 Controller 클래스 "list.do" 요청 부분에서 구분해서 처리해야한다
- Controller 클래스에서 "list.do" 요청 부분만
@RequestMapping("list.do") // 전체 목록, 검색 목록
public String list(String pageNum, Board board, Model model) {
final int rowPerPage = 10; // 화면에 출력할 데이터 갯수
if (pageNum == null || pageNum.equals("")) {
pageNum = "1";
}
int currentPage = Integer.parseInt(pageNum); // 현재 페이지 번호
// int total = bs.getTotal();
int total = bs.getTotal(board); // 검색 (데이터 갯수)
int startRow = (currentPage - 1) * rowPerPage + 1;
int endRow = startRow + rowPerPage - 1;
PagingPgm pp = new PagingPgm(total, rowPerPage, currentPage);
board.setStartRow(startRow);
board.setEndRow(endRow);
// List<Board> list = bs.list(startRow, endRow);
int no = total - startRow + 1; // 화면 출력 번호
List<Board> list = bs.list(board);
model.addAttribute("list", list);
model.addAttribute("no", no);
model.addAttribute("pp", pp);
// 검색
model.addAttribute("search", board.getSearch());
model.addAttribute("keyword", board.getKeyword());
return "list";
}
<넘어오는 값>
- 글 작성 후 "list.do" 로 요청해서 넘어왔을때는 pageNum 이 넘어오지 않는다, null 이 된다
- 검색 창에서 "확인" 을 눌러서 "list.do" 로 요청해서 넘어왔을때는 pageNum 값이 넘어온다
- 검색을 했을때 넘어오는 search , keyword 값들이 매개변수에 선언한 DTO Board 객체 board 에 저장되게 된다
- 즉 전체 목록을 구하고자 할때는 search, keyword 값이 넘어오지 않을 것이고, 검색 목록을 구하고자 할때는 search, keyword 값이 넘어올 것이다
- search, keyword 값 (DTO Board 객체 board) 이 넘어오는지 유무 에 따라 전체 목록을 구해올지, 부분 목록을 구해올지 판별 가능
+ DTO Board 클래스 안에 search, keyword 컬럼도 만들어져 있음
<기본 변수 & 파생변수>
- 페이지 번호 pageNum 가 전달되지 않았을떄는 pageNum 을 "1" 로 설정해줌
- 기본변수 rowPerPage : 화면에 출력할 데이터 개수
- 기본변수 currentPage : 현재 페이지 번호
- 기본변수 total : 총 데이터 개수 또는 검색된 데이터 총 개수, getTotal() 메소드를 호출해서 그룹함수 count 로 구함
<getTotal() 로 총 데이터 개수 구하기, 검색된 데이터 총 개수 구하기 구별하는 법>
- 전체 데이터 개수도 이 변수 total 에 저장되고, 검색된 데이터 개수도 이 변수 total 에 저장해야한다
- 같은 위치에서 전체 데이터 목록도 구하고, 검색시엔 검색된 데이터 목록을 구하기때문에 이렇게 처리해야함
- 그러므로 전체 데이터 개수를 구하는 것과 검색된 데이터 개수를 구하는 경우가 구분되어야함
- getTotal() 메소드를 호출하면서 search, keyword 를 저장한 DTO 객체 board 를 매개변수로 전달함
- 그러면, 총 데이터 개수를 구할땐 객체 board 가 null 이고 검색한 데이터 총 개수를 구할땐 객체 board 에 keyword, search 값이 존재한다
- Mapper 파일의 데이터 개수 구하는 SQL문 에서 동적 SQL문을 쓰고 있다
* getTotal( ) 메소드 아래에서 설명
<getTotal() 에서 돌아온 후>
- startRow, endRow 변수 값을 구한다
- PagingPgm 클래스 객체 pp 를 생성하면서 기본변수 3개 total, rowPerPage, currentPage 를 생성자의 매개변수로 전달
* PaginPgm 클래스 아래에 설명
<목록 구하기>
- 목록을 잘라주기 위해 startRow, endRow 를 매개변수에서 만들어진 DTO Board 객체 board 에 Setter 메소드로 세팅
+ DTO Board 클래스 안에 startRow, endRow컬럼도 만들어져 있음
+ 화면 출력번호를 구해서 변수 no 에 저장
- 검색을 했을때는 DTO 객체 board 안에 search, keyword, startRow, endRow 값이 저장되어있다
- 검색을 하지 않았을때는 DTO 객체 board 안에 startRow, endRow 값만 저장되어있게 된다
- 목록을 구하기 위한 Service 클래스의 list() 메소드를 호출하며 board 를 매개변수로 전달
* list() 메소드 아래에 설명
<list() 에서 돌아온 후>
- 구한 목록을 list 에 저장 후 Model 객체에 저장해서 list.jsp 에 전달
- 페이징 처리에 필요한 값들을 저장한 PaginPgm 객체 pp 를 Model 객체에 저장해서 list.jsp 에 전달, View 에서 pp의 Getter 메소드로 변수들을 불러옴
- 화면 출력번호 no 도 Model 객체에 저장해서 list.jsp에 전달
- 검색했을때 검색된 리스트에서 페이징 처리를 하려면 search 와 keyword 가 필요하므로 search, keyword 도 Model 객체에 저장해서 list.jsp 에 전달
+ list.jsp 로 search, keyword 를 전달해야하는 이유
- list.jsp 로 이동할때는 search, keyword 가 필요함
- 검색했을때 검색된 리스트에서 페이징 처리를 하려면 search 와 keyword 가 필요하다
- list.jsp 에서 search, keyword 를 쓰는 코드 (아래)
- ${search} 로 가져오고 있다
- 또한 list.jsp 에서 search, keyword 를 쓰는 코드 (아래)
- keyword 값이 empty 면 검색을 하지 않은 경우, keyword 값이 empty 가 아니면 검색을 한 경우 로 if 태그로 나눠서 처리
- 즉, 전체 데이터 목록도 페이징 처리를 따로 하고, 검색한 데이터도 페이징 처리를 따로 해야하므로 keyword 필요
- 페이징 처리 = [1] [2] [3] 같은 페이지 선택할 수 있는 메뉴바 만들고 원하는 페이지를 클릭할 수 있게 하는 것
- 전체 데이터 목록 출력시에는 페이지 번호만 가지고 가지만 검색된 데이터를 구할때는 "list.do" 로 요청하면서 search, keyword 를 가져감
- search, keyword 를 가져가야만 Controller 의 "list.do" 처리 부분에서, 검색된 데이터 목록 요청인지 전체 데이터 목록 요청인지 판별 가능
* 위에서 설명했음
- Service 클래스 BoardServiceImpl.java 에서 getTotal() 메소드 부분만
public int getTotal(Board board) {
return bd.getTotal(board);
}
- DAO 클래스 BoardDaoImpl.java 에서 getTotal() 메소드 부분만
public int getTotal(Board board) {
return sst.selectOne("boardns.getTotal",board);
}
- Mapper 파일 Board.xml 에서 id 가 "getTotal" 인 SQL문 부분만
<select id="getTotal" parameterType="board" resultType="int">
select count(*) from board
<where>
<if test="keyword != null and search !='subcon'">
${search} like '%'||#{keyword}||'%'
</if>
<if test="keyword != null and search=='subcon'">
subject like '%'||#{keyword}||'%' or
content like '%'||#{keyword}||'%'
</if>
</where>
</select>
- 가장 위의 select 문은 count(*) 그룹함수를 사용해서 총 데이터 개수를 구하는 코드이다
- where 절 대신 where 태그를 사용해서 동적 SQL문으로 작성되었다
<전체 데이터 총 개수를 가져올때>
- 전체 데이터를 가져올때는 keyword 도 null 이고 search 도 null 이므로 where 태그 안에 만족하는 조건이 없게 된다
- select count(*) from board 만 적용되어 전체 데이터의 개수를 가져오게 된다
<where 태그, if 태그 사용>
- 동적 SQL문 이다
- where 태그와 if 태그로 특정 조건을 만족할때만 where 절을 추가하는 것과 같은 효과
<keyword 가 null 이 아니고 search 가 'subcon'(제목 + 내용) 인 경우>
- 즉 검색 대상이 제목, 내용, 작성자 인 경우
- search 에 저장된 값은 subject, content, writer 등이 될 수 있다, 이처럼 가변적인 값인 경우 #{search} 가 아닌 ${search} 로 작성해야한다
- keyword 에 저장된 값을 포함하는 데이터의 개수를 검색하게 된다
+ || 로 문자열 연결
<keyword 가 null 이 아니고 search 가 'subcon'(제목 + 내용) 인 경우>
- 즉 검색 대상이 제목 + 내용 인 경우
- or 연산자로 연결해서 subject, content 컬럼에서 특정 키워드를 포함한 데이터의 개수를 검색하게 된다
- 설명보다는 코드를 보기
+ JSTL 의 if 태그와 비슷
+ 동적 SQL문
- JSTL 의 태그들과 비슷하다, if, choose, when, otherwise, foreach 태그 등
- 검색기능을 구현할때 사용
- where 태그와 if 태그로 특정 조건을 만족할때만 where 절을 추가하는 것과 같은 효과
- 나중에 찾아보고 공부하기
PagingPgm 클래스
- PagingPgm.java
package board1.service;
public class PagingPgm {
private int total; // 데이터 갯수
private int rowPerPage; // 화면에 출력할 데이터 갯수
private int pagePerBlk = 10; // 블럭당 페이지 갯수 (1개의 블럭당 10개의 페이지)
private int currentPage; // 현재 페이지 번호
private int startPage; // 각 블럭의 시작 페이지
private int endPage; // 각 블럭의 끝 페이지
private int totalPage; // 총 페이지 수
public PagingPgm(int total, int rowPerPage, int currentPage) {
this.total = total;
this.rowPerPage = rowPerPage;
this.currentPage = currentPage;
totalPage = (int) Math.ceil((double) total / rowPerPage);
startPage = currentPage - (currentPage - 1) % pagePerBlk; // 1, 11, 21...
endPage = startPage + pagePerBlk - 1; // 10, 20, 30...
if (endPage > totalPage)
endPage = totalPage;
}
public int getTotal() {
return total;
}
public void setTotal(int total) {
this.total = total;
}
public int getRowPerPage() {
return rowPerPage;
}
public void setRowPerPage(int rowPerPage) {
this.rowPerPage = rowPerPage;
}
public int getPagePerBlk() {
return pagePerBlk;
}
public void setPagePerBlk(int pagePerBlk) {
this.pagePerBlk = pagePerBlk;
}
public int getCurrentPage() {
return currentPage;
}
public void setCurrentPage(int currentPage) {
this.currentPage = currentPage;
}
public int getStartPage() {
return startPage;
}
public void setStartPage(int startPage) {
this.startPage = startPage;
}
public int getEndPage() {
return endPage;
}
public void setEndPage(int endPage) {
this.endPage = endPage;
}
public int getTotalPage() {
return totalPage;
}
public void setTotalPage(int totalPage) {
this.totalPage = totalPage;
}
}
- 기본변수, 파생변수들이 필드이다
- 기본변수 total, rowPerPage, currentPage
- 파생변수 startPage, endPage, totalPage
- pagePerBlk : 블럭 당 페이지 개수, 즉 1 개 블럭 당 10개의 페이지로 설정했다
- 생성자 매개변수로 기본변수 3개를 전달받아서 파생 변수 값들을 구해준다
- 아래쪽엔 Getter / Setter 메소드로 만들어져 있다
- 이런식으로 따로 클래스를 만들어서 페이징 처리를 하는 경우도 많다
- Mapper 파일 Board.xml 에서 id 가 "list" 인 SQL문 부분만
<!-- <select id="list" parameterType="hashMap" resultMap="boardResult"> -->
<select id="list" parameterType="board" resultMap="boardResult">
select * from (select a.*,rowNum rn from (
select * from board
<where>
<if test="keyword != null and search!='subcon'">
${search} like '%'||#{keyword}||'%'
</if>
<if test="keyword != null and search=='subcon'">
subject like '%'||#{keyword}||'%' or
content like '%'||#{keyword}||'%'
</if>
</where>
order by ref desc,re_step) a )
where rn between #{startRow} and #{endRow}
</select>
- 첫번째 서브쿼리는 rowNum 컬럼에 대한 별칭을 rn 으로 지정하는 역할
<where 태그 시작>
<keyword 가 null 이 아니고 search 가 'subcon'(제목 + 내용) 인 경우>
- 즉 검색 대상이 제목, 내용, 작성자 인 경우
- search 에 저장된 값은 subject, content, writer 등이 될 수 있다, 이처럼 가변적인 값인 경우 #{search} 가 아닌 ${search} 로 작성해야한다
- keyword 에 저장된 값을 포함하는 데이터의 개수를 검색하게 된다
+ || 로 문자열 연결
<keyword 가 null 이 아니고 search 가 'subcon'(제목 + 내용) 인 경우>
- 즉 검색 대상이 제목 + 내용 인 경우
- or 연산자로 연결해서 subject, content 컬럼에서 특정 키워드를 포함한 데이터의 개수를 검색하게 된다
- 즉 해당 검색어를 포함한 제목이 있거나 해당 검색어를 포함한 내용이 있다면 그 데이터들만 가져옴
<where 태그 끝난 후>
- 검색을 먼저하고 정렬을 나중에 해야한다, 그러므로 where 조건 후 order by 가 와야함
- 두번째 서브쿼리에서 정렬을 해야할때, ref 로 내림차순, re_step 으로 오름차순 정렬
- 객체 board 에 저장되어 넘어온 startRow, endRow 값을 where 절에 사용
+ between A and B = A 이상 B 이하
+ 해당 SQL문에 resultMap 이 있지만 지금은 board 테이블 컬럼과 DTO Board 의 프로퍼티 명이 같으므로 쓰지 않아도 됨
- DTO Board 에 테이블 board 에는 없는 프로퍼티가 있는 경우여도 이름이 같으므로 모두 자동 매핑 되므로 resultMap 을 쓰지 않아도 된다
- MyBatis 는 null 값을 허용하지 않기때문에 null 값이 들어가면 오류가 생김
- jdbcType=VARCHAR 속성을 넣어서 null 값을 허용시켜야 한다
Controller 클래스에서 바로 다시 요청하기
return "redirect:member_login.do";
- Controller 클래스에서 위 문장을 작성하면, 다시 "member_login.do" 로 요청하며 로그인 폼으로 가게 됨
- 이전까지는 Controller 클래스에서 View 페이지로만 이동했다
- 만약 return 하며 다시 요청을 할때는 "redirect:" 를 붙여야한다
회원 관리 프로그램 (이어서)
id 중복검사
- member.jsp 부분
//아이디 중복확인
$.ajax({
type:"POST",
url:"member_idcheck.do",
data: {"memid":memid},
success: function (data) {
alert("return success="+data);
if(data==1){ //중복 ID
var newtext='<font color="red">중복 아이디입니다.</font>';
$("#idcheck").text('');
$("#idcheck").show();
$("#idcheck").append(newtext);
$("#join_id").val('').focus();
return false;
}else{ //사용 가능한 ID
var newtext='<font color="blue">사용가능한 아이디입니다.</font>';
$("#idcheck").text('');
$("#idcheck").show();
$("#idcheck").append(newtext);
$("#join_pwd1").focus();
}
}
,
error:function(e){
alert("data error"+e);
}
});//$.ajax
- ajax() 메소드를 사용했고 url 로 "member_idcheck.do" 로 요청한다, 전달할 데이터는 json 형태로 전달
- 여기서 전달한 값은 받는 쪽에서 request.getParameter() 또는 @RequestParam("") 으로 받음
- 콜백함수로 1 또는 -1 을 돌려받도록 했다, 그것에 맞춰서 처리를 함
- Controller 클래스 MemberAction.java 에서 "member_idcheck.do" 요청 처리 부분만
// ID중복검사 ajax함수로 처리부분
@RequestMapping(value = "/member_idcheck.do", method = RequestMethod.POST)
public String member_idcheck(@RequestParam("memid") String id, Model model) throws Exception {
System.out.println("id:"+id);
int result = memberService.checkMemberId(id);
model.addAttribute("result", result);
return "member/idcheckResult";
}
- 요청을 받아서 @RequestParam("memid") 로 전달된 값 memid 를 받아온다
- 이 memid 는 사용자가 입력했던 아이디이다
- checkMemberId() 메소드를 호출하며 중복검사를 한다 * 아래에 설명
- 그 결과 1 또는 -1 을 result 변수로 받고 그걸 Model 객체에 저장 후 jsp/idcheckResult.jsp 로 이동
- Service 클래스 MemberServiceImpl.java 에서 checkMemberId() 메소드 부분만
public int checkMemberId(String id) throws Exception{
return memberDao.checkMemberId(id);
}
- DAO 클래스 MemberDaoImpl.java 에서 checkMemberId() 메소드 부분만
/***** 아이디 중복 체크 *****/
// @Transactional
public int checkMemberId(String id) throws Exception {
int re = -1; // 사용 가능한 ID
MemberBean mb = sqlSession.selectOne("login_check", id);
if (mb != null)
re = 1; // 중복id
return re;
}
- 검색된 결과가 있으면 중복 id 이므로 -1 이 리턴되고, 검색된 결과가 없으면 중복이 아니므로 1 을 리턴한다
- Mapper 파일 member.xml 에서 id 가 "login_check" 인 SQL문 부분만
<!-- 로그인 인증 -->
<select id="login_check" parameterType="String" resultType="member">
select * from join_member where join_id=#{id} and join_state=1
</select>
- 이제는 검색할때 where 조건문에 join_state = 1 이라는 조건을 추가해서 현재 가입된 회원중에서만 검색해야한다
- 해당 id 의 회원 상세정보를 DTO 객체에 저장해서 돌려준다
- 해당 SQL문은 id 중복검사시에도 사용하고 로그인 시에도 사용함
- 다시 돌아가며 DAO -> Service -> Controller -> View 페이지로 넘어왔을때
- idCheckResult.jsp
${result}
- 이 값이 출력되므로 member.js 의 콜백함수로 반환되는 값임, 성공시 1 이 반환, 실패시 -1 이 반환됨
로그인 기능
- 가입이 끝나고 로그인 부분을 보자
return "redirect:member_login.do";
- 회원가입이 끝나면 Conroller 클래스에서 return redirect: 에 의해 "member_login.do" 로 요청한다
- Controller 클래스 MemberAction.java 에서 "member_login.do" 요청 부분만
/* 로그인 폼 뷰 */
@RequestMapping(value = "/member_login.do")
public String member_login() {
return "member/member_login";
// member 폴더의 member_login.jsp 뷰 페이지 실행
}
- 유효성 검사, 비밀번호 찾기 팝업창을 띄워주는 함수 정의, 입력양식 으로 구성되어 있음
- 아이디, 비밀번호를 입력 후 "로그인" 버튼 클릭시 "member_login_ok.do" 로 요청, 그 요청에서 아이디, 비번이 일치하는지 DB에서 확인한다
- Controller 클래스 MeberAction.java 에서 "member_login_ok.do" 요청 부분만
/* 로그인 인증 */
@RequestMapping(value = "/member_login_ok.do", method = RequestMethod.POST)
public String member_login_ok(@RequestParam("id") String id,
@RequestParam("pwd") String pwd,
HttpSession session,
Model model) throws Exception {
int result=0;
MemberBean m = memberService.userCheck(id);
if (m == null) { // 등록되지 않은 회원일때
result = 1;
model.addAttribute("result", result);
return "member/loginResult";
} else { // 등록된 회원일때
if (m.getJoin_pwd().equals(pwd)) {// 비번이 같을때
session.setAttribute("id", id);
String join_name = m.getJoin_name();
String join_profile = m.getJoin_profile();
model.addAttribute("join_name", join_name);
model.addAttribute("join_profile", join_profile);
return "member/main";
} else {// 비번이 다를때
result = 2;
model.addAttribute("result", result);
return "member/loginResult";
}
}
}
- 사용자가 입력한 아이디 , 비밀번호를 @RequestParam 으로 받음
- 회원인증 성공시 SESSION 으로 공유설정해야하므로 session 객체를 매개변수에 선언해서 받는다, 자동으로 생성됨
- Service 클래스의 userCheck() 메소드를 호출해서 회원 인증을 한다, 이때 id 를 전달
- id 만으로 DB에서 해당 id를 가진 데이터가 있는지 확인 후 그 데이터를 가져와서 객체 m 에 저장
- 데이터가 있는 경우에는 그 데이터에서 DB 비밀번호를 m.getJoin_pwd() 로 가져와서 사용자가 입력한 비밀번호가 맞는지 확인하고 있다
- 아이디에 해당하는 데이터가 존재하고, 비밀번호도 일치시 session 객체로 id 를 공유설정함
- 로그인이 되어있는 한 계속 SESSION 값 id 가 공유됨, 사용 가능
- id 가 틀렸을땐 변수 result 에 1을 저장, 비번 틀렸을떈 변수 result 에 2 를 저장
- 이후로그인 성공시 main.jsp 로 이동,로그인 실패시 loginResult.jsp 로 이동해서 처리
- main.jsp 로 이동할때 사용자의 이름, 프로필 첨부파일명을 Model 객체에 저장해서 전달
- Service 클래스 MemberServiceImpl.java 에서 userCheck() 메소드 부분만
public MemberBean userCheck(String id) throws Exception{
return memberDao.userCheck(id);
}
- DAO 클래스 MemberDaoImpl.java 에서 userCheck() 메소드 부분만
/* 로그인 인증 체크 */
// @Transactional
public MemberBean userCheck(String id) throws Exception {
return sqlSession.selectOne("login_check", id);
}
- Mapper 파일 member.xml 에서 id 가 "login_check" 인 SQL문 부분만
<!-- 로그인 인증 -->
<select id="login_check" parameterType="String" resultType="member">
select * from join_member where join_id=#{id} and join_state=1
</select>
- 조건식을 써서 pwdok 가 empty 인 경우와 empty 가 아닌경우로 나누어서 다른 내용을 실행함
1. 처음 이 파일에 올때는 pwdok 이 empty 인 상태이다,
2. 사용자가 아이디, 비번을 쓰고 "찾기" 클릭시 "pwd_find_ok.do" 로 요청하며 폼에 작성된 아이디, 비번값을전송한다
* 아래에서 설명
3. DB에서 아이디, 비번이 일치되면 비밀번호를 메일을 보낸 후 Controller 클래스에서 pwdok 에서 pwdok 에 메세지를 저장한 뒤 다시 return 으로 pwd_find.jsp 로 온다
4. 다시 돌아오면 pwdok 가 empty 가 아니게 되므로 아래의 if 태그를 실행, 즉 아래 화면이 출력됨
- Controller 클래스 MemberAction.java 에서 "pwd_find_ok.do" 요청 부분만
/* 비번찾기 완료 */
@RequestMapping(value = "/pwd_find_ok.do", method = RequestMethod.POST)
public String pwd_find_ok(@ModelAttribute MemberBean mem, HttpServletResponse response, Model model)
throws Exception {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
MemberBean member = memberService.findpwd(mem);
if (member == null) {// 값이 없는 경우
return "member/pwdResult";
} else {
// Mail Server 설정
String charSet = "utf-8";
String hostSMTP = "smtp.naver.com";
String hostSMTPid = "아이디@naver.com";
String hostSMTPpwd = "비밀번호"; // 비밀번호 입력해야함
// 보내는 사람 EMail, 제목, 내용
String fromEmail = "아이디@naver.com";
String fromName = "관리자";
String subject = "비밀번호 찾기";
// 받는 사람 E-Mail 주소
String mail = member.getJoin_email();
try {
HtmlEmail email = new HtmlEmail();
email.setDebug(true);
email.setCharset(charSet);
email.setSSL(true);
email.setHostName(hostSMTP);
email.setSmtpPort(587);
email.setAuthentication(hostSMTPid, hostSMTPpwd);
email.setTLS(true);
email.addTo(mail, charSet);
email.setFrom(fromEmail, fromName, charSet);
email.setSubject(subject);
email.setHtmlMsg("<p align = 'center'>비밀번호 찾기</p><br>" + "<div align='center'> 비밀번호 : "
+ member.getJoin_pwd() + "</div>");
email.send();
} catch (Exception e) {
System.out.println(e);
}
model.addAttribute("pwdok", "등록된 email을 확인 하세요~!!");
return "member/pwd_find";
}
}
- 넘어온 아이디와 회원이름은 @ModelAttribute 로 DTO MemberBean 객체 mem 에 바로 받아서 저장한다
- 그 객체 mem 을 사용해서 Service 클래스의 findpwd() 메소드를 호출해서 회원 상세정보를 받아옴
+ SQL문에 값 1개만 전달 가능하므로 id 와 이름을 DTO 객체에 저장해서 통째로 넘김
+ findpwd() 를 따라가보자 * 아래에서 설명
- findpwd() 에서 돌아온 후, 돌아온 회원의 상세정보를 객체 member 에 저장
- member 값이 없다면 없는 회원이므로 pwdResult.jsp 로 이동, 값이 있다면 메일을 보내기위한 설정을 한다
- 메일을 보낸 후 Model 객체를 통해 "pwdok" 에 메세지를 저장하고 다시 pwd_find.jsp 로 이동
이메일 보내는 설정 및 보내는 코드 (Controller 클래스 MemberAction.java 에서 "pwd_find_ok.do" 요청 부분 일부)
// Mail Server 설정
String charSet = "utf-8";
String hostSMTP = "smtp.naver.com";
String hostSMTPid = "아이디@naver.com";
String hostSMTPpwd = "비밀번호"; // 비밀번호 입력해야함
// 보내는 사람 EMail, 제목, 내용
String fromEmail = "아이디@naver.com";
String fromName = "관리자";
String subject = "비밀번호 찾기";
// 받는 사람 E-Mail 주소
String mail = member.getJoin_email();
try {
HtmlEmail email = new HtmlEmail();
email.setDebug(true);
email.setCharset(charSet);
email.setSSL(true);
email.setHostName(hostSMTP);
email.setSmtpPort(587);
email.setAuthentication(hostSMTPid, hostSMTPpwd);
email.setTLS(true);
email.addTo(mail, charSet);
email.setFrom(fromEmail, fromName, charSet);
email.setSubject(subject);
email.setHtmlMsg("<p align = 'center'>비밀번호 찾기</p><br>" + "<div align='center'> 비밀번호 : "
+ member.getJoin_pwd() + "</div>");
email.send();
} catch (Exception e) {
System.out.println(e);
}
- 받는 사람의 이메일 주소로는 객체 member 에서 member.getJoin_email() 로 회원의 이메일 주소를 가져와서 설정
- HtmlEmail 객체 email 를 생성, setSubject() 로 제목 설정, setHtmlMsg() 로 내용 설정하며 검색된 비밀번호를 보냄
- Service 클래스 MemberServiceImpl.java 에서 findpwd() 메소드 부분만
public MemberBean findpwd(MemberBean m)throws Exception {
return memberDao.findpwd(m);
}
- DAO 클래스 MemberDaoImpl.java 에서 findpwd() 메소드 부분만
/* 비번 검색 */
// @Transactional
public MemberBean findpwd(MemberBean pm) throws Exception {
return sqlSession.selectOne("pwd_find", pm);
}
- Mapper 파일 member.xml 에서 id 가 "pwd_find" 인 SQL문 부분만
<!-- 비번 검색 -->
<select id="pwd_find" resultType="member" parameterType="member">
select * from join_member where join_id=#{join_id} and join_name=#{join_name}
</select>
- 사용자가 비밀번호 찾기 폼에 입력한 id 와 이름이 모두 일치하는 데이터를 검색한다
댓글 게시판 프로그램
실습 준비
- 클라우드의 springboard 프로젝트 다운, 압축 해제, import 하기
파일들 살펴보기 : pom.xml
- inject 라이브러리 : @inject 어노테이션을 쓰기 위한 라이브러리, @Autowired 와 같은 역할
- 메일 보내기 위한 라이브러리 등 라이브러리가 들어가있다
파일들 살펴보기 : web.xml
- 반드시 WEB-INF 폴더 하위에 있어야함
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<filter>
<filter-name>CharacterEncodingFilter</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>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- The definition of the Root Spring Container shared by all Servlets
and Filters -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/root-context.xml</param-value>
</context-param>
<!-- Creates the Spring Container shared by all Servlets and Filters -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Processes application requests -->
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
</web-app>
- url-patteron 을 *.do 로 설정했다
- servlet-context.xml 과 root-context.xml 을 불러옴
- filter 와 filter-mapping 을 통해 한글값 인코딩 처리를 함
파일들 살펴보기 : servlet-context.xml
- 이름과 위치가 지정되어있지 않다
- resources 폴더 하위에 있을 수도 있음
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
<!-- Enables the Spring MVC @Controller programming model -->
<annotation-driven />
<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
<resources mapping="/resources/**" location="/resources/" />
<!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<beans:property name="prefix" value="/jsp/" />
<beans:property name="suffix" value=".jsp" />
</beans:bean>
<context:component-scan base-package="myspring" />
</beans:beans>
- base-package 를 지정했다 = 어노테이션 기반으로 쓰겠다는 의미
- base-package 를 "myspring" 으로 지정, 이게 JAVA 파일이 저장될 최상위 디렉토리이다
- webapp 가 기준이므로 prefix 로 설정된 /jsp/ 에서 jsp 폴더는 webapp 폴더 하위에 있어야한다
- 폴더 jsp 는 View 파일이 저장될 최상위 디렉토리이다.
파일들 살펴보기 : mybatis-config.xml
- MyBatis 환경설정 파일이다
-MyBatis 환경설정 파일mybatis-config.xml (이름이 다를 수도 있음) 과 Mapper 파일은 주로 resources 폴더에 있음
- 이후 프로젝트할땐 localhost 대신 AWS 에 오라클을 설치하고, 받은 IP 를 여기 작성한다
댓글 게시판 테이블 생성
- root-context.xml 에서 지정했던 계정 (spring 계정)을 활성화 시키기
- Connection Profile 도 설정하기
- board53.sql
--board53.sql
select * from tab;
select * from board53;
create table board53(
board_num number(38) primary key
, board_name varchar2(50) not null
, board_pass varchar2(30) not null
, board_subject varchar2(100) not null
, board_content varchar2(4000) not null
, board_re_ref number
, board_re_lev number
, board_re_seq number
, board_readcount number
, board_date date
);
create sequence board53_num_seq
increment by 1 start with 1 nocache;
- 실행해서 테이블 board53, 시퀀스 board53_num_seq 를 생성
테이블 board53 컬럼 설명
- board_num : 글 번호, board53_num_seq 값을 넣음
- board_pass : 글 비밀번호, 비밀번호를 알야아 수정 / 삭제 가능
- board_re_ref : 댓글 관련 컬럼 1, 원문은 board_num 과 값 같음, 댓글은 부모의 boadr_re_ref 와 값 같음
- board_re_lev : 댓글 관련 컬럼 2, 댓글의 깊이 저장, 원문은 0, 댓글은 1, 대댓글은 2
- board_re_seq : 댓글 관련 컬럼 3, 댓글의 출력 순서 저장, 원문은 0, 댓글들은 바뀜
- 값의 변화가 빈번하게 일어나는 것은 number 타입, 아닌 것은 varchar2 타입으로 설정
+ Spring 에서는 어노테이션 기반이므로 Model 2 와 달리 action 으로 <%=request.getContext() 가 없어도 잘 찾아간다
- 원문 글 작성 폼의 입력양식 name 값 을 DTO 프로퍼티명과 일치시킨다
- 입력 후 "등록" 클릭시 "board_write_ok.do" 로 요청한다
- Controller 클래스 BoardController.java 에서"board_write_ok.do" 요청 부분만
/* 게시판 저장 */
@RequestMapping(value = "/board_write_ok.do", method = RequestMethod.POST)
public String board_write_ok(@ModelAttribute BoardBean board)
throws Exception {
// public String board_write_ok(@RequestParam HashMap board)
// throws Exception {
boardService.insert(board);// 저장 메서드 호출
return "redirect:/board_list.do";
}
- 입력양식의 name 값을 DTO 프로퍼티명과 일치시켰으므로 @ModelAttribute 를 통해 입력되어 넘어온 값들을 모두 생성된 DTO 객체 board 에 저장
- 이 DTO 객체 board 에 board_re_ref, board_re_lev, board_re_seq, board_readcount 는 자동으로 0으로 초기화된다
+ DTO int 형 멤버변수(필드, 프로퍼티) 는 자동으로 0 으로 초기화 된다
+ 주석으로 설정된 부분은 Map 을 통해서 넘어온 값들을 받는 방법이다, 키 밸류 형태로 저장됨
- Service 클래스의 메소드 insert() 를 호출하고, 삽입할 글을 담은 객체 board 를 매개변수로 전달
<돌아온 후>
- 글 작성 이후에 목록 페이지로 돌아갈 것이므로 여기서 redirect: 를 써서 "board_list.do" 로 요청
- Service 클래스 BoardServiceImpl.java 에서 insert() 메소드 부분만
/* 게시판 저장 */
public void insert(BoardBean b) throws Exception {
boardDao.insertBoard(b);
}
- DAO 클래스 BoardServiceImpl.java 에서 insertBoard() 메소드 부분만
/* 게시판 저장 */
public void insertBoard(BoardBean b) throws Exception {
sqlSession.insert("Test.board_insert", b);
}
- Mapper 파일 중 namespace 가 Test 인 board.xml 에서 id 가 "board_insert" 인 SQL문 부분만
<!-- 게시판 저장 -->
<insert id="board_insert" parameterType="board">
insert into board53
(board_num,board_name,board_pass,board_subject,
board_content,board_re_ref,board_re_lev,board_re_seq,board_readcount,board_date)
values
(board53_num_seq.nextval,#{board_name},#{board_pass},#{board_subject},
#{board_content},board53_num_seq.nextval,0,0,0,SYSDATE)
</insert>
- 원문이므로 board_re_ref 는 board_num 과 같은 값이 들어간다
- 즉 board_num 과 board_re_ref 는 같은 시퀀스로 값이 입력됨
- 원믄 글이므로 board_re_lev, board_re_seq 는 0 이 들어감, 글 작성 SQL문이므로 조회수도 0 으로 설정
- 작성 날짜는 SYSDATE 로 넣기
- 나머지는 전달받은 객체 board 로 부터 값을 꺼내서 세팅
글 목록 (게시판) 기능
- 게시판 글 목록 출력은 많이 했으므로 간략한 설명 또는 처음하는 부분만 설명
- Controller 클래스 BoardController.java 에서 "board_list.do" 요청 부분만
/* 게시판 목록 */
@RequestMapping(value = "/board_list.do")
public String list(HttpServletRequest request, Model model) throws Exception {
List<BoardBean> boardlist = new ArrayList<BoardBean>();
int page = 1;
int limit = 10; // 한 화면에 출력할 레코드수
if (request.getParameter("page") != null) {
page = Integer.parseInt(request.getParameter("page"));
}
// 총 리스트 수를 받아옴.
int listcount = boardService.getListCount();
// 페이지 번호(page)를 DAO클래스에게 전달한다.
boardlist = boardService.getBoardList(page); // 리스트를 받아옴.
// 총 페이지 수.
int maxpage = (int) ((double) listcount / limit + 0.95); // 0.95를 더해서 올림
// 처리.
// 현재 페이지에 보여줄 시작 페이지 수(1, 11, 21 등...)
int startpage = (((int) ((double) page / 10 + 0.9)) - 1) * 10 + 1;
// 현재 페이지에 보여줄 마지막 페이지 수.(10, 20, 30 등...)
int endpage = maxpage;
if (endpage > startpage + 10 - 1)
endpage = startpage + 10 - 1;
model.addAttribute("page", page);
model.addAttribute("startpage", startpage);
model.addAttribute("endpage", endpage);
model.addAttribute("maxpage", maxpage);
model.addAttribute("listcount", listcount);
model.addAttribute("boardlist", boardlist);
return "board/board_list";
}
- 자세한 설명은 원문 글 작성 기능 설명 후
- 기본 변수와 파생변수들을 만들고, 그 페이지에 해당하는 리스트를 받아온 후 변수와 리스트를 Model 객체에 저장한 후 board_list.jsp 로 이동
총 페이지 수 계산 부분 (BoardController.java 부분)
// 총 페이지 수.
int maxpage = (int) ((double) listcount / limit + 0.95); // 0.95를 더해서 올림 처리
- 이렇게 해도 계산 된다, 새로운 방법
startpage, endpage 변수 설정(BoardController.java 부분)
// 현재 페이지에 보여줄 시작 페이지 수(1, 11, 21 등...)
int startpage = (((int) ((double) page / 10 + 0.9)) - 1) * 10 + 1;
// 현재 페이지에 보여줄 마지막 페이지 수.(10, 20, 30 등...)
int endpage = maxpage;
- 이렇게 해도 계산 된다, 새로운 방법
+ DAO 클래스 BoardDaoImpl.java 에서 getListCount() 부분만
/* 게시판 총 갯수 */
public int getListCount() throws Exception {
int count = 0;
count = ((Integer) sqlSession.selectOne("Test.board_count")).intValue();
return count;
}
- selectOne() 메소드의 리턴자료형은 Object 이다, 원래는 (Integer) 과 .intValue() 로 다운캐스팅, 언박싱을 해야한다
- 하지만 생략해도 됨, MyBatis 기능이다
+ Mapper 파일 board.xml 에서 id 가 "board_list" 인 SQL문 부분만
<!-- 게시판 목록 (page번호를 전달받아서 startRow와 endRow를 구함) -->
<select id="board_list" parameterType="int" resultType="board">
<![CDATA[
select * from
(select rownum rnum,BOARD_NUM,BOARD_NAME,BOARD_SUBJECT,BOARD_CONTENT,
BOARD_RE_REF,BOARD_RE_LEV,BOARD_RE_SEQ,BOARD_READCOUNT,
BOARD_DATE from
(select * from board53 order by BOARD_RE_REF desc,BOARD_RE_SEQ asc))
where rnum >= ((#{page}-1) * 10+1) and rnum <= (#{page} * 10)
]]>
</select>
- 두번째 서브쿼리를 별칭을 쓰지 않고 있으므로, 첫번째 서브쿼리에서 별칭.* 으로 처리하는 대신 일일히 두번째 서브쿼리의 모든 컬럼을 쓰고 있다
- board_re_ref 로 내림차순 정렬하고 두번째 정렬로 board_re_seq 를 오름차순으로 정렬함
+ 같은 부모를 가진 댓글들의 board_re_ref 는 부모의 board_re_ref 값으로 모두 같다
- < , > 를 인식하지 못하므로 대신해서 > %lt; 를 쓰거나 또는 전체를 CDATA 로 묶은 뒤 < , > 사용
- "boardContent.do" 로 요청하며 글 번호 no 와 페이지 번호 page 를 가져간다
- 글 번호와 페이지 번호를 최대 3번까지 전달하게 되는데 여기가 전달하는 출발점이다
ex) 목록 페이지 -> 상세 페이지 -> 수정/삭제 폼
- Controller 클래스 BoardController.java 에서 "boardContent.do" 요청 부분을 처리하자
// 상세 페이지 : 조회수 1 증가 + 상세 정보 구하기
@RequestMapping("boardcontent.do")
public String boardcontent(int no, int page, Model model) {
bs.updatecount(no); // 조회수 1 증가
Board board = bs.getBoard(no); // 상세 정보 구하기
String content = board.getContent().replace("\n", "<br>");
model.addAttribute("board", board);
model.addAttribute("content", content);
model.addAttribute("page", page);
return "board/boardcontent";
}
- 앞에서 넘어온 글 번호, 페이지 번호의 변수명과 같은 이름을 써서 바로 값이 들어가게끔 한다, @RuequestParam 생략
- 앞에서 받아온 Service 객체 bs 를 사용해서 updatecount() , getBoard() 메소드를 호출한다
상세 페이지 Controller 처리에서 해야할 DB 연동 2가지
1. updatecount() : 조회수 증가 Update 문
- 글 번호 no 를 전달한다
- 리턴 받지 않기로 함
2. getBoard() : 상세 정보 구하기 Select 문
- 글 번호 no 를 전달한다
- 리턴은 DTO 클래스 Board 형으로 받는다
- 이 후 내용 content 의 줄바꿈 처리를 하기 위해 내용을 따로 구하고 replace() 메소드를 써서 바꾼 후 내용을 따로 Model 객체에 저장한다
View 페이지로 가져갈 값 3가지
1. getBoard() 에서 리턴받은 상세 정보 DTO 객체 board
2. 줄이 바뀐 내용 content
3. 목록 페이지에서 넘어온 페이지 번호 page
- 글 번호는 객체 board 안에 들어있으므로 페이지 번호만 따로 가져간다
- 이후 board 폴더 하위에 boardcontent.jsp 를 생성해야함
<조회수 증가 Update 문> - Service 클래스 BoardService.java 에서 updatecontent() 메소드 작성
public void updatecount(int no) {
dao.updatecount(no);
}
- 리턴 받지 않으므로 return 을 쓰지 않는다, 그냥 DAO의 메소드 updatecount() 만 호출함
- DAO 클래스 BoardDao.java 에서 updatecontent() 메소드 작성
public void updatecount(int no) {
session.update("hit", no);
}
- Mapper 파일에서 id 가 "hit" 인 SQL문을 불러옴, 전달하는 값은 글 번호인 no
- Mapper 파일 board.xml 에서 id 가 "hit" 인 조회수 증가 Update SQL문 작성
<!-- 조회수 1 증가 -->
<update id="hit" parameterType="int">
update myboard set readcount=readcount+1 where no=#{no}
</update>
- 넘어온 값이 글 번호 no 이므로 parameterType 은 "int", where 절에는 #{no} 를 사용
<상세 정보 구하기 Select 문>
- Service 클래스 BoardService.java 에서 getBoard() 메소드 작성
public Board getBoard(int no) {
return dao.getBoard(no);
}
- 위에서 주입받은 DAO 객체 dao 로 getBoard() 메소드 호출, 글 번호 전달
- DAO 클래스 BoardDao.java 에서 getBoard() 메소드 작성
public Board getBoard(int no) {
return session.selectOne("content", no);
}
- Mapper 파일에서 id 가 "content" 인 SQL문을 불러옴, 전달하는 값은 글 번호인 no
- 글 1개 에 대한 상세정보를 가져오므로 selectOne() 메소드 사용
- Mapper 파일 board.xml 에서 id 가 "content" 인 조회수 증가 SelectSQL문 작성
<!-- 상세정보 구하기 -->
<select id="content" parameterType="int" resultType="board">
select * from myboard where no = #{no}
</select>
- View 페이지인 boardcontent.jsp 를 WEB-INF/views/board/ 폴더 하위에 생성하기
- 상세 페이지에서 '수정'을 누르면 "boardupdateform.do" 로 요청한다, 요청하면서 글 번호, 페이지 번호를 전달
- Controller 클래스 BoardController.java 에서 "boardupdateform.do" 요청 부분을 처리하자
// 수정 폼
@RequestMapping("boardupdateform.do")
public String boardupdateform(int no, int page, Model model) {
Board board = bs.getBoard(no); // 상세 정보 구하기
model.addAttribute("board", board);
model.addAttribute("page", page);
return "board/boardupdateform";
}
- 앞에서 넘어온 글 번호, 페이지 번호의 변수명과 같은 이름을 써서 바로 값이 들어가게끔 한다, @RuequestParam 생략
- 앞의 상세 페이지를 처리할때 만든 상세정보를 가져오는 메소드 getBoard() 를 호출해서 상세 정보 객체를 구해온다
- 수정 폼 boardupdateform.jsp 에서 정보 입력 후 '수정' 을 누르면 "boardupdate.do" 로 요청한다
- hidden 객체를 사용해서 글 번호와 페이지 번호를 "boardupdate.do" 를 요청하며 전달한다
- 수정 위해 글 번호 필요, 수정 후 원래 페이지로 돌아가기 위해 페이지 번호가 필요
- 수정폼에서 넘어간 값들은 받을 때 DTO 객체에 저장해야한다, 페이지 번호 page 만 DTO 안에 들어갈 수 없으므로 따로 받아야한다
- 비밀번호 값이 맞는 경우에만 수정할 것
글 수정
- 앞의 글 수정 폼 boardupdateform.jsp 에서 입력 후 "글수정" 을 누르면 "boardupdate.do" 료 요청
- Controller 클래스 BoardController.java 에서 "boardupdate.do" 요청 부분을 처리하자
// 글 수정
@RequestMapping("boardupdate.do")
public String boardupdate(Board board, int page, Model model) {
int result = 0;
Board old = bs.getBoard(board.getNo());
// 비번 비교
if(old.getPasswd().equals(board.getPasswd())) { // 비번 일치시
result = bs.update(board); // 글 수정
} else { // 비번 불일치시
result = -1;
}
model.addAttribute("result", result);
model.addAttribute("board", board);
model.addAttribute("page", page);
return "board/updateresult";
}
- 넘어온 값들은 DTO 객체에 저장해야한다, @ModelAttribute 를 써서 (생략) 넘어온 값들을 바로 DTO 객체 board 에 저장
- 페이지 번호 page 만 DTO 안에 들어갈 수 없으므로 @RequestParam("page") (생략) 을 써서 따로 받는다
- 수정 성공 메세지를 뿌릴 것이므로 result 값을 Model 객체에 저장해서 넘겨줄 것, Model 객체도 선언함
글 수정 요청에서 해야할 DB연동 2가지
1. 비번 비교 Select SQL
- 비번 비교를 위해 hidden 으로 넘어온 값인 글 번호 no 를 써서 DB에 저장된 해당 글의 상세 정보를 가져옴
- 이때, 상세 정보를 가져오는 메소드인 getBoard() 를 호출해서 상세정보를 구함
2. 글 수정 Update SQL
- Service 클래스의 메소드 update() 호출
- 전달하는 값은 수정할 데이터인 board
- 돌려받는 값은 글 수정 성공 개수, 즉 성공시 1 을 result 에 돌려받음
<View 와 관련>
- 이후 View 에서 수정 성공 / 실패 메세지 처리를 할 것이므로 Model 객체에 result 를 저장해서 전달
- View 에서 원래 페이지로 돌아가기 위해 Model 객체에 페이지 번호 page 를 저장해서 전달
- 수정 완료 후 상세페이지로 갈 때는 글 번호 no 와 페이지 번호 page 를 가져가야하므로 Model 객체에 글 번호를 가지고 있는 객체 board 를 Model 객체에 저장해서 전달
- 만약, 목록페이지로 갈 때는 Model 에 객체 board 를 가져갈 필요없다
- Service 클래스 BoardService.java 에서 update() 메소드 생성
public int update(Board board) {
return dao.update(board);
}
- DAO 클래스 BoardDao.java 에서 update() 메소드 생성
public int update(Board board) {
return session.update("update", board);
}
- Mapper 파일에서 id 가 "update" 인 SQL문을 불러옴, 전달하는 값은 수정할 데이터인 객체 board
- 수정 성공시 1 을 자동으로 돌려준다
- Mapper 파일 board.xml 에서 id 가 "update" 인 Update SQL문 작성
<!-- 글 수정 -->
<update id="update" parameterType="board">
update myboard set writer=#{writer},subject=#{subject},
content=#{content},register=sysdate where no=#{no}
</update>
- Mapper 파일의 SQL문에선 SQL문 끝에 ; 를 찍으면 안된다
- View 페이지 updateresult.jsp 를 board 폴더 하위에 생성 및 작성
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<c:if test="${result == 1}">
<script>
alert("글 수정 성공");
location.href="boardlist.do?page=${page}"; // 목록 페이지
// location.href="boardcontent.do?no=${board.no}&page=${page}"; // 상세 페이지
</script>
</c:if>
<c:if test="${result != 1}">
<script>
alert("글 수정 실패");
history.go(-1);
</script>
</c:if>
</body>
</html>
- if 태그를 사용하기 위해 JSTL core 라이브러리를 불러온다
- 수정 성공 후 목록 페이지로 가기 위해서 페이지 번호 page 를 전달해야 한다
- 수정 성공 후 상세 페이지로 가기 위해서 글 번호 no, 페이지 번호 page 를 전달해야한다
- 수정 실패 시 result 는 -1 이므로 "글 수정 실패" 메세지 출력 후 다시 이전 페이지인 수정 폼으로 돌아간다
- 상세 페이지에서 '삭제'를 누르면 "boarddeleteform.do" 로 요청한다, 요청하면서 글 번호, 페이지 번호를 전달
- 글 번호는 객체 board 안에 있으므로 ${board.no} 로 가져오기
- Controller 클래스 BoardController.java 에서 "boarddeleteform.do" 요청 부분을 처리하자
- 앞에서 넘어온 글 번호, 페이지 번호의 변수명과 같은 이름을 써서 바로 값이 들어가게끔 한다, @RuequestParam 생략
- 글 삭제 폼에서는 여기서 글 번호, 페이지 번호 값을 받아서 Model 객체에 저장해서 가져가도 되지만, 글 삭제 폼에서 param 으로 바로 받아도 된다
- 그러므로 여기서는 아무값도 받아서 Model 객체에 저장해서 전달하지 않는다
- Model 2 에서도 View 에서 View 로 갈때 Controller 클래스를 거치지만 Contorller 에서 값을 중간 저장하고 전달하지 않았다, 바로 View 에서 전달한 값을 이동한 View 에서 getParameter() 로 받아왔었음, 여기서도 같은 방법을 사용 가능함
- 글 삭제 폼 boarddeleteform.jsp 에서 비밀번호 입력 후 "글삭제" 클릭시 "boarddelete.do" 로 요청
- Controller 클래스 BoardController.java 에서 "boarddelete.do" 요청 부분을 처리하자
// 글 삭제
@RequestMapping("boarddelete.do")
public String boarddelete(Board board, int page, Model model) {
int result = 0;
Board old = bs.getBoard(board.getNo()); // 상세 ㅈ어보 구하기
// 비번 비교
if(old.getPasswd().equals(board.getPasswd())) { // 비번 일치시
result = bs.delete(board.getNo()); // 글 삭제
} else { // 비번 불일치시
result = -1;
}
model.addAttribute("result", result);
model.addAttribute("page", result);
return "board/deleteresult";
}
- 넘어온 값들은 DTO 객체에 저장해야한다, @ModelAttribute 를 써서 (생략) 넘어온 값들을 바로 DTO 객체 board 에 저장
- 페이지 번호 page 만 DTO 안에 들어갈 수 없으므로 @RequestParam("page") (생략) 을 써서 따로 받는다
- 삭제 성공 메세지를 뿌릴 것이므로 result 값을 Model 객체에 저장해서 넘겨줄 것, Model 객체도 선언함
글 삭제 요청에서 해야할 DB연동 2가지
1. 비번 비교 Select SQL
- 비번 비교를 위해 hidden 으로 넘어온 값인 글 번호 no 를 써서 DB에 저장된 해당 글의 상세 정보를 가져옴
- 이때, 상세 정보를 가져오는 메소드인 getBoard() 를 호출해서 상세정보를 구함
2. 글 수정 Update SQL
- Service 클래스의 메소드 delete() 호출
- 전달하는 값은 삭제할 글 번호인 no
- 돌려받는 값은 글 수정 성공 개수, 즉 성공시 1 을 result 에 돌려받음
- 삭제 성공 이후 View deleteresult.jsp 에서 목록 페이지로 갈 것이므로 페이지 번호 page 를 View 로 전달줘야함
+ 상세 페이지로 갈때는 글 번호도 전달해야한다
- Service 클래스 BoardService.java 에서 delete() 메소드 생성
public int delete(int no) {
return dao.delete(no);
}
- DAO 클래스 BoardDao.java 에서 update() 메소드 생성
public int delete(int no) {
return session.delete("delete", no);
}
- Mapper 파일에서 id 가 "delete" 인 SQL문을 불러옴, 전달하는 값은 수정할 글의 글 번호 no
- 삭제 성공시 1 을 자동으로 돌려준다
- Mapper 파일 board.xml 에서 id 가 "delete" 인 DeleteSQL문 작성
<!-- 글 삭제 -->
<delete id="delete" parameterType="int">
delete from myboard where no = #{no}
</delete>
- View 페이지 deleteresult.jsp 를 board 폴더 하위에 생성 및 작성
- 사원명을 클릭하면 "empView.do" 로 요청한다, 요청하면서 사원 번호인 empno 를 GET 방식으로 전달함
Controller 클래스 EmpController.java 에서 "empView.do" 요청 부분만
// 사원 상세페이지
@RequestMapping("empView.do")
public String empView(int empno, Model model) {
Emp emp = es.select(empno); // 사원 상세 정보 구하기
model.addAttribute("emp", emp);
return "emp/empView";
}
- 앞에서 넘어온 값인 사원번호를 @RequestParam (생략) 으로 바로 변수 empno 로 값을 받는다
- 자동으로 int 형으로 형변환되어 저장됨
- 1명에 대한 상세정보를 DB에서 구해와서 View 에 뿌려야 하므로 Model 객체를 매개변수에 선언해서 받음
<돌아온 후>
- 사원의 상세정보는 select() 가 리턴되면서 Emp DTO 객체에 저장되어 돌아온다, 그걸 Emp DTO 객체 emp 에 저장함
- 그 객체 emp 를 Model 객체에 저장하고 /WEB-INF/views/emp/empView.jsp 파일로 이동
- View 페이지로 이동할때 prefix, suffix 를 빼고 작성하고, 하위의 패키지는 적어줘야하므로 "emp/empView" 로 작성
Service 클래스 EmpServiceImpl.java 에서 select() 메소드 부분만
public Emp select(int empno) {
return ed.select(empno);
}
- DB 연동을 해야하므로 Service 로 넘어왔다
DAO 클래스 EmpDaoImpl.java 에서 select() 메소드 부분만
public Emp select(int empno) {
return sst.selectOne("empns.select", empno);
}
- root-context.xml 에서 SqlSession bean 객체를 생성했고, 그걸 DAO 클래스에 주입했으므로 selectOne() 사용 가능
- 전달된 사원번호 empno 를 selectOne() 을 호추랗며 그대로 전달함
- 사원 1명에 대한 상세정보를 구해야하므로 selectOne() 을 사용
Mapper 파일 Emp.xml 에서 id 가 "select" 인 태그 부분만
<select id="select" parameterType="int" resultType="emp">
select * from emp where empno=#{empno}
</select>
- 전달받은 사원 번호로 해당 사원의 상세정보(모든 정보) 를 검색함
- 전달받은 값은 사원번호이므로 전달받은 자료형 parameterType 은 "int"
- 돌려주는 자료형 resultType 은 Emp DTO alias 인 "emp"
- 다시 DAO -> Service -> Controller 로 갔다가 View 로 오게 된다
- emp.deptno (해당 사원의 부서) 와 dept.deptno (forEach에 의해 모든 부서의 부서번호가 차례대로 들어감) 가 같으면 selected 함으로서 해당 사원이 가입할때 등록했던 부서는 select-option 에서 선택되어 나타나도록 했다
+ jQuery 로 처리하면 간단하게 처리 가능
- 사원 번호을 readonly 속성으로 비활성화했다, readonly 이므로 값이 넘어간다!
- select-option 에 출력되는 내용은 부서명(부서번호) 형식이지만, 저장하는 값인 value 는 부서 번호가 된다
- 사원 수정폼에 수정할 정보를 입력하고 '수정' 을 누르면 action 인 "empUpdate.do" 로 요청한다
프로젝트 myBatis2 : 사원 정보 수정
Controller 클래스 EmpController.java 에서 "empUpdate.do" 요청 부분만
// 사원 정보 수정
@RequestMapping("empUpdate.do")
public String empUpdate(Emp emp, Model model) {
int result = es.update(emp);
model.addAttribute("deptno", emp.getDeptno());
model.addAttribute("result", result);
return "emp/empUpdate";
}
- 사원 수정폼에서 넘어온 값들을 @ModelAttribuate (생략) 을 사용해서 Setter 메소드들로 한번에 Emp DTO 객체인 emp 에 저장한다
- Emp Service 객체 es 를 사용해서 update() 메소드를 호출하고, 호출할때 수정할 정보를 저장한 객체 emp 를 전달
- 수정할 사원을 특정하는 사원 번호는 앞의 사원 수정폼에서 넘어왔으므로 emp 객체 안 emp.empno 에 저장되어있음
<돌아온 후>
- 수정 성공시 result 에는 1이 저장된다
- 수정이 끝난 후 부서 상세 페이지로 돌아가므로, 해당 사원이 소속된 부서의 부서번호가 필요하다, 그래서 emp.getDeptno() 를 "deptno" 네임으로 Model 객체에 저장
- View 페이지에서 수정 성공 / 실패 처리를 하기 위해 update() 에서 돌려받은 값 result 를 Model 객체에 저장
- /WEB-INF/views/emp/empUpdate.jsp 로 이동
Service 클래스 EmpServiceImpl.java 에서 update() 메소드 부분만
public int update(Emp emp) {
return ed.update(emp);
}
DAO 클래스 EmpDaoImpl.java 에서 update() 메소드 부분만
public int update(Emp emp) {
return sst.update("empns.update", emp);
}
- 수정할 정보를 담은 Emp DTO 객체 emp 를 받아서 그대로 전달
- 수정 성공시 자동으로 1을 리턴한다
Mapper 파일 Emp.xml 에서 id 가 "update" 태그 부분만
<update id="update" parameterType="emp">
update emp set ename=#{ename},job=#{job},sal=${sal},
comm=#{comm},deptno=#{deptno} where empno=#{empno}
</update>
- "emp" 는 MyBatis 환경설정 파일 Configuration.xml 에서 설정한 Emp DTO 의 alias 이다
- 객체 emp 가 넘어왔으므로 #{ename} 은 emp.getEname() 과 같은 의미
- 사원번호는 객체 emp 의 emp.empno 에 저장되어 있으므로 #{empno} 로 가져와서 where 절에 넣는다
- '삭제' 를 누르면 "empDelete.do" 료 요청한다, 요청하면서 사원 번호인 empno 를 전달
Controller 클래스 EmpController.java 에서 "empDelete.do" 요청 부분만
// 사원 삭제
@RequestMapping("empDelete.do")
public String empDelete(int empno, Model model) {
Emp emp = es.select(empno); // 사원 상세 정보 구하기
int result = es.delete(empno);
model.addAttribute("result", result);
model.addAttribute("deptno", emp.getDeptno());
return "emp/empDelete";
}
- 앞에서 넘어온 사원번호 empno 를 @RequestParam("empno") (생략) 을 통해 바로 매개변수의 empno 에 저장한다 사원 삭제할때 DB 연동 작업 2가지
1. 사원 상세 정보 구하기
- 사원을 삭제 한 후 그 사원이 속해있었던 부서의 부서 상세 페이지로 이동할 것이다, 그러려면 부서 번호가 필요함
- 부서 상세 페이지로 가기 위해, 삭제될 사원의 상세 정보를 구해서 거기서 부서 번호를 가져올 것 - 사원의 상세 정보를 구하기 위해 select() 메소드를 호출하며, 사원번호를 전달
2. 사원 삭제 하기
- 사원 삭제를 위해 delete() 메소드를 호출하며, 사원번호를 전달
<돌아온 후>
- 삭제된 사원 상세정보를 받은 emp 에서 emp.getDeptno() 로 사원이 소속되어있던 부서번호를 가져와서 Model 객체에 저장하고 empDelete.jsp 로 이동
1. 사원 상세 정보 구하기
- 이전에 여러번 했으므로 설명 생략
2. 사원 삭제하기
Service 클래스 EmpServiceImpl.java 에서 delete() 메소드 부분만
public int delete(int empno) {
return ed.delete(empno);
}
DAO 클래스 EmpDaoImpl.java 에서 delete() 메소드 부분만
public int delete(int empno) {
return sst.delete("empns.delete", empno);
}
Mapper 파일 Emp.xml 에서 id 가 "delete" 인 태그 부분만
<delete id="delete" parameterType="int">
delete from emp where empno=#{empno}
</delete>
- 이 폼에서 넘어가는 내용은 writer, passwd, subject, content 이다
- 입력하고 "글작성" 클릭시 "boardwrite.do" 로 넘어간다, Controller -> Service -> DAO (Mapper 파일) 으로 가야한다
- Controller 클래스 BoardController.java 에서 "boardwrite.do" 요청 부분을 처리하자
- 아래 내용을 작성
// 글 작성
@RequestMapping("boardwrite.do")
public String boardwrite(Board board, Model model) {
int result = bs.insert(board);
model.addAttribute("result", result);
return "board/insertresult";
}
- 폼에서 넘어온 값을 DTO 객체로 한번에 받기 위해 @ModelAttribute 사용
- 메소드 명은 요청 이름값과 같게 맞췄다
- 글을 작성(insert) 한 후 성공 / 실패를 View 에서 처리하기위해 어떠한 값들을 가져갈 것이므로 Model 객체 선언해서 받기
- Service 객체 bs 를 사용해서 Service 클래스의 insert() 메소드를 호출, 이때 삽입할 데이터인 객체 board 를 가져감, 리턴자료형은 int 이다
- Service 클래스 BoardService.java 에서 insert() 메소드를 생성해야한다
public int insert(Board board) {
return dao.insert(board);
}
- Controller 에서 이 메소드를 호출해야하므로 public, 리턴자료형은 int, 메소드 명은 insert(), 매개변수 자료형은 Board
- DAO 의 insert() 를 호출
- 이 코드를 복사 후 DAO 클래스 BoardDao.java 로 가서 복붙 하고 수정
- BoardDao.java 에서 insert() 메소드 생성
public int insert(Board board) {
return session.insert("insert", board);
}
- 이 insert() 가 실제 SQL문을 수행하고 성공시 1 을 반환함
- Mapper 파일인 Board.xml 에 id 가 "insert" 인 insert (글 작성) 문을 생성해야함
<!-- 글 작성 -->
<insert id="insert" parameterType="board">
insert into myboard values(myboard_seq.nextval,#{writer},
#{passwd},#{subject},#{content},0,sysdate)
</insert>
- 전달되는 값이 DTO Board 객체 board 이므로 DTO Board 의 alias 인 "board" 를 parameterType 에 설정
- no 컬럼은 sequence 인 myboard_seq 로 세팅
- 앞에서 객체 board 가 넘어왔으므로 #{writer}, #{passwd}, #{subject}, #{content} 로 세팅
- JSTL core 라이브러리 사용하기 위해 core 라이브러리 불러오는 코드 한 줄 추가
- 글 작성 성공하면 목록 페이지로 갈 것이므로 "boardlist.do" 요청하기
- 글 작성 실패시 이전 페이지인 글 작성 폼으로 돌아감
- index 파일을 실행시켜서 글 작성폼에서 글을 작성해보자
+ 글 수정 / 삭제 시 비밀번호가 맞아야 삭제되도록 하기 위해 비밀번호를 입력받는 것임
- "boardlist.do" 요청 처리를 하지 않았으므로 오류가 뜬다
- Controller 클래스 BoardController.java 에서 "boardlist.do" 요청을 처리하는 코드를 작성하자 (미완성, 수정 전)
// 글 목록
@RequestMapping("boardlist.do")
public String boardlist(HttpServletRequest request, Model model) {
int page = 1; // 현재 페이지 번호
int limit = 10; // 한 화면에 출력할 데이터 갯수
if(request.getParameter("page") != null) {
page = Integer.parseInt(request.getParameter("page"));
}
int startRow = (page - 1) * limit + 1;
int endRow = page * limit;
int listcount = bs.getCount(); // 총 데이터 갯수
System.out.println("listcount : " + listcount);
return "board/boardlist";
}
- 기본변수, 파생변수를 설정해서 DB에서 목록을 구해오고, 페이징 처리를 하고, 그걸 View 에 뿌려야함
- request 객체를 매개변수로 부터 받는다, 이렇게 매개변수에 선언하면 자동으로 request 객체를 받아옴
- 결과를 View 페이지에 뿌려야하므로 Model 객체도 매개변수에 선언해서 받아온다
1. 기본변수 page : 현재 페이지를 저장할 변수
2. 기본변수 limit : 한 화면에 출력할 데이터 개수를 저장하는 변수
3. 파생변수 starRow, endRow : page, limit 을 이용해서 구해준다, page, limit 로 생성 가능하므로 굳이 생성하지 않아도 된다
4. 기본변수 listcount : 총 데이터 갯수를 저장, DB 와 연동해서 count(*) 로 가져올 것
<총 데이터 개수 가져오기>
- Service 클래스의 getCount() 를 호출해서 리턴받는다 * 아래에서 Service 에서 getCount() 를 생성함
- 나머지 부분은 아래에서 다시 완성
- Service 클래스 BoardService.java 에서 getCount() 메소드를 생성해야함
public int getCount() {
return dao.getCount();
}
- DAO 클래스 BoardDao.java 에서 getCount() 메소드 생성하기
public int getCount() {
return session.selectOne("count");
}
- 그룹함수는 결과가 1개이므로 selectOne() 메소드 사용, id 가 "count" 인 SQL문 불러오기
- Mapper 파일 Board.xml 에서 id 가 "count" 인 SQL문 생성
<!-- 총 데이터 갯수 -->
<select id="count" resultType="int">
select count(*) from myboard
</select>
- 돌려주는 값이 총 데이터 갯수이므로 resultType 은 "int"
- 총 데이터 갯수를 가져왔는지 확인하기 위해 현재 상태에서 index 파일에서 "boardlist.do" 로 요청해보자
- index 파일 실행 하고 콘솔창 보기
- listcount 값이 찍혀 나왔다, 글을 하나 작성했었으므로 listcount : 1 이 맞다
- 이제 목록을 가져오는 작업을 Controller 부터 처리하자
- Controller 클래스 BoardController.java 에서 "boardlist.do" 요청을 처리하는 코드를 작성하자 (이어서, 완성, 추가되었다)
// 글 목록
@RequestMapping("boardlist.do")
public String boardlist(HttpServletRequest request, Model model) {
int page = 1; // 현재 페이지 번호
int limit = 10; // 한 화면에 출력할 데이터 갯수
if(request.getParameter("page") != null) {
page = Integer.parseInt(request.getParameter("page"));
}
int startRow = (page - 1) * limit + 1;
int endRow = page * limit;
int listcount = bs.getCount(); // 총 데이터 갯수
System.out.println("listcount : " + listcount);
List<Board> boardlist = bs.getBoardList(page); // 게시판 목록
System.out.println("boardlist : " + boardlist);
// 총 페이지
int pageCount = listcount / limit + ((listcount % limit == 0) ? 0 : 1);
int startPage = ((page - 1) / 10) * limit + 1; // 1, 11, 21...
int endPage = startPage + 10 - 1; // 10, 20, 30...
if(endPage > pageCount) endPage = pageCount;
model.addAttribute("page", page);
model.addAttribute("listcount", listcount);
model.addAttribute("boardlist", boardlist); // 리스트
model.addAttribute("pageCount", pageCount);
model.addAttribute("startPage", startPage);
model.addAttribute("endPage", endPage);
return "board/boardlist";
}
<목록 가져오기>
- MyBatis 로 처리하면 DAO -> Mapper 파일로 갈때 단 하나의 값만 전달가능하다
- limit 은 고정되어있고, startRow, endRow 는 나중에 페이지 번호 page 로 계산 가능하므로 페이지번호 page 만 넘기기
+ 또는 map 객체에 키밸류 형태로 저장하는 방법도 있다
- getBoardList() 에서 글들이 저장된 리스트를 반환하도록 할 것 * 아래에서 설명
<돌아온 후>
- getBoardList() 에서 돌아온 후 콘솔창 출력해보면 boadlist 에 내가 썼던 글의 주소가 출력된다
5. 파생변수 pageCount : 총 페이지 수를 구함, listcount 와 limit 사용
6. 파생변수 startPage & endPage : 각(현재) 블럭의 시작 페이지, 끝 페이지, page 와 limit 사용
- 이후 기본변수, 파생변수, 가져온 목록(List) 들을 Model 객체에 저장하고 board/boardlist.jsp 로 이동
- Service 클래스 BoardService.java 에서 목록을 가져오는 메소드 getBoardList() 를 생성하자
public List<Board> getBoardList(int page) {
return dao.getBoardList(page);
}
- DAO 클래스 BoardDao.java 에서 getBoardList() 를 작성하자
public List<Board> getBoardList(int page) {
return session.selectList("list", page);
}
- 여러개의 글(DTO 객체) 을 가져올 것이므로 selectList() 메소드 사용
- 페이지 번호 page 를 전달함, 받는 값은 DTO 객체들이 저장된 리스트이다
- Mapper 파일 Baord.xml 에서 id 가 "list" 인 SQL문 작성
<!-- 글 목록 -->
<!-- > : > , < : < -->
<select id="list" parameterType="int" resultType="board">
<![CDATA[
select * from (select rownum rnum, board.* from (
select * from myboard order by no desc) board )
where rnum >= ((#{page}-1) * 10 + 1) and rnum <= (#{page} * 10)
]]>
</select>
- 페이지 번호가 넘어오므로 parameterType 은 "int" , 돌려주는 값은 DTO 가 저장된 리스트 이므로 resultType 은 "board"(DTO alias)
- limit 은 10으로 고정되어있으므로 10 으로 쓰고, startRow, endRow 는 페이지 번호 page 로 계산 가능해서 사용하기
+ <. > 인식을 못하므로 > , $lt; 사용, 또는 <![CDATA[ ]]> 로 전체 SQL문으로 감싸주면 부등호 기호 <, > 를 인식함
- View 페이지를 보기 전에 페이징 처리를 확인하기 위해 myboard.sql 에서 데이터를 강제로 많이 삽입하자
insert into myboard values(myboard_seq.nextval,'Lay','1234','Practice1',
'Content',0,systdate)
- View 페이지 board/boardlist.jsp 를 생성해서 Contorller 에서 Model 객체에 저장된 값들을 활용해서 목록을 출력하자
- 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" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>게시판 목록</title>
</head>
<body>
<a href="boardform.do">글작성</a> <br>
글갯수 : ${listcount }
<table border=1 align=center width=700>
<caption>게시판 목록</caption>
<tr>
<th>번호</th>
<th>제목</th>
<th>작성자</th>
<th>날짜</th>
<th>조회수</th>
</tr>
<!-- 화면 출력 번호 -->
<c:set var="num" value="${listcount - (page - 1) * 10}"/>
<c:forEach var="b" items="${boardlist}">
<tr>
<td>${num}
<c:set var="num" value="${num-1}"/>
</td>
<td>
<a href="boardcontent.do?no=${b.no}&page=${page}">${b.subject}</a>
</td>
<td>${b.writer}</td>
<td>
<fmt:formatDate value="${b.register}"
pattern="yyyy-MM-dd HH:mm:ss"/>
</td>
<td>${b.readcount}</td>
</tr>
</c:forEach>
</table>
<br>
<!-- 페이지 처리 -->
<center>
<c:if test="${listcount > 0}">
<!-- 1페이지로 이동 -->
<a href="boardlist.do?page=1" style="text-decoration:none"> << </a>
<!-- 이전 블럭으로 이동 -->
<c:if test="${startpage > 10}">
<a href="boardlist.do?page=${startPage - 10}">[이전]</a>
</c:if>
<!-- 각 블럭에 10개 페이지 출력 -->
<c:forEach var="i" begin="${startPage}" end="${endPage}">
<c:if test="${i == page}"> <!-- 현재 페이지 -->
[${i}]
</c:if>
<c:if test="${i != page}"> <!-- 현재 페이지가 아닌 경우-->
<a href="boardlist.do?page=${i}">[${i}]</a>
</c:if>
</c:forEach>
<!-- 다음 블럭으로 이동 -->
<c:if test="${endPage < pageCount}">
<a href="boardlist.do?page=${startPage + 10}">[다음]</a>
</c:if>
<!-- 마지막 페이지로 이동 -->
<a href="boardlist.do?page=${pageCount}" style="text-decoration:none"> >> </a>
</c:if>
</center>
</body>
</html>
- JSTL core 라이브러리, 국제화 라이브러리를 불러오기
7. 파생변수 num : 화면 출력 번호, 코어 라이브러리 set 태그 사용해서 생성했음, listcount, page 를 사용해서 생성
- forEach 태그 items 안에 목록 리스트인 ${boardlist} 를 써서 하나씩 글을 가져오기
- forEach 루프가 돌아갈때마다 num 을 1씩 감소시키기, 이때 코어 라이브러리 set 태그 사용해서 재정의 하는 방식