[Spring project] 멀티 게시판
개발환경
Windows10
JavaSE8(JDK8)
전자정부표준프레임워크개발환경3.9
SpringFramework4.3.9.RELEASE
apache-tomcat9.0.6
Oracle21cExpressionEdition
SQLDeveloper
테이블 명세서
테이블 SQL
board_category | 카테고리 정보 저장 |
board | 게시글 정보 저장 |
board_upload_file | 게시글 첨부파일 저장 |
🍏테이블 생성
DROP TABLE BOARD_UPLOAD_FILE;
DROP TABLE BOARD_CATEGORY;
DROP TABLE BOARD;
drop table member;
CREATE TABLE BOARD (
BOARD_ID NUMBER CONSTRAINT PK_BOARD_BOARD_ID PRIMARY KEY,
CATEGORY_ID NUMBER,
WRITER VARCHAR2(20) NOT NULL,
EMAIL VARCHAR2(100),
PASSWORD VARCHAR2(20) NOT NULL,
TITLE VARCHAR2(500) NOT NULL,
CONTENT CLOB,
WRITE_DATE DATE DEFAULT SYSDATE NOT NULL,
MASTER_ID NUMBER,
REPLY_NUMBER NUMBER,
REPLY_STEP NUMBER,
READ_COUNT NUMBER DEFAULT 0
);
CREATE TABLE BOARD_CATEGORY (
CATEGORY_ID NUMBER CONSTRAINT PK_BOARD_CATEGORY_ID PRIMARY KEY,
CATEGORY_NAME VARCHAR2(100) NOT NULL,
CATEGORY_DESCRIPTION VARCHAR2(100)
);
CREATE TABLE BOARD_UPLOAD_FILE (
FILE_ID NUMBER CONSTRAINT PK_BOARD_FILE_ID PRIMARY KEY,
BOARD_ID NUMBER,
FILE_NAME VARCHAR2(235),
FILE_SIZE VARCHAR2(45),
FILE_CONTENT_TYPE VARCHAR2(500),
FILE_DATA BLOB
);
create table member(
userid varchar2(50) primary key,
name varchar2(50) not null,
password varchar2(150) not null,
email varchar2(100) not null,
phone varchar2(50),
role varchar2(20) default 'role_user'
);
🍏테이블 제약조건
ALTER TABLE BOARD_CATEGORY
ADD CONSTRAINT UK_BOARD_CATEGORY_NAME UNIQUE (CATEGORY_NAME)
USING INDEX;
ALTER TABLE BOARD
ADD CONSTRAINT FK_BOARD_CATEGORY_ID
FOREIGN KEY (CATEGORY_ID) REFERENCES BOARD_CATEGORY (CATEGORY_ID);
ALTER TABLE BOARD_UPLOAD_FILE
ADD CONSTRAINT FK_BOARD_BOARD_ID
FOREIGN KEY (BOARD_ID) REFERENCES BOARD (BOARD_ID);
alter table member
add constraint uk_member_email
unique(email) using index;
--BOARD
CREATE SEQUENCE BOARD_SEQ
START WITH 1
INCREMENT BY 1
NOCACHE;
CREATE OR REPLACE TRIGGER BOARD_ID_TRIGGER
BEFORE INSERT ON BOARD
FOR EACH ROW
BEGIN
IF :NEW.BOARD_ID IS NULL THEN
SELECT BOARD_SEQ.NEXTVAL INTO :NEW.BOARD_ID FROM DUAL;
END IF;
END;
/
🍏sample data 삽입
board_category 테이블에 sample data 입력
INSERT INTO BOARD_CATEGORY (CATEGORY_ID, CATEGORY_NAME, CATEGORY_DESCRIPTION)
VALUES (1, '게시판', '답변형 멀티게시판');
INSERT INTO BOARD_CATEGORY (CATEGORY_ID, CATEGORY_NAME, CATEGORY_DESCRIPTION)
VALUES (2, '자료실', '파일 업로드 자료실');
INSERT INTO BOARD_CATEGORY (CATEGORY_ID, CATEGORY_NAME, CATEGORY_DESCRIPTION)
VALUES (3, '갤러리', '이미지 갤러리');
COMMIT;
board테이블에 sample data 입력
INSERT INTO BOARD (BOARD_ID, CATEGORY_ID, WRITER, EMAIL, PASSWORD, TITLE, CONTENT,
WRITE_DATE, MASTER_ID, REPLY_NUMBER, REPLY_STEP)
VALUES (1, 1, '홍길동', 'hong@hong.com', '1234', '방가요', '내용없음', '2015-12-20',
1, 0, 0);
INSERT INTO BOARD (BOARD_ID, CATEGORY_ID, WRITER, EMAIL, PASSWORD, TITLE, CONTENT,
WRITE_DATE, MASTER_ID, REPLY_NUMBER, REPLY_STEP)
VALUES (2, 1, '이순신', 'lee@lee.com', '1234', '나도', '내용없음', '2015-12-21', 2,
0, 0);
INSERT INTO BOARD (BOARD_ID, CATEGORY_ID, WRITER, EMAIL, PASSWORD, TITLE, CONTENT,
WRITE_DATE, MASTER_ID, REPLY_NUMBER, REPLY_STEP)
VALUES (3, 1, '홍길동', 'hong@hong.com', '1234', '오랜만이야~ 순신', '그렇지',
'2015-12-22', 2, 4, 1);
INSERT INTO BOARD (BOARD_ID, CATEGORY_ID, WRITER, EMAIL, PASSWORD, TITLE, CONTENT,
WRITE_DATE, MASTER_ID, REPLY_NUMBER, REPLY_STEP)
VALUES (4, 1, '무명씨', 'noname@name.com', '1234', '할루', '재미없음', '2015-12-23',
4, 0, 0);
INSERT INTO BOARD (BOARD_ID, CATEGORY_ID, WRITER, EMAIL, PASSWORD, TITLE, CONTENT,
WRITE_DATE, MASTER_ID, REPLY_NUMBER, REPLY_STEP)
VALUES (5, 1, '홍길서', 'seo@hong.com', '1234', '나도야 순신', '나도나도',
'2015-12-24', 2, 1, 1);
INSERT INTO BOARD (BOARD_ID, CATEGORY_ID, WRITER, EMAIL, PASSWORD, TITLE, CONTENT,
WRITE_DATE, MASTER_ID, REPLY_NUMBER, REPLY_STEP)
VALUES (6, 1, '조심씨', 'josim@josim.com', '1234', '조심해 길서', '안전하게',
'2015-12-25', 2, 2, 2);
INSERT INTO BOARD (BOARD_ID, CATEGORY_ID, WRITER, EMAIL, PASSWORD, TITLE, CONTENT,
WRITE_DATE, MASTER_ID, REPLY_NUMBER, REPLY_STEP)
VALUES (7, 1, '안전씨', 'an@anjeon.com', '1234', '자나깨나', '불조심', '2015-12-26',
4, 1, 1);
INSERT INTO BOARD (BOARD_ID, CATEGORY_ID, WRITER, EMAIL, PASSWORD, TITLE, CONTENT,
WRITE_DATE, MASTER_ID, REPLY_NUMBER, REPLY_STEP)
VALUES (8, 1, '소심씨', 'so@so.com', '1234', '조심해는 잘삐져', '조심씨',
'2015-12-27', 2, 3, 3);
COMMIT;
⚠️ 변경후 commit; 필수!!
파일 다운로드
리소스 파일 다운
https://javaspecialist.co.kr/board/1155/
헤더 파일 다운
https://javaspecialist.co.kr/board/1156/
국제화 설정 파일 다운
https://javaspecialist.co.kr/board/1157/
프로젝트 구조
Spring Legacy Project
Templates : Spring MVC Project
Project name: MultiBoard
top-level package: com.example.myapp
코드
🍏설정파일
pom.xml
=라이브러리 의존성 관리.
프로젝트 정보 설정 등..
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>myapp</artifactId>
<name>Ch7MultiBoard</name>
<packaging>war</packaging>
<version>1.0.0-BUILD-SNAPSHOT</version>
<properties>
<java-version>1.8</java-version>
<org.springframework-version>5.3.39</org.springframework-version>
<org.aspectj-version>1.6.10</org.aspectj-version>
<org.slf4j-version>1.6.6</org.slf4j-version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${org.springframework-version}</version>
<exclusions>
<!-- Exclude Commons Logging in favor of SLF4j -->
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<!-- ADD LIBRARY ===================================-->
<!-- AOP -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.22</version>
</dependency>
<!-- Spring JDBC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<!-- Connection Pool -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
<version>2.9.0</version>
</dependency>
<!-- Oracle JDBC Driver -->
<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc8</artifactId>
<version>21.1.0.0</version>
</dependency>
<!-- MyBatis & MyBatis-Spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
<!-- File upload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
<!-- XSS Filter -->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.15.3</version>
</dependency>
<!-- XSS Filter -->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.15.3</version>
</dependency>
<!-- ======================================== -->
생략...
web.xml
<?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 https://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<!-- Root spring container = 설정 파일 지정 (root-context.xml) -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/root-context.xml</param-value>
</context-param>
<!-- Creates the Spring Container -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Encoding Filter = 모든 요청을 UTF-8로 encoding -->
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- SERVLET = 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>/</url-pattern>
</servlet-mapping>
</web-app>
root-context.xml
=애플리케이션 전체에서 사용되는 설정이나 bean 정의
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mybatis-spring="http://mybatis.org/schema/mybatis-spring"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://mybatis.org/schema/mybatis-spring
http://mybatis.org/schema/mybatis-spring-1.2.xsd
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
<!-- DataSource = connect with Database -->
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
<property name="driverClassName" value="oracle.jdbc.OracleDriver" />
<property name="url" value="jdbc:oracle:thin:@localhost:1521:xe" />
<property name="username" value="hr" />
<property name="password" value="hr" />
</bean>
<!-- JdBC Tamplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- SQL Session Factory = create MyBatis SQL session -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="mapperLocations" value="classpath:mapper/**/*.xml" />
</bean>
<!-- Transaction Manager = database transaction management-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- Transaction 적용 활성화 -->
<tx:annotation-driven />
<!-- Exception Resolver = 예외 처리-->
<bean id="exceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="java.lang.RuntimeException">error/runtime</prop>
</props>
</property>
<property name="defaultErrorView" value="error/default" />
</bean>
<!-- MyBatis, Spring sccan -->
<!-- BOARD -->
<mybatis-spring:scan base-package="com.example.myapp.board.dao"/>
<context:component-scan base-package="com.example.myapp.board.service"/>
<!-- MEMBER -->
<mybatis-spring:scan base-package="com.example.myapp.member.dao"/>
<context:component-scan base-package="com.example.myapp.member.service"/>
</beans>
servlet-context.xml
<?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:mvc="http://www.springframework.org/schema/mvc"
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">
<!-- activate Spring MVC annotation -->
<annotation-driven />
<!-- view resolver = controller에서 반환하는 viewName을 실제 경로로 변환 -->
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<beans:property name="prefix" value="/WEB-INF/views/" />
<beans:property name="suffix" value=".jsp" />
</beans:bean>
<!-- 특정 URL은 controller 거치지 않고 바로 view 반환 ex) /요청시 home.jsp반환 -->
<view-controller path="/" view-name="home" />
<!-- static resource mapping -->
<mvc:annotation-driven/>
<mvc:resources mapping="/**" location="/resources/" />
<mvc:resources mapping="/js/**" location="/resources/js/" />
<mvc:resources mapping="/css/**" location="/resources/css/" />
<mvc:resources mapping="/images/**" location="/resources/images/" />
<!-- Spring에서 다중 파일 업로드 가능하게 -->
<beans:bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<beans:property name="maxUploadSize" value="50000000" />
</beans:bean>
<!-- Controller 자동 등록 -->
<!-- BOARD -->
<context:component-scan base-package="com.example.myapp.board.controller"/>
<!-- MEMBER -->
<context:component-scan base-package="com.example.myapp.member.controller"/>
<!-- INTERCEPTOR -->
<interceptors>
<interceptor>
<mapping path="/file/**"/>
<mapping path="/board/write/**"/>
<mapping path="/board/update/**"/>
<mapping path="/board/reply/**"/>
<mapping path="/board/delete/**"/>
<beans:bean class="com.example.myapp.common.filter.LoginInterceptor"/>
</interceptor>
</interceptors>
</beans:beans>
🍏BOARD
Board Model
Board.java
package com.example.myapp.board.model;
import java.sql.Timestamp;
import org.springframework.web.multipart.MultipartFile;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Setter
@Getter
@ToString(exclude = "file")
public class Board {
private int boardId;
private int categoryId;
private String writer;
private String email;
private String password;
private String title;
private String content;
private Timestamp writeDate;
private int masterId;
private int readCount;
private int replyNumber;
private int replyStep;
private int page;
private MultipartFile file;
private int fileId;
private String fileName;
private long fileSize;
private String fileContentType;
}
BoardCategory.java
package com.example.myapp.board.model;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Setter
@Getter
@ToString
public class BoardCategory {
private int categoryId;
private String categoryName;
private String categoryDescription;
}
BoardUploadFile.java
package com.example.myapp.board.model;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Setter
@Getter
@ToString(exclude = "fileData")
public class BoardUploadFile {
private int fileId; // 파일 아이디, 1씩 증가
private int boardId; // 첨부파일이 있는 게시글의 아이디(글번호)
private String fileName; // 파일 이름
private long fileSize; // 파일 크기
private String fileContentType; // 파일 타입
private byte[] fileData; // 파일 데이터
}
Board DAO
IBoardCategoryRepository.java
package com.example.myapp.board.dao;
import java.util.List;
import com.example.myapp.board.model.BoardCategory;
public interface IBoardCategoryRepository {
int selectMaxCategoryId();
List<BoardCategory> selectAllCategory();
void insertNewCategory(BoardCategory boardCategory);
void updateCategory(BoardCategory boardCategory);
void deleteCategory(int categoryId);
}
IBoardRepository.java
package com.example.myapp.board.dao;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import com.example.myapp.board.model.Board;
import com.example.myapp.board.model.BoardUploadFile;
public interface IBoardRepository {
List<Board> selectArticleListByCategory(@Param("categoryId") int categoryId,
@Param("start") int start,
@Param("end") int end);
Board selectArticle(int boardId);
void updateReadCount(int boardId);
int selectMaxArticleNo();
int selectMaxFileId();
void insertArticle(Board board);
void insertFileData(BoardUploadFile file);
BoardUploadFile getFile(int fileId);
void updateReplyNumber(@Param("masterId") int masterId,
@Param("replyNumber") int replyNumber);
void replyArticle(Board board);
String getPassword(int boardId);
void updateArticle(Board board);
void updateFileData(BoardUploadFile file);
Board selectDeleteArticle(int boardId);
void deleteFileData(int boardId);
void deleteArticleByBoardId(int boardId);
void deleteReplyFileData(int boardId);
void deleteArticleByMasterId(int boardId);
int selectTotalArticleCount();
int selectTotalArticleCountByCategoryId(int categoryId);
int selectTotalArticleCountByKeyword(String keyword);
List<Board> searchListByContentKeyword(@Param("keyword") String keyword,
@Param("start") int start,
@Param("end") int end);
}
Board Service
BoardCategoryService.java
package com.example.myapp.board.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.example.myapp.board.dao.IBoardCategoryRepository;
import com.example.myapp.board.model.BoardCategory;
@Service
public class BoardCategoryService implements IBoardCategoryService {
@Autowired
IBoardCategoryRepository boardCategoryRepository;
@Override
public List<BoardCategory> selectAllCategory() {
return boardCategoryRepository.selectAllCategory();
}
@Override
public void insertNewCategory(BoardCategory boardCategory) {
int newCategoryId = boardCategoryRepository.selectMaxCategoryId() + 1;
boardCategory.setCategoryId(newCategoryId);
boardCategoryRepository.insertNewCategory(boardCategory);
}
@Override
public void updateCategory(BoardCategory boardCategory) {
boardCategoryRepository.updateCategory(boardCategory);
}
@Override
public void deleteCategory(int categoryId) {
boardCategoryRepository.deleteCategory(categoryId);
}
}
BoardService.java
package com.example.myapp.board.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.example.myapp.board.dao.IBoardRepository;
import com.example.myapp.board.model.Board;
import com.example.myapp.board.model.BoardUploadFile;
@Service
public class BoardService implements IBoardService {
@Autowired
IBoardRepository boardRepository;
@Transactional
public void insertArticle(Board board) {
board.setBoardId(boardRepository.selectMaxArticleNo() + 1);
boardRepository.insertArticle(board);
}
@Transactional
public void insertArticle(Board board, BoardUploadFile file) {
board.setBoardId(boardRepository.selectMaxArticleNo() + 1);
boardRepository.insertArticle(board);
if (file != null && file.getFileName() != null && !file.getFileName().equals("")) {
file.setBoardId(board.getBoardId());
file.setFileId(boardRepository.selectMaxFileId() + 1);
boardRepository.insertFileData(file);
}
}
@Override
public List<Board> selectArticleListByCategory(int categoryId, int page) {
int start = (page - 1) * 10 + 1;
return boardRepository.selectArticleListByCategory(categoryId, start, start + 9);
}
@Transactional
public Board selectArticle(int boardId) {
boardRepository.updateReadCount(boardId);
return boardRepository.selectArticle(boardId);
}
@Override
public BoardUploadFile getFile(int fileId) {
return boardRepository.getFile(fileId);
}
@Transactional
public void replyArticle(Board board) {
boardRepository.updateReplyNumber(board.getMasterId(), board.getReplyNumber());
board.setBoardId(boardRepository.selectMaxArticleNo() + 1);
board.setReplyNumber(board.getReplyNumber() + 1);
board.setReplyStep(board.getReplyStep() + 1);
boardRepository.replyArticle(board);
}
@Transactional
public void replyArticle(Board board, BoardUploadFile file) {
boardRepository.updateReplyNumber(board.getMasterId(), board.getReplyNumber());
board.setBoardId(boardRepository.selectMaxArticleNo() + 1);
board.setReplyNumber(board.getReplyNumber() + 1);
board.setReplyStep(board.getReplyStep() + 1);
boardRepository.replyArticle(board);
if (file != null && file.getFileName() != null && !file.getFileName().equals("")) {
file.setBoardId(board.getBoardId());
file.setFileId(boardRepository.selectMaxFileId() + 1);
boardRepository.insertFileData(file);
}
}
@Override
public String getPassword(int boardId) {
return boardRepository.getPassword(boardId);
}
@Override
public void updateArticle(Board board) {
boardRepository.updateArticle(board);
}
@Transactional
public void updateArticle(Board board, BoardUploadFile file) {
boardRepository.updateArticle(board);
if (file != null && file.getFileName() != null && !file.getFileName().equals("")) {
file.setBoardId(board.getBoardId());
if (file.getFileId() > 0) {
boardRepository.updateFileData(file);
} else {
file.setFileId(boardRepository.selectMaxFileId() + 1);
boardRepository.insertFileData(file);
}
}
}
@Override
public Board selectDeleteArticle(int boardId) {
return boardRepository.selectDeleteArticle(boardId);
}
@Transactional
public void deleteArticle(int boardId, int replyNumber) {
if (replyNumber > 0) {
boardRepository.deleteFileData(boardId);
boardRepository.deleteArticleByBoardId(boardId);
} else if (replyNumber == 0) {
boardRepository.deleteReplyFileData(boardId);
boardRepository.deleteArticleByMasterId(boardId);
} else {
throw new RuntimeException("WRONG_REPLYNUMBER");
}
}
@Override
public int selectTotalArticleCount() {
return boardRepository.selectTotalArticleCount();
}
@Override
public int selectTotalArticleCountByCategoryId(int categoryId) {
return boardRepository.selectTotalArticleCountByCategoryId(categoryId);
}
@Override
public List<Board> searchListByContentKeyword(String keyword, int page) {
int start = (page - 1) * 10 + 1;
return boardRepository.searchListByContentKeyword("%" + keyword + "%", start, start + 9);
}
@Override
public int selectTotalArticleCountByKeyword(String keyword) {
return boardRepository.selectTotalArticleCountByKeyword("%" + keyword + "%");
}
}
IBoardCategoryService.java
package com.example.myapp.board.service;
import java.util.List;
import com.example.myapp.board.model.BoardCategory;
public interface IBoardCategoryService {
List<BoardCategory> selectAllCategory();
void insertNewCategory(BoardCategory boardCategory);
void updateCategory(BoardCategory boardCategory);
void deleteCategory(int categoryId);
}
IBoardService.java
package com.example.myapp.board.service;
import java.util.List;
import com.example.myapp.board.model.Board;
import com.example.myapp.board.model.BoardUploadFile;
public interface IBoardService {
void insertArticle(Board board);
void insertArticle(Board board, BoardUploadFile file);
List<Board> selectArticleListByCategory(int categoryId, int page);
Board selectArticle(int boardId);
BoardUploadFile getFile(int fileId);
void replyArticle(Board board);
void replyArticle(Board board, BoardUploadFile file);
String getPassword(int boardId);
void updateArticle(Board board);
void updateArticle(Board board, BoardUploadFile file);
Board selectDeleteArticle(int boardId);
void deleteArticle(int boardId, int replyNumber);
int selectTotalArticleCount();
int selectTotalArticleCountByCategoryId(int categoryId);
List<Board> searchListByContentKeyword(String keyword, int page);
int selectTotalArticleCountByKeyword(String keyword);
}
Board Controller
boardController.java
=Spring MVC Framework로 Board 기능 구현.
클라이언트 요청 처리 데이터 가져와 JSP에전달.,
package com.example.myapp.board.controller;
@Controller
public class BoardController {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
IBoardService boardService;
@Autowired
IBoardCategoryService categoryService;
//상품 조회
@GetMapping("/board/cat/{categoryId}/{page}")
public String getListByCategory(@PathVariable int categoryId, @PathVariable int page, HttpSession session, Model model) {
session.setAttribute("page", page);
model.addAttribute("categoryId", categoryId);
List<Board> boardList = boardService.selectArticleListByCategory(categoryId, page);
model.addAttribute("boardList", boardList);
int bbsCount = boardService.selectTotalArticleCountByCategoryId(categoryId);
int totalPage = bbsCount > 0 ? (int) Math.ceil(bbsCount / 10.0) : 0;
model.addAttribute("totalPageCount", totalPage);
model.addAttribute("page", page);
return "board/list";
}
@GetMapping("/board/cat/{categoryId}")
public String getListByCategory(@PathVariable int categoryId, HttpSession session, Model model) {
return getListByCategory(categoryId, 1, session, model);
}
@GetMapping("/board/{boardId}/{page}")
public String getBoardDetails(@PathVariable int boardId, @PathVariable int page, Model model) {
Board board = boardService.selectArticle(boardId);
model.addAttribute("board", board);
model.addAttribute("page", page);
model.addAttribute("categoryId", board.getCategoryId());
logger.info("getBoardDetails " + board.toString());
return "board/view";
}
@GetMapping("/board/{boardId}")
public String getBoardDetails(@PathVariable int boardId, Model model) {
return getBoardDetails(boardId, 1, model);
}
//WHRITE
@GetMapping(value = "/board/write/{categoryId}")
public String writeArticle(@PathVariable int categoryId, HttpSession session, Model model) {
String csrfToken = UUID.randomUUID().toString();
session.setAttribute("csrfToken", csrfToken);
List<BoardCategory> categoryList = categoryService.selectAllCategory();
model.addAttribute("categoryList", categoryList);
model.addAttribute("categoryId", categoryId);
return "board/write";
}
@PostMapping(value = "/board/write")
public String writeArticle(Board board, String csrfToken, HttpSession session, RedirectAttributes redirectAttrs) {
logger.info("/board/write : " + board.toString() + csrfToken);
String sessionToken = (String) session.getAttribute("csrfToken");
if (csrfToken == null || !csrfToken.equals(sessionToken)) {
throw new RuntimeException("CSRF Token Error.");
}
try {
board.setContent(board.getContent().replace("\r\n", "<br>"));
board.setTitle(Jsoup.clean(board.getTitle(), Safelist.basic()));
board.setContent(Jsoup.clean(board.getContent(), Safelist.basic()));
MultipartFile mfile = board.getFile();
if (mfile != null && !mfile.isEmpty()) {
BoardUploadFile file = new BoardUploadFile();
file.setFileName(mfile.getOriginalFilename());
file.setFileSize(mfile.getSize());
file.setFileContentType(mfile.getContentType());
file.setFileData(mfile.getBytes());
boardService.insertArticle(board, file);
} else {
boardService.insertArticle(board);
}
} catch (Exception e) {
e.printStackTrace();
redirectAttrs.addFlashAttribute("message", e.getMessage());
}
return "redirect:/board/cat/" + board.getCategoryId();
}
//File upload
@GetMapping("/file/{fileId}")
public ResponseEntity<byte[]> getFile(@PathVariable int fileId) {
BoardUploadFile file = boardService.getFile(fileId); //file 정보 조회
//응답 헤더 설정
final HttpHeaders headers = new HttpHeaders();
String[] mtypes = file.getFileContentType().split("/");
headers.setContentType(new org.springframework.http.MediaType(mtypes[0], mtypes[1]));
headers.setContentLength(file.getFileSize());
//파일 이름 encoding, 첨부 파일 설정
try {
String encodedFileName = java.net.URLEncoder.encode(file.getFileName(), "UTF-8");
headers.setContentDispositionFormData("attachment", encodedFileName);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
//파일 데이터 return
return new ResponseEntity<byte[]>(file.getFileData(), headers, HttpStatus.OK);
}
//답글 작성
@GetMapping(value = "/board/reply/{boardId}")
public String replyArticle(@PathVariable int boardId, Model model) {
Board board = boardService.selectArticle(boardId);
board.setWriter("");
board.setEmail("");
board.setTitle("[Re]" + board.getTitle());
board.setContent("\n\n\n----------\n" + board.getContent().replaceAll("<br>", "\n"));
model.addAttribute("board", board);
model.addAttribute("next", "reply");
return "board/reply";
}
@PostMapping(value = "/board/reply")
public String replyArticle(Board board, RedirectAttributes redirectAttrs, HttpSession session) {
logger.info("/board/reply : " + board.toString());
try {
board.setContent(board.getContent().replace("\r\n", "<br>")); // \r,\n을 <br>로 바꾸어 줄바꿈 유지
board.setTitle(Jsoup.clean(board.getTitle(), Safelist.basic()));
board.setContent(Jsoup.clean(board.getContent(), Safelist.basic()));
//업로드한 파일 처리
MultipartFile mfile = board.getFile();
if (mfile != null && !mfile.isEmpty()) {
BoardUploadFile file = new BoardUploadFile();
file.setFileName(mfile.getOriginalFilename());
file.setFileSize(mfile.getSize());
file.setFileContentType(mfile.getContentType());
file.setFileData(mfile.getBytes());
boardService.replyArticle(board, file); //게시글과 파일 데이터 저장
} else {
boardService.replyArticle(board); //게시글만 저장
}
} catch (Exception e) {
e.printStackTrace();
redirectAttrs.addFlashAttribute("message", e.getMessage());
}
//Redirection
if (session.getAttribute("page") != null) {
//page값이 있을 경우
return "redirect:/board/cat/" + board.getCategoryId() + "/" + (Integer) session.getAttribute("page");
} else {
//page값 없을 경우
return "redirect:/board/cat/" + board.getCategoryId();
}
}
// UPDATE
@GetMapping(value = "/board/update/{boardId}")
public String updateArticle(@PathVariable int boardId, Model model) {
//모든 카테고리 정보
List<BoardCategory> categoryList = categoryService.selectAllCategory();
//게시물 정보
Board board = boardService.selectArticle(boardId);
model.addAttribute("categoryList", categoryList);
model.addAttribute("categoryId", board.getCategoryId());
board.setContent(board.getContent().replaceAll("<br>", "\r\n")); //<br>을 다시 \r \n으로
model.addAttribute("board", board);
return "board/update";
}
@PostMapping(value = "/board/update")
public String updateArticle(Board board, RedirectAttributes redirectAttrs) {
logger.info("/board/update " + board.toString());
String dbPassword = boardService.getPassword(board.getBoardId());
//wrong password!
if (!board.getPassword().equals(dbPassword)) {
redirectAttrs.addFlashAttribute("passwordError", "게시글 비밀번호가 다릅니다");
return "redirect:/board/update/" + board.getBoardId();
}
//correct password!
try {
board.setContent(board.getContent().replace("\r\n", "<br>"));
board.setTitle(Jsoup.clean(board.getTitle(), Safelist.basic()));
board.setContent(Jsoup.clean(board.getContent(), Safelist.basic()));
//file upload
MultipartFile mfile = board.getFile();
if (mfile != null && !mfile.isEmpty()) {
logger.info("/board/update : " + mfile.getOriginalFilename());
BoardUploadFile file = new BoardUploadFile();
file.setFileId(board.getFileId());
file.setFileName(mfile.getOriginalFilename());
file.setFileSize(mfile.getSize());
file.setFileContentType(mfile.getContentType());
file.setFileData(mfile.getBytes());
logger.info("/board/update : " + file.toString());
boardService.updateArticle(board, file);
} else {
boardService.updateArticle(board);
}
} catch (Exception e) {
e.printStackTrace();
redirectAttrs.addFlashAttribute("message", e.getMessage());
}
return "redirect:/board/" + board.getBoardId();
}
//DELETE
@GetMapping(value = "/board/delete/{boardId}")
public String deleteArticle(@PathVariable int boardId, Model model) {
Board board = boardService.selectDeleteArticle(boardId);
model.addAttribute("categoryId", board.getCategoryId());
model.addAttribute("boardId", boardId);
model.addAttribute("replyNumber", board.getReplyNumber());
return "board/delete";
}
@PostMapping(value = "/board/delete")
public String deleteArticle(Board board, HttpSession session, RedirectAttributes model) {
try {
String dbpw = boardService.getPassword(board.getBoardId());
//correct password
if (dbpw.equals(board.getPassword())) {
boardService.deleteArticle(board.getBoardId(), board.getReplyNumber());
return "redirect:/board/cat/" + board.getCategoryId() + "/" + (Integer) session.getAttribute("page");
}
//wrong password
else {
model.addFlashAttribute("message", "WRONG_PASSWORD_NOT_DELETED");
return "redirect:/board/delete/" + board.getBoardId();
}
} catch (Exception e) {
model.addAttribute("message", e.getMessage());
e.printStackTrace();
return "error/runtime";
}
}
//SEARCH
@GetMapping("/board/search/{page}")
//keyword=검색 키워드(공백이어도 됨), page=페이지 번호
public String search(@RequestParam(required = false, defaultValue = "") String keyword, @PathVariable int page, HttpSession session, Model model) {
try {
List<Board> boardList = boardService.searchListByContentKeyword(keyword, page);
model.addAttribute("boardList", boardList);
int bbsCount = boardService.selectTotalArticleCountByKeyword(keyword);
int totalPage = bbsCount > 0 ? (int) Math.ceil(bbsCount / 10.0) : 0;
model.addAttribute("totalPageCount", totalPage);
model.addAttribute("page", page);
model.addAttribute("keyword", keyword);
//logger.info(totalPage + ":" + page + ":" + keyword);
} catch (Exception e) {
e.printStackTrace();
}
return "board/search";
}
}
🍏Member
Member.java
package com.example.myapp.member.model;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Setter
@Getter
@ToString
public class Member {
private String userid;
private String name;
private String password;
private String password2;
private String phone;
private String email;
private String role;
}
IMemberService.java
package com.example.myapp.member.service;
import java.util.List;
import com.example.myapp.member.model.Member;
public interface IMemberService {
void insertMember(Member member);
Member selectMember(String userid);
List<Member> selectAllmembers();
void updateMember(Member member);
void deleteMember(Member member);
String getPassword(String userid);
}
MemberService.java
package com.example.myapp.member.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.example.myapp.member.dao.IMemberRepository;
import com.example.myapp.member.model.Member;
@Service
public class MemberService implements IMemberService{
@Autowired
IMemberRepository memberDao;
@Override
public void insertMember(Member member)
{
memberDao.insertMember(member);
}
@Override
public Member selectMember(String userid)
{
return memberDao.selectMember(userid);
}
@Override
public List<Member> selectAllmembers() {
return memberDao.selectAllMembers();
}
@Override
public void updateMember(Member member) {
memberDao.updateMember(member);
}
@Override
public void deleteMember(Member member) {
memberDao.deleteMember(member);
}
@Override
public String getPassword(String userid) {
return memberDao.getPassword(userid);
}
}
IMemberRepository.java
package com.example.myapp.member.dao;
import java.util.List;
import com.example.myapp.member.model.Member;
public interface IMemberRepository {
void insertMember(Member member);
Member selectMember(String userid);
List<Member> selectAllMembers();
void updateMember(Member member);
void deleteMember(Member member);
String getPassword(String userid);
}
MemberController.java
UUID=universally unique Identifier. 고유한 ID생성.
CSRF Token=공격 방어 보안 메커니즘. 각 요청에 고유 tocken을 포함시켜 이것이 실제 사용자 의도인지 확인
package com.example.myapp.member.controller;
@Controller
public class MemberController {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
IMemberService memberService;
//INSERT
@GetMapping(value = "/member/insert")
public String insertMember(HttpSession session, Model model) {
String csrfToken = UUID.randomUUID().toString(); //고유한 UUID생성
session.setAttribute("csrfToken", csrfToken);
logger.info("/member/insert, GET - CSRF Token: {}", csrfToken);
model.addAttribute("member", new Member());
return "member/form";
}
@PostMapping(value = "/member/insert")
public String insertMember(Member member, String csrfToken, HttpSession session, Model model) {
String sessionToken = (String) session.getAttribute("csrfToken");
//ERROR
if (csrfToken == null || !csrfToken.equals(sessionToken)) {
throw new RuntimeException("CSRF Token Error.");
}
//NO ERROR
try {
//different password
if (!member.getPassword().equals(member.getPassword2())) {
model.addAttribute("member", member);
model.addAttribute("message", "MEMBER_PW_RE");
return "member/form";
}
//correct password
memberService.insertMember(member);
} catch (DuplicateKeyException e) {
//ID check
member.setUserid(null);
model.addAttribute("member", member);
model.addAttribute("message", "ID_ALREADY_EXIST");
return "member/form";
}
session.invalidate();
return "home";
}
//LOGIN
@GetMapping(value = "/member/login")
public String login() {
return "member/login";
}
@PostMapping(value = "/member/login")
public String login(String userid, String password, HttpSession session, Model model) {
Member member = memberService.selectMember(userid);
//check member
if (member != null) {
logger.info(member.toString());
//check password
String dbPassword = member.getPassword();
if (dbPassword.equals(password)) {
session.setMaxInactiveInterval(600);
session.setAttribute("userid", userid);
session.setAttribute("name", member.getName());
session.setAttribute("email", member.getEmail());
} else {
session.invalidate();
model.addAttribute("message", "WRONG_PASSWORD");
}
} else {
session.invalidate();
model.addAttribute("message", "USER_NOT_FOUND");
}
return "member/login";
}
//LOGOUT
@GetMapping(value = "/member/logout")
public String logout(HttpSession session, HttpServletRequest request) {
session.invalidate();
return "home";
}
//UPDATE(CHANGE USER INFO)
@GetMapping(value = "/member/update")
public String updateMember(HttpSession session, Model model) {
String userid = (String) session.getAttribute("userid");
//check userid
if (userid != null && !userid.equals("")) {
Member member = memberService.selectMember(userid);
model.addAttribute("member", member);
model.addAttribute("message", "UPDATE_USER_INFO");
return "member/update";
} else {
model.addAttribute("message", "NOT_LOGIN_USER");
return "member/login";
}
}
@PostMapping(value = "/member/update")
public String updateMember(Member member, HttpSession session, Model model) {
try {
memberService.updateMember(member);
model.addAttribute("message", "UPDATED_MEMBER_INFO");
model.addAttribute("member", member);
session.setAttribute("email", member.getEmail());
return "member/login";
} catch (Exception e) {
model.addAttribute("message", e.getMessage());
e.printStackTrace();
return "member/error";
}
}
//DELETE
@GetMapping(value = "/member/delete")
public String deleteMember(HttpSession session, Model model) {
String userid = (String) session.getAttribute("userid");
//check userid
if (userid != null && !userid.equals("")) {
Member member = memberService.selectMember(userid);
model.addAttribute("member", member);
model.addAttribute("message", "MEMBER_PW_RE");
return "member/delete";
} else {
model.addAttribute("message", "NOT_LOGIN_USER");
return "member/login";
}
}
@PostMapping(value = "/member/delete")
public String deleteMember(String password, HttpSession session, Model model) {
try {
Member member = new Member();
member.setUserid((String) session.getAttribute("userid"));
String dbpw = memberService.getPassword(member.getUserid());
//check password
if (password != null && password.equals(dbpw)) {
member.setPassword(password);
memberService.deleteMember(member);
session.invalidate();
return "member/login";
} else {
model.addAttribute("message", "WRONG_PASSWORD");
return "member/delete";
}
} catch (Exception e) {
model.addAttribute("message", "DELETE_FAIL");
e.printStackTrace();
return "member/delete";
}
}
}
🍏Filter
loginInterceptor.java
package com.example.myapp.common.filter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
try {
//get email
String email = (String) request.getSession().getAttribute("email");
//check email
if (email == null || email.equals("")) {
response.sendRedirect(request.getContextPath() + "/member/login");
return false;
}
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
🍏Mapper
BoardCategoryMaper.xml
<?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 namespace="com.example.myapp.board.dao.IBoardCategoryRepository">
<!-- select all categories -->
<select id="selectAllCategory" resultType="com.example.myapp.board.model.BoardCategory">
SELECT
category_id AS categoryId,
category_name AS categoryName,
category_description AS categoryDescription
FROM board_category
ORDER BY category_id
</select>
<!-- select max category id -->
<select id="selectMaxCategoryId" resultType="int">
SELECT
NVL(MAX(category_id), 0) AS categoryId
FROM board_category
</select>
<!-- insert new category -->
<insert id="insertNewCategory" parameterType="com.example.myapp.board.model.BoardCategory">
INSERT INTO board_category
(category_id, category_name, category_description)
VALUES
(#{categoryId}, #{categoryName}, #{categoryDescription})
</insert>
<!-- update category -->
<update id="updateCategory" parameterType="com.example.myapp.board.model.BoardCategory">
UPDATE board_category
SET
category_name = #{categoryName},
category_description = #{categoryDescription}
WHERE
category_id = #{categoryId}
</update>
<!-- delete category -->
<delete id="deleteCategory" parameterType="int">
DELETE FROM board_category
WHERE category_id = #{categoryId}
</delete>
</mapper>
BoardMapper.xml
<?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 namespace="com.example.myapp.board.dao.IBoardRepository">
<select id="selectArticleListByCategory" parameterType="hashmap" resultType="com.example.myapp.board.model.Board">
SELECT
board_id AS "boardId",
category_id AS "categoryId",
writer AS "writer",
email AS "email",
title AS "title",
write_date AS "writeDate",
master_id AS "masterId",
reply_number AS "replyNumber",
reply_step AS "replyStep",
read_count AS "readCount"
FROM (
SELECT
board_id, category_id, writer, email, title, write_date,
master_id, reply_number, reply_step, read_count,
rownum AS rnum
FROM (
SELECT * FROM board
WHERE category_id=#{categoryId}
ORDER BY master_id DESC, reply_number, reply_step
)
)
WHERE rnum BETWEEN #{start} AND #{end}
</select>
<select id="selectArticle" parameterType="int" resultType="com.example.myapp.board.model.Board">
SELECT
board.board_id AS "boardId",
category_id AS "categoryId",
writer AS "writer",
email AS "email",
title AS "title",
content AS "content",
read_count AS "readCount",
write_date AS "writeDate",
master_id AS "masterId",
reply_number AS "replyNumber",
reply_step AS "replyStep",
board_upload_file.file_id AS "fileId",
board_upload_file.file_name AS "fileName",
board_upload_file.file_size AS "fileSize",
board_upload_file.file_content_type AS "fileContentType"
FROM board
LEFT OUTER JOIN board_upload_file
ON board.board_id = board_upload_file.board_id
WHERE board.board_id=#{boardId}
</select>
<update id="updateReadCount" parameterType="int">
UPDATE board
SET read_count = read_count + 1
WHERE board_id = #{boardId}
</update>
<select id="selectMaxArticleNo" resultType="int">
SELECT NVL(MAX(board_id), 0) AS "articleNo" FROM board
</select>
<select id="selectMaxFileId" resultType="int">
SELECT NVL(MAX(file_id), 0) AS "fileId" FROM board_upload_file
</select>
<insert id="insertArticle" parameterType="com.example.myapp.board.model.Board">
INSERT INTO board
(board_id, category_id, writer, email, password, title, content, write_date, master_id, reply_number, reply_step)
VALUES
(#{boardId}, #{categoryId}, #{writer}, #{email}, #{password}, #{title}, #{content}, SYSDATE, #{boardId}, 0, 0)
</insert>
<insert id="insertFileData" parameterType="com.example.myapp.board.model.BoardUploadFile">
INSERT INTO board_upload_file
(file_id, board_id, file_name, file_size, file_content_type, file_data)
VALUES
(#{fileId}, #{boardId}, #{fileName}, #{fileSize}, #{fileContentType}, #{fileData})
</insert>
<select id="getFile" parameterType="int" resultType="com.example.myapp.board.model.BoardUploadFile">
SELECT
file_id AS "fileId",
board_id AS "boardId",
file_name AS "fileName",
file_size AS "fileSize",
file_content_type AS "fileContentType",
file_data AS "fileData"
FROM board_upload_file
WHERE file_id = #{fileId}
</select>
<update id="updateReplyNumber" parameterType="hashmap">
UPDATE board
SET reply_number = reply_number + 1
WHERE master_id = #{masterId} AND reply_number > #{replyNumber}
</update>
<insert id="replyArticle" parameterType="com.example.myapp.board.model.Board">
INSERT INTO board
(board_id, category_id, writer, email, password, title, content, write_date, master_id, reply_number, reply_step)
VALUES
(#{boardId}, #{categoryId}, #{writer}, #{email}, #{password}, #{title}, #{content}, SYSDATE, #{masterId}, #{replyNumber}, #{replyStep})
</insert>
<select id="getPassword" parameterType="int" resultType="string">
SELECT password FROM board WHERE board_id = #{boardId}
</select>
<update id="updateArticle" parameterType="com.example.myapp.board.model.Board">
UPDATE board
SET
category_id = #{categoryId},
writer = #{writer},
email = #{email},
title = #{title},
content = #{content},
write_date = SYSDATE
WHERE board_id = #{boardId}
</update>
<update id="updateFileData" parameterType="com.example.myapp.board.model.BoardUploadFile">
UPDATE board_upload_file
SET
file_name = #{fileName},
file_size = #{fileSize},
file_content_type = #{fileContentType},
file_data = #{fileData}
WHERE file_id = #{fileId}
</update>
<select id="selectDeleteArticle" parameterType="int" resultType="com.example.myapp.board.model.Board">
SELECT
category_id AS "categoryId",
master_id AS "masterId",
reply_number AS "replyNumber"
FROM board
WHERE board_id = #{boardId}
</select>
<delete id="deleteFileData" parameterType="int">
DELETE FROM board_upload_file
WHERE EXISTS (
SELECT board_id FROM board
WHERE board.board_id = #{boardId}
AND board.board_id = board_upload_file.board_id
)
</delete>
<delete id="deleteReplyFileData" parameterType="int">
DELETE FROM board_upload_file
WHERE EXISTS (
SELECT board_id FROM board
WHERE board.master_id = #{boardId}
AND board.board_id = board_upload_file.board_id
)
</delete>
<delete id="deleteArticleByBoardId" parameterType="int">
DELETE FROM board WHERE board_id = #{boardId}
</delete>
<delete id="deleteArticleByMasterId" parameterType="int">
DELETE FROM board WHERE master_id = #{boardId}
</delete>
<select id="selectTotalArticleCount" resultType="int">
SELECT COUNT(board_id) AS "count" FROM board
</select>
<select id="selectTotalArticleCountByCategoryId" parameterType="int" resultType="int">
SELECT COUNT(board_id) AS "count" FROM board
WHERE category_id = #{categoryId}
</select>
<select id="selectTotalArticleCountByKeyword" parameterType="string" resultType="int">
SELECT COUNT(*) FROM board
WHERE title LIKE #{keyword} OR content LIKE #{keyword}
</select>
<select id="searchListByContentKeyword" parameterType="hashmap" resultType="com.example.myapp.board.model.Board">
SELECT
board_id AS "boardId",
category_id AS "categoryId",
writer AS "writer",
email AS "email",
title AS "title",
write_date AS "writeDate",
master_id AS "masterId",
reply_number AS "replyNumber",
reply_step AS "replyStep",
read_count AS "readCount"
FROM (
SELECT
board_id, category_id, writer, email, title, write_date,
master_id, reply_number, reply_step, read_count,
rownum AS rnum
FROM (
SELECT * FROM board
WHERE title LIKE #{keyword} OR content LIKE #{keyword}
ORDER BY master_id DESC, reply_number, reply_step
)
)
WHERE rnum BETWEEN #{start} AND #{end}
</select>
</mapper>
MemberMapper.xml
<?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 namespace="com.example.myapp.member.dao.IMemberRepository">
<insert id="insertMember" parameterType="com.example.myapp.member.model.Member">
INSERT INTO member (userid, name, password, phone, email)
VALUES (#{userid}, #{name}, #{password}, #{phone}, #{email})
</insert>
<select id="selectMember" parameterType="string" resultType="com.example.myapp.member.model.Member">
SELECT userid, name, password, phone, email, role
FROM member
WHERE userid = #{userid}
</select>
<select id="selectAllMembers" resultType="com.example.myapp.member.model.Member">
SELECT userid, name, password, phone, address, role
FROM member
</select>
<update id="updateMember" parameterType="com.example.myapp.member.model.Member">
UPDATE member
SET name = #{name}, password = #{password}, phone = #{phone}, email = #{email}
WHERE userid = #{userid}
</update>
<delete id="deleteMember" parameterType="com.example.myapp.member.model.Member">
DELETE FROM member
WHERE userid = #{userid} AND password = #{password}
</delete>
<select id="getPassword" parameterType="string" resultType="string">
SELECT password
FROM member
WHERE userid = #{userid}
</select>
</mapper>
🍏Tags
paging.tag
=게시판에서 카테고리별로 글 목록을 10개씩 나누어서 보여준다.
categoryId, nowPage, totalPageCount값 동적으로 받아와 페이지 링크 성
<%@ tag language="java" pageEncoding="UTF-8" trimDirectiveWhitespaces="true" %>
<%@ tag body-content="empty" %>
<%@ attribute name="categoryId" type="java.lang.Integer" required="true" %>
<%@ attribute name="totalPageCount" type="java.lang.Integer" required="true" %>
<%@ attribute name="nowPage" type="java.lang.Integer" required="true" %>
<%
int totalPageBlock = (int) Math.ceil(totalPageCount / 10.0);
int nowPageBlock = (int) Math.ceil(nowPage / 10.0);
int startPage = (nowPageBlock - 1) * 10 + 1;
int endPage = Math.min(totalPageCount, nowPageBlock * 10);
String contextPath = application.getContextPath();
contextPath = (contextPath == null || contextPath.trim().isEmpty()) ? "" : contextPath;
out.println("<nav aria-label=\"Page navigation\">");
out.println("<ul class=\"pagination\">");
if (nowPageBlock > 1) {
out.print("<li>");
out.print("<a href=\"" + contextPath + "/board/cat/" + categoryId + "/" + (startPage - 1) + "\" aria-label=\"Previous\">");
out.print("◀</a>");
out.println("</li>");
}
for (int i = startPage; i <= endPage; i++) {
if (i == nowPage) {
out.print("<li class=\"active\">");
} else {
out.print("<li>");
}
out.print("<a href=\"" + contextPath + "/board/cat/" + categoryId + "/" + i + "\">");
out.print(i);
out.print("</a>");
out.println("</li>");
}
if (nowPageBlock < totalPageBlock) {
out.print("<li>");
out.print("<a href=\"" + contextPath + "/board/cat/" + categoryId + "/" + (endPage + 1) + "\" aria-label=\"Next\">");
out.print("▶</a>");
out.println("</li>");
}
out.println("</ul>");
out.println("</nav>");
%>
reply.tag
<%@ tag pageEncoding="utf-8" trimDirectiveWhitespaces="true" %>
<%@ tag body-content="empty" %>
<%@ attribute name="replynum" type="java.lang.Integer" %>
<%@ attribute name="replystep" type="java.lang.Integer" %>
<%
if (replynum == 0) {
out.print(""); // 메인 글
} else {
for (int i = 0; i < replystep; i++) {
out.print(" "); // 공백
}
out.print("└"); // 답변글
}
%>
search-paging.tag
<%@ tag language="java" pageEncoding="UTF-8" trimDirectiveWhitespaces="true"%>
<%@ tag body-content="empty"%>
<%@ attribute name="totalPageCount" type="java.lang.Integer" required="true"%>
<%@ attribute name="nowPage" type="java.lang.Integer" required="true"%>
<%@ attribute name="keyword" type="java.lang.String" required="false"%>
<%
//전체 페이지 블럭수
int totalPageBlock = (int) (Math.ceil(totalPageCount / 10.0));
//현재 페이지
int nowPageBlock = (int) Math.ceil(nowPage / 10.0);
int startPage = (nowPageBlock - 1) * 10 + 1;
int endPage = 0;
String contextPath = application.getContextPath();
//check contextPath
if (contextPath == null || contextPath.trim().equals("")) {
contextPath = "";
}
//현재 페이지 블록의 마지막 페이지 계싼
if (totalPageCount > nowPageBlock * 10) {
endPage = nowPageBlock * 10;
} else {
endPage = totalPageCount;
}
//HTML////////////////////////////////////////////////////
out.println("<nav aria-label=\"Page navigation\">");
out.println("<ul class=\"pagination\">");
//이전 페이지
if (nowPageBlock > 1) {
out.print("<li>");
out.print("<a href=\"" + contextPath + "/board/search/" + (startPage - 1) + "?keyword=" + keyword + "\" aria-label=\"Previous\">");
out.print("◀</a>");
out.println("</li>");
}
//각 페이지 링크
for (int i = startPage; i <= endPage; i++) {
if (i == nowPage) {
out.print("<li class=\"active\">");
} else {
out.print("<li>");
}
out.print("<a href=\"" + contextPath + "/board/search/" + i + "?keyword=" + keyword + "\">");
out.print(i);
out.print("</a>");
out.println("</li>");
}
//다음 페이지
if (nowPageBlock < totalPageBlock) {
out.print("<li>");
out.print("<a href=\"" + contextPath + "/board/search/" + (endPage + 1) + "?keyword=" + keyword + "\" aria-label=\"Next\">");
out.print("▶</a>");
out.println("</li>");
}
//END
out.println("</ul>");
out.println("</nav>");
%>
🍏VIEW
board
delete.jsp
<%@ page contentType="text/html; charset=utf-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<fmt:setBundle basename="i18n/board"/>
<!DOCTYPE html>
<html>
<jsp:include page="/WEB-INF/views/include/staticFiles.jsp"/>
<body>
<jsp:include page="/WEB-INF/views/include/bodyHeader.jsp"/>
<div class="container">
<div class="pg-opt">
<div class="row">
<div class="col-md-6 pc">
<h2><fmt:message key="DELETE_ARTICLE"/></h2>
</div>
<div class="col-md-6">
<ol class="breadcrumb">
<li><fmt:message key="BOARD"/></li>
<li class="active"><fmt:message key="DELETE_ARTICLE"/></li>
</ol>
</div>
</div>
</div>
<div class="content">
<h3><fmt:message key="DELETE_MSG"/></h3>
<form action='<c:url value="/board/delete"/>' class="form-inline" method="post">
<input type="hidden" name="boardId" value="${boardId}">
<input type="hidden" name="replyNumber" value="${replyNumber}">
<input type="hidden" name="categoryId" value="${categoryId}">
<div class="form-group">
<div class="col-sm-8">
<input type="password" name="password" class="form-control" required>
<c:if test="${!empty message}">
<br><span style="color:red;"><fmt:message key="${message}"/></span>
</c:if>
</div>
<div class="col-sm-2">
<input type="submit" class="btn btn-danger" value="<fmt:message key='DELETE_ARTICLE'/>">
</div>
</div>
</form>
</div>
</div>
<jsp:include page="/WEB-INF/views/include/footer.jsp"/>
</body>
</html>
list.jsp
<%@ page contentType="text/html; charset=utf-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<fmt:setBundle basename="i18n/board" />
<%@ taglib prefix="jk" tagdir="/WEB-INF/tags" %>
<!DOCTYPE html>
<html>
<jsp:include page="/WEB-INF/views/include/staticFiles.jsp" />
<body>
<jsp:include page="/WEB-INF/views/include/bodyHeader.jsp" />
<div class="container">
<div class="pg-opt">
<div class="row">
<div class="col-md-6">
<h2>
<fmt:message key="BOARD_LIST" />
<c:if test="${empty name}">
<small style="color:red;">
<fmt:message key="LOGIN" />
</small>
</c:if>
</h2>
</div>
<div class="col-md-6">
<ol class="breadcrumb">
<li><fmt:message key="BOARD" /></li>
<li class="active"><fmt:message key="BOARD_LIST" /></li>
</ol>
</div>
</div>
</div>
${message}
<div class="content">
<form action="<c:url value='/board/search/1' />" method="get">
<div class="pull-right" style="margin-bottom: 5px;">
<div class="col-xs-9">
<input type="text" name="keyword" class="form-control" />
</div>
<input type="submit" class="btn btn-warning" value="<fmt:message key='SEARCH' />" />
</div>
</form>
<table class="table table-hover table-bordered">
<thead>
<tr>
<td><fmt:message key="BOARD_ID" /></td>
<td class="pc"><fmt:message key="WRITER" /></td>
<td><fmt:message key="SUBJECT" /></td>
<td class="pc"><fmt:message key="WRITE_DATE" /></td>
<td class="pc"><fmt:message key="READ_COUNT" /></td>
</tr>
</thead>
<c:forEach var="board" items="${boardList}">
<tr>
<td>${board.boardId}</td>
<td class="pc">${board.writer}</td>
<td>
<jk:reply replynum="${board.replyNumber}" replystep="${board.replyStep}" />
<c:url var="viewLink" value="/board/${board.boardId}/${page}" />
<a href="${viewLink}">${board.title}</a>
</td>
<td class="pc">
<fmt:formatDate value="${board.writeDate}" pattern="YYYY-MM-dd" />
</td>
<td class="pc">${board.readCount}</td>
</tr>
</c:forEach>
</table>
<table class="table">
<tr>
<td align="left">
<jk:paging categoryId="${categoryId}" totalPageCount="${totalPageCount}" nowPage="${page}" />
</td>
<td align="right">
<a href='<c:url value="/board/write/${categoryId}" />'>
<button type="button" class="btn btn-info">
<fmt:message key="WRITE_NEW_ARTICLE" />
</button>
</a>
</td>
</tr>
</table>
</div>
</div>
<jsp:include page="/WEB-INF/views/include/footer.jsp" />
</body>
</html>
reply.jsp
<%@ page contentType="text/html; charset=utf-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<fmt:setBundle basename="i18n/board"/>
<!DOCTYPE html>
<html>
<jsp:include page="/WEB-INF/views/include/staticFiles.jsp"/>
<body>
<jsp:include page="/WEB-INF/views/include/bodyHeader.jsp"/>
<div class="container">
<div class="pg-opt">
<div class="row">
<div class="col-md-6 pc">
<h2><fmt:message key="REPLY_ARTICLE"/></h2>
</div>
<div class="col-md-6">
<ol class="breadcrumb">
<li><fmt:message key="BOARD"/></li>
<li class="active"><fmt:message key="REPLY_ARTICLE"/></li>
</ol>
</div>
</div>
</div>
<div class="content">
<form action="<c:url value='/board/reply'/>" method="post" enctype="multipart/form-data" class="form-horizontal">
<div class="form-group">
<label class="control-label col-sm-2" for="writer"><fmt:message key="WRITER"/></label>
<div class="col-sm-2">
<input type="text" name="writer" id="writer" class="form-control" value="${sessionScope.name}" ${!empty sessionScope.name ? "readonly" : ""} required>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="email"><fmt:message key="EMAIL"/></label>
<div class="col-sm-4">
<input type="text" name="email" id="email" class="form-control" value="${sessionScope.email}" ${!empty sessionScope.email ? "readonly" : ""} autocomplete="off" required>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="password"><fmt:message key="PASSWORD"/></label>
<div class="col-sm-2">
<input type="password" name="password" id="password" class="form-control" required>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="title"><fmt:message key="TITLE"/></label>
<div class="col-sm-8">
<input type="text" name="title" id="title" class="form-control" value="${board.title}" required>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="content"><fmt:message key="CONTENT"/></label>
<div class="col-sm-8">
<textarea name="content" id="content" rows="10" cols="100" class="form-control">${board.content}</textarea>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="file"><fmt:message key="FILE"/></label>
<div class="col-sm-8">
<input type="file" id="file" name="file">
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-8">
<input type="hidden" name="boardId" value="${board.boardId}">
<input type="hidden" name="categoryId" value="${board.categoryId}">
<input type="hidden" name="masterId" value="${board.masterId}">
<input type="hidden" name="replyNumber" value="${board.replyNumber}">
<input type="hidden" name="replyStep" value="${board.replyStep}">
<input type="submit" class="btn btn-info" value="<fmt:message key='SAVE'/>">
<input type="reset" class="btn btn-info" value="<fmt:message key='CANCEL'/>">
</div>
</div>
</form>
</div>
</div>
<jsp:include page="/WEB-INF/views/include/footer.jsp"/>
</body>
</html>
search.jsp
<%@ page contentType="text/html; charset=utf-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<fmt:setBundle basename="i18n/board"/>
<%@ taglib prefix="jk" tagdir="/WEB-INF/tags"%>
<!DOCTYPE html>
<html>
<jsp:include page="/WEB-INF/views/include/staticFiles.jsp"/>
<body>
<jsp:include page="/WEB-INF/views/include/bodyHeader.jsp"/>
<div class="container">
<div class="pg-opt">
<div class="row">
<div class="col-md-6 pc">
<h2><fmt:message key="BOARD_LIST"/>
<c:if test="${empty name}">
<small style="color:red;"><fmt:message key="LOGIN"/></small>
</c:if>
</h2>
</div>
<div class="col-md-6">
<ol class="breadcrumb">
<li><fmt:message key="BOARD"/></li>
<li class="active"><fmt:message key="BOARD_LIST"/></li>
</ol>
</div>
</div>
</div>
${message}
<div class="content">
<form action="<c:url value='/board/search/1'/>" method="get">
<div class="pull-right" style="margin-bottom: 5px;">
<div class="col-xs-9">
<input type="text" name="keyword" class="form-control">
</div>
<input type="submit" class="btn btn-warning" value="<fmt:message key='SEARCH'/>">
</div>
</form>
<table class="table table-hover table-bordered">
<thead>
<tr>
<td><fmt:message key="BOARD_ID"/></td>
<td class="pc"><fmt:message key="WRITER"/></td>
<td><fmt:message key="SUBJECT"/></td>
<td class="pc"><fmt:message key="WRITE_DATE"/></td>
<td class="pc"><fmt:message key="READ_COUNT"/></td>
</tr>
</thead>
<c:forEach var="board" items="${boardList}">
<tr>
<td>${board.boardId}</td>
<td class="pc">${board.writer}</td>
<td>
<a href='<c:url value="/board/${board.boardId}"/>'>${board.title}</a>
</td>
<td class="pc"><fmt:formatDate value="${board.writeDate}" pattern="YYYY-MM-dd"/></td>
<td class="pc">${board.readCount}</td>
</tr>
</c:forEach>
</table>
<table class="table">
<tr>
<td align="left">
<jk:search-paging totalPageCount="${totalPageCount}" nowPage="${page}" keyword="${keyword}"/>
</td>
<td align="right">
</td>
</tr>
</table>
</div>
</div>
<jsp:include page="/WEB-INF/views/include/footer.jsp"/>
</body>
</html>
update.jsp
<%@ page contentType="text/html; charset=utf-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<fmt:setBundle basename="i18n/board"/>
<!DOCTYPE html>
<html>
<jsp:include page="/WEB-INF/views/include/staticFiles.jsp"/>
<body>
<jsp:include page="/WEB-INF/views/include/bodyHeader.jsp"/>
<div class="container">
<div class="pg-opt">
<div class="row">
<div class="col-md-6 pc">
<h2><fmt:message key="UPDATE_ARTICLE"/></h2>
</div>
<div class="col-md-6">
<ol class="breadcrumb">
<li><fmt:message key="BOARD"/></li>
<li class="active"><fmt:message key="UPDATE_ARTICLE"/></li>
</ol>
</div>
</div>
</div>
<div class="content">
<form action="<c:url value='/board/update'/>" method="post" enctype="multipart/form-data" class="form-horizontal">
<c:if test="${!empty categoryList}">
<div class="form-group">
<label class="control-label col-sm-2" for="categoryId"><fmt:message key="CATEGORY"/></label>
<div class="col-sm-4">
<select name="categoryId" id="categoryId" class="form-control" required>
<c:forEach var="category" items="${categoryList}">
<option value="${category.categoryId}" ${category.categoryId eq board.categoryId ? "selected" : ""}>${category.categoryName}</option>
</c:forEach>
</select>
</div>
</div>
</c:if>
<div class="form-group">
<label class="control-label col-sm-2" for="writer"><fmt:message key="WRITER"/></label>
<div class="col-sm-2">
<input type="text" name="writer" id="writer" class="form-control" value="${board.writer}" readonly>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="email"><fmt:message key="EMAIL"/></label>
<div class="col-sm-4">
<input type="text" name="email" id="email" class="form-control" value="${board.email}" autocomplete="off" required readonly>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="password"><fmt:message key="PASSWORD"/></label>
<div class="col-sm-2">
<input type="password" name="password" id="password" class="form-control" required>
</div>${passwordError}
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="title"><fmt:message key="TITLE"/></label>
<div class="col-sm-8">
<input type="text" name="title" id="title" class="form-control" value="${board.title}" required>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="content"><fmt:message key="CONTENT"/></label>
<div class="col-sm-8">
<textarea name="content" id="content" rows="15" cols="100" class="form-control">${board.content}</textarea>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="file"><fmt:message key="FILE"/></label>
<div class="col-sm-8">
<input type="hidden" name="fileId" value="${board.fileId}">
<input type="file" id="file" name="file">${board.fileName}
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-8">
<input type="hidden" name="boardId" value="${board.boardId}">
<input type="hidden" name="masterId" value="${board.masterId}">
<input type="hidden" name="replyNumber" value="${board.replyNumber}">
<input type="hidden" name="replyStep" value="${board.replyStep}">
<input type="submit" class="btn btn-info" value="<fmt:message key='UPDATE'/>">
<input type="reset" class="btn btn-info" value="<fmt:message key='CANCEL'/>">
</div>
</div>
</form>
</div>
</div>
<jsp:include page="/WEB-INF/views/include/footer.jsp"/>
</body>
</html>
view.jsp
<%@ page contentType="text/html; charset=utf-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<fmt:setBundle basename="i18n/board"/>
<!DOCTYPE html>
<html>
<jsp:include page="/WEB-INF/views/include/staticFiles.jsp"/>
<body>
<jsp:include page="/WEB-INF/views/include/bodyHeader.jsp"/>
<div class="container">
<div class="pg-opt">
<div class="row">
<div class="col-md-6 pc">
<h2><fmt:message key="CONTENT"/></h2>
</div>
<div class="col-md-6">
<ol class="breadcrumb">
<li><fmt:message key="BOARD"/></li>
<li class="active"><fmt:message key="CONTENT"/></li>
</ol>
</div>
</div>
</div>
<div class="content">
<table class="table table-bordered">
<tr class="pc">
<td colspan=2 align="right">
<a href='<c:url value="/board/cat/${categoryId}/${page}"/>'><button type="button" class="btn btn-info"><fmt:message key="BOARD_LIST"/></button></a>
<a href='<c:url value="/board/write/${categoryId}"/>'><button type="button" class="btn btn-info"><fmt:message key="WRITE_NEW_ARTICLE"/></button></a>
<a href='<c:url value="/board/reply/${board.boardId}"/>'><button type="button" class="btn btn-info"><fmt:message key="REPLY"/></button></a>
<a href='<c:url value="/board/update/${board.boardId}"/>'><button type="button" class="btn btn-info"><fmt:message key="UPDATE"/></button></a>
<a href='<c:url value="/board/delete/${board.boardId}"/>'><button type="button" class="btn btn-info"><fmt:message key="DELETE"/></button></a>
</td>
</tr>
<tr>
<td width="20%"><fmt:message key="BOARD_ID"/></td>
<td>${board.boardId}</td>
</tr>
<tr>
<td width="20%"><fmt:message key="WRITER"/></td>
<td>${board.writer}</td>
</tr>
<tr>
<td width="20%"><fmt:message key="WRITE_DATE"/></td>
<td><fmt:formatDate value="${board.writeDate}" pattern="YYYY-MM-dd HH:mm:ss"/></td>
</tr>
<tr>
<td><fmt:message key="SUBJECT"/> </td>
<td>${board.title}</td>
</tr>
<tr>
<td><fmt:message key="CONTENT"/></td>
<td class="board_content">${board.content}</td>
</tr>
<c:if test="${!empty board.fileName}">
<tr>
<td><fmt:message key="FILE"/></td>
<td>
<c:set var="len" value="${fn:length(board.fileName)}"/>
<c:set var="filetype" value="${fn:toUpperCase(fn:substring(board.fileName, len-4, len))}"/>
<c:if test="${(filetype eq '.JPG') or (filetype eq 'JPEG') or (filetype eq '.PNG') or (filetype eq '.GIF')}"><img src='<c:url value="/file/${board.fileId}"/>' class="img-thumbnail"><br></c:if>
<a href='<c:url value="/file/${board.fileId}"/>'>${board.fileName} (<fmt:formatNumber>${board.fileSize}</fmt:formatNumber>byte)</a>
</td>
</tr>
</c:if>
<tr>
<td colspan=2 align="right">
<a href='<c:url value="/board/cat/${categoryId}/${page}"/>'><button type="button" class="btn btn-info"><fmt:message key="BOARD_LIST"/></button></a>
<a href='<c:url value="/board/write/${categoryId}"/>'><button type="button" class="btn btn-info"><fmt:message key="WRITE_NEW_ARTICLE"/></button></a>
<a href='<c:url value="/board/reply/${board.boardId}"/>'><button type="button" class="btn btn-info"><fmt:message key="REPLY"/></button></a>
<a href='<c:url value="/board/update/${board.boardId}"/>'><button type="button" class="btn btn-info"><fmt:message key="UPDATE"/></button></a>
<a href='<c:url value="/board/delete/${board.boardId}"/>'><button type="button" class="btn btn-info"><fmt:message key="DELETE"/></button></a>
</td>
</tr>
</table>
</div>
</div>
<jsp:include page="/WEB-INF/views/include/footer.jsp"/>
</body>
</html>
write.jsp
<%@ page contentType="text/html; charset=utf-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<fmt:setBundle basename="i18n/board"/>
<!DOCTYPE html>
<html>
<jsp:include page="/WEB-INF/views/include/staticFiles.jsp"/>
<body>
<jsp:include page="/WEB-INF/views/include/bodyHeader.jsp"/>
<div class="container">
<div class="pg-opt">
<div class="row">
<div class="col-md-6 pc">
<h2><fmt:message key="WRITE_NEW_ARTICLE"/></h2>
</div>
<div class="col-md-6">
<ol class="breadcrumb">
<li><fmt:message key="BOARD"/></li>
<li class="active"><fmt:message key="WRITE_NEW_ARTICLE"/></li>
</ol>
</div>
</div>
</div>
<div class="content">
<form action="<c:url value='/board/write'/>" method="post" enctype="multipart/form-data" class="form-horizontal">
<input type="hidden" name="csrfToken" value="${sessionScope.csrfToken}">
<c:if test="${!empty categoryList}">
<div class="form-group">
<label class="control-label col-sm-2" for="categoryId"><fmt:message key="CATEGORY"/></label>
<div class="col-sm-4">
<select name="categoryId" id="categoryId" class="form-control" required>
<c:forEach var="category" items="${categoryList}">
<option value="${category.categoryId}" ${category.categoryId eq requestScope.categoryId ? "selected" : ""}>${category.categoryName}</option>
</c:forEach>
</select>
</div>
</div>
</c:if>
<div class="form-group">
<label class="control-label col-sm-2" for="writer"><fmt:message key="WRITER"/></label>
<div class="col-sm-2">
<input type="text" name="writer" id="writer" class="form-control" value="${sessionScope.name}" ${!empty sessionScope.name ? "readonly" : ""} autocomplete="off" required>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="email"><fmt:message key="EMAIL"/></label>
<div class="col-sm-4">
<input type="text" name="email" id="email" class="form-control" value="${sessionScope.email}" ${!empty sessionScope.email ? "readonly" : ""} autocomplete="off" required>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="password"><fmt:message key="PASSWORD"/></label>
<div class="col-sm-2">
<input type="password" name="password" id="password" class="form-control" required>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="title"><fmt:message key="SUBJECT"/></label>
<div class="col-sm-8">
<input type="text" name="title" id="title" class="form-control" required>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="content"><fmt:message key="CONTENT"/></label>
<div class="col-sm-8">
<textarea name="content" id="content" rows="10" cols="100" class="form-control"></textarea>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="file"><fmt:message key="FILE"/></label>
<div class="col-sm-8">
<input type="file" id="file" name="file"><span id="droparea" class="help-block"><fmt:message key="FILESIZE_ERROR"/></span>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-8">
<input type="hidden" name="boardId" value="${board.boardId != null ? board.boardId : 0}">
<input type="hidden" name="masterId" value="${board.masterId != null ? board.masterId : 0}" />
<input type="hidden" name="replyNumber" value="${board.replyNumber != null ? board.replyNumber : 0}" />
<input type="hidden" name="replyStep" value="${board.replyStep != null ? board.replyStep : 0}" />
<input type="submit" class="btn btn-info" value="<fmt:message key="SAVE"/>">
<input type="reset" class="btn btn-info" value="<fmt:message key="CANCEL"/>">
</div>
</div>
</form>
</div>
</div>
<jsp:include page="/WEB-INF/views/include/footer.jsp"/>
</body>
</html>
error
runtime.jsp
<%@ page contentType="text/html; charset=utf-8" isErrorPage="true" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%
response.setStatus(200);
%>
<!DOCTYPE html>
<html>
<jsp:include page="/WEB-INF/views/include/staticFiles.jsp" />
<body>
<jsp:include page="/WEB-INF/views/include/bodyHeader.jsp" />
<div class="container">
<div class="content">
<div class="jumbotron">
<h2 style="color:red;">${exception.message}</h2>
<p>
<!--
Failed URL: ${url}
Exception: ${exception.message}
<c:forEach items="${exception.stackTrace}" var="ste">
${ste}
</c:forEach>
-->
</p>
<p><a class="btn btn-primary" href="<c:url value='/' />">Home</a></p>
</div>
</div>
</div>
<jsp:include page="/WEB-INF/views/include/footer.jsp" />
</body>
</html>
include
bodyHeader.jsp
<%@page import="com.example.myapp.board.service.BoardCategoryService"%>
<%@page import="java.util.List"%>
<%@page import="com.example.myapp.board.model.BoardCategory"%>
<%@page import="com.example.myapp.board.service.IBoardCategoryService"%>
<%@page import="org.springframework.web.context.support.WebApplicationContextUtils"%>
<%@page import="org.springframework.web.context.WebApplicationContext"%>
<%@page contentType="text/html; charset=UTF-8" trimDirectiveWhitespaces="true" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<fmt:setBundle basename="i18n/header" />
<!-- HEADER -->
<div class="container">
<div id="divHeaderWrapper">
<header class="header-standard-2">
<!-- MAIN NAV -->
<div class="navbar navbar-wp navbar-arrow mega-nav" role="navigation">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<i class="fa fa-bars icon-custom"></i>
</button>
<a class="navbar-brand" href="<c:url value="/"/>" title="">
<fmt:message key="TITLE"/>
</a><div style="padding-left:15px; width:200px">http://www.happy.co.kr</div>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav navbar-right">
<li class="hidden-md hidden-lg">
<div class="bg-light-gray">
<form class="form-horizontal form-light p-15" role="form">
<div class="input-group input-group-lg">
<input type="text" class="form-control" placeholder="I want to find ...">
<span class="input-group-btn">
<button class="btn btn-white" type="button">
<i class="fa fa-search"></i>
</button>
</span>
</div>
</form>
</div>
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><strong><fmt:message key="BOARD"/></strong></a>
<ul class="dropdown-menu">
<li><a href="<c:url value='/board/cat/1'/>">게시판</a>
<li><a href="<c:url value='/board/cat/2'/>">자료실</a>
<li><a href="<c:url value='/board/cat/3'/>">겔러리</a>
</ul>
</li>
<li class="dropdown">
<a href='<c:url value="/member/login"/>' class="dropdown-toggle" data-toggle="dropdown"><strong><fmt:message key="MEMBER"/></strong></a>
<ul class="dropdown-menu">
<li><a href="<c:url value='/member/login'/>"><fmt:message key="MY_INFO"/></a>
<li><a href="<c:url value='/member/update'/>"><fmt:message key="UPDATE_USER_INFO"/></a>
<li><a href="<c:url value='/member/delete'/>"><fmt:message key="EXIT_MEMBER"/></a>
<li><a href="<c:url value='/member/logout'/>"><fmt:message key="SIGN_OUT"/></a>
<li role="separator" class="divider"></li>
<li><a href="<c:url value='/member/insert'/>"><fmt:message key="JOIN_MEMBER"/></a>
</ul>
</li>
<li class="dropdown dropdown-aux animate-click" data-animate-in="animated" data-animate-out="animated fadeOutDown" style="z-index:500;">
<a href="#" class="dropdown-form-toggle" data-toggle="dropdown"><i class="fa fa-search"></i></a>
<ul class="dropdown-menu dropdown-menu-user">
<li id="dropdownForm">
<div class="dropdown-form">
<form class="form-horizontal form-light p-15" action="<c:url value='/board/search'/>" method="post" role="form">
<div class="input-group">
<input type="text" class="form-control" name="keyword" placeholder="키워드를 입력하세요.">
<span class="input-group-btn">
<input type="submit" class="btn btn-base" value="Go">
</span>
</div>
</form>
</div>
</li>
</ul>
</li>
<li><div>
<c:if test="${empty email}">
<br><a href="<c:url value="/member/login"/>" class="btn btn-danger"><fmt:message key="SIGN_IN"/></a>
</c:if>
<c:if test="${!empty email}">
<br><a href="<c:url value='/member/login'/>" class="btn btn-danger"><fmt:message key="MY_INFO"/></a>
</c:if>
</div>
</li>
</ul>
</div><!--/.nav-collapse -->
</div>
</header>
</div>
</div>
footer.jsp
<%@ page contentType="text/html; charset=utf-8" trimDirectiveWhitespaces="true"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!-- FOOTER -->
<footer class="footer">
<div class="container">
<div class="row">
<div class="col-md-3">
<div class="col">
<h4>Contact us</h4>
<ul>
<li>Phone: 010 1234 5678</li>
<li>Email: <a href="mailto:xxx@yyy.com" title="Email Us">xxx@yyy.com</a></li>
<li><a href="http://www.yourhost.com">http://www.yourhost.com</a></li>
</ul>
</div>
</div>
<div class="col-md-3">
<div class="col">
<h4>Mailing list</h4>
<p>Sign up if you would like to receive</p>
<form action='#' method="post" class="form-horizontal form-light">
<div class="input-group">
<input type="email" name="email" class="form-control" placeholder="Your email address..." required>
<span class="input-group-btn">
<input type="submit" class="btn btn-base" value="GO!">
</span>
</div>
</form>
</div>
</div>
<div class="col-md-3">
<div class="col col-social-icons">
<h4>Follow us</h4>
<a href="#"><i class="fa fa-facebook"></i></a>
<a href="#"><i class="fa fa-google-plus"></i></a>
<a href="#"><i class="fa fa-linkedin"></i></a>
<a href="#"><i class="fa fa-twitter"></i></a>
</div>
</div>
<div class="col-md-3">
<div class="col">
<h4>About us</h4>
<p class="no-margin">
Java developer please
<a href="<c:url value="/"/>" class="btn btn-block btn-base btn-icon fa-check"><span>Try it now</span></a>
</p>
</div>
</div>
</div>
</div>
</footer>
staticFiles.jsp
<%@ page contentType="text/html; charset=utf-8" trimDirectiveWhitespaces="true" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<fmt:setBundle basename="i18n/header" />
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="robots" content="index, follow">
<title><fmt:message key="TITLE"/></title>
<link href="<c:url value='/favicon.png'/>" rel="icon" type="image/png">
<link rel="stylesheet" href="<c:url value='/css/default.css'/>">
<link rel="stylesheet" href="<c:url value='/css/bootstrap.css'/>">
<link rel="stylesheet" href="<c:url value='/css/global-style.css'/>" media="screen">
<script src="<c:url value='/js/jquery-3.6.3.js'/>"></script>
<script src="<c:url value='/js/bootstrap.js'/>"></script>
</head>
member
delete.jsp
<%@ page contentType="text/html; charset=utf-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<fmt:setBundle basename="i18n/member"/>
<!DOCTYPE html>
<html>
<jsp:include page="/WEB-INF/views/include/staticFiles.jsp"/>
<body>
<jsp:include page="/WEB-INF/views/include/bodyHeader.jsp"/>
<div class="container">
<div class="pg-opt">
<div class="row">
<div class="col-md-6 pc">
<h2><fmt:message key="EXIT_MEMBER"/></h2>
</div>
<div class="col-md-6">
<ol class="breadcrumb">
<li><fmt:message key="MEMBER"/></li>
<li class="active"><fmt:message key="EXIT_MEMBER"/></li>
</ol>
</div>
</div>
</div>
<div class="content">
<form action="<c:url value='/member/delete'/>" method="post" class="form-horizontal">
<div class="form-group">
<label class="control-label col-sm-2" for="password"><fmt:message key="MEMBER_PW"/></label>
<div class="col-sm-4">
<input type="password" name="password" id="password" class="form-control">
<h4 style="color:red;"><fmt:message key="${message}"/></h4>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-8">
<input type="submit" class="btn btn-info" value="<fmt:message key='DELETE_USER_INFO'/>">
</div>
</div>
</form>
</div>
</div>
<jsp:include page="/WEB-INF/views/include/footer.jsp"/>
</body>
</html>
form.jsp
<%@ page contentType="text/html; charset=utf-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<fmt:setBundle basename="i18n/member"/>
<!DOCTYPE html>
<html>
<jsp:include page="/WEB-INF/views/include/staticFiles.jsp"/>
<body>
<jsp:include page="/WEB-INF/views/include/bodyHeader.jsp"/>
<div class="container">
<div class="pg-opt">
<div class="row">
<div class="col-md-6 pc">
<h2><fmt:message key="INSERT_USER_INFO"/></h2>${message}
</div>
<div class="col-md-6">
<ol class="breadcrumb">
<li><fmt:message key="MEMBER"/></li>
<li class="active"><fmt:message key="INSERT_USER_INFO"/></li>
</ol>
</div>
</div>
</div>
<div class="content">
<form action="<c:url value='/member/insert'/>" method="post" id="joinForm" class="form-horizontal">
<input type="hidden" name="csrfToken" value="${sessionScope.csrfToken}">
<div class="form-group">
<label class="control-label col-sm-2" for="userid"><fmt:message key="MEMBER_ID"/></label>
<div class="col-sm-4">
<input type="text" name="userid" id="userid" value="${member['userid']}" ${empty member.userid ? "" : "readonly"} title="<fmt:message key='USERID_TITLE'/>" pattern="\w+" class="form-control" placeholder="<fmt:message key="MEMBER_ID"/>" required>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="password"><fmt:message key="MEMBER_PW"/></label>
<div class="col-sm-4">
<input type="password" name="password" id="password" value="${member.password}" class="form-control" title="<fmt:message key='PASSWORD_TITLE'/>" pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{6,}" required>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="password2"><fmt:message key="MEMBER_PW_RE"/></label>
<div class="col-sm-4">
<input type="password" name="password2" id="password2" class="form-control" required>
<span id="passwordConfirm"></span>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="name"><fmt:message key="MEMBER_NAME"/></label>
<div class="col-sm-4">
<input type="text" name="name" id="name" value="${member.name}" class="form-control" autocomplete="off" required>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="phone"><fmt:message key="MEMBER_PHONE"/></label>
<div class="col-sm-6">
<input type="text" name="phone" id="phone" value="${member.phone}" class="form-control" autocomplete="off" required>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="email"><fmt:message key="MEMBER_EMAIL"/></label>
<div class="col-sm-8">
<input type="email" name="email" id="email" value="${member.email}" class="form-control" autocomplete="off" required>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-8">
<input type="submit" class="btn btn-info" value="<fmt:message key="SAVE"/>">
<input type="reset" class="btn btn-info" value="<fmt:message key="CANCEL"/>">
</div>
</div>
</form>
</div>
</div>
<jsp:include page="/WEB-INF/views/include/footer.jsp"/>
</body>
<script type="text/javascript">
var pw1 = document.querySelector("#password");
var pw2 = document.querySelector("#password2");
var pwConfirm = document.querySelector("#passwordConfirm");
pw2.onkeyup = function(event) {
if (pw1.value !== pw2.value) {
pwConfirm.innerText = "비밀번호가 일치하지 않습니다.";
} else {
pwConfirm.innerText = "";
}
}
</script>
</html>
login.jsp
<%@ page contentType="text/html; charset=utf-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<fmt:setBundle basename="i18n/member"/>
<!DOCTYPE html>
<html>
<jsp:include page="/WEB-INF/views/include/staticFiles.jsp"/>
<body>
<jsp:include page="/WEB-INF/views/include/bodyHeader.jsp"/>
<div class="container">
<div class="pg-opt">
<div class="row">
<div class="col-md-6 pc">
<h2><fmt:message key="LOGIN"/><small style="color:red"><fmt:message key="${not empty message ? message : 'BLANK'}"/></small></h2>
</div>
<div class="col-md-6">
<ol class="breadcrumb">
<li><fmt:message key="MEMBER"/></li>
<li class="active"><fmt:message key="LOGIN"/></li>
</ol>
</div>
</div>
</div>
<div class="content">
<c:if test="${empty sessionScope.userid}">
<form action="<c:url value='/member/login'/>" method="post" class="form-horizontal">
<div class="form-group">
<label class="control-label col-sm-2" for="id"><fmt:message key="MEMBER_ID"/></label>
<div class="col-sm-8">
<input type="text" name="userid" id="id" class="form-control" placeholder="<fmt:message key='MEMBER_ID'/>">
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="pw"><fmt:message key="MEMBER_PW"/></label>
<div class="col-sm-8">
<input type="password" name="password" id="pw" class="form-control" placeholder="<fmt:message key='MEMBER_PW'/>">
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-8">
<input type="submit" class="btn btn-info" value="<fmt:message key='SIGN_IN'/>">
<input type="reset" class="btn btn-info" value="<fmt:message key='CANCEL'/>">
<a href="<c:url value='/member/insert'/>" class="btn btn-success"><fmt:message key="INSERT_USER_INFO"/></a>
</div>
</div>
</form>
</c:if>
<c:if test="${not empty sessionScope.userid}">
<h4>${userid}</h4>
<h4>${email}</h4>
<a href="<c:url value='/member/update'/>">[<fmt:message key="UPDATE_USER_INFO"/>]</a>
<a href="<c:url value='/member/logout'/>">[<fmt:message key="SIGN_OUT"/>]</a>
<a href="<c:url value='/member/delete'/>">[<fmt:message key="EXIT_MEMBER"/>]</a>
</c:if>
</div>
</div>
<jsp:include page="/WEB-INF/views/include/footer.jsp"/>
</body>
</html>
update.jsp
<%@ page contentType="text/html; charset=utf-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<fmt:setBundle basename="i18n/member" />
<!DOCTYPE html>
<html>
<jsp:include page="/WEB-INF/views/include/staticFiles.jsp" />
<body>
<jsp:include page="/WEB-INF/views/include/bodyHeader.jsp" />
<div class="container">
<div class="pg-opt">
<div class="row">
<div class="col-md-6 pc">
<h2>
<fmt:message key="UPDATE_USER_INFO" />
<small><fmt:message key="${message}" /></small>
</h2>
</div>
<div class="col-md-6">
<ol class="breadcrumb">
<li><fmt:message key="MEMBER" /></li>
<li class="active"><fmt:message key="UPDATE_USER_INFO" /></li>
</ol>
</div>
</div>
</div>
<div class="content">
<form action="<c:url value='/member/update' />" method="post" id="joinForm" class="form-horizontal">
<div class="form-group">
<label class="control-label col-sm-2" for="userid">
<fmt:message key="MEMBER_ID" />
</label>
<div class="col-sm-4">
<input type="text" name="userid" id="userid" value="${member['userid']}" ${empty member.userid ? "" : "readonly"} class="form-control" placeholder="<fmt:message key='MEMBER_ID' />" required>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="password">
<fmt:message key="MEMBER_PW" />
</label>
<div class="col-sm-4">
<input type="password" name="password" id="password" class="form-control" title="<fmt:message key='PASSWORD_TITLE' />" pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{6,}" required>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="password2">
<fmt:message key="MEMBER_PW_RE" />
</label>
<div class="col-sm-4">
<input type="password" name="password2" id="password2" class="form-control" required>
<span id="passwordConfirm"></span>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="name">
<fmt:message key="MEMBER_NAME" />
</label>
<div class="col-sm-4">
<input type="text" name="name" id="name" value="${member.name}" class="form-control" required>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="phone">
<fmt:message key="MEMBER_PHONE" />
</label>
<div class="col-sm-6">
<input type="text" name="phone" id="phone" value="${member.phone}" class="form-control" required>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="email">
<fmt:message key="MEMBER_EMAIL" />
</label>
<div class="col-sm-8">
<input type="text" name="email" id="email" value="${member.email}" class="form-control" required>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-8">
<input type="submit" class="btn btn-info" value="<fmt:message key='SAVE' />">
<input type="reset" class="btn btn-info" value="<fmt:message key='CANCEL' />">
</div>
</div>
</form>
</div>
</div>
<jsp:include page="/WEB-INF/views/include/footer.jsp" />
</body>
<script type="text/javascript">
var pw1 = document.querySelector("#password");
var pw2 = document.querySelector("#password2");
var pwConfirm = document.querySelector("#passwordConfirm");
pw2.onkeyup = function(event) {
if (pw1.value !== pw2.value) {
pwConfirm.innerText = "비밀번호가 일치하지 않습니다.";
} else {
pwConfirm.innerText = "";
}
};
</script>
</html>
home
home.jsp
<%@ page contentType="text/html; charset=UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<fmt:setBundle basename="i18n/header"/>
<!DOCTYPE html>
<html>
<jsp:include page="/WEB-INF/views/include/staticFiles.jsp"/>
<body>
<jsp:include page="/WEB-INF/views/include/bodyHeader.jsp"/>
<div class="container">
<div class="pg-opt">
<div class="row">
<div class="col-md-6 pc">
<h2><fmt:message key="HOME"/></h2>
</div>
<div class="col-md-6">
<ol class="breadcrumb">
<li><fmt:message key="DASHBOARD"/></li>
<li class="active"><fmt:message key="HOME"/></li>
</ol>
</div>
</div>
</div>
<div class="content">
<div class="alert alert-warning page-header">
<h3><fmt:message key="WELCOME_MESSAGE"/></h3>
</div>
<div class="row">
<div class="col-xs-12 col-sm-6 col-md-4 col-lg-4">
<a href="board/cat/1">카테고리1 게시판</a><br>
<a href="board/cat/2">카테고리2 게시판</a><br>
<a href="board/cat/3">카테고리3 게시판</a><br>
</div>
<div class="col-xs-12 col-sm-6 col-md-4 col-lg-4">
Das ist nicht einfach!!!
</div>
<div class="col-xs-12 col-sm-6 col-md-4 col-lg-4">
hallo
</div>
<div class="col-xs-12 col-sm-6 col-md-4 col-lg-4">
hallo
</div>
</div>
<div class="progress">
<div class="progress-bar progress-bar-danger" role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" style="width: 100%;">
<span class="sr-only"></span>
</div>
</div>
<div class="progress">
<div class="progress-bar progress-bar-warning" role="progressbar" aria-valuenow="80" aria-valuemin="0" aria-valuemax="100" style="width: 80%;">
<span class="sr-only"></span>
</div>
</div>
<div class="alert alert-info">
<ol>
<li>welcome welcome</li>
</ol>
</div>
</div>
</div>
<jsp:include page="/WEB-INF/views/include/footer.jsp"/>
</body>
</html>
결과