테이블 생성
CREATE TABLE SPRING_USERS ( USER_ID VARCHAR2(100) CONSTRAINT SPRING_USER_ID_PK PRIMARY KEY, USER_PASSWORD VARCHAR2(64) NOT NULL, USER_NAME VARCHAR2(100) NOT NULL, USER_EMAIL VARCHAR2(255) NOT NULL CONSTRAINT SPRING_USER_EMAIL_UK UNIQUE, USER_TEL VARCHAR2(20) NOT NULL, USER_PHOTO VARCHAR2(100) DEFAULT 'default.png', USER_DELETED CHAR(1) DEFAULT 'N', USER_UPDATED_DATE DATE DEFAULT SYSDATE, USER_CREATED_DATE DATE DEFAULT SYSDATE ); CREATE TABLE SPRING_USER_ROLES ( USER_ID VARCHAR2(100) NOT NULL CONSTRAINT SPRING_USER_ROLE_USER_ID_PK REFERENCES SPRING_USERS (USER_ID), USER_ROLE_NAME VARCHAR2(20) NOT NULL CONSTRAINT SPRING_USER_ROLE_NAME_CK CHECK (USER_ROLE_NAME IN ('ROLE_GUEST', 'ROLE_USER', 'ROLE_ADMIN')) ); ALTER TABLE SPRING_USER_ROLES ADD CONSTRAINT SPRING_USER_ROLE_PK PRIMARY KEY (USER_ID, USER_ROLE_NAME);
- 사용자는 역할을 가지게 된다. (손님, 사용자, 관리자)
- 게스트는 글 작성X, 사용자는 글 작성O, 관리자는 관리 기능O
- 같은 아이디로 같은 권한을 가질 수 없다.
기초 작업
home.jsp <%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" trimDirectiveWhitespaces="true"%> <!DOCTYPE html> <html lang="ko"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous"> <title>애플리케이션</title> </head> <body> <c:set var="menu" value="home" /> <%@ include file="common/navbar.jsp" %> <div class="container"> <div class="row mb-3"> <div class="col-12"> <div class="border p-3 bg-light"> <h1 class="mb-4">Spring MVC 샘플 애플리케이션</h1> <p>Spring MVC를 활용한 웹 애플리케이션입니다.</p> </div> </div> </div> </div> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script> </body> </html>
tags.jsp <%@ page contentType="text/html; charset=UTF-8" pageEncoding="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" %> <%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
navbar.jsp <%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" trimDirectiveWhitespaces="true"%> <nav class="navbar navbar-expand-sm bg-dark navbar-dark mb-3"> <div class="container"> <ul class="navbar-nav me-auto"> <li class="nav-item"><a class="nav-link ${menu eq 'home' ? 'active' : '' }" href="/home">샘플 애플리케이션</a></li> </ul> <ul class="navbar-nav"> <li class="nav-item"><a class="nav-link" href="/logout">로그아웃</a></li> <li class="nav-item"><a class="nav-link ${menu eq 'login' ? 'active' : '' }" href="/login">로그인</a></li> <li class="nav-item"><a class="nav-link ${menu eq 'register' ? 'active' : '' }" href="/register">회원가입</a></li> </ul> </div> </nav>
mybatis-config.xml <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd" > <configuration> <settings> <setting name="jdbcTypeForNull" value="NULL"/> <setting name="logImpl" value="SLF4J"/> </settings> </configuration>
db.properties ### 데이터베이스 연결 정보 설정 db.driver-class-name=oracle.jdbc.OracleDriver db.url=jdbc:oracle:thin:@localhost:1521:xe db.username=hr db.password=zxcv1234
HomeController.java package com.sample.web.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import com.sample.service.UserService; import com.sample.web.request.UserRegisterForm; @Controller public class HomeController { /* * 로그인을 위해 service가 필요하다. */ @Autowired private UserService userService; @GetMapping("/home") public String home() { return "home"; } }
실행결과
회원가입 기능
register-form.jsp <%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" trimDirectiveWhitespaces="true"%> <!DOCTYPE html> <html lang="ko"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous"> <title>애플리케이션</title> </head> <body> <c:set var="menu" value="register" /> <%@ include file="common/navbar.jsp" %> <div class="container"> <div class="row mb-3"> <div class="col-12"> <h1 class="border bg-light p-2 fs-4">회원가입</h1> </div> </div> <div class="row mb-3"> <div class="col-12"> <p>회원가입 정보를 입력하세요</p> <form id="form-register" class="border bg-light p-3" method="post" action="register"> <div class="mb-3"> <label class="form-label">접속 권한</label> <div> <div class="form-check form-check-inline"> <input class="form-check-input" type="checkbox" name="roleName" value="ROLE_GUEST" checked> <label class="form-check-label">게스트</label> </div> <div class="form-check form-check-inline"> <input class="form-check-input" type="checkbox" name="roleName" value="ROLE_USER" disabled="disabled"> <label class="form-check-label">사용자</label> </div> <div class="form-check form-check-inline"> <input class="form-check-input" type="checkbox" name="roleName" value="ROLE_ADMIN" disabled="disabled"> <label class="form-check-label">관리자</label> </div> </div> </div> <div class="mb-3"> <label class="form-label">아이디</label> <input type="text" class="form-control form-control-sm" name="id" /> </div> <div class="mb-3"> <label class="form-label">비밀번호</label> <input type="password" class="form-control form-control-sm" name="password" /> </div> <div class="mb-3"> <label class="form-label">이름</label> <input type="text" class="form-control form-control-sm" name="name" /> </div> <div class="mb-3"> <label class="form-label">이메일</label> <input type="text" class="form-control form-control-sm" name="email" /> </div> <div class="mb-3"> <label class="form-label">전화번호</label> <input type="text" class="form-control form-control-sm" name="tel" /> </div> <div class="text-end"> <a href="/home" class="btn btn-secondary btn-sm">취소</a> <button type="submit" class="btn btn-primary btn-sm">가입</button> </div> </form> </div> </div> </div> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script> </body> </html>
login-form.jsp <%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" trimDirectiveWhitespaces="true"%> <!DOCTYPE html> <html lang="ko"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous"> <title>애플리케이션</title> </head> <body> <c:set var="menu" value="login" /> <%@ include file="common/navbar.jsp" %> <div class="container"> <div class="row mb-3"> <div class="col-12"> <h1 class="border bg-light p-2 fs-4">로그인</h1> </div> </div> <div class="row mb-3"> <div class="col-12"> <p>아이디와 비밀번호를 입력하고 로그인 버튼을 클릭하세요</p> <form id="form-register" class="border bg-light p-3" method="post" action="login"> <div class="mb-3"> <label class="form-label">아이디</label> <input type="text" class="form-control form-control-sm" name="id" /> </div> <div class="mb-3"> <label class="form-label">비밀번호</label> <input type="password" class="form-control form-control-sm" name="password" /> </div> <div class="text-end"> <a href="/home" class="btn btn-secondary btn-sm">취소</a> <button type="submit" class="btn btn-primary btn-sm">로그인</button> </div> </form> </div> </div> </div> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script> </body> </html>
User.java package com.sample.vo; import java.sql.Date; import org.apache.ibatis.type.Alias; @Alias("User") public class User { private String id; private String password; private String name; private String email; private String tel; private String photo; private String deleted; private Date updatedDate; private Date createdDate; public User() {} 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 getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getTel() { return tel; } public void setTel(String tel) { this.tel = tel; } public String getPhoto() { return photo; } public void setPhoto(String photo) { this.photo = photo; } public String getDeleted() { return deleted; } public void setDeleted(String deleted) { this.deleted = deleted; } public Date getUpdatedDate() { return updatedDate; } public void setUpdatedDate(Date updatedDate) { this.updatedDate = updatedDate; } public Date getCreatedDate() { return createdDate; } public void setCreatedDate(Date createdDate) { this.createdDate = createdDate; } }
UserRole.java
package com.sample.vo;
import org.apache.ibatis.type.Alias;
@Alias("UserRole")
public class UserRole {
private String userId;
private String roleName;
public UserRole() {}
public UserRole(String userId, String roleName) {
this.userId = userId;
this.roleName = roleName;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
}
UserMapper.java
package com.sample.mapper;
import org.apache.ibatis.annotations.Mapper;
import com.sample.vo.User;
@Mapper
public interface UserMapper {
void insertUser(User user);
void updateUser(User user);
User getUserById(String userId);
User getUserByEmail(String email);
}
UserRoleMapper.java
package com.sample.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import com.sample.vo.UserRole;
@Mapper
public interface UserRoleMapper {
void insertUserRole(UserRole userRole);
void deleteUserRole(UserRole userRole);
List<UserRole> getUserRolesByUserId(String userId);
}
users.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.sample.mapper.UserMapper">
<!--
void insertUser(User user)
-->
<insert id="insertUser" parameterType="User">
insert into spring_users
(user_id, user_password, user_name, user_email, user_tel)
values
(#{id}, #{password}, #{name}, #{email}, #{tel})
</insert>
<!--
void updateUser(User user)
(id는 식별자이기 때문에 절대 바꿀 수 없다.)
-->
<update id="updateUser" parameterType="User">
update
spring_users
set
user_password = #{password},
user_tel = #{tel},
user_photo = #{photo},
user_deleted = #{deleted},
user_updated_date = sysdate
from
user_id = #{id}
</update>
<!--
User getUserById(String userId)
-->
<select id="getUserById" parameterType="string" resultType="User">
select
user_id as id,
user_password as password,
user_name as name,
user_email as email,
user_tel as tel,
user_photo as photo,
user_deleted as deleted,
user_updated_date as updatedDate,
user_created_date as createdDate
from
spring_users
where
user_id = #{value}
</select>
<!--
User getUserByEmail(String email)
-->
<select id="getUserByEmail" parameterType="string" resultType="User">
select
user_id as id,
user_password as password,
user_name as name,
user_email as email,
user_tel as tel,
user_photo as photo,
user_deleted as deleted,
user_updated_date as updatedDate,
user_created_date as createdDate
from
spring_users
where
user_email = #{value}
</select>
</mapper>
user-roles.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.sample.mapper.UserRoleMapper">
<!--
void insertUserRole(UserRole userRole)
-->
<insert id="insertUserRole" parameterType="UserRole">
insert into spring_user_roles
(user_id, user_role_name)
values
(#{userId}, #{roleName})
</insert>
<!--
void deleteUserRole(UserRole userRole)
-->
<delete id="deleteUserRole" parameterType="UserRole">
delete from
spring_user_roles
where
user_id = #{userId}
and user_role_name = {#roleName}
</delete>
<!--
List<UserRole> getUserRolesByUserId(String userId)
-->
<select id="getUserRolesByUserId" parameterType="string" resultType="UserRole">
select
user_id as id,
user_role_name as roleName,
from
spring_user_roles
where
user_id = #{value}
</select>
</mapper>
UserService.java
package com.sample.service;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.sample.exception.ApplicationException;
import com.sample.mapper.UserMapper;
import com.sample.mapper.UserRoleMapper;
import com.sample.vo.User;
import com.sample.vo.UserRole;
import com.sample.web.request.UserRegisterForm;
@Service
public class UserService {
/*
* Service 객체에 Mapper가 주입된다.
*/
@Autowired
private UserMapper userMapper;
@Autowired
private UserRoleMapper userRoleMapper;
public void registerUser(UserRegisterForm userRegisterForm) {
User savedUser = userMapper.getUserById(userRegisterForm.getId());
if (savedUser != null) {
throw new ApplicationException("["+userRegisterForm.getId()+"] 사용할 수 없는 아이디입니다.");
}
savedUser = userMapper.getUserByEmail(userRegisterForm.getEmail());
if(savedUser != null) {
throw new ApplicationException("["+userRegisterForm.getEmail()+"] 사용할 수 없는 이메일입니다.");
}
User user = new User();
// 이름이 같은 것끼리 복사된다.(userRegisterForm에 있는 값이 User에 복사된다. / User에 없는 값은 복사되지 않는다.)
BeanUtils.copyProperties(userRegisterForm, user);
userMapper.insertUser(user);
UserRole userRole = new UserRole(userRegisterForm.getId(), userRegisterForm.getRoleName());
userRoleMapper.insertUserRole(userRole);
}
public void login(String id, String password) {
}
public void changePassword(String id, String oldPassword, String password) {
}
public void deleteUser(String userId) {
}
public void addUserRole(UserRole userRole) {
}
}
실행결과(회원가입 화면)
실행결과(회원가입 후)
- 컨트롤러 작성하기
@Controller
public class HomeController {
// 요청 URI "locahost/home"과 매핑되는 요청핸들러 메소드다.
@GetMapping("/home")
public String home() {
// 뷰 이름 반환하기, 실제 jsp 경로 -> "/WEB-INF/views/" + home + ".jsp"
return "home";
}
}
@Controller
@RequestMapping("/user")
public class UserController {
// 요청 URI "locahost/user/detail"과 매핑되는 요청핸들러 메소드다.
@GetMapping("/detail")
public String detail() {
// 뷰 이름 반환하기, 실제 jsp 경로 -> "/WEB-INF/views/" + user/detail + ".jsp"
return "user/detail";
}
}
@Controller
@RequestMapping("/posts")
public class PostsController {
// 요청 URI "locahost/posts/list"과 매핑되는 요청핸들러 메소드다.
@GetMapping("/list")
public String list() {
// 뷰 이름 반환하기, 실제 jsp 경로 -> "/WEB-INF/views/" + posts/detail + ".jsp"
return "posts/list";
}
// 요청 URI "locahost/posts/detail"과 매핑되는 요청핸들러 메소드다.
@GetMapping("/detail")
public String detail() {
// 뷰 이름 반환하기, 실제 jsp 경로 -> "/WEB-INF/views/" + posts/detail + ".jsp"
return "posts/detail";
}
}
스프링 MVC의 예외처리
@ControllerAdvice 어노테이션
- 모든 컨트롤러에서 공통으로 사용하는 기능이 구현된 클래스에 적용하는 어노테이션이다.
- 대표적인 공통기능
* 예외처리
* 파라미터값 변환
@ExceptionHandler 어노테이션
- 예외처리를 담당하는 핸들러 메소드에 적용하는 어노테이션이다.
- 예시
@ExceptionHandler(예외클래스.class)
public String handle예외클래스(예외클래스 변수명) {
return "오류페이지이름";
}
* 컨트롤러에서 요청을 처리하다가 @ExceptionHandler에 지정한 예외가 발생하면 예외처리 메소드가 자동으로 실행된다.
* 예외처리 메소드는 매개변수로 발생한 예외객체를 전달 받을 수 있다.
* 예외처리 메소드가 반환하는 오류페이지(JSP)로 사용자의 요청을 내부이동시킨다.
- 예외처리 규칙
* 발생한 예외클래스와 일치하는 @ExceptionHandler(예외클래스.class) 정의가 있으면 해당 예외처리 메소드가 실행된다.
* 발생한 예외클래스와 일치하는 @ExceptionHandler(예외클래스.class) 정의가 없고,
@ExceptionHandler(부모예외클래스.class) 정의가 있으면 해당 예외처리 메소드가 실행된다.
* 발생한 예외클래스와 일치하는 @ExceptionHandler(예외클래스.class) 정의도 있고,
@ExceptionHandler(부모예외클래스.class) 정의도 있으면 더 구체적인 예외클래스가 지정된 예외처리 메소드가 실행된다.
error/500.jsp
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"
trimDirectiveWhitespaces="true" isErrorPage="true"%>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
<title>애플리케이션</title>
</head>
<body>
<c:set var="menu" value="home" />
<%@ include file="../common/navbar.jsp" %>
<div class="container">
<div class="row mb-3">
<div class="col-12">
<div class="alert alert-danger">
<h1 class="mb-4">Spring MVC 샘플 애플리케이션 오류 페이지</h1>
<p>오류 내용 : <strong class="text-danger"><%=exception.getMessage() %></strong><p>
</div>
<p class="text-end">
<a href="/home" class="btn btn-link">홈</a>으로 이동해서 다시 시도해보시기 바랍니다.
</p>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
</body>
</html>
ExceptionHandlerControllerAdvice.java
package com.sample.web.advice;
import org.springframework.dao.DataAccessException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import com.sample.exception.ApplicationException;
@ControllerAdvice
public class ExceptionHandlerControllerAdvice {
@ExceptionHandler(ApplicationException.class)
public String handleApplicationException(ApplicationException ex) {
ex.printStackTrace();
return "error/500";
}
@ExceptionHandler(DataAccessException.class)
public String handleDataAccessException(DataAccessException ex) {
ex.printStackTrace();
return "error/db";
}
@ExceptionHandler(RuntimeException.class)
public String handleRuntimeException(RuntimeException ex) {
ex.printStackTrace();
return "error/server";
}
}
실행결과