|
Back-End/Spring 2019. 7. 6. 22:05
-Spring boot-
- 2014년부터 개발되었으며 spring legacy project에 비해서 설정이 매우 간소화됨
WAS(tomcat) 가 포함되어 있으므로 서버 설정이 간소화됨
아직 실무에서 많이 사용되고 있지 않지만 향후 spring legacy project를 대체하리라 예상됨
1) Spring Starter Project 생성
Name : 프로젝트 이름 (spring03_boot)
Artifact : spring03_boot
Package : spring03
-프로젝트 생성-
마우스 우클릭 => Spring Starter Project 생성 => 이름과 패키지 설정 => SQL : MySQL, JDBC, Mybatis 체크
=> Template Engines : Thymeleaf 체크 => Web : Web 체크 => next => finish
|
Boot Version : 최신 버전 2.1.6 으로 체크
SQL : MySQL, JDBC, Mybatis 체크
Template Engines : Thymeleaf 체크
Web : Web 체크
- spring legacy project와 다른점 -
1. pom.xml에서 스프링 프레임워크 버전이 빠져있다. (자동으로 셋팅이 되어있기 때문, 설정이 숨겨져 있다.)
2. src/main/java에 있는 Spring03BootApplication.java (클래스) 에 가보면 메인 메소드 (public static void main(String[] args) 가 있는것을 확인할 수 있다.
(즉, 이 클래스를 실행시키면 프로젝트를 실행할 수 있다. 레거시프로젝트에서는 메인메소드가 따로 없었고, 프로젝트 자체를 실행해야만 실행이 되었다.)
3. 또, 이 클래스를 run을 하려고 하면 Java Application과 Spring Boot App 중에 하나를 선택하라고 하는데, Spring Boot App로 실행을 하여야 한다.
4. 클래스를 실행하게 되면 스프링 부트 버전과 톰캣 버전 등 정보가 인포로 출력이 되고, 현재는 데이터베이스 드라이버 설정이 안되어있어서 에러가 발생한다.
2) Spring boot 프로젝트의 실행 방법
시작 클래스 : 프로젝트이름+Application.java
tomcat가 내장되어 있음
Run as => Spring Boot App 를 선택하면 됨
기존에 실행중인 톰캣은 중지시키고 실행시켜야 한다.
3) 템플릿 엔진
spring boot application에서는 jsp 대신 template을 사용하는 것을 권장하고 있다.
(기본적으로 jsp도 사용할 수는 있지만 설정을 따로 해야만 사용할 수가 있어 왠만하면 template를 사용한다.)
spring boot에서 사용가능한 template에는 여러 종류가 있는데 그 중에서 타임리프 (Thyme leaf) 를 활용하여 실습
thyme - 백리향
- 웹 템플릿 엔진 -
스프링 MVC와의 통합 모듈을 제공하며, 애플리케이션에서 JSP로 만든 기능들을 완전히 대체할 수 있음
타임리프의 목표는 세련되고 잘다듬어진 템플릿을 제공하는 것
(하지만 문법을 새로 배워야 한다는 단점이 있음)
|
4) jsp를 view로 사용하는 방법
jsp를 사용하기 위해서는 추가 작업이 필요하다.
- 기타 다른점들 -
src/main/resources 하위에 static 폴더에는 리소스 파일들 (js,css 등)이 들어가고,
src/main/resources 하위에 templates 폴더에는 템플릿 (jsp 파일아님)이 들어간다.
src/main/resources 하위에 application.properties는 설정파일이다.
그리고 스프링부트 프로젝트에서는 context.xml등 설정파일이 없고, web.xml도 존재하지 않는다. (기본설정이 숨어있기때문)
|
1. src/main 하위에 디렉토리 추가
src/main/webapp
src/main/webapp/WEB-INF
src/main/webapp/WEB-INF/views
|
2. application.properties 설정
3. pom.xml에서 thymeleaf 라이브러리를 주석처리해야함
(jsp페이지에서 출력할지, thymeleaf를 사용해서 출력할지 둘중에 하나를 선택해야 하기 때문)
- 실습예제 -
pom.xml에 코드를 추가
<dependencies> 위에 <repositories> 추가 (오라클 라이브러리 저장소)
|
<!-- 추가된 부분 (ojdbc6 다운로드를 위한 저장소 -->
<repositories>
<repository>
<id>codelds</id>
<url>https://code.lds.org/nexus/content/groups/main-repo</url>
</repository>
</repositories>
|
cs |
<dependencies> 교체
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
|
.<dependencies>
<!-- @Inject -->
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
<!-- 타임리프 템플릿 관련 라이브러리 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- spring boot auto restart(설정, 클래스가 바뀌면 auto restart) -->
<!-- 스프링레거시프로젝트에서는 자바코드가 바뀌면 자동으로 서버가 다시시작되는데 부트에서는
그런것이 없기 때문에 서버가 재시작할 수 있도록 설정을 해주어야 한다. -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<!-- jsp 라이브러리 , 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>
<!-- 오라클 라이브러리 -->
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc6</artifactId>
<version>11.2.0.3</version>
</dependency>
<!-- 스프링 부트용 jdbc 라이브러리 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<!-- mybatis와 spring를 연결해서 쓸 수 있는 라이브러리 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.1</version>
</dependency>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
|
cs |
src/main/resources/application.properties 수정
application.properties파일에 속성을 들어가보면 인코딩 언어가 ISO-8859-1로 되어있어서 한글이 나오지 않는다.
그렇기 때문에 설정을 바꿔주어야 한다.
속성 => other => utf8로 바꿔주면 된다.
application.properties 기본 코드 유형
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
#/src/main/resources/application.properties
#이 페이지에서는 #이 주석처리이다.
# db connection(oracle) 오라클 사용시 추가하는 코드
spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver
spring.datasource.url=jdbc:oracle:thin:@localhost:1521:xe
spring.datasource.username=spring
spring.datasource.password=1234
# db connection(mysql) mysql 사용시 추가하는 코드
#spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#spring.datasource.url=jdbc:mysql://localhost:3306/web
#spring.datasource.username=spring
#spring.datasource.password=1234
#http port
server.port=80
#view resolver (템플릿 대신 jsp 페이지를 사용할 경우)
#spring.mvc.view.prefix=/WEB-INF/views/
#spring.mvc.view.suffix=.jsp
#server.servlet.jsp.init-parameters.development=true
#custom error page 내가 편집한 에러페이지를 사용하고 싶을때 사용하는 코드
server.error.whiteabel.enabled=false
#thymeleaf auto refresh #뷰가 시작되면 자동으로 재시작되게 하는 옵션
spring.thymeleaf.cache=false
|
cs |
이 예제에서 사용할 코드 (오라클 사용)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
.spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver
spring.datasource.url=jdbc:oracle:thin:@localhost:1521/xe
spring.datasource.username=spring
spring.datasource.password=1234
#http port (스프링 부트에는 톰캣이 내장되어 있다, 기본번호는 8080이고, 번호를 바꾸고 싶으면 여기다 지정하면 된다.)
# change port number (default 8080)
server.port=80
#view resolver (템플릿 대신 jsp 페이지를 사용할 경우)
spring.mvc.view.prefix=/WEB-INF/views/ #prefix와 suffix는 계층을 이동시킬때 쓰이는 코드
spring.mvc.view.suffix=.jsp
server.jsp-servlet.init-parameters.development=true #jsp에서 수정이되면 서버가 자동으로 재시작되는 옵션
# custom error page 내가 편집한 에러페이지를 사용하고 싶을때 사용하는 코드
server.error.whitelabel.enabled=false
spring.thymeleaf.cache=false
|
cs |
-스프링 부트와 데이터베이스 연동-
1. application.properties에서 db연동 설정 해주기
|
spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver
spring.datasource.url=jdbc:oracle:thin:@localhost:1521:xe
spring.datasource.username=spring
spring.datasource.password=1234
|
cs |
주로 mybatis를 사용하게 되는데 mybatis에서 sql명령어를 실행하는 방식
1. xml파일에다 sql명령어를 작성해서 mapper을 불러다 쓰는 방식 (가장 많이 사용하는 방식)
2. 자바코드에다 sql명령어를 포함시켜 사용한 방식이 있었다.
@SpringBootApplication은 스프링 부트 애플리케이션 bean으로 만들어주는 어노테이션 (시작 클래스)
1. mapper을 사용하는 방법
1. xml을 사용할때는 @Bean 어노테이션을 붙여서 sqlSessionFactory를 리턴하는 메소드를 만들어
거기다가 데이터 소스 설정을 하고 아래의 코드를 추가하면 mapper xml을 사용할 수 있는 방식이다.
Resource[ ] res = new PathMatchingResourcePatternResolver( )
.getResources("classpath:mappers/*Mapper.xml");
bean.setMapperLocations(res);
|
spring03 => src/main/resources => mappers폴더 생성 => xml파일을 만들어준후에 위처럼 코드를 작성하면 mapper xml을 사용할 수 있다.
2. 자바코드에다 sql명령어를 포함시켜 사용하는 방식
Spring03BootApplication.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
.package com.example.spring03_boot;
import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
//현재 클래스를 스프링 부트 애플리케이션의 시작 클래스로 등록함
//main 메소드가 있기 때문에 시작 클래스로 등록하여야 한다.
@SpringBootApplication
//sql mapper의 위치를 지정 (model 하위에 mapper이 들어간다는 뜻)
@MapperScan("com.example.spring03_boot.model")
public class Spring03BootApplication {
public static void main(String[] args) {
SpringApplication.run(Spring03BootApplication.class, args);
}
//javax.sql.DataSource
// DataSource => SqlSessionFactory
// => SqlSessionTemplate => SqlSession
//<bean>태그에 대응되는 코드 설정
//스프링 레거시프로젝트에서는 xml에 작성했던 코드를 스프링부트에서는 이쪽에 코드로 작성한 것이다.
//sqlSessionFactory가 sqlsessionTemplate를 만들고, sqlsessionTemplate가 sqlsession을 만든다.
@Bean //자바코드로 bean을 등록
public SqlSessionFactory sqlSessionFactory(
DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean=new SqlSessionFactoryBean();
bean.setDataSource(dataSource); //데이터소스 설정
return bean.getObject();
}
@Bean
public SqlSessionTemplate sqlSession(
SqlSessionFactory factory) {
return new SqlSessionTemplate(factory);
}
}
|
cs |
컨트롤러, 서비스, 뷰는 스프링 레거시 방식과 동일하게 사용한다.
HelloController.java 컨트롤러 생성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
.package com.example.spring03_boot.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
//컨트롤러는 스프링 레거시 방식과 동일하게 사용하면 된다.
@Controller //컨트롤러 빈으로 등록함
public class HelloController {
@RequestMapping("/") //시작 페이지, 자료를 저장해서 페이지를 넘길때는 ModelAndView가 주로 사용된다.
public ModelAndView hello(ModelAndView mav) {
mav.setViewName("hello"); //뷰의 이름, 풀네임으로 적어도 되지만 길기 때문에 application.properties에 prefix와 suffix에 나눠서 적어주었다.
아래 코드와 합치면 뷰의 경로는 /WEB-INF/views/hello.jsp 가 된다.
spring.mvc.view.prefix=/WEB-INF/views/ #prefix와 suffix는 계층을 이동시킬때 쓰이는 코드
spring.mvc.view.suffix=.jsp
mav.addObject("message","스프링 부트 애플리케이션"); //데이터를 저장
return mav; //화면에 출력, hello페이지로 넘어가 출력이 되게 된다.
}
}
|
cs |
src/main/webapp/WEB-INF/views/hello.jsp 파일을 생성
1
2
3
4
5
6
7
8
9
10
11
12
|
.<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
${message} //컨트롤러에서 넘어온 메시지 값이 출력된다.
</body>
</html>
|
cs |
프로젝트를 실행할때는 프로젝트 우클릭 run as를 클릭하고, Spring Boot App를 클릭하면 서버가 실행이 되고, 관련정보가 로그로 출력된다.
스프링 부트 프로젝트에서는 프로젝트를 실행해도 웹 브라우저가 바로 출력되지 않고, 웹 브라우저를 수동으로 켜서 주소를 입력해주어야 한다.
-웹 브라우저에서 실행-
주소창에 컨텍스트 패스를 입력하지 않는다.
크롬브라우저를 열고 주소창에 http://localhost/hello.do 를 치면, "스프링 부트 애플리케이션" 메시지가 출력이 된다.
아래 책은 제가 공부할때 활용했던 책으로 추천드리는 책이니 한번씩 읽어보시는것을 추천드립니다!! ㅎㅎ
이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.
Back-End/Spring 2019. 7. 5. 18:21
Spring boot는 일반적으로 사용되는 Spring Project와 몇가지 차이점이 있다.
일단 프로젝트의 목적 자체의 차이가 조금 있는데, Spring boot의 경우 웹 컨테이너(Tomcat)를 내장하고 있고 최소한의 설정으로 쉽게 Spring Application 을 만들기 위한 목적으로 설계된 프레임워크 내의 플랫폼이라 할 수 있다. 그렇기 때문에 Spring boot로 Set up 할 경우 편리하게 Dependency 문제가 해결되고, 빠르게 웹 어플리케이션을 만들 수 있는 장점이 있다. 심지어 starter-pack 이라는 dependency 를 통해 아주 간단하게 웹 어플리케이션을 만들 수 있다.
반면 Spring-boot 위에서 동작하기 때문에 확장성 등의 고려에 있어서 모든 부분을 직접 설정해주어야 하는 Spring Project 가 더 이점이 있다고 할 수 있다. 아직까지는 비즈니스 영역에 있어서 Spring Project 가 사용되고 있지만 점차적으로 Spring-boot 가 사용될 것으로 보인다.
Spring boot 는 또한 최근에 발생한 기술이기 때문에 여러가지 버전 호환 문제가 아직 존재한다. Java 버전으로는6부터 지원을 하지만 Spring boot 버전에 따라서 특히 REST나 JPA의이용을위해 Spring boot 1.4 이상의 버전을 필요로 한다.
이 때 Spring boot의 버전에 따라 내장 톰캣의 버전에 차이가 생기게 된다. 가령 Spring boot 1.2.4 이상의 버전에서는 내장 Tomcat8 이 사용되고 그 이하 버전에서는 Tomcat7 이 사용된다.
그렇기 때문에 각 버전간 호환을 맞춰주는 수고로움이 들게 된다. 그렇지 않으면 Tomcat의 버전별 LifeCycle 에러를 맞보게 된다. Spring boot는 기본으로 설정된 Embed 톰캣 사용 설정을 하지 않게 명시하여 외장 톰캣을 사용할 수도 있다.
출처 : https://jins-dev.tistory.com/entry/Spring-legacy-와-Spring-boot-의-차이점 [Jins' Dev Inside]
Back-End/Spring 2019. 7. 5. 17:19
가. 도로명 주소 사이트
- 행정 자치부 http://www.juso.go.kr
- 개발자센터에서 API 신청 가능 (무료로)
https://www.juso.go.kr/addrlink/devAddrLinkRequestWrite.do?returnFn=write&cntcMenu=URL
나. 우편번호, 주소 서비스 방법
1) 직접 구축 2018년 12월 기준 건물 DB 다운로드 141MB, 압축풀면 1.72GB https://www.juso.go.kr/addrlink/addressBuildDevNew.do?menu=mainJusoDb
- 예를 들어 세종시 자료를 액셀에서 읽어들임 (구분자 : ㅣ)
- 텍스트 파일을 엑셀로 읽어서 (구분자 : ㅣ) csv로 변환한 후 필요한 컬럼만 선택한 후 저장
- 데이터를 import 할 테이블을 미리 생성한 후
- SQL Developer에서 import하여 테이블로 저장함.
2) API 사용
다음 API 사용
-http://postcode.map.daum.net/guide (키를 발급할 필요없이 바로 사용 가능 하다)
|
-직접 구축 사용법-
1. https://www.juso.go.kr/addrlink/addressBuildDevNew.do?menu=mainJusoDb에서 자료를 다운로드하고 압축
2. Excel을 열고, "열기" 버튼을 누르고 압축을 푼 파일을 열고, 다음 3단계를 진행
직접구축은 비용이 많이 들고 최신화할때마다 번거롭다.
그렇기 때문에 다음 API를 사용한다.
- 실습 예제 -
1. menu.jsp에 도로명 주소 하이퍼링크를 추가한다. | <a href="${path}/member/address.do">도로명주소</a> | | cs |
2.MemberController.java에 다음 메소드를 추가 | @RequestMapping("address.do") public String address() { return "member/join"; } | cs |
3. join.jsp 페이지를 추가하고 다음 API를 사용해서 작성 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> <%@ include file="../include/header.jsp" %> <script src="http://dmaps.daum.net/map_js_init/postcode.v2.js"></script> <script> //daum객체는 위에서 설정한 라이브러리 안쪽에 들어있다. function daumZipCode() { new daum.Postcode({ oncomplete: function(data) { // 팝업에서 검색결과 항목을 클릭했을때 //실행할 코드를 작성하는 부분. // 각 주소의 노출 규칙에 따라 주소를 조합한다. // 내려오는 변수가 값이 없는 경우엔 공백('')값을 가지므로, //이를 참고하여 분기 한다. var fullAddr = ''; // 최종 주소 변수 var extraAddr = ''; // 조합형 주소 변수 // 사용자가 선택한 주소 타입에 따라 해당 주소 값을 // 가져온다. // 사용자가 도로명 주소를 선택했을 경우 if (data.userSelectedType === 'R') { //R은 도로명 주소이다. fullAddr = data.roadAddress; } else { // 사용자가 지번 주소를 선택했을 경우(J) fullAddr = data.jibunAddress; //도로명 주소가 아니라면.. 지번주소. } // 사용자가 선택한 주소가 도로명 타입일때 조합한다. if(data.userSelectedType === 'R'){ //법정동명이 있을 경우 추가한다. if(data.bname !== ''){ extraAddr += data.bname; } //도로명 주소일때는 법에 맞춰서 '동' 이름을 추가해야 한다. // 건물명이 있을 경우 추가한다. if(data.buildingName !== ''){ extraAddr += (extraAddr !== '' ? ', ' + data.buildingName : data.buildingName); } // 조합형주소의 유무에 따라 양쪽에 괄호를 추가하여 최종 주소를 만든다. fullAddr += (extraAddr !== '' ? ' ('+ extraAddr +')' : ''); } // 우편번호와 주소 정보를 해당 필드에 넣는다. //5자리 새우편번호 사용 document.getElementById('zipcode').value = data.zonecode; document.getElementById('address1').value = fullAddr; //address1에 확정된 주소값의 풀네임이 들어간다. // 커서를 상세주소 필드로 이동한다. // 커서를 이동시켜서 깜빡이게끔 한다. document.getElementById('address2').focus(); } }).open(); } </script> </head> <body> <%@ include file="../include/menu.jsp" %> <form name="form1" method="post"> <!-- 쓰기는 안되고 읽기만 되도록함--> 우편번호 : <input name="zipcode" id="zipcode" readonly size="10"> <!-- daumZipCode()는 다음 사이트에서 가져온 API --> <input type="button" onclick="daumZipCode()" value="우편번호 찾기"><br> 주소 : <input name="address1" id="address1" size="60"><br> 상세주소 : <input name="address2" id="address2"> </form> </body> </html> | cs |
Back-End/Spring 2019. 7. 4. 18:19
첨부파일이 저장되는 테이블을 작성
테이블이 중복될수 있으므로 테이블과 테이블에 관련된 제약조건을 삭제 | drop table attach cascade constraints; | cs |
테이블 생성 | create table attach ( fullName varchar2(150) not null, //첨부파일 이름, null값 방지, 파일이름은 uuid를 이용해서 중복되지 않도록 할 예정 bno number not null, //게시글 번호, null값 방지 regdate date default sysdate, //등록날짜, 현재시간으로 설정 primary key(fullName) //기본키를 첨부파일이름으로 설정 ); | cs |
attach테이블의 bno 속성(글번호)이 board테이블에 bno 속성 (글번호)를 참조하도록 외래키로 설정함. (제약조건 설정) (게시글 번호가 꼬이면 안되기 때문, 게시글이 삭제되면 같이 삭제될수 있게끔...) | alter table attach add constraint fk_board_attach //fk_board_attach는 제약조건의 이름 foreign key (bno) references board(bno); | cs |
중복될 수 있으므로 먼저 시퀀스를 삭제
board 테이블의 bno 컬럼을 위한 시퀀스를 생성 | create sequence seq_board start with 1 //시작값을 1로하고 increment by 1; //1씩 증가하도록 설정함 | cs |
===============================================================================================
view.jsp 페이지에서 common.js를 include 해야한다. | <script src="${path}/include/js/common.js"></script> | cs |
view.jsp 중 일부 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | $(".fileDrop").on("dragenter dragover",function(e){ //기본 효과 막음, 기본효과가 있으면 이미지 파일이 실행되어 버림 e.preventDefault(); }); $(".fileDrop").on("drop",function(e){ e.preventDefault(); //첫번째 첨부파일 //드롭한 파일을 폼 데이터에 추가함 var files=e.originalEvent.dataTransfer.files; var file=files[0]; //폼 데이터에 첨부파일 추가 (0번 인덱스이므로 첫번째 파일만 붙였다.) var formData=new FormData(); formData.append("file",file); $.ajax({ //비동기 방식으로 호출 url: "${path}/upload/uploadAjax", // uploadAjax를 호출함 data: formData, // formData를 보내고 dataType: "text", // text 타입으로 보낸다. processData: false, // 요청으로 보낸 데이터를 query string 형태로 변환할지 여부 contentType: false, // 서버로 보내지는 데이터의 기본값 type: "post", //호출처리가 완료된 다음 (성공한 후)에 실행되는 구문 //컨트롤러에서 업로드 경로와 상태코드 (200)을 리턴해서 이쪽으로 보낸다. success: function(data){ //콜백 함수 //console.log(data); //data : 업로드한 파일 정보와 Http 상태 코드 var fileInfo=getFileInfo(data); //첨부파일의 정보 //console.log(fileInfo); var html="<a href='"+fileInfo.getLink+"'>"+ fileInfo.fileName+"</a><br>"; html += "<input type='hidden' class='file' value='" +fileInfo.fullName+"'>"; //hidden 태그를 추가 $("#uploadedList").append(html); //div에 추가 } }); }); | cs |
AjaxUploadController.java | // 업로드한 파일은 MultipartFile 변수에 저장됨 @ResponseBody // json 형식으로 리턴 @RequestMapping(value = "/upload/uploadAjax", method = RequestMethod.POST, produces = "text/plain;charset=utf-8") public ResponseEntity<String> uploadAjax(MultipartFile file) throws Exception { //파일의 정보를 로그에 출력 logger.info("originaName:" +file.getOriginalfilename()); logger.info("size:" +file.getSize()); logger.info("contentType:" +file.getContentType()); //new ResponseEntity (데이터, 상태코드) //new ResponseEntity (업로드된 파일이름, 상태코드)
return new ResponseEntity<String>( UploadFileUtils.uploadFile(uploadPath, file.getOriginalFilename(), file.getBytes()), HttpStatus.OK); } | cs |
view.jsp 중 일부 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | //목록 버튼 $("#btnList").click(function(){ location.href="${path}/board/list.do"; }); //수정 버튼 $("#btnUpdate").click(function(){ //첨부파일 이름들을 폼에 추가 var str=""; //id가 uploadList인것들 (div)에 클래스에 file인것 각각 (each) $("#uploadedList .file").each(function(i){ str+="<input type='hidden' name='files["+i+"]' value='" +$(this).val()+"'>"; //파일이름에다 인덱스 번호를 하나씩 붙인다. }); //폼에 hidden 태그들을 추가함 $("#form1").append(str); document.form1.action="${path}/board/update.do"; //위에서 만든 폼을 가지고 update.do로 보낸다. document.form1.submit(); }); | cs |
지금까지 보낸 내용들이 dto안에 다 쌓인다.
BoardServiceImpl.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 | package com.example.spring02.service.board; import java.util.List; import javax.inject.Inject; import javax.servlet.http.HttpSession; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.example.spring02.model.board.dao.BoardDAO; import com.example.spring02.model.board.dto.BoardDTO; @Service // service bean public class BoardServiceImpl implements BoardService { @Inject //dao를 호출하기 때문에 의존성을 주입 BoardDAO boardDao; //첨부파일 레코드 삭제 @Override public void deleteFile(String fullName) { boardDao.deleteFile(fullName); } @Override public List<String> getAttach(int bno) { return boardDao.getAttach(bno); } // 1.글쓰기 - 게시물 번호 생성 // 2.첨부파일 등록-게시물 번호 사용 @Transactional @Override public void create(BoardDTO dto) throws Exception { //board 테이블에 레코드 추가 boardDao.create(dto); //attach 테이블에 레코드 추가 String[] files=dto.getFiles(); //첨부파일 이름 배열 if(files==null) return; //첨부파일이 없으면 skip for(String name : files) { boardDao.addAttach(name); //attach 테이블에 insert } } @Override public BoardDTO read(int bno) throws Exception { return boardDao.read(bno); } @Transactional //트랜잭션 처리 method //코드 수정과 첨부파일을 첨부하는 기능이 동시에 같이 들어가야하기 때문에 //일관성을 유지하기 위해서 트랜잭션 처리를 실시한다. //만약 두개중에 하나라도 되지않으면 작업을 취소시키기고, 롤백을 한다. @Override public void update(BoardDTO dto) throws Exception { boardDao.update(dto); //board 테이블 수정 //attach 테이블 수정 String[] files=dto.getFiles(); if(files==null) return; for(String name : files) { System.out.println("첨부파일 이름:"+name); boardDao.updateAttach(name, dto.getBno()); } } @Transactional @Override public void delete(int bno) throws Exception { //reply 레코드 삭제 //attach 레코드 삭제 //첨부파일 삭제 //board 레코드 삭제 boardDao.delete(bno); } @Override public List<BoardDTO> listAll( //매개변수는 시작 레코드번호, 끝번호, 옵션과 키워드가 들어간다 String search_option, String keyword,int start, int end) throws Exception { return boardDao.listAll(search_option,keyword,start,end); } //조회수 증가 처리 @Override public void increaseViewcnt(int bno, HttpSession session) throws Exception { long update_time=0; if(session.getAttribute("update_time_"+bno)!=null) { //최근에 조회수를 올린 시간 update_time= (long)session.getAttribute("update_time_"+bno); } long current_time=System.currentTimeMillis(); //일정 시간이 경과한 후 조회수 증가 처리 if(current_time - update_time > 5*1000) { //조회수 증가 처리 boardDao.increateViewcnt(bno); //조회수를 올린 시간 저장 session.setAttribute("update_time_"+bno, current_time); } } @Override public int countArticle( String search_option, String keyword) throws Exception { return boardDao.countArticle(search_option,keyword); } | cs |
BoardDAOImpl.java | //첨부파일 정보 수정 @Override public void updateAttach(String fullName, int bno) { Map<String,Object> map=new HashMap<>(); //값을 여러개 담을때는 haspmap를 사용한다. map.put("fullName", fullName); //첨부파일 이름 map.put("bno", bno); //게시물 번호 sqlSession.insert("board.updateAttach", map); //updateAttach mapper을 호출 } | cs |
boardMapper.xml | <!-- 다른 mapper와 중복되지 않도록 네임스페이스 기재 --> <mapper namespace="board"> <!-- 새로운 첨부파일 추가 --> <insert id="updateAttach"> //attach테이블에 입력받은 파일이름과 게시글 번호를 삽입한다 insert into attach (fullName, bno) values ( #{fullName}, #{bno} ) </insert> | cs |
BoardDAOImpl.java | //레코드 수정 @Override public void update(BoardDTO dto) throws Exception { sqlSession.update("board.update", dto); } | cs |
boardMapper.xml | <mapper namespace="board"> <!-- 게시물 내용 수정 --> <update id="update"> //게시글의 내용 수정, 게시글 번호가 맞으면 제목과 내용을 입력한 대로 수정해서 갱신함 update board set title=#{title}, content=#{content} where bno=#{bno} </update> | cs |
view.jsp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | listAttach(); //첨부파일 리스트를 출력하는 함수 function listAttach(){ $.ajax({ type: "post", url: "${path}/board/getAttach/${dto.bno}", success: function(list){ // list : json //console.log(list); $(list).each(function(){ var fileInfo=getFileInfo(this); //console.log(fileInfo); var html="<div><a href='"+fileInfo.getLink+"'>" +fileInfo.fileName+"</a> "; <c:if test="${sessionScope.userid == dto.writer}"> html+="<a href='#' class='file_del' data-src='" +this+"'>[삭제]</a></div>"; </c:if> $("#uploadedList").append(html); }); } }); } | cs |
BoardController.java 중 일부 | //첨부파일 목록을 리턴 //ArrayList를 json 배열로 변환하여 리턴 //view에서 넘긴 bon를 경로값 (url에 포함된 변수)로 받아서 맵핑한다. @RequestMapping("getAttach/{bno}") @ResponseBody //view가 아닌 데이터 자체를 리턴할 때 사용하는 어노테이션 //(즉, 화면이 바뀌는게 아니라 데이터가 넘어간다는 의미.. 리턴한 값) public List<String> getAttach(@PathVariable("bno") int bno){ return boardService.getAttach(bno); }
| cs |
BoardServiceImpl.java 중 일부 | @Override public List<String> getAttach(int bno) { return boardDao.getAttach(bno); } | cs |
BoardDAOImpl.java 중 일부 | //첨부파일 리스트 @Override public List<String> getAttach(int bno) { return sqlSession.selectList("board.getAttach", bno); } | cs |
boardMapper.xml 중 일부 | <!-- 다른 mapper와 중복되지 않도록 네임스페이스 기재 --> <mapper namespace="board">
<!-- 첨부파일 목록 --> <select id="getAttach" resultType="String"> //결과타입은 필드가 1개이므로 String이다. select fullName //attach 테이블에서 첨부파일이름을 검색 (단, 글번호가 내가 선택한 글번호여야됨) //그리고 날짜의 내림차순으로 정렬시킨다. from attach where bno=#{bno} order by regdate desc </select> | cs |
여기서 검색한 결과값이 컨트롤러로 넘어간다.
============================첨부 파일 삭제=============================================
view.jsp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | //첨부파일 리스트를 출력하는 함수 function listAttach(){ $.ajax({ type: "post", url: "${path}/board/getAttach/${dto.bno}", //getAttach(파일얻을때) 게시글 번호를 보냄 //컨트롤러로 이동 //위의 구문이 처리가 정상적으로 처리되었을때 실행되는 구문 (success) //list는 배열 BoardController.java
| //첨부파일 목록을 리턴 //ArrayList를 json 배열로 변환하여 리턴 //view에서 넘긴 bon를 경로값 (url에 포함된 변수)로 받아서 맵핑한다. @RequestMapping("getAttach/{bno}") @ResponseBody // view가 아닌 데이터 자체를 리턴 public List<String> getAttach(@PathVariable int bno){ return boardService.getAttach(bno); } | cs |
BoardServiceImpl.java | @Override public List<String> getAttach(int bno) { return boardDao.getAttach(bno); } | cs |
BoardDAOImpl.java | //첨부파일 리스트 @Override public List<String> getAttach(int bno) { return sqlSession.selectList("board.getAttach", bno); } | cs |
boardMapper.xml | <!-- 다른 mapper와 중복되지 않도록 네임스페이스 기재 --> <mapper namespace="board"> <!-- 첨부파일 목록 --> <select id="getAttach" resultType="String"> //attach테이블로부터 파일이름을 검색, (내가 원하는 글번호로), 작성날짜의 내림차순으로 검색 select fullName from attach where bno=#{bno} order by regdate desc </select> | cs |
success: function(list){ // list : json //console.log(list); $(list).each(function(){ //list 변수 각각에 대하여 //현재 파일의 파일 정보 var fileInfo=getFileInfo(this); //console.log(fileInfo); //파일정보를 보는 하이퍼링크를 만듦 (div에 하이퍼링크를 만듦) var html="<div><a href='"+fileInfo.getLink+"'>" +fileInfo.fileName+"</a> "; //------------파일 삭제------------- //세션에 담긴 userid와 dto에 담긴 작성자가 같으면 //담아놓은 파일을 삭제하기 위해서 링크를 건다. <c:if test="${sessionScope.userid == dto.writer}"> html+="<a href='#' class='file_del' data-src='" +this+"'>[삭제]</a></div>"; </c:if> //추가 $("#uploadedList").append(html); }); } }); } | cs |
view.jsp (위쪽의 #uploadedList)와 연결되는 코드) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | //첨부파일 삭제 //id가 uploadedList인 태그의 class가 file_del인 태그 클릭 $("#uploadedList").on("click",".file_del",function(e){ var that=$(this); //클릭한 태그 //data: {fileName: $(this).attr("data-src") }, $.ajax({ type: "post", url: "${path}/upload/deleteFile",
AjaxUploadController.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | @ResponseBody //뷰가 아닌 데이터를 리턴 @RequestMapping(value="/upload/deleteFile" ,method=RequestMethod.POST) public ResponseEntity<String> deleteFile(String fileName){ logger.info("fileName:"+fileName); //fileName에는 이미지 파일의 경우 썸네일 파일 이름이 넘어옴 String formatName=fileName.substring( fileName.lastIndexOf(".")+1); //확장자 검사 MediaType mType=MediaUtils.getMediaType(formatName); if(mType != null) { //이미지 파일이면 원본이미지 삭제 //파일이름의 앞부분과 확장자명을 분리하기 위한 코드 String front=fileName.substring(0, 12); String end=fileName.substring(14); // File.separatorChar : 유닉스 / 윈도우즈\ new File(uploadPath+(front+end).replace( '/',File.separatorChar)).delete(); //썸네일 삭제 } //원본 파일 삭제(이미지이면 썸네일 삭제) new File(uploadPath+fileName.replace( '/',File.separatorChar)).delete(); //파일 삭제 //레코드 삭제 boardService.deleteFile(fileName);
BoardService.java | //첨부파일 레코드 삭제 @Override public void deleteFile(String fullName) { boardDao.deleteFile(fullName); } | cs |
BoardDAOImpl.java | //첨부파일 레코드 삭제 @Override public void deleteFile(String fullName) { sqlSession.delete("board.deleteAttach", fullName); } | cs |
boardMapper.xml | <!-- 첨부파일 레코드 삭제 --> <delete id="deleteAttach"> delete from attach where fullName=#{fullName} </delete> | cs |
return new ResponseEntity<String>("deleted" ,HttpStatus.OK); //잘 삭제가 되었으면 deleted가 view로 넘어가고 상태메시지200을 리턴시킨다. } } | cs |
data: "fileName="+ $(this).attr("data-src"), dataType: "text", //컨트롤러에서 삭제가 성공하면 결과값이 result로 넘어오고 //result의 값이 deleted와 같으면 화면에서 div 태그를 제거한다. success: function(result){ if(result=="deleted"){ //컨트롤러에서 넘어온 리턴값이 deleted면 첨부파일이 정상적으로 삭제되었다는 뜻이므로 밑의 구문을 생성 //화면에서 태그 제거 that.parent("div").remove(); //that(this를 의미)의 클릭한 태그의 parent의 div를 삭제 //div태그를 삭제해야 파일에 관한 링크와 정보들이 삭제된다. } } }); }); | cs |
첨부파일이 올라간 상태에서 글을 쓰고 확인을 누르면 mapper에서 id가 addAttach인 쿼리를 호출
파일업로드를 하게되면 트랜잭션 처리를 해야 된다.
왜냐하면 게시글이 올라가고, 파일도 등록되어야 하는데 (board 테이블에도 insert되어야 하고, attach테이블에도 insert되어야 한다)
그렇게 되어야 문제가 발생되지 않기 때문에, 둘 중에 하나만 된다면 롤백시켜야 한다.
===게시글을 작성시, 파일이 첨부되어야 하기 때문에 게시물 쓰기 코드에 트랜잭션을 처리하는 코드를 추가=====================
write.jsp 중 일부 | <h2>글쓰기</h2> <form id="form1" name="form1" method="post" action="${path}/board/insert.do">
BoardController.java | // write.jsp에서 입력한 내용들이 BoardDTO에 저장됨 @RequestMapping("insert.do") public String insert(@ModelAttribute BoardDTO dto , HttpSession session) //세션은 아이디를 확인하기위해서 필요하므로 세션을 가져온다. throws Exception { // 로그인한 사용자의 아이디 String writer=(String)session.getAttribute("userid"); dto.setWriter(writer); //로그인한 아이디를 dto에 저장 //레코드 저장 boardService.create(dto); //서비스와 dao를 통해서 레코드에 추가가된다. //게시물 목록으로 이동 return "redirect:/board/list.do"; } | cs |
BoardService.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | // 1.글쓰기 - 게시물 번호 생성 // 2.첨부파일 등록-게시물 번호 사용 @Transactional //게시물 작성과 파일업로드가 둘다 완료가 된 상태에서만 글을 //써야하기 때문에 트랜잭션 처리를 해주어야 한다. @Override public void create(BoardDTO dto) throws Exception { //board 테이블에 레코드 추가 boardDao.create(dto); //attach 테이블에 레코드 추가 String[] files=dto.getFiles(); //첨부파일 이름 배열 if(files==null) return; //첨부파일이 없으면 skip for(String name : files) { boardDao.addAttach(name); //attach 테이블에 insert //아직 글번호가 확정되지 않은 상태이기 때문에 (글번호는 작성을 해야 생기기때문) 글번호를 넣는 //파일업로드는 게시글이 작성이 되지 않으면 업로드가 되지 않게 해야 에러가 발생되지 않는다. } } | cs |
boardMapper.xml | <!-- 첨부파일 정보 저장 --> <insert id="addAttach"> //attach테이블에 파일이름 (입력받은 파일이름) 과, 게시글번호(게시판 테이블에 추가된 번호-방금추가된것)를 추가한다, insert into attach (fullName, bno) values ( #{fullName}, seq_board.currval ) //board.currval은 board의 시퀀스의 최종발급된 번호.. </insert> | cs |
| cs |
view.jsp에 삭제버튼을 추가하는 코드 추가 | $("#btnDelete").click(function(){ if(confirm("삭제하시겠습니까?")){ document.form1.action = "${path}/board/delete.do"; document.form1.submit();
BoardController.java | @RequestMapping("delete.do") public String delete(int bno) throws Exception { boardService.delete(bno); //삭제 처리 return "redirect:/board/list.do"; //목록으로 이동 } | cs |
BoardServiceImpl.java | @Transactional //게시글 작성과 마찬가지로 게시글이 삭제될때 첨부파일도 같이 삭제되어야 하기 때문에 트랜잭션 처리를 해준다. @Override public void delete(int bno) throws Exception { //reply 레코드 삭제 //attach 레코드 삭제 //첨부파일 삭제 //board 레코드 삭제 boardDao.delete(bno); } | cs |
BoardDAOImpl.java | @Override public void delete(int bno) throws Exception { sqlSession.delete("board.deleteArticle", bno); //mapper로 게시글 번호를 넘긴다. } | cs |
boardMapper.xml | <delete id="deleteArticle"> delete from board where bno=#{bno} </delete> | cs |
} }); | cs |
Back-End/API 2019. 7. 4. 16:35
.html().html()은 선택한 요소 안의 내용을 가져오거나, 다른 내용으로 바꿉니다. .text()와 비슷하지만 태그의 처리가 다릅니다. 문법 1HTML 태그를 포함하여 선택한 요소 안의 내용을 가져옵니다. 예를 들어 | var jb = $( 'h1' ).html(); |
는 h1 요소의 내용을 변수 jb에 저장합니다. 문법 2이전 내용을 지우고 새로운 내용을 넣습니다. 예를 들어 | $( 'div' ).html( '<h1>Lorem</h1>' ); |
는 div 요소의 내용을 <h1>Lorem</h1>로 바꿉니다.
출처 https://www.codingfactory.net/10324#html
Back-End/Spring 2019. 7. 4. 15:01
AJAX 란??
AJAX란 비동기 JavaScript와 XML을 말합니다.
서버측 Scripts와 통신하기 위한 XMLHttpRequest 객체를 사용하는 것을 말합니다.
서버측으로 다양한 형식 (JSON, XML, HTML 및 일반 텍스트 형식 등)의 정보를 주고 받을 수 있습니다.
AJAX의 강력한 특징은 페이지 전체를 새로고침 하지 않고서도 수행되는 "비동기성" 입니다.
-동기식과 비동기식- 동기식 방식 : 클라이언트가 서버에게 요청하고 응답이 올 때까지 기다리는 것.
비동기식 방식 : 클라이언트가 서버에게 요청을 하고 응답이 오기 전까지 다른일을 할 수 있다.
이러한 비동기성을 통해 사용자의 Event가 있으면 전체 페이지가 아닌 일부분만을 업데이트 할 수 있다.
AJAX 문법 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | $.ajax({ type : 'post', //데이터 전송 타입 url : '/test', //URL async : false, //비동기화 여부 header : { //Http header "Content-Type" : "application/json", "X-HTTP-Method-Override" : "POST" ), dataType : 'text', //데이터의 타입 data : JSON.stringify({//보낼 데이터 "no" : no "name" : name "nick" : nick }), success : function (result) { //결과 성공, 성공했을시 출력되는 것 console.log(result); }, error : function(request, status, error) { //결과 에러, 실패했을시 출력되는 것 } }) | cs |
AJAX 옵션 옵션 이름 |
설명 |
async |
동기, 비동기 지정 (boolean) |
complete (xhr, status) |
ajax 완료 이벤트 리스너 지정 (function) |
data |
요청 매개변수 지정(object, string) |
error (xhr, status, error) |
ajax 실패 시 이벤트 리스너 지정 (function) |
jsonp |
JSONP 매개변수 이름 지정 (string) |
jsonpCallback |
JSONP 콜백함수 이름 지정 (string, function) |
success (data, status, xhr) |
ajax 성공 이벤트 리스너 지정 (function, array) |
timeout |
만료시간 지정 (number) |
type |
GET 또는 POST 등을 지정함 |
url | 대상 url을 지정함 |
$.getJSON( ) 와 $.post( ) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | $.getJSON( url [, data] [, success] ) { } // $.getJSON은 아래 ajax 함수의 약자 이다. $.ajax({ dataType : "json", url : url, data : data, success : success }); ============================================= $.post( url [, data] [, success] [, dataType] ) { } //$.post는 아래 ajax 함수의 약자이다. $.ajax({ type : "POST", url : url, data : data, success : success, dataType : dataType }); | cs |
Back-End/Spring 2019. 7. 4. 14:18
게시판 만들기 (댓글 쓰기 / 댓글 쓰기 / 댓글 갯수)
데이터베이스에서 중복될 위험을 방지하기위해 reply 테이블 및 관련 제약조건을 삭제
|
drop table reply cascade constraints;
|
cs |
댓글 관련 자료를 저장할 reply 테이블을 생성
|
create table reply (
rno number not null primary key, //댓글의 고유번호, null값이 들어갈 수 없고, 기본키로 설정함
bno number default 0, //게시글(원글)의 번호, 기본값을 0으로 설정
replytext varchar2(1000) not null, //댓글 내용의 글자수를 설정하고, null값이 들어가지 못하게함
replyer varchar2(50) not null, //댓글 작성자 아이디의 글자수를 설정하고, null값이 들어가지 못하게함
regdate date default sysdate, //댓글 작성 날짜의 기본값을 현재 시간으로 설정함
updatedate date default sysdate //댓글 수정 날짜의 기본값을 현재 시간으로 설정함
);
|
cs |
다른테이블의 기본키를 참조할 외래키를 설정 (제약조건을 설정) - 원글과 꼬이는 것을 방지하기 위해서
(댓글과 원글이 꼬이면 안되기 때문에.. 예를 들면 기존에 게시글이 삭제되면 그 게시글에 달려있는 댓글도 같이 삭제되어야 한다.)
|
alter table reply add constraint fk_board //reply테이블의 bno속성을 board테이블의 기본키인 (bno)를 참조하는 외래키로 설정함
foreign key(bno) references board(bno); //또한 board테이블의 (bno)의 값이 변경되면 연쇄적으로 reply테이블의 (bno)의 값도 같이 변경된다.
|
cs |
댓글 번호 관리를 위한 시퀀스를 설정하기 위해 설정
|
drop sequence reply_seq; //중복될수도 있으니 시퀀스를 삭제.
create sequence reply_seq //시작값을 1로하고 1씩 증가하는 reply_seq 시퀀스를 생성함.
start with 1
increment by 1;
|
cs |
작업후에 결과를 반영하기 위해 commit 하기
view.jsp 중 일부
"댓글쓰기" 버튼을 누르면 서버에 저장이 되고 저장된 목록을 출력할 예정
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
<!-- 댓글 작성 -->
<!-- 너비와 정렬방식 설정 -->
<div style="width:700px; text-align:center;">
<!-- 세션에 저장되어있는 userid가 null이 아닐때 -->
<!-- 그러니까 로그인을 한 상태이어야만 댓글을 작성 할 수 있다.-->
<c:if test="${sessionScope.userid != null }">
<textarea rows="5" cols="80" id="replytext"
placeholder="댓글을 작성하세요"></textarea>
<br>
<!-- 댓글쓰기 버튼을 누르면 id값인 btnReply값이 넘어가서 -->
<!-- 위쪽에 있는 스크립트 구문이 실행되고 -->
<!-- 내가 댓글을 작성한 값이 스크립트문을 거쳐서 컨트롤러로 맵핑되게 된다. -->
<button type="button" id="btnReply">댓글쓰기</button>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
//댓글 쓰기 (버튼을 눌러서 id값이 넘어와 실행되는 자바스크립트 구문)
$("#btnReply").click(function(){
var replytext=$("#replytext").val(); //댓글 내용
var bno="${dto.bno}"; //게시물 번호
var param={ "replytext": replytext, "bno": bno};
//var param="replytext="+replytext+"&bno="+bno;
$.ajax({
type: "post", //데이터를 보낼 방식
url: "${path}/reply/insert.do", //데이터를 보낼 url
data: param, //보낼 데이터
success: function(){ //데이터를 보내는것이 성공했을시 출력되는 메시지
alert("댓글이 등록되었습니다.");
listReply2(); //댓글 목록 출력
}
});
});
|
cs |
</c:if>
</div>
<!-- 댓글 목록 -->
<!-- 댓글이 등록이 되면 listReply에 댓글이 쌓이게 된다 -->
<div id="listReply"></div>
|
//댓글 목록 출력 함수
function listReply(){
$.ajax({
type: "get", //get방식으로 자료를 전달한다
url: "${path}/reply/list.do?bno=${dto.bno}", //컨트롤러에 있는 list.do로 맵핑하고 게시판 번호도 같이 보낸다.
success: function(result){ //자료를 보내는것이 성 공했을때 출력되는 메시지
//result : responseText 응답텍스트(html)
$("#listReply").html(result);
}
});
}
|
cs |
</body>
</html>
|
cs |
댓글 관련 자료를 저장할 DTO를 생성
ReplyDTO.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
|
package com.example.spring02.model.board.dto;
import java.util.Date;
public class ReplyDTO {
private int rno; //댓글 번호
private int bno; //게시물 번호
private String replytext; //댓글 내용
private String replyer; //댓글 작성자 id
private String name; //댓글 작성자 이름
private Date regdate; //java.util.Date, 작성일자
private Date updatedate; //수정일자
private String secret_reply; //비밀댓글 여부
private String writer; //member 테이블의 id
//getter,setter,toString()
public int getRno() {
return rno;
}
public void setRno(int rno) {
this.rno = rno;
}
public int getBno() {
return bno;
}
public void setBno(int bno) {
this.bno = bno;
}
public String getReplytext() {
return replytext;
}
public void setReplytext(String replytext) {
this.replytext = replytext;
}
public String getReplyer() {
return replyer;
}
public void setReplyer(String replyer) {
this.replyer = replyer;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getRegdate() {
return regdate;
}
public void setRegdate(Date regdate) {
this.regdate = regdate;
}
public Date getUpdatedate() {
return updatedate;
}
public void setUpdatedate(Date updatedate) {
this.updatedate = updatedate;
}
public String getSecret_reply() {
return secret_reply;
}
public void setSecret_reply(String secret_reply) {
this.secret_reply = secret_reply;
}
public String getWriter() {
return writer;
}
public void setWriter(String writer) {
this.writer = writer;
}
@Override
public String toString() {
return "ReplyDTO [rno=" + rno + ", bno=" + bno + ", replytext=" + replytext + ", replyer=" + replyer + ", name="
+ name + ", regdate=" + regdate + ", updatedate=" + updatedate + ", secret_reply=" + secret_reply + ", writer="
+ writer + "]";
}
}
|
cs |
ReplyController.java 작성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
package com.example.spring02.controller.board;
import java.util.List;
import javax.inject.Inject;
import javax.servlet.http.HttpSession;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
import com.example.spring02.model.board.dto.ReplyDTO;
import com.example.spring02.service.board.ReplyService;
// @ResponseBody를 붙이지 않아도 뷰가 아닌 데이터 리턴 가능
@RestController // spring 4.0부터 사용 가능
@RequestMapping("reply/*") //공통적인 url pattern
public class ReplyController {
@Inject //서비스를 호출하기위해서 의존성을 주입
ReplyService replyService;
//댓글리스트를 호출할때 맵핑되는 메소드
@RequestMapping("list.do")
public ModelAndView list(int bno, ModelAndView mav) {
List<ReplyDTO> list=replyService.list(bno); //댓글 목록
mav.setViewName("board/reply_list"); //뷰의 이름
mav.addObject("list", list); //뷰에 전달할 데이터 저장
return mav; //뷰로 이동
}
//댓글 목록을 ArrayList로 리턴
@RequestMapping("list_json.do")
public List<ReplyDTO> list_json(int bno){
return replyService.list(bno);
}
@RequestMapping("insert.do") //세부적인 url pattern
public void insert(ReplyDTO dto, HttpSession session) {
//댓글 작성자 아이디
//현재 접속중인 사용자 아이디
String userid=(String)session.getAttribute("userid");
dto.setReplyer(userid);
//댓글이 테이블에 저장됨
replyService.create(dto);
//jsp 페이지로 가거나 데이터를 리턴하지 않음
}
}
|
cs |
ReplyService.java 작성
1
2
3
4
5
6
7
8
9
10
11
12
13
|
package com.example.spring02.service.board;
import java.util.List;
import com.example.spring02.model.board.dto.ReplyDTO;
public interface ReplyService {
public List<ReplyDTO> list(int bno); //댓글 리스트
public int count(int bno); //댓글 수
public void create(ReplyDTO dto); //댓글 작성
}
|
cs |
ReplyServiceImpl.java 작성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
package com.example.spring02.service.board;
import java.util.List;
import javax.inject.Inject;
import org.springframework.stereotype.Service;
import com.example.spring02.model.board.dao.ReplyDAO;
import com.example.spring02.model.board.dto.ReplyDTO;
@Service //service bean
public class ReplyServiceImpl implements ReplyService {
@Inject
ReplyDAO replyDao; //dao를 호출하기위해서 의존성을 주입
//댓글 목록
@Override
public List<ReplyDTO> list(int bno) {
return replyDao.list(bno);
}
@Override
public int count(int bno) {
return 0;
}
//댓글 쓰기
@Override
public void create(ReplyDTO dto) {
replyDao.create(dto);
}
}
|
cs |
ReplyDAO.java
1
2
3
4
5
6
7
8
9
10
11
12
|
package com.example.spring02.model.board.dao;
import java.util.List;
import com.example.spring02.model.board.dto.ReplyDTO;
public interface ReplyDAO {
public List<ReplyDTO> list(int bno); //댓글 목록
public int count(int bno); //댓글 수
public void create(ReplyDTO dto); //댓글 작성
}
|
cs |
ReplyDAOImpl.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
package com.example.spring02.model.board.dao;
import java.util.List;
import javax.inject.Inject;
import org.apache.ibatis.session.SqlSession;
import org.springframework.stereotype.Repository;
import com.example.spring02.model.board.dto.ReplyDTO;
@Repository //dao bean
public class ReplyDAOImpl implements ReplyDAO {
@Inject //의존관계 주입
SqlSession sqlSession;
//댓글 목록
@Override
public List<ReplyDTO> list(int bno) {
return sqlSession.selectList("reply.listReply", bno);
}
@Override
public int count(int bno) {
return 0;
}
//댓글 추가
@Override
public void create(ReplyDTO dto) {
sqlSession.insert("reply.insertReply", dto);
}
}
|
cs |
replyMapper.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 다른 mapper와 중복되지 않도록 네임스페이스 기재 -->
<mapper namespace="reply">
<insert id="insertReply">
<!-- 댓글을 추가 (댓글번호, 글번호, 댓글내용, 댓글작성자를 추가 댓글번호는 시퀀스로 추가 -->
insert into reply (rno,bno,replytext,replyer) values
( reply_seq.nextval, #{bno}, #{replytext}, #{replyer} )
</insert>
<select id="listReply"
resultType="com.example.spring02.model.board.dto.ReplyDTO">
<!-- 댓글의 작성자와 회원 아이디가 같고, 글번호가 같을 경우에 댓글 리스트를 검색 (내림차순으로)-->
select rno,bno,replyer,regdate,updatedate,name,replytext
from reply r, member m
where r.replyer=m.userid and bno=#{bno}
order by rno
</select>
</mapper>
|
cs |
view.jsp
1
2
3
4
5
6
7
8
9
10
11
12
|
//댓글 목록 출력 함수
function listReply(num){
$.ajax({
type: "post", //post방식으로 자료를
url: "${path}/reply/list.do?bno=${dto.bno}&curPage", //컨트롤러에 있는 list.do로 맵핑하고 현재페이지와 게시판 번호도 같이 보낸다.
success: function(result){ //자료를 보내는것이 성공했을때 출력되는 메시지
//result : responseText 응답텍스트(html)
console.log(result);
$("#listReply").html(result);
}
});
}
|
cs |
댓글 갯수 확인 쿼리
|
select bno, title, writer, name, regdate, viewcnt,
(select count(*) from reply where bno = b.bno)cnt //게시글 번호, 제목, 작성자, 이름, 시간, 조회수, 그리고 댓글의 수를 검색 (cnt라는 약자로함)
from board b, member m //테이블 2개를 조인.
where b.writer = m.userid //board의 writer과 member의 userid가 같은것만
order by bno desc; //조건에 맞는 자료를 게시글 번호의 내림차순으로 검색
|
cs |
이 쿼리를 사용하기 위해 boardMapper.xml에 추가함
boardMapper.xml (listAll에 있는 쿼리문을 수정. 댓글의 개수를 제목옆에 출력할 수 있도록)
|
<select id="listAll"
resultType="com.example.spring02.model.board.dto.BoardDTO">
<!-- 결과는 boardDTO타입이 된 -->
<include refid="paging_header" />
<!-- ref는 다른테이블을 의미한다. -->
<!-- 번호, 제목, 작성자, 이름, 날짜, 조회수 , 그리고 댓글의 갯수를 검색 -->
<!-- board 테이블과 member 테이블로 부터 검색 -->
select bno,title,writer,name,regdate,viewcnt,show
,(select count(*) from reply where bno=b.bno) cnt
from board b, member m
|
cs |
list.jsp에 방금 사용한 쿼리의 댓글 개수 (cnt값) 을 출력하기 위해 값을 받아온다.
|
<c:if test="${row.cnt > 0}"> //댓글의 개수가 0보다 크면 실행되는 구문
<span style="color:red;">( ${row.cnt} )</span> //댓글의 개수를 출력하고 색깔을 빨간색으로 한다.
</c:if>
|
cs |
아래 책은 제가 공부할때 활용했던 책으로 추천드리는 책이니 한번씩 읽어보시는것을 추천드립니다!! ㅎㅎ
이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.
Back-End/Spring 2019. 7. 4. 12:17
게시판 만들기 (상세화면)
list.jsp | <c:forEach var="row" items="${map.list}"> <!-- 컨트롤러에서 map안에 list를 넣었기 때문에 이렇게 받는다. --> <tr> <td>${row.bno}</td> <td>
//클릭하면 컨트롤러의 view.do로 이동하고, 게시물번호, 페이지번호, //검색 옵션, 키워드를 같이 넘긴다. //그렇게 같이 넘겨야 페이지나 검색이 풀리지 않는다.
<a href="${path}/board/view.do?bno=${row.bno}> &curPage=${map.pager.curPage} //페이지 번호 &search_option=${map.search_option} &keyword=${map.keyword}">${row.title}</a></td> <td>${row.name}</td> <td><fmt:formatDate value="${row.regdate}" pattern="yyyy-MM-dd HH:mm:ss"/> </td> <td>${row.viewcnt}</td> </tr> </c:forEach> | cs |
BoardController.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | @RequestMapping("view.do", method=RequestMethod.GET) //list.jsp 페이지에서 넘긴 게시물번호, 페이지번호, 검색옵션, 키워드를 받는다. public ModelAndView view(@RequestParam int bno, @RequestParam int curPage, @RequestParam String search_option, @RequestParam String keyword, HttpSession session) throws Exception { //조회수 증가 처리 boardService.increaseViewcnt(bno);
BoardServiceImpl.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | //조회수 증가 처리 //조회수 처리를 할때 일정 시간이 지난후 다시 클릭할때만 조회수가 증가하도록 설정. @Override public void increaseViewcnt(int bno, HttpSession session) //글번호와 세션에 저장된 값들을 매개값으로 받음 throws Exception { long update_time=0; // null을 방지하기 위해 초기값을 null로 설정 if(session.getAttribute("update_time_"+bno)!=null) { //최근에 조회수를 올린 시간이 null이 아니면 update_time= (long)session.getAttribute("update_time_"+bno); } long current_time=System.currentTimeMillis(); //일정 시간이 경과한 후 조회수 증가 처리 if(current_time - update_time > 5*1000) { //그러니까 조회수가 1증가했을때로부터 5000초 후에 다시 클릭을 해야 조회수가 다시 1증가한다는 말.. //조회수 증가 처리 boardDao.increateViewcnt(bno); //조회수를 올린 시간 저장 session.setAttribute("update_time_"+bno, current_time); } } | cs |
BoardDAOImpl.java | //조회수 증가 처리 @Override public void increateViewcnt(int bno) throws Exception { sqlSession.update("board.increaseViewcnt", bno); } | cs |
boardMapper.xml |
<mapper namespace="board">
<!-- 조회수 증가 처리 --> <update id="increaseViewcnt"> //글번호에 해당하는 조회수를 +1하는 쿼리문 update board set viewcnt=viewcnt+1 where bno=#{bno} </update>
| cs |
ModelAndView mav=new ModelAndView(); mav.setViewName("board/view"); //포워딩할 뷰의 이름 //view로 자료를 넘기기 위해 mav에 값들을 저장해서 view.jsp로 리턴시킴 mav.addObject("dto", boardService.read(bno)); //상세보기를 한번 클릭하면 조회수를 1 증가시켜야 하므로 조회수 증가 구문
BoardServiceImpl.java | @Override public BoardDTO read(int bno) throws Exception { return boardDao.read(bno); } | cs |
BoardDAOImpl.java | //게시물 조회 @Override public BoardDTO read(int bno) throws Exception { return sqlSession.selectOne("board.read", bno); } | cs |
boardMapper.xml | <mapper namespace="board"> <select id="read" resultType="com.example.spring02.model.board.dto.BoardDTO"> //board테이블의 작성자와 member테이블의 유저아이디가 같고, 글번호가 클릭한 글번호와 같은 //글번호, 제목, 조회수, 날짜, 내용, 이름, 작성자를 검색. select bno,title,regdate,content,viewcnt,name,writer from board b, member m where b.writer=m.userid and bno=#{bno} </select> | cs |
mav.addObject("curPage", curPage); mav.addObject("search_option", search_option); mav.addObject("keyword", keyword); return mav; // views/board/view.jsp로 넘어가서 출력됨 }
| cs |
view.jsp (네모칸 안에 있는 것들 확인) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 | <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!-- views/board/view.jsp --> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> <%@ include file="../include/header.jsp" %> <script src="${path}/include/js/common.js"></script> <!-- ckeditor의 라이브러리를 사용하기 위해 추가하는 코드 --> <script src="${path}/ckeditor/ckeditor.js"></script>
<script> $(function(){ //자동으로 실행되는 코드 //댓글 목록 출력 listReply2(); //댓글 쓰기 $("#btnReply").click(function(){ var replytext=$("#replytext").val(); //댓글 내용 var bno="${dto.bno}"; //게시물 번호 var param={ "replytext": replytext, "bno": bno}; //var param="replytext="+replytext+"&bno="+bno; $.ajax({ type: "post", url: "${path}/reply/insert.do", data: param, success: function(){ alert("댓글이 등록되었습니다."); listReply2(); //댓글 목록 출력 } }); }); $(".fileDrop").on("dragenter dragover",function(e){ //기본 효과 막음 e.preventDefault(); }); $(".fileDrop").on("drop",function(e){ e.preventDefault(); //첫번째 첨부파일 var files=e.originalEvent.dataTransfer.files; var file=files[0]; //폼 데이터에 첨부파일 추가 var formData=new FormData(); formData.append("file",file); $.ajax({ url: "${path}/upload/uploadAjax", data: formData, dataType: "text", processData: false, contentType: false, type: "post", success: function(data){ //console.log(data); //data : 업로드한 파일 정보와 Http 상태 코드 var fileInfo=getFileInfo(data); //console.log(fileInfo); var html="<a href='"+fileInfo.getLink+"'>"+ fileInfo.fileName+"</a><br>"; html += "<input type='hidden' class='file' value='" +fileInfo.fullName+"'>"; $("#uploadedList").append(html); } }); }); //목록 버튼 $("#btnList").click(function(){ location.href="${path}/board/list.do"; }); //수정 버튼 $("#btnUpdate").click(function(){ //첨부파일 이름들을 폼에 추가 var str=""; $("#uploadedList .file").each(function(i){ str+= "<input type='hidden' name='files["+i+"]' value='" +$(this).val()+"'>"; }); $("#form1").append(str); document.form1.action="${path}/board/update.do"; document.form1.submit(); }); //삭제 버튼 $("#btnDelete").click(function(){ if(confirm("삭제하시겠습니까?")){ document.form1.action="${path}/board/delete.do"; document.form1.submit(); } }); listAttach(); //첨부파일 삭제 //id가 uploadedList인 태그의 class가 file_del인 태그 클릭 $("#uploadedList").on("click",".file_del",function(e){ var that=$(this); //클릭한 태그 //data: {fileName: $(this).attr("data-src") }, $.ajax({ type: "post", url: "${path}/upload/deleteFile", data: "fileName="+ $(this).attr("data-src"), dataType: "text", success: function(result){ if(result=="deleted"){ //화면에서 태그 제거 that.parent("div").remove(); } } }); }); $("#btnSave").click(function(){ var str=""; // uploadedList 내부의 .file 태그 각각 반복 $("#uploadedList .file").each(function(i){ console.log(i); //hidden 태그 구성 str += "<input type='hidden' name='files["+i+"]' value='" + $(this).val()+"'>"; }); //폼에 hidden 태그들을 붙임 $("#form1").append(str); document.form1.submit(); }); }); //댓글 목록 출력 함수 function listReply(){ $.ajax({ type: "get", url: "${path}/reply/list.do?bno=${dto.bno}", success: function(result){ //result : responseText 응답텍스트(html) $("#listReply").html(result); } }); } //타임스탬프값(숫자형)을 문자열 형식으로 변환 function changeDate(date){ date = new Date(parseInt(date)); year=date.getFullYear(); month=date.getMonth(); day=date.getDate(); hour=date.getHours(); minute=date.getMinutes(); second=date.getSeconds(); strDate = year+"-"+month+"-"+day+" "+hour+":"+minute+":"+second; return strDate; } function listReply2(){ $.ajax({ type: "get", contentType: "application/json", url: "${path}/reply/list_json.do?bno=${dto.bno}", success: function(result){ console.log(result); var output="<table>"; for(var i in result){ var repl=result[i].replytext; repl = repl.replace(/ /gi," ");//공백처리 repl = repl.replace(/</gi,"<"); //태그문자 처리 repl = repl.replace(/>/gi,">"); repl = repl.replace(/\n/gi,"<br>"); //줄바꿈 처리 output += "<tr><td>"+result[i].name; date = changeDate(result[i].regdate); output += "("+date+")"; output += "<br>"+repl+"</td></tr>"; } output+="</table>"; $("#listReply").html(output); } }); } //첨부파일 리스트를 출력하는 함수 function listAttach(){ $.ajax({ type: "post", url: "${path}/board/getAttach/${dto.bno}", success: function(list){ // list : json //console.log(list); $(list).each(function(){ var fileInfo=getFileInfo(this); //console.log(fileInfo); var html="<div><a href='"+fileInfo.getLink+"'>" +fileInfo.fileName+"</a> "; <c:if test="${sessionScope.userid == dto.writer}"> html+="<a href='#' class='file_del' data-src='" +this+"'>[삭제]</a></div>"; </c:if> $("#uploadedList").append(html); }); } }); } </script> <style> <!--css로 스타일 시트를 적용--> .fileDrop { width: 600px; height: 100px; border: 1px dotted gray; background-color: gray; } </style> </head> <body> <%@ include file="../include/menu.jsp" %> <h2>게시물 보기</h2> <!-- 게시물을 작성하기 위해 컨트롤러의 insert.do로 맵핑 --> <form id="form1" name="form1" method="post" action="${path}/board/insert.do"> <div>제목 <input name="title" id="title" size="80" value="${dto.title}" placeholder="제목을 입력하세요"> <!-- placeholder은 제목을 입력할 수 있도록 도움말을 출력함 --> </div> <div>조회수 : ${dto.viewcnt} </div> <div style="width:800px;"> 내용 <textarea id="content" name="content" rows="3" cols="80" placeholder="내용을 입력하세요">${dto.content}</textarea> <!-- 마찬가지로 내용을 입력하도록 도움말을 출력함 --> <script> // ckeditor 적용 //id가 content인 태그 (글의 내용을 입력하는 태그)를 ck에디터를 적용한다는 의미 CKEDITOR.replace("content",{ filebrowserUploadUrl: "${path}/imageUpload.do", height: "300px" }); </script>
</div> <div> 첨부파일을 등록하세요 <div class="fileDrop"></div> <div id="uploadedList"></div> </div>
<div style="width:700px; text-align:center;"> <!-- 수정,삭제에 필요한 글번호를 hidden 태그에 저장 --> <input type="hidden" name="bno" value="${dto.bno}"> <!-- 본인만 수정,삭제 버튼 표시 --> <c:if test="${sessionScope.userid == dto.writer}"> <button type="button" id="btnUpdate">수정</button> <button type="button" id="btnDelete">삭제</button> </c:if> <!-- 목록은 본인이 아니어도 확인이 가능하게 할 예정 --> <button type="button" id="btnList">목록</button> </div> </form>
<!-- 댓글 작성 --> <div style="width:700px; text-align:center;"> <c:if test="${sessionScope.userid != null }"> <textarea rows="5" cols="80" id="replytext" placeholder="댓글을 작성하세요"></textarea> <br> <button type="button" id="btnReply">댓글쓰기</button> </c:if> </div> <!-- 댓글 목록 --> <div id="listReply"></div> </body> </html> | cs |
|