exception 패키지 생성
exception 패키지 내 CommonExceptionHandler.class 생성
@ControllerAdvice 어노테이션 선언 -> 예외처리 핸들러 클래스임을 명시!
@Slf4j 어노테이션 선언
handle 메소드 생성 - @ExceptionHandler 어노테이션 선언
@ExceptionHandler
=> 괄호 안에 설정한 예외 타입을 해당 메서드가 처리한다는 의미
=> IOException, SQLException, NullPointerException, ArrayIndexOutOfBoundsException,
=> ArtimeticException(0으로 나눌경우)
package kr.or.ddit.exception;
import org.springframework.http.HttpStatus;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.NoHandlerFoundException;
import lombok.extern.slf4j.Slf4j;
//스프링 컨트롤러에서 발생하는 예외를 처리하는 핸들러 클래스임을 명시함
@Slf4j
@ControllerAdvice
public class CommonExceptionHandler {
//괄호 안에 설정한 예외 타입을 해당 메소드가 처리한다는 의미
//IOException, SQLException, NullPointerException, ArrayIndexOutOfBoundsException,
//ArtimeticException(0으로 나눌경우)
//Exception -> 최상위 에러 클래스
@ExceptionHandler(Exception.class)
public String handle(Exception e , Model model) {
log.error("CommonExceptionHandler->handle : " + e.toString());
e.getStackTrace(); // 배열형태로 온다 => jsp에서(view) 배열(c:forEach) 사용가능
model.addAttribute("exception" , e);
//forwarding : jsp
return "error/errorCommon";
}
// 여기서 404오류를 처리하기 위해선??
/*
404를 프로그래밍적으로 처리하고 싶다면 404 발생 시 예외를 발생시키도록 설정해야 한다.
(기본적으로 404는 exception 상황이 아니다.)
이를 위해 web.xml에서 DispatcherServlet을 등록할 때 throwExceptionIfNoHandlerFound
초기화 파라미터를 true로 설정한다.
*/
@ExceptionHandler(NoHandlerFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public String handle404(Exception e) {
// Exception핸들러 어노테이션 선언
log.error("CommonExceptionHandler -> handle404 : " + e.toString());
return "error/error404";
}
}
errorCommon.jsp 생성
<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ page isErrorPage="true" %>
<section class="content">
<div class="error-page">
<!-- model.addAttribute("exception" , e); -->
<h2 class="headline text-warning">${exception.getMessage() }</h2>
<div class="error-content">
<c:forEach var="stack" items="${exception.getStackTrace()}" varStatus="stat">
<!-- 오류메시지가 순서대로 반복문 형태로 출력 -->
<h3>
<i class="fas fa-exclamation-triangle text-warning"></i>${stack.toString()}
</h3>
</c:forEach>
<p>
<%=exception.getMessage()%> 만약 메인으로 이동하고자 하면 <a href="/">메인</a>을 클릭해주세요.
</p>
<form class="search-form">
<div class="input-group">
<input type="text" name="search" class="form-control"
placeholder="Search">
<div class="input-group-append">
<button type="submit" name="submit" class="btn btn-warning">
<i class="fas fa-search"></i>
</button>
</div>
</div>
</form>
</div>
</div>
</section>
-- 1교시 끝
web.xml 에서 주석처리 -> 컨트롤러에서 처리하기 위해서
<!-- <error-page> -->
<!-- <error-code>404</error-code> -->
<!-- <location>/error/error404</location> -->
<!-- </error-page> -->
servlet 부분에 아래코드 추가작성
<!-- 404 오류를 처리할 수 있도록 설정 시작 -->
<init-param>
<param-name>throwExceptionIfNoHandlerFound</param-name>
<param-value>true</param-value>
</init-param>
<!-- 404 오류를 처리할 수 있도록 설정 끝 -->
web.xml 에 아래 코드 추가
<!-- 입력값을 검증하기 위한 라이브러리 의존 관계 정의 시작
스프링
M(Model) : Service, ServiceImple, Mapper
V(View) : JSP
C(Controller) : Controller
Bean(자바빈 클래스, ArticleVO) Validation(유효성검사) 기능을 이용해
요청 파라미터 값이 바인딩된(멤버변수에 세팅된) 도메인 클래스(ArticleVO)의 입력값 검증을 함
요청 파라미터 : ?articleNo=112&title=개똥이
public String write(골뱅이ModelAttribute ArticleVO articleVO) -->
<!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-validator -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.2.5.Final</version>
</dependency>
<!-- 입력값을 검증하기 위한 라이브러리 의존 관계 정의 끝 -->
// 2교시 끝
package kr.or.ddit.vo;
import java.util.List;
import org.hibernate.validator.constraints.NotBlank;
import lombok.Data;
/**
* 스프링 MVC는 Bean Validation 기능을 이용해 요청 파라미터 값이 바인딩된 도메인 클래스의 입력값 검증을 함
// 요청 파라미터 : {studId=a001...}
// => 바인딩
// articleVO.setArticleNo(12), articleVO.setWriterId("a001")
// 자바빈 클래스(=도메인 클래스)
// PoJo(Plain(단순한, 원래의) old(지향) Java Object)
Bean Validation이 제공하는 제약 어노테이션
- NotNull : 빈 값 체크(int타입)
- NotBlank : null 체크, trim후 길이가 0인지 체크(String타입)
- Size : 글자 수 체크
- Email : 이메일 주소 형식 체크
- Past : 오늘보다 과거 날짜(ex. 생일)
- Future : 미래 날짜 체크(ex. 예약일)
- AssertFalse : false 값만 통과 가능
- AssertTrue : true 값만 통과 가능
- DecimalMax(value=) : 지정된 값 이하의 실수만 통과 가능
- DecimalMin(value=) : 지정된 값 이상의 실수만 통과 가능
- Digits(integer=,fraction=) : 대상 수가 지정된 정수와 소수 자리수보다 적을 경우 통과 가능
- Future : 대상 날짜가 현재보다 미래일 경우만 통과 가능
- Past : 대상 날짜가 현재보다 과거일 경우만 통과 가능
- Max(value) : 지정된 값보다 아래일 경우만 통과 가능
- Min(value) : 지정된 값보다 이상일 경우만 통과 가능
- NotNull : null 값이 아닐 경우만 통과 가능
- Null : null일 겨우만 통과 가능
- Pattern(regex=, flag=) : 해당 정규식을 만족할 경우만 통과 가능
- Size(min=, max=) : 문자열 또는 배열이 지정된 값 사이일 경우 통과 가능
- Valid : 대상 객체의 확인 조건을 만족할 경우 통과 가능
*/
//자바빈 클래스
//PoJo위배
@Data
public class StudVO {
private int rnum;
@NotBlank
private String studId;
@NotBlank
private String studNm;
@NotBlank
private String studPw;
private String enabled;
private String studDet;//학생 상세->STUD_DET
private String[] hobby;//학생 취미들
private String gender;//성별
private String nationality;//국적
//StudVO : StudAuthVO = 1 : N
private List<StudAuthVO> studAuthVOList;
//STUD : HOBBY = 1 : N
//중첩된(Nested) 자바빈
private List<HobbyVO> hobbyVOList;
//일반 생성자
public StudVO(String studId, String studNm, String studPw) {
this.studId = studId;
this.studNm = studNm;
this.studPw = studPw;
}
//기본 생성자
public StudVO() {}
}
SpringFormController에 register메소드 수정
/*
요청URI : /springform/register
요청파라미터 : {studId=a005,studNm=개똥이,studPw=java, studDet=상세정보
, hobby=[sports,movie], gender=female, nationality=korea}
요청방식 : post
*/
//2. 컨트롤러 메서드의 매개변수로 자바빈즈 객체가 전달이 되면
// forwarding하는 JSP에 다시 화면으로 전달함
// 입력값 검증을 할 도메인 클래스(StudVO)에 @Validated를 지정함
// 입력값 검증 대상의 도메인 클래스 직후에 BindingResult를 정의함
// BindingResult에는 요청 파라미터 데이터의 바인딩(set..) 오류와 입력값 검증 오류 정보가 저장됨
// result.hasErrors() : 바인딩 도중 오류 발생 시 true를 반환함
@PostMapping("/register")
public String register(@Validated StudVO studVO , BindingResult result) {
//StudVO[studId=a005,studNm=개똥이,studPw=java, enabled=null
// , studDet=상세정보, hobby=[sports,movie], hobbyVOList=null
// , gender=female, nationality=korea]
log.info("register : " + studVO);
log.error("바인딩 result : " + result.hasErrors());
// int result = this.service.register(studVO);
// log.info("register->result : " + result); // true(오류발생) , false(오류없음)
//forwarding
return "springform/registerForm";
}
/*
스프링 자체적으로 유효성 검증(validation)을 해준다
registerForm.jsp 에 해당 코드 작성
<tr>
<th>학생 명</th>
<!-- name 및 id 속성이 path속성으로 합쳐짐 -->
<td>
<form:input path="studNm" />
<font color="red">
<form:errors path="studNm" />
</font>
</td>
</tr>
<tr>
<th>학생 비밀번호</th>
<!-- 포워딩 되면 바인딩 된 에러메시지가 출력된다 -->
<td><form:password path="studPw" />
<font color="red">
<form:errors path="studPw" />
</font>
</td>
</tr>
유효성 검증 방법
1. @Validated
2. <font color="red"><form:errors path="studNm" /></font>(form에러메세지)
3. 정규식 적용
등등..
@NotBlank 어노테이션에 메세지 값 설정 가능
확인
자동등록 버튼만들기?
create.jsp에 아래 코드 작성
<div class="card-footer">
<!-- ajax(button) 인지 submit인지 잘 체크!! -->
<button type="submit" class="btn btn-primary">등록</button>
<button type="button" id="btnAuto" class="btn btn-primary">자동입력</button>
<!-- <button type="button" id="btnAjaxSubmit" class="btn btn-primary">등록</button> -->
</div>
위쪽 스크립트에서 function 부분에 아래 코드 작성
$("#btnAuto").on("click" , function () {
console.log("자동입력버튼 클릭")
$("#emailAdres").val("test@test.com");
$("#password").val("java");
$("#no1").val("1111-1111-1111-1111");
$("#validMonth1").val("202408");
$("#no2").val("2222-2222-2222-2222");
$("#validMonth2").val("202508");
$("#likesTitle1").val("운동");
$("#likesCont1").val("야구");
$("#likesTitle2").val("독서");
$("#likesCont2").val("추리소설");
});
버튼 클릭 후 자동입력버튼 작동 하는지 확인
QuickController createPost메소드에 @Validated 선언
QuickVO에 유효성 검증 어노테이션 추가
package kr.or.ddit.vo;
import java.util.Date;
import java.util.List;
import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.NotBlank;
import org.springframework.web.multipart.MultipartFile;
import lombok.Data;
@Data
public class QuickVO {
@NotBlank(message = "이메일을 입력해주세요")
@Email
private String emailAdres;
@NotBlank(message = "비밀번호를 입력해주세요")
private String password;
private String registerId;
private Date registDt;
private String updaterId;
private Date updateDt;
private MultipartFile[] uploadFile;
// QUICK : QUICK_ATTACH = 1 : N
private List<QuickAttachVO> quickAttachVOList;
// QUICK : CARD = 1 : N QuickVO 테이블에 추가
private List<CardVO> cardVOList;
// QUICK : LIKES = 1 : N QuickVO 테이블에 추가
private List<LikesVO> likesVOList;
}
package kr.or.ddit.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import kr.or.ddit.service.QuickService;
import kr.or.ddit.vo.QuickVO;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RequestMapping("/quick")
@Controller
public class QuickController {
@Autowired
QuickService quickService;
/**
Quick 폼화면
* @return
*/
@GetMapping("/create")
public String create() {
// forwarding : jsp
return "quick/create"; // quick/create폴더
}
/**
요청 URI : /quick/createPost
요청파라미터 : {emailAdres=test@test.com,password=java,uploadFile=파일객체}
요청방식 : post
부모테이블(QUICK) : 자식테이블(QUICK_ATTACH) = 1 : N
registerId : admin
*/
@ResponseBody
@PostMapping("/createAjaxPost")
public int createAjaxPost(QuickVO quickVO) {
log.info("quickVO1 : " + quickVO);
quickVO.setUpdaterId("admin");
int result = this.quickService.createPost(quickVO);
log.info("result : " + result);
return result;
}
@PostMapping("/createPost")
public String createPost(@Validated QuickVO quickVO , BindingResult brResult) {
quickVO.setUpdaterId("admin");
log.info("createPost -> quickVO : " + quickVO);
log.error("BindingResult : " + brResult.hasErrors());//true : 오류발생 , false : 오류없음
//오류가 발생한다면
if(brResult.hasErrors()) {
//검사 결과 오류 확인
List<ObjectError> allErrors = brResult.getAllErrors();
//객체와 관련된 오류
List<ObjectError> globalErrors = brResult.getGlobalErrors();
//멤버변수와 관련된 오류
List<FieldError> fieldErrors = brResult.getFieldErrors();
for(ObjectError objectError : allErrors) {
log.info("allError : " + objectError);
}
for(ObjectError objectError : globalErrors) {
log.info("globalError : " + objectError);
}
for(FieldError fieldError: fieldErrors) {
log.info("fieldError : " + fieldError.getDefaultMessage());
}
// redirect로는 brResult가 안넘어가고 , forwarding일때만 바인딩 오류 정보가 넘겨짐
return "quick/create"; // if문 아래는 실행하지 않는다
}
int result = this.quickService.createPost(quickVO);
log.info("createPost -> result : " + result);
return "redirect:/quick/detail?emailAdres="+quickVO.getEmailAdres();
}
/*
* 요청URI : /quick/detail
요청파라미터 : {emailAdres=test@test.com}
요청방식 : get
*/
@GetMapping("/detail")
public String detail(@RequestParam("emailAdres") String emailAdres , Model model) {
log.info("detail -> " + emailAdres);
QuickVO quickVO = this.quickService.detail(emailAdres);
model.addAttribute("quickVO" , quickVO);
return "quick/detail";
}
}
http://localhost/quick/create 페이지에서 이메일/패스워드 미 입력후 콘솔창 확인
if문에서 작성한 에러코드 콘솔에서 뜨는지 확인
jsp에서도 처리
quick/create.jsp에 아래 form 태그라이브러리 추가
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
quick/create.jsp에서 폼태그 스프링 폼태그로 변환
<form:form modelAttribute="quickVO" action="/quick/createPost?${_csrf.parameterName}=${_csrf.token}"
method="post" enctype="multipart/form-data">
<div class="card-body">
<div class="form-group">
<label for="exampleInputEmail1">Email address</label>
<!-- form:input => input type = "text" -->
<form:input class="form-control" path="emailAdres" placeholder="이메일" />
<font color="blue">
<form:errors path="emailAdres" />
</font>
</div>
<div class="form-group">
<label for="exampleInputPassword1">Password</label>
<!-- form:password => input type="password" -->
<form:password class="form-control" path="password" placeholder="비밀번호" />
<font color="red">
<form:errors path="password" />
</font>
</div>
...
...
...
...
</form:form>
QuickController create메소드 수정 -> form 태그에서 model값을 지정하기 위해
@GetMapping("/create")
public String create(@ModelAttribute("quickVO") QuickVO quickVO) {
// <form:form modelAttribute="quickVO"
// forwarding : jsp
return "quick/create"; // quick/create폴더
}
CRUD해보기
테이블 : CONTACT_INFO
컬럼 :
CI_CD varchar2(10) N.N CI20240205001
CI_NAME varchar2(10) N.N 방문자명
CI_MAIL varchar2(10) N.N 이메일
CI_SUBJ varchar2(10) N.N 방문 주제
CI_MESG CLOB타입 방문 내용
CI_REG_DT DATE 방문 예정일
REGISTER_ID 'admin'
REGISTER_DT sysdate(default처리)
create.jsp에 아래 코드 추가
<script type="text/javascript" src="/resources/ckeditor5/ckeditor.js"></script>
<link type="text/css" rel="stylesheet" href="/resources/ckeditor5/css/sample.css" media="screen" />
마지막 줄에 아래 코드 추가
<script>
<script type="text/javascript">
ClassicEditor.create( document.querySelector('#hpropOpinion'),{ckfinder:{uploadUrl:'/image/upload?${_csrf.parameterName}=${_csrf.token}'}})
.then(editor=>{window.editor=editor;})
.catch(err=>{console.error(err.stack);});
</script>
에디터에 작성시 에디터 란에 추가
$(function(){
$(".ck-blurred").keyup(function () {
console.log("str : " + window.editor.getData());
$("#ciMesg").val(window.editor.getData());
});
$(".ck-blurred").on("focusout",function () {
// focusout : 커서가 영역 밖으로 나올때 마지막 데이터까지 추가
$("#ciMesg").val(window.editor.getData());
});
UploadController 생성
package kr.or.ddit.controller;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Controller
public class UploadController {
//root-context.xml에 작성해놓은 경로
@Autowired
String uploadFolderDirect;
//ckeditor이미지 업로드 , data전송 -> ResponseBody어노테이션 추가(객체화)
@ResponseBody
@PostMapping("/upload/uploads")
public Map<String , Object> uploads(MultipartHttpServletRequest request) throws IllegalStateException, IOException{
// request에 이미지 객체가 담져겨 있다
// ckeditor 에서 파일을 보낼 때 upload : [파일] 형식으로 해서 넘어오기 때문에 upload라는 키의 밸류를 받아서 uploadFile에 저장함
// 열기 누르는 순간
MultipartFile uploadFile = request.getFile("upload");
log.info("uploads -> uploadFile : " + uploadFile);
// 파일의 오리지널 명
String originalFileName = uploadFile.getOriginalFilename();
log.info("uploads -> originalFileName : " + originalFileName);
// 파일의 확장자(개똥이.jpg)
String ext = originalFileName.substring(originalFileName.indexOf("."));
log.info("ext : " + ext); // .jpg
String newFileName = UUID.randomUUID() + ext; // sadlfkjsafd.jpg
// 이미지를 현재 경로와 연관된 파일에 저장하기 위해 현재 경로를 알아냄
// String realPath = request.getServletContext().getRealPath("/");
String url = request.getRequestURL().toString();
log.info("uploads->url(bef) : " + url);
// http://localhost/
// http://192.168.93.73/
url = url.substring(0, url.indexOf("/", 7));
log.info("uploads->url(aft) : " + url);
// 업로드 폴더에 저장
// 물리적 저장 경로.../upload + "\\" + sadlfkjsafd.jpg
String savePath = this.uploadFolderDirect+ "\\" + newFileName;
log.info("uploads -> savePath : " + savePath);
// 웹 경로
String uploadPath = "/resources/upload/" + newFileName;
// 설계
File file = new File(savePath);
// 파일 업로드 처리
uploadFile.transferTo(file);
Map<String, Object>map = new HashMap<>();
map.put("uploaded" , true);
map.put("url" , url + uploadPath);
// map : {uploaded = true , url=http://localhost/resources/upload/asdasdasd~~.jpg}
log.info("uploads -> map : " + map);
return map;
}
}
root-context.xml에 추가 -> 업로드 하는 동안까지의 이미지를 가져옴
<bean id="uploadFolderDirect" class="java.lang.String">
<constructor-arg value="C:\\eGovFrameDev-3.10.0-64bit\\workspace\\.metadata\\.plugins\\org.eclipse.wst.server.core\\tmp0\\wtpwebapps\\springProj\\resources\\upload"></constructor-arg>
</bean>
validation 체크
package kr.or.ddit.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import kr.or.ddit.service.ContactService;
import kr.or.ddit.vo.ContactInfoVO;
import lombok.extern.slf4j.Slf4j;
@RequestMapping("/contactInfo")
@Controller
@Slf4j
public class ContactInfoController {
@Autowired
ContactService contactService;
@Autowired
String uploadFolder;
@GetMapping("/create")
public String index() {
log.info("");
return "contactInfo/create";
}
/*
요청URI : /contactInfo/createPost
요청파라미터 : {ciName=개똥이,ciMail=test@test.com,ciSubj=채용상담,ciImgUrl=파일객체,
ciMesg=채용절차에 대한 상담,ciRegDt=2024/02/17}
요청방식 : post
*/
@PostMapping("/createPost")
public String createPost(@Validated ContactInfoVO contactInfoVO , BindingResult brResult) {
log.info("createPost->contactInfoVO : " + contactInfoVO);
contactInfoVO.setRegisterId("admin");
// brResult : true(오류발생) , false(오류없음)
// 파일업로드 및 insert처리(Impl에서..)
return "redirect:/contactInfo/detail?ciCd="+contactInfoVO.getCiCd();
}
}
package kr.or.ddit.vo;
import java.util.Date;
import javax.validation.constraints.Future;
import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.NotBlank;
import org.springframework.format.annotation.DateTimeFormat;
import lombok.Data;
@Data
public class ContactInfoVO {
private String ciCd;
@NotBlank(message="이름을 입력해 주세요")
private String ciName;
@NotBlank(message="이메일주소를 입력해 주세요")
@Email
private String ciMail;
@NotBlank(message="제목을 입력해 주세요")
private String ciSubj;
private String ciImgUrl;
private String ciMesg;
// String타입인 "2024-02-17" -> Date타입으로 변환
@DateTimeFormat(pattern="yyyy-MM-dd")
@Future(message="오늘 이후의 날짜만 선택 가능합니다.") // 오늘 이후의 날짜 체크(예약날짜이기 때문에)
private Date ciRegDt;
private String registerId;
private Date registDt;
}