[Spring] 게시판 intelliJ+SpringMVC+Maven+Tomcat+JPA+H2 DB 환경으로 만들어 보자 ↗

개발 환경
intelliJ IDEA (Ultimate)
SpringMVC 5.2.3.RELEASE
Maven
Tomcat 9
JPA 5.4.12.Final
H2 database 1.4.200
게시판을 구현한 2-Layered 아키텍처

- 톰캣 서버를 구동하면
- web.xml 파일을 로딩하여 Servlet Container 구동
- Servlet Container는 web.xml 파일에 등록된 ContextLoaderListener 객체를 생성(Pre Loading)한다.
- ContextLoaderListener 객체는 applicationContext.xml 파일을 로딩하여 Spring Container(ROOT)를 구동한다. 이때 ServiceImpl 클래스나 DAO 객체들이 메모리에 생성된다.
- 사용자가 ".do" 요청을 서버에 전달하면 Servlet Container는 DispatcherServlet 객체를 생성한다.
- DispatcherServlet 객체는 dispatcher-servlet.xml 파일을 로딩하여 두 번째 Spring Container(자식 컨테이너)를 구동한다.
- 두 번째 Spring Container가 Controller 객체를 메모리에 생성한다.
- Spring Container(ROOT)가 생성한 비즈니스 객체를 Controller에서 참조하여 사용할 수 있다.
파일별 개발 순서
- Maven으로 intelliJ Project 생성
- Spring MVC & JPA configuration 추가
- H2 DB 서버 구동
./h2.sh -webAllowOthers
- 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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | |
<modelVersion>4.0.0</modelVersion> | |
<groupId>com.dokylee</groupId> | |
<artifactId>day1_ex</artifactId> | |
<version>1.0-SNAPSHOT</version> | |
<dependencies> | |
<!-- JPA, Hibernate --> | |
<dependency> | |
<groupId>org.hibernate</groupId> | |
<artifactId>hibernate-entitymanager</artifactId> | |
<version>5.4.12.Final</version> | |
</dependency> | |
<!-- spring orm library --> | |
<dependency> | |
<groupId>org.springframework</groupId> | |
<artifactId>spring-orm</artifactId> | |
<version>5.2.3.RELEASE</version> | |
</dependency> | |
<!-- h2 database --> | |
<dependency> | |
<groupId>com.h2database</groupId> | |
<artifactId>h2</artifactId> | |
<version>1.4.200</version> | |
</dependency> | |
<!-- AspectJ --> | |
<dependency> | |
<groupId>org.aspectj</groupId> | |
<artifactId>aspectjrt</artifactId> | |
<version>1.9.5</version> | |
</dependency> | |
<dependency> | |
<groupId>org.aspectj</groupId> | |
<artifactId>aspectjweaver</artifactId> | |
<version>1.9.5</version> | |
</dependency> | |
<!-- DBCP --> | |
<dependency> | |
<groupId>commons-dbcp</groupId> | |
<artifactId>commons-dbcp</artifactId> | |
<version>1.4</version> | |
</dependency> | |
<dependency> | |
<groupId>javax.servlet</groupId> | |
<artifactId>javax.servlet-api</artifactId> | |
<version>3.1.0</version> | |
<scope>provided</scope> | |
</dependency> | |
<!-- FileUpload --> | |
<dependency> | |
<groupId>commons-fileupload</groupId> | |
<artifactId>commons-fileupload</artifactId> | |
<version>1.4</version> | |
</dependency> | |
<!-- JSTL --> | |
<!-- https://mvnrepository.com/artifact/javax.servlet.jsp.jstl/jstl-api --> | |
<dependency> | |
<groupId>javax.servlet.jsp.jstl</groupId> | |
<artifactId>jstl-api</artifactId> | |
<version>1.2</version> | |
</dependency> | |
<!-- https://mvnrepository.com/artifact/org.apache.taglibs/taglibs-standard-impl --> | |
<dependency> | |
<groupId>org.apache.taglibs</groupId> | |
<artifactId>taglibs-standard-impl</artifactId> | |
<version>1.2.5</version> | |
</dependency> | |
<!-- Jackson2 --> | |
<dependency> | |
<groupId>com.fasterxml.jackson.core</groupId> | |
<artifactId>jackson-databind</artifactId> | |
<version>2.10.2</version> | |
</dependency> | |
<!-- Mybatis --> | |
<dependency> | |
<groupId>org.mybatis</groupId> | |
<artifactId>mybatis</artifactId> | |
<version>3.5.4</version> | |
</dependency> | |
<!-- Mybatis Spring --> | |
<dependency> | |
<groupId>org.mybatis</groupId> | |
<artifactId>mybatis-spring</artifactId> | |
<version>2.0.1</version> | |
</dependency> | |
</dependencies> | |
</project> |
- src/main/java/com/springbook/biz/board/BoardVO.java
package com.springbook.biz.board; | |
import com.fasterxml.jackson.annotation.JsonIgnore; | |
import org.springframework.web.multipart.MultipartFile; | |
import javax.persistence.*; | |
import javax.xml.bind.annotation.XmlAccessType; | |
import javax.xml.bind.annotation.XmlAccessorType; | |
import javax.xml.bind.annotation.XmlAttribute; | |
import javax.xml.bind.annotation.XmlTransient; | |
import java.util.Date; | |
// VO(Value Object) | |
@Entity | |
@Table(name = "BOARD") | |
@XmlAccessorType(XmlAccessType.FIELD) | |
public class BoardVO { | |
@Id | |
@GeneratedValue | |
@XmlAttribute // seq를 속성으로 표현하라는 의미 | |
private int seq; | |
private String title; | |
private String writer; | |
private String content; | |
@Temporal(TemporalType.DATE) | |
private Date regDate = new Date(); | |
private int cnt; | |
@Transient | |
@XmlTransient | |
private String searchCondition; | |
@Transient | |
@XmlTransient | |
private String searchKeyword; | |
@Transient | |
@XmlTransient | |
private MultipartFile uploadFile; | |
@JsonIgnore | |
public MultipartFile getUploadFile() { | |
return uploadFile; | |
} | |
public void setUploadFile(MultipartFile uploadFile) { | |
this.uploadFile = uploadFile; | |
} | |
@JsonIgnore | |
public String getSearchCondition() { | |
return searchCondition; | |
} | |
public void setSearchCondition(String searchCondition) { | |
this.searchCondition = searchCondition; | |
} | |
@JsonIgnore | |
public String getSearchKeyword() { | |
return searchKeyword; | |
} | |
public void setSearchKeyword(String searchKeyword) { | |
this.searchKeyword = searchKeyword; | |
} | |
public int getSeq() { | |
return seq; | |
} | |
public void setSeq(int seq) { | |
this.seq = seq; | |
} | |
public String getTitle() { | |
return title; | |
} | |
public void setTitle(String title) { | |
this.title = title; | |
} | |
public String getWriter() { | |
return writer; | |
} | |
public void setWriter(String writer) { | |
this.writer = writer; | |
} | |
public String getContent() { | |
return content; | |
} | |
public void setContent(String content) { | |
this.content = content; | |
} | |
public Date getRegDate() { | |
return regDate; | |
} | |
public void setRegDate(Date regDate) { | |
this.regDate = regDate; | |
} | |
public int getCnt() { | |
return cnt; | |
} | |
public void setCnt(int cnt) { | |
this.cnt = cnt; | |
} | |
@Override | |
public String toString() { | |
return "BoardVO [ seq = "+seq+", title = "+title | |
+", writer = "+writer+", content = "+content | |
+", regDate = "+regDate+", cnt = "+cnt+" ]"; | |
} | |
} |
- src/main/java/com/springbook/biz/board/impl/BoardDAOJPA.java
package com.springbook.biz.board.impl; | |
import com.springbook.biz.board.BoardService; | |
import com.springbook.biz.board.BoardVO; | |
import com.springbook.biz.common.JDBCUtil; | |
import org.mybatis.spring.SqlSessionTemplate; | |
import org.springframework.beans.factory.annotation.Autowired; | |
import org.springframework.stereotype.Repository; | |
import javax.persistence.EntityManager; | |
import javax.persistence.PersistenceContext; | |
import java.sql.Connection; | |
import java.sql.PreparedStatement; | |
import java.sql.ResultSet; | |
import java.util.ArrayList; | |
import java.util.List; | |
// DAO(Data Access Object) | |
@Repository | |
public class BoardDAOJPA { | |
@PersistenceContext | |
private EntityManager em; | |
// CRUD 기능의 메소드 구현 | |
// 글 등록 | |
public void insertBoard(BoardVO vo) { | |
System.out.println("===> JPA로 insertBoard() 기능 처리"); | |
em.persist(vo); | |
} | |
// 글 수정 | |
public void updateBoard(BoardVO vo) { | |
System.out.println("===> JPA로 updateBoard() 기능 처리"); | |
em.merge(vo); | |
} | |
// 글 삭제 | |
public void deleteBoard(BoardVO vo) { | |
System.out.println("===> JPA로 deleteBoard() 기능 처리"); | |
em.remove(em.find(BoardVO.class, vo.getSeq())); | |
} | |
// 글 상세 조회 | |
public BoardVO getBoard(BoardVO vo) { | |
System.out.println("===> JPA로 getBoard() 기능 처리"); | |
return (BoardVO) em.find(BoardVO.class, vo.getSeq()); | |
} | |
// 글 목록 조회 | |
public List<BoardVO> getBoardList(BoardVO vo) { | |
System.out.println("===> JPA로 getBoardList() 기능 처리"); | |
return em.createQuery("from BoardVO b order by b.seq desc").getResultList(); | |
} | |
} |
- src/main/java/com/springbook/biz/board/BoardService.java (BoardDAOJPA.java로부터 생성 "Refactor->Extract->Interface")
package com.springbook.biz.board; | |
import java.util.List; | |
public interface BoardService { | |
// CRUD 기능의 메소드 구현 | |
// 글 등록 | |
void insertBoard(BoardVO vo); | |
// 글 수정 | |
void updateBoard(BoardVO vo); | |
// 글 삭제 | |
void deleteBoard(BoardVO vo); | |
// 글 상세 조회 | |
BoardVO getBoard(BoardVO vo); | |
List<BoardVO> getBoardList(BoardVO vo); | |
// List<BoardVO> getBoardList(); | |
} |
- src/main/java/com/springbook/biz/board/impl/BoardServiceImpl.java
package com.springbook.biz.board.impl; | |
import com.springbook.biz.board.BoardService; | |
import com.springbook.biz.board.BoardVO; | |
import org.springframework.beans.factory.annotation.Autowired; | |
import org.springframework.stereotype.Service; | |
import java.util.List; | |
@Service("boardService") | |
public class BoardServiceImpl implements BoardService { | |
@Autowired | |
private BoardDAOJPA boardDAO; | |
public void insertBoard(BoardVO vo) { | |
// System.out.println(vo.getSeq()); | |
// if(vo.getSeq()==0){ | |
// throw new IllegalArgumentException("0번 글 노노해"); | |
// } | |
boardDAO.insertBoard(vo); | |
} | |
public void updateBoard(BoardVO vo) { | |
boardDAO.updateBoard(vo); | |
} | |
public void deleteBoard(BoardVO vo) { | |
boardDAO.deleteBoard(vo); | |
} | |
public BoardVO getBoard(BoardVO vo) { | |
return boardDAO.getBoard(vo); | |
} | |
public List<BoardVO> getBoardList(BoardVO vo) { | |
return boardDAO.getBoardList(vo); | |
} | |
} |
- src/main/java/com/springbook/biz/user/UserVO.java - JPA로 수정 전
package com.springbook.biz.user; | |
// VO(Value Object) | |
public class UserVO { | |
private String id; | |
private String password; | |
private String name; | |
private String role; | |
public String getId() { | |
return id; | |
} | |
public void setId(String id) { | |
this.id = id; | |
} | |
public String getPassword() { | |
return password; | |
} | |
public void setPassword(String password) { | |
this.password = password; | |
} | |
public String getName() { | |
return name; | |
} | |
public void setName(String name) { | |
this.name = name; | |
} | |
public String getRole() { | |
return role; | |
} | |
public void setRole(String role) { | |
this.role = role; | |
} | |
@Override | |
public String toString() { | |
return "UserVO [ id = "+id+", password = "+password+", name = "+name+", role = "+role+" ]"; | |
} | |
} |
- src/main/java/com/springbook/biz/user/impl/UserDAO.java - JPA로 수정 전
package com.springbook.biz.user.impl; | |
import com.springbook.biz.common.JDBCUtil; | |
import com.springbook.biz.user.UserService; | |
import com.springbook.biz.user.UserVO; | |
import org.springframework.stereotype.Repository; | |
import java.sql.Connection; | |
import java.sql.PreparedStatement; | |
import java.sql.ResultSet; | |
//@Repository("userDAO") | |
public class UserDAO { | |
// JDBC 관련 변수 | |
private Connection conn = null; | |
private PreparedStatement stmt = null; | |
private ResultSet rs = null; | |
// SQL 명령어들 | |
private final String USER_GET = "select * from users where id=? and password=?"; | |
// CRUD 기능의 메소드 구현 | |
//회원 등록 | |
public UserVO getUser(UserVO vo) { | |
UserVO user = null; | |
try { | |
System.out.println("===> JDBC로 getUser() 기능 처리"); | |
conn = JDBCUtil.getConnection(); | |
stmt = conn.prepareStatement(USER_GET); | |
stmt.setString(1, vo.getId()); | |
stmt.setString(2, vo.getPassword()); | |
rs = stmt.executeQuery(); | |
if (rs.next()) { | |
user = new UserVO(); | |
user.setId(rs.getString("ID")); | |
user.setPassword(rs.getString("PASSWORD")); | |
user.setName(rs.getString("NAME")); | |
user.setRole(rs.getString("ROLE")); | |
} | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} finally { | |
JDBCUtil.close(stmt, conn); | |
} | |
return user; | |
} | |
} |
- src/main/java/com/springbook/biz/user/UserService.java (UserDAO.java 로부터 생성) - JPA로 수정 전
package com.springbook.biz.user; | |
public interface UserService { | |
// CRUD 기능의 메소드 구현 | |
//회원 등록 | |
UserVO getUser(UserVO vo); | |
} |
- src/main/java/com/springbook/biz/user/impl/UserServiceImpl.java - JPA로 수정 전
package com.springbook.biz.user.impl; | |
import com.springbook.biz.user.UserService; | |
import com.springbook.biz.user.UserVO; | |
import org.springframework.beans.factory.annotation.Autowired; | |
import org.springframework.stereotype.Service; | |
@Service("userService") | |
public class UserServiceImpl implements UserService { | |
@Autowired | |
private UserDAOSpring userDAO; | |
// public void setUserDAO(UserDAO userDAO) { | |
// this.userDAO = userDAO; | |
// } | |
public UserVO getUser(UserVO vo) { | |
return userDAO.getUser(vo); | |
} | |
} |
- src/main/resources/applicationContext.xml
<?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:context="http://www.springframework.org/schema/context" | |
xmlns:p="http://www.springframework.org/schema/p" | |
xmlns:aop="http://www.springframework.org/schema/aop" | |
xmlns:tx="http://www.springframework.org/schema/tx" | |
xsi:schemaLocation="http://www.springframework.org/schema/beans | |
http://www.springframework.org/schema/beans/spring-beans.xsd | |
http://www.springframework.org/schema/context | |
https://www.springframework.org/schema/context/spring-context.xsd | |
http://www.springframework.org/schema/aop | |
https://www.springframework.org/schema/aop/spring-aop.xsd | |
http://www.springframework.org/schema/tx | |
https://www.springframework.org/schema/tx/spring-tx.xsd"> | |
<context:component-scan base-package="com.springbook.biz"></context:component-scan> | |
<!-- AOP 어노테이션 알아먹게 하기 in 어드바이스 클래스--> | |
<aop:aspectj-autoproxy></aop:aspectj-autoproxy> | |
<!-- DataSource 설정 --> | |
<context:property-placeholder location="classpath:config/database.properties" /> | |
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> | |
<property name="driverClassName" value="${jdbc.driverClassName}" /> | |
<property name="url" value="${jdbc.url}" /> | |
<property name="username" value="${jdbc.username}" /> | |
<property name="password" value="${jdbc.password}" /> | |
</bean> | |
<!-- spring과 JPA 연동 설정 --> | |
<bean id="jpaVendorAdaptor" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"></bean> | |
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> | |
<property name="dataSource" ref="dataSource"></property> | |
<property name="jpaVendorAdapter" ref="jpaVendorAdaptor"></property> | |
<property name="packagesToScan" value="com.springbook.biz.board"></property> | |
<property name="jpaProperties"> | |
<props> | |
<prop key="hibernate.dialect">org.hibernate.dialect.H2Dialect</prop> | |
<prop key="hibernate.show_sql">true</prop> | |
<prop key="hibernate.format_sql">true</prop> | |
<prop key="hibernate.use_sql_comments">true</prop> | |
<prop key="hibernate.id.new_generator_mappings">true</prop> | |
<prop key="hibernate.hbm2ddl.auto">update</prop> | |
</props> | |
</property> | |
</bean> | |
<!-- Transaction 설정 --> | |
<bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager"> | |
<property name="entityManagerFactory" ref="entityManagerFactory" /> | |
</bean> | |
<tx:advice id="txAdvice" transaction-manager="txManager"> | |
<tx:attributes> | |
<tx:method name="get*" read-only="true" /> | |
<tx:method name="*" /> | |
</tx:attributes> | |
</tx:advice> | |
<!-- Spring JDBC 설정 --> | |
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> | |
<property name="dataSource" ref="dataSource" /> | |
</bean> | |
<aop:config> | |
<aop:pointcut id="allPointcut" expression="execution(* com.springbook.biz..*Impl.*(..))" /> | |
<aop:advisor pointcut-ref="allPointcut" advice-ref="txAdvice" /> | |
</aop:config> | |
<!-- Spring과 Mybatis 연동 설정 --> | |
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionFactoryBean"> | |
<property name="configLocation" value="classpath:sql-map-config.xml"></property> | |
<property name="dataSource" ref="dataSource"></property> | |
</bean> | |
<bean class="org.mybatis.spring.SqlSessionTemplate"> | |
<constructor-arg ref="sqlSession"></constructor-arg> | |
</bean> | |
</beans> |
- 설정
Build > Build Artifacts > war exploded > Edit > Output directory > [view(jsp) 파일들의 루트 폴더]로 설정
- web/WEB-INF/dispatcher-servlet.xml
<?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:context="http://www.springframework.org/schema/context" | |
xmlns:mvc="http://www.springframework.org/schema/mvc" | |
xsi:schemaLocation="http://www.springframework.org/schema/beans | |
http://www.springframework.org/schema/beans/spring-beans.xsd | |
http://www.springframework.org/schema/context | |
https://www.springframework.org/schema/context/spring-context.xsd | |
http://www.springframework.org/schema/mvc | |
https://www.springframework.org/schema/mvc/spring-mvc.xsd"> | |
<context:component-scan base-package="com.springbook.view"></context:component-scan> | |
<!-- http 응답 body 변환 --> | |
<mvc:annotation-driven></mvc:annotation-driven> | |
<!-- 어노테이션 기반의 예외 일괄 처리 @ExceptionHandler를 class에서 쓰기 위한 설정 --> | |
<!-- <mvc:annotation-driven></mvc:annotation-driven>--> | |
<!-- 파일 업로드 설정 --> | |
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> | |
<property name="maxUploadSize" value="100000" /> | |
</bean> | |
<!-- 예외 처리 설정 --> | |
<bean id="exceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> | |
<property name="exceptionMappings"> | |
<props> | |
<prop key="java.lang.ArithmeticException"> | |
common/arithmeticError.jsp | |
</prop> | |
<prop key="java.lang.NullPointerException"> | |
common/nullPointerError.jsp | |
</prop> | |
</props> | |
</property> | |
<property name="defaultErrorView" value="common/error.jsp" /> | |
</bean> | |
<!-- 다국어 설정 --> | |
<!-- MessageSource 등록 --> | |
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> | |
<property name="basenames"> | |
<list> | |
<value>message.messageSource</value> | |
</list> | |
</property> | |
</bean> | |
<!-- LocaleResolver 등록 --> | |
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver"></bean> | |
<!-- LocaleChangeInterceptor 등록 --> | |
<mvc:interceptors> | |
<bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"> | |
<property name="paramName" value="lang" /> | |
</bean> | |
</mvc:interceptors> | |
</beans> |
- web/WEB-INF/web.xml
<?xml version="1.0" encoding="UTF-8"?> | |
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" | |
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" | |
version="4.0"> | |
<filter> | |
<filter-name>characterEncoding</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>characterEncoding</filter-name> | |
<url-pattern>*.do</url-pattern> | |
</filter-mapping> | |
<context-param> | |
<param-name>contextConfigLocation</param-name> | |
<param-value>classpath:applicationContext.xml</param-value> | |
</context-param> | |
<listener> | |
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> | |
</listener> | |
<servlet> | |
<servlet-name>dispatcher</servlet-name> | |
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> | |
<load-on-startup>1</load-on-startup> | |
</servlet> | |
<servlet-mapping> | |
<servlet-name>dispatcher</servlet-name> | |
<url-pattern>*.do</url-pattern> | |
</servlet-mapping> | |
</web-app> |
- 다국어 처리 파일들 -> dispatcher-servlet.xml에 설정
- src/main/resources/message/messageSource_en.properties
# login.jsp | |
message.user.login.title=LOGIN | |
message.user.login.id=ID | |
message.user.login.password=PASSWORD | |
message.user.login.loginBtn=LOG-IN | |
message.user.login.language.en=English | |
message.user.login.language.ko=Korean | |
# getBoardList.jsp | |
message.board.list.mainTitle=BOARD LIST | |
message.board.list.welcomeMsg=! Welcome to my BOARD | |
message.board.list.search.condition.title=TITLE | |
message.board.list.search.condition.content=CONTENT | |
message.board.list.search.condition.btn=Search | |
message.board.list.table.head.seq=SEQ | |
message.board.list.table.head.title=TITLE | |
message.board.list.table.head.writer=WRITER | |
message.board.list.table.head.regDate=REGDATE | |
message.board.list.table.head.cnt=CNT | |
message.board.list.link.insertBoard=Insert Board |
- src/main/resources/message/messageSource_ko_KR.properties
# login.jsp | |
message.user.login.title=로그인 | |
message.user.login.id=아이디 | |
message.user.login.password=비밀번호 | |
message.user.login.loginBtn=로그인 | |
message.user.login.language.en=영어 | |
message.user.login.language.ko=한글 | |
# getBoardList.jsp | |
message.board.list.mainTitle=게시글 목록 | |
message.board.list.welcomeMsg=님! 게시판에 오신 걸 환영합니다. | |
message.board.list.search.condition.title=제목 | |
message.board.list.search.condition.content=내용 | |
message.board.list.search.condition.btn=검색 | |
message.board.list.table.head.seq=번호 | |
message.board.list.table.head.title=제목 | |
message.board.list.table.head.writer=작성자 | |
message.board.list.table.head.regDate=등록일 | |
message.board.list.table.head.cnt=조회수 | |
message.board.list.link.insertBoard=새글 등록 |
- View 파일들
- index.jsp (Main Page)
<%-- | |
Created by IntelliJ IDEA. | |
User: dokylee | |
Date: 01/03/2020 | |
Time: 6:32 오후 | |
To change this template use File | Settings | File Templates. | |
--%> | |
<%@ page contentType="text/html;charset=UTF-8" language="java" %> | |
<html> | |
<head> | |
<title>Main Page</title> | |
</head> | |
<body> | |
<center> | |
<h1>게시판 프로그램</h1> | |
<hr> | |
<a href="login.do">로그인</a><br><br><br> | |
<a href="getBoardList.do">글 목록 바로가기</a><br><br><br> | |
<a href="dataTransform.do">글 목록 변환 처리</a><br> | |
<hr> | |
</center> | |
</body> | |
</html> |
- login.jsp
<%-- | |
Created by IntelliJ IDEA. | |
User: dokylee | |
Date: 28/02/2020 | |
Time: 12:10 오전 | |
To change this template use File | Settings | File Templates. | |
--%> | |
<%@ page contentType="text/html;charset=UTF-8" language="java" %> | |
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> | |
<html> | |
<head> | |
<title><spring:message code="message.user.login.title" /></title> | |
</head> | |
<body> | |
<center> | |
<h1><spring:message code="message.user.login.title" /></h1> | |
<a href="login.do?lang=en"> | |
<spring:message code="message.user.login.language.en" /> | |
</a> | |
<a href="login.do?lang=ko"> | |
<spring:message code="message.user.login.language.ko" /> | |
</a> | |
<hr> | |
<form action="login.do" method="post"> | |
<table border="1" cellpadding="0" cellspacing="0"> | |
<tr> | |
<td bgcolor="orange"><spring:message code="message.user.login.id" /></td> | |
<td><input type="text" name="id" value="${user.id}"/></td> | |
</tr> | |
<tr> | |
<td bgcolor="orange"><spring:message code="message.user.login.password" /></td> | |
<td><input type="password" name="password" value="${user.password}"/></td> | |
</tr> | |
<tr> | |
<td colspan="2" align="center"> | |
<input type="submit" value="<spring:message code="message.user.login.loginBtn" />" /> | |
</td> | |
</tr> | |
</table> | |
</form> | |
</center> | |
</body> | |
</html> |
- getBoardList.jsp
<%-- | |
Created by IntelliJ IDEA. | |
User: dokylee | |
Date: 28/02/2020 | |
Time: 12:49 오전 | |
To change this template use File | Settings | File Templates. | |
--%> | |
<%@ page contentType="text/html;charset=UTF-8" language="java" %> | |
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> | |
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> | |
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> | |
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> | |
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> | |
<html> | |
<head> | |
<title><spring:message code="message.board.list.mainTitle" /></title> | |
</head> | |
<body> | |
<center> | |
<h1><spring:message code="message.board.list.mainTitle" /></h1> | |
<h3>${userName}<spring:message code="message.board.list.welcomeMsg" /><a href="logout.do">Log-out</a></h3> | |
<!--검색 시작--> | |
<form action="getBoardList.do" method="post"> | |
<table border="1" cellpadding="0" cellspacing="0" width="700"> | |
<tr> | |
<td align="right"> | |
<select name="searchCondition"> | |
<c:forEach items="${conditionMap}" var="option"> | |
<option value="${option.value}">${option.key}</option> | |
</c:forEach> | |
</select> | |
<input name="searchKeyword" type="text" /> | |
<input type="submit" value="<spring:message code="message.board.list.search.condition.btn" />" /> | |
</td> | |
</tr> | |
</table> | |
</form> | |
<!--검색 종료--> | |
<table border="1" cellpadding="0" cellspacing="0" width="700"> | |
<tr> | |
<th bgcolor="orange" width="100"><spring:message code="message.board.list.table.head.seq" /></th> | |
<th bgcolor="orange" width="200"><spring:message code="message.board.list.table.head.title" /></th> | |
<th bgcolor="orange" width="150"><spring:message code="message.board.list.table.head.writer" /></th> | |
<th bgcolor="orange" width="150"><spring:message code="message.board.list.table.head.regDate" /></th> | |
<th bgcolor="orange" width="100"><spring:message code="message.board.list.table.head.cnt" /></th> | |
</tr> | |
<c:forEach items="${boardList}" var="board"> | |
<tr> | |
<td>${board.seq}</td> | |
<td align="left"><a href="getBoard.do?seq=${board.seq}">${board.title}</a></td> | |
<td>${board.writer}</td> | |
<td><fmt:formatDate value="${board.regDate}" pattern="yyyy-MM-dd" /></td> | |
<td>${board.cnt}</td> | |
</tr> | |
</c:forEach> | |
</table> | |
<br> | |
<a href="insertBoard.jsp"><spring:message code="message.board.list.link.insertBoard" /></a> | |
</center> | |
</body> | |
</html> |
- getBoard.jsp
<%--<%@ page import="com.springbook.biz.board.BoardVO" %>--%> | |
<%--<%@ page import="com.springbook.biz.board.impl.BoardDAO" %>--%> | |
<%-- | |
Created by IntelliJ IDEA. | |
User: dokylee | |
Date: 28/02/2020 | |
Time: 2:36 오전 | |
To change this template use File | Settings | File Templates. | |
--%> | |
<%@ page contentType="text/html;charset=UTF-8" language="java" %> | |
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> | |
<%--<%--%> | |
<%-- BoardVO board = (BoardVO) session.getAttribute("board");--%> | |
<%--%>--%> | |
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> | |
<html> | |
<head> | |
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" > | |
<title>글 상세</title> | |
</head> | |
<body> | |
<center> | |
<h1>글 상세</h1> | |
<a href="logout.do">Log-out</a> | |
<hr> | |
<form action="updateBoard.do" method="post"> | |
<input name="seq" type="hidden" value="${board.seq}"> | |
<table border="1" cellpadding="0" cellspacing="0"> | |
<tr> | |
<td bgcolor="orange" width="70">제목</td> | |
<td align="left"><input name="title" type="text" value="${board.title}"></td> | |
</tr> | |
<tr> | |
<td bgcolor="orange">작성자</td> | |
<td align="left">${board.writer}</td> | |
</tr> | |
<tr> | |
<td bgcolor="orange">내용</td> | |
<td align="left"> | |
<textarea name="content" rows="10" cols="40">${board.content}</textarea> | |
</td> | |
</tr> | |
<tr> | |
<td bgcolor="orange">등록일</td> | |
<td align="left"><fmt:formatDate value="${board.regDate}" pattern="yyyy-MM-dd" /></td> | |
</tr> | |
<tr> | |
<td bgcolor="orange">조회수</td> | |
<td align="left">${board.cnt}</td> | |
</tr> | |
<tr> | |
<td colspan="2" align="center"> | |
<input type="submit" value="글 수정" > | |
</td> | |
</tr> | |
</table> | |
</form> | |
<hr> | |
<a href="insertBoard.jsp">글등록</a> | |
<a href="deleteBoard.do?seq=${board.seq}">글삭제</a> | |
<a href="getBoardList.do">글목록</a> | |
</center> | |
</body> | |
</html> |
- insertBoard.jsp
<%-- | |
Created by IntelliJ IDEA. | |
User: dokylee | |
Date: 28/02/2020 | |
Time: 3:02 오전 | |
To change this template use File | Settings | File Templates. | |
--%> | |
<%@ page contentType="text/html;charset=UTF-8" language="java" %> | |
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> | |
<html> | |
<head> | |
<title>새글 등록</title> | |
</head> | |
<body> | |
<center> | |
<h1>글 등록</h1> | |
<a href="logout.do">Log-out</a> | |
<hr> | |
<form action="insertBoard.do" method="post" enctype="multipart/form-data"> | |
<table border="1" cellpadding="0" cellspacing="0"> | |
<tr> | |
<td bgcolor="orange" width="70">제목</td> | |
<td align="left"> | |
<input type="text" name="title" > | |
</td> | |
</tr> | |
<tr> | |
<td bgcolor="orange">작성자</td> | |
<td align="left"> | |
<input type="text" name="writer" size="10"> | |
</td> | |
</tr> | |
<tr> | |
<td bgcolor="orange">내용</td> | |
<td align="left"> | |
<textarea name="content" rows="10" cols="40" ></textarea> | |
</td> | |
</tr> | |
<tr> | |
<td bgcolor="orange" width="70">업로드</td> | |
<td align="left"> | |
<input type="file" name="uploadFile"> | |
</td> | |
</tr> | |
<tr> | |
<td colspan="2" align="center"> | |
<input type="submit" value=" 새글 등록 " /> | |
</td> | |
</tr> | |
</table> | |
</form> | |
<a href="getBoardList.do">글 목록 가기</a> | |
</center> | |
</body> | |
</html> |
- Controller들
- src/main/java/com/springbook/view/board/BoardController.java
package com.springbook.view.board; | |
import com.springbook.biz.board.BoardListVO; | |
import com.springbook.biz.board.BoardService; | |
import com.springbook.biz.board.BoardVO; | |
import com.springbook.biz.board.impl.BoardDAO; | |
import org.springframework.beans.factory.annotation.Autowired; | |
import org.springframework.stereotype.Controller; | |
import org.springframework.ui.Model; | |
import org.springframework.web.bind.annotation.*; | |
import org.springframework.web.multipart.MultipartFile; | |
import java.io.File; | |
import java.io.IOException; | |
import java.util.HashMap; | |
import java.util.List; | |
import java.util.Map; | |
@Controller | |
@SessionAttributes("board") // model에 "board"라는 이름으로 저장되는 데이터가 있다면, 그 데이터를 세션(HttpSession)에도 자동으로 저장하라는 설정 | |
public class BoardController { | |
@Autowired | |
private BoardService boardService; | |
@RequestMapping("/dataTransform.do") | |
@ResponseBody | |
public BoardListVO dataTransform(BoardVO vo) { | |
vo.setSearchCondition("TITLE"); | |
vo.setSearchKeyword(""); | |
List<BoardVO> boardList = boardService.getBoardList(vo); | |
BoardListVO boardListVO = new BoardListVO(); | |
boardListVO.setBoardList(boardList); | |
return boardListVO; | |
} | |
// 검색 조건 목록 설정 | |
@ModelAttribute("conditionMap") | |
public Map<String, String> searchConditionMap() { | |
Map<String, String> conditionMap = new HashMap<String, String>(); | |
conditionMap.put("제목", "TITLE"); | |
conditionMap.put("내용", "CONTENT"); | |
return conditionMap; | |
} | |
// 글 등록 | |
@RequestMapping(value = "/insertBoard.do") | |
public String insertBoard(BoardVO vo) throws IOException { | |
System.out.println("글 등록 처리"); | |
MultipartFile uploadFile = vo.getUploadFile(); | |
if(!uploadFile.isEmpty()) { | |
String fileName = uploadFile.getOriginalFilename(); | |
uploadFile.transferTo(new File("/Users/dokylee/Desktop/java_proj/upload-imgs/"+fileName)); | |
} | |
boardService.insertBoard(vo); | |
// return "getBoardList.do"; // 포워딩 방식: 브라우저의 URL 변하지 않음 => "/insertBoard.do" 그대로 | |
return "redirect:getBoardList.do"; // URL => "/getBoardList.do" | |
} | |
// 글 수정 | |
@RequestMapping(value = "/updateBoard.do") | |
public String updateBoard(@ModelAttribute("board") BoardVO vo) { | |
// ㄴ> 세션에 board로 저장된 데이터가 있으면 vo에 할당한 후, 사용자가 입력한 파라미터값을 vo 객체에 할당 ==> 그래서 이전정보+새로운정보 모두 가능 | |
boardService.updateBoard(vo); | |
return "getBoardList.do"; | |
} | |
// 글 삭제 | |
@RequestMapping(value = "/deleteBoard.do") | |
public String deleteBoard(BoardVO vo) { | |
boardService.deleteBoard(vo); | |
return "getBoardList.do"; | |
} | |
//글 상세 조회 | |
@RequestMapping(value = "/getBoard.do") | |
public String getBoard(BoardVO vo, Model model) { | |
model.addAttribute("board", boardService.getBoard(vo)); | |
return "getBoard.jsp"; | |
} | |
// 글 목록 검색 | |
@RequestMapping(value = "/getBoardList.do") | |
// public String getBoardList(@RequestParam(value = "searchCondition", defaultValue = "TITLE", required = false) String condition, | |
// @RequestParam(value = "searchKeyword", defaultValue = "", required = false) String keyword, | |
// BoardVO vo, Model model) { | |
public String getBoardList(BoardVO vo, Model model) { | |
// Null Check | |
if(vo.getSearchCondition() == null) vo.setSearchCondition("TITLE"); | |
if(vo.getSearchKeyword() == null) vo.setSearchKeyword(""); | |
System.out.println("검색 조건: "+vo.getSearchCondition()); | |
System.out.println("검색 단어: "+vo.getSearchKeyword()); | |
// Model 정보 저장 | |
model.addAttribute("boardList", boardService.getBoardList(vo)); | |
return "getBoardList.jsp"; // View 이름 리턴 | |
} | |
} |
- src/main/java/com/springbook/view/user/LoginController.java
package com.springbook.view.user; | |
import com.springbook.biz.user.UserVO; | |
import com.springbook.biz.user.impl.UserDAO; | |
import com.springbook.biz.user.impl.UserDAOSpring; | |
import org.springframework.stereotype.Controller; | |
import org.springframework.web.bind.annotation.ModelAttribute; | |
import org.springframework.web.bind.annotation.RequestMapping; | |
import org.springframework.web.bind.annotation.RequestMethod; | |
import javax.servlet.http.HttpSession; | |
@Controller | |
public class LoginController { | |
@RequestMapping(value = "/login.do", method = RequestMethod.GET) | |
public String loginView(@ModelAttribute("user") UserVO vo) { | |
System.out.println("로그인 화면으로 이동"); | |
vo.setId("admin"); | |
vo.setPassword("1234"); | |
return "login.jsp"; | |
} | |
@RequestMapping(value = "/login.do", method = RequestMethod.POST) | |
public String login(UserVO vo, UserDAO userDAO, HttpSession session) { | |
if(vo.getId() == null || vo.getId().equals("")) { | |
throw new IllegalArgumentException("아이디는 반드시 입력해야 합니다."); | |
} | |
System.out.println("로그인 처리"); | |
UserVO user = userDAO.getUser(vo); | |
if(user != null) { | |
session.setAttribute("userName", user.getName()); | |
return "getBoardList.do"; | |
} | |
else return "login.jsp"; | |
} | |
} |
- src/main/java/com/springbook/view/user/LogoutController.java
package com.springbook.view.user; | |
import javax.servlet.http.HttpSession; | |
import org.springframework.stereotype.Controller; | |
import org.springframework.web.bind.annotation.RequestMapping; | |
@Controller | |
public class LogoutController { | |
@RequestMapping(value = "/logout.do") | |
public String logout(HttpSession session) { | |
session.invalidate(); | |
return "login.jsp"; | |
} | |
} |
- 예외처리 화면 핸들러
- src/main/java/com/springbook/view/common/CommonExceptionHandler.java
package com.springbook.view.common; | |
import org.springframework.web.bind.annotation.ControllerAdvice; | |
import org.springframework.web.bind.annotation.ExceptionHandler; | |
import org.springframework.web.servlet.ModelAndView; | |
public class CommonExceptionHandler { | |
@ExceptionHandler(ArithmeticException.class) | |
public ModelAndView handleArithmeticException(Exception e) { | |
ModelAndView mav = new ModelAndView(); | |
mav.addObject("exception", e); | |
mav.setViewName("/common/arithmeticError.jsp"); | |
return mav; | |
} | |
@ExceptionHandler(NullPointerException.class) | |
public ModelAndView handleNullPointerException(Exception e) { | |
ModelAndView mav = new ModelAndView(); | |
mav.addObject("exception", e); | |
mav.setViewName("/common/nullPointerError.jsp"); | |
return mav; | |
} | |
@ExceptionHandler(Exception.class) | |
public ModelAndView handleException(Exception e) { | |
ModelAndView mav = new ModelAndView(); | |
mav.addObject("exception", e); | |
mav.setViewName("/common/error.jsp"); | |
return mav; | |
} | |
} |
- 어드바이스 (AOP 구현)
추가 구현 후 업데이트 예정
전체 프로젝트 파일
Github> https://github.com/dokylee54/java-spring-boardEx/tree/master/day3
dokylee54/java-spring-boardEx
Contribute to dokylee54/java-spring-boardEx development by creating an account on GitHub.
github.com
![]() |
|