[문항1] 1. 다음은 정보시스템 구축 시 운영체제 관련 요구사항을 반영하여 운영체제를 선택하려고 한다. 각 괄호에 들어갈 운영체제를 아래의 보기에서 골라서 입력하시오.[15점] [보기 : 운영 체제의 종류 - 윈도우즈(Windows), 유닉스(UNIX), 리눅스(Linux), IOS, Android]
일반적으로 [ (1) ] 기반 시스템이 하드웨어 및 소프트웨어 소유 비용이 가장 적게 소요된다. 유지 및 관리 비용 측면에서는 [ (2) ] 기반 시스템이 강점을 가진다. 안정적이고 신뢰할 수 있으며 대용량 처리를 위해서는 [ (3) ] 기반 시스템이 선호되고 있다.
1 : 리눅스(Linux)
2 : 윈도우즈(Windows)
3 : 유닉스(UNIX)
[문항2] 요구사항 분석을 통해 요구사항을 기술할 때에 확인 되어야 될 작업이 아닌 것은 무엇인가?[10점]
1) 요구사항의 확인(Validation) 2) 요구사항 구현의 검증(Verification) 3) 비용 추정 4) 검증 가능한 서책 존재 유무
4) 검증 가능한 서책 존재 유무
[문항3] 다음은 DBMS 종류 중 어느 것에 대한 설명인가? [10점]
SQLite
[문항4] 다음은 무엇에 대한 설명인가?[10점] 실세계 문제에 대한 모델링이 소프트웨어 요구사항 분석의 핵심이며, 모델은 문제가 발생하는 상황에 대한 이해를 증진시키고 해결책을 설명한다.
1) 개념 모델링 2) 요구사항 할당 3) 정형 분석 4) 요구사항 협상
1) 개념 모델링
[문항5] 다음 중 요구사항 분석 기법이 아닌 것은 무엇인가?[10점]
1) 요구사항 분류((Requirement Classification) 2) 개념 모델링(Conceptual Modeling) 3) 요구사항 할당((Requirement Allocation) 4) 결과 분석(Result Analytics)
4) 결과 분석(Result Analytics)
[문항6] 다음 ( ) 안에 들어갈 단어로 올바른 것은 무엇인가?[10점]
운영체제와 소프트웨어 애플리케이션 사이에 위치하는 ( )는 소프트 웨어 애플리케이션에게 운영체제가 제공하는 서비스를 추가 및 확장하여 제공하는 컴퓨터 소프트웨어를 말한다.
[문항8] 클라이언트의 요구 사항에 따라서 아래 테이블 작성 후 3명의 고객을 입력하는 문장을 작성하시오? [25점]
CREATE TABLE Client (
CNO CHAR(3) not null,
CNAME VARCHAR2(10) not null,
CID VARCHAR2(14) not null
);
INSERT INTO Client VALUES('1','lay','lay99');
INSERT INTO Client VALUES('2','kei','kei00');
INSERT INTO Client VALUES('3','mimi','mimi01');
- 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 폴더 하위에 생성 및 작성