*View에서 커맨드 객체에 접근하기.
커맨드 객체는 자동으로 반환되는 모델에 추가된다.
=용도 Form에서 Command객체를 받아서 비즈니스 로직을 처리 한 후, View에 그 데이터를 다시 전달할때 사용한다.
ModelAttribute Annotaion은
컨트롤러가 커맨드객체로 전달받은 데이터를
다시 View에 주기 위함이다.
@ModelAttribute를 사용하면 컨트롤러가 View에 전달할 데이터의 이름을 지정 할 수 있다.
//Form 데이터를 command객체로 만들어서 Controller에서 넘겨 받을 수 있다.
@Controller
public class BoardController {
@RequestMapping(“board/saveBoard.do”)
public String hello(Board command) {
…
}
}
//hello메서드에 @ModelAttribute를 파라미터로 사용해서 View가 전달한 Command객체의 이름을 변경한다.
//따라서 View는 ${faq.title}로 데이터를 접근 할 수 있다.
//Command객체의 이름의 객체를 faq로 변경한다는것.
@Controller
@RequestMapping(“/hello.do”)
public class HelloController {
@RequestMapping(method=RequestMethod.GET)
public String hello(@ModelAttribute(“faq”) Board board) {
…
}
//일반적이지 않지만,
메서드 위에 @ModelAttribute("Attribute Name") 하면
Controller가 View로 전달하는 Attribute의 이름이 지정한 Attribute이름으로 변경된다.
@Controller
public class HelloController {
@ModelAttribute(“modelAttrMessage")
public String getModelAttrMessage() {
return "bye bye...";
}
@RequestMapping("hello.do")
public String sayHello(Model model) {
model.addAttribute("message", "안녕하세요~");
return "hello";
}
}
...
</html>
<body>
${message}
${modelAttrMessage}
</body>
</html>
*폼입력값 검증
스프링은 유효성 검사를 위한 Validator 인터페이스와 유효성 검사 결과를 저장할 Errors인터페이스를 제공한다.
//Validator 인터페이스를 이용한 폼 입력값 검증
package org.springframework.validation;
public interface Validator {
boolean supports(Class<?> clazz); à Validator가 해당 클래스에 대한 validation 지원 여부
void validate(Object target, Errors errors);
à validation실행 validation결과 문제가 있는 경우 errors 객체에 문제에 대한 정보를 저장
}
//Validator 구현(스프링API제공)
package com.kosta.board.validator;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import com.kosta.board.domain.Board;
public class BoardValidator implements Validator {
@Override
public boolean supports(Class<?> arg0) {
return Board.class.isAssignableFrom(arg0);
}
@Override
public void validate(Object object, Errors errors) {
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "title", "required", "제목은 반드시 입력하세요.");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "content", "required", "내용을 입력하세요.");
}
}
//Valid 어노테이션과 @InitBinder 어노테이션을 이용한 검증 실행
@Valid: 스프링 프레임워크가 validation을 호출하도록 어노테이션을 이용하여 설정함
@InitBinder: Validator를 바인딩
@RequestMapping("saveBoard.do")
public ModelAndView saveBoard(@Valid Board board, BindingResult result) {
ModelAndView mav = new ModelAndView();
if (result.hasErrors()) {
mav.setViewName("board/write");
mav.addObject("board", board);
return mav;
}
boardService.saveBoard(board);
mav.setViewName("board/list");
mav.addObject("boards", boardService.findBoards());
return mav;
}
@InitBinder
protected void initBinder(WebDataBinder binder) {
binder.setValidator(new BoardValidator());
}
//Validation 결과 출력
spring 커스텀 태그를 사용
<%@taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<%@taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
…
…
<spring:hasBindErrors name="board" />
<form action="saveBoard.do" method="post">
<input type="hidden" name="number" value="${(board.number == '' || board.number == null) ? -1 : board.number}" />
<table>
<tr>
<td>제목</td>
<td><input type="text" name="title" value="${board.title}" /><form:errors path="board.title" />
*RESTFUL Service
URI에 정보를 포함하는것!
예>detaul_board?seq=10처럼 파라미터로 보내는것이 아니라, detail_board/10처럼 URI에 포함해서 받는다.
@PathVariable 어노테이션을 이용한 URI템플릿
-@RequestMapping 어노테이션값으로 {템플릿변수}를 사용한다.
-@PathVariable 어노테이션을 이용해서 {템플릿변수}와 동일한 이름을 갖는 파라미터를 추가한다.
//RequestMapping에 {seq}를 추가하고,
메서드 파라미터에 @PathVariable int seq를 추가한다.
= request.getParameter 해서 받아올 필요가 없다!
@RequestMapping("/detail_board{seq}")
public String detail_board(@PathVariable int seq, Model model) {
//int seq = Integer.parseInt(request.getParameter("seq"));
Board detail_board = dao.detailBoard(seq);
model.addAttribute("board", detail_board);
return "detail";
}
*Tiles
템플릿 기술
웹사이트 템플릿이란 쉽게 말해 여러분의 필요에 맞게 이미지나 동영상 혹은 다른 요소를 첨가해서 맞춤 제작하기 쉽도록 미리 만들어 놓은 웹페이지를 의미합니다.(출처위시캣)
스프링 타일즈는
뷰단의 탑,사이드메뉴,하단,메인 등을 페이지 include 방식으로 나누는
기존구조를 쉽게 적용하기 위한 템플릿 프레임워크입니다
장점은 include 디자인을 변경하면 페이지를 전체적으로 수정해야하는 번거로움을 없애고
일관적인 페이지 관리를 가능하도록합니다(출처: 물고기개발자님의 블로그)
<서버는 그저 데이터를 던져주고, 클라이언트 프로그램(웹브라우저 등..)에서 예쁘게 보여주도록 한다! vue.js react.js>
추가할것 -> tiles 라이브러리, header body footer, tiles라이브러리 xml
-예제: header, footer는 고정하고 페이지에 따라 body만 변경하도록 구현
//tiles 설정에 필요한 라이브러리 dependency 추가
<!-- tiles! -->
<!-- https://mvnrepository.com/artifact/org.apache.tiles/tiles-jsp -->
<dependency>
<groupId>org.apache.tiles</groupId>
<artifactId>tiles-jsp</artifactId>
<version>3.0.7</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.tiles/tiles-core -->
<dependency>
<groupId>org.apache.tiles</groupId>
<artifactId>tiles-core</artifactId>
<version>3.0.7</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.tiles/tiles-api -->
<dependency>
<groupId>org.apache.tiles</groupId>
<artifactId>tiles-api</artifactId>
<version>3.0.7</version>
</dependency>
// footer, header.jsp
//footer.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
********* 푸터 영역 **********
//header.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
********* 헤더 영역 **********
//template.jsp
//tablib에서 prefix tiles 추가.
//template를 구성해준다. 예제에서는 헤더영역과 푸터영역은 고정이고 바디영역만 변경되도록 한다.
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="tiles" uri="http://tiles.apache.org/tags-tiles" %>
<!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>Insert title here</title>
</head>
<body>
<!-- 헤더영역 -->
<tiles:insertAttribute name="header"/>
<hr>
<!-- 바디영역 -->
<tiles:insertAttribute name="body"/>
<hr>
<!-- 푸터영역 -->
<tiles:insertAttribute name="footer"/>
</body>
</html>
//tiles에서 설정에 사용하는 xml.
definition name은 해당 요청이 들어올때 실행되는것이다.
1) base_layout은 고정해서 표시할 영역이다. 아래 definition에서는 base_layout을 extends하여 사용할것이다.
2)아래에서는 기본적으로 base_layout을 extends하여 body만 jsp로 변경해주는 내용이다.
예를들어, inser_form으로 요청이 들어올때 기존에는 ViewResolver에서 jsp파일로 연결해줬지만,
이제는 tiles를 타서 헤더와 푸터를 표현해 준후, body에 개별적으로 jsp 내용을 넣어준다.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE tiles-definitions PUBLIC
"-//Apache Software Foundation//DTD Tiles Configuration 3.0//EN"
"http://tiles.apache.org/dtds/tiles-config_3_0.dtd">
<!-- header랑 footer는 바뀌지 않고 body만 바뀐다. -->
<!-- template을 요청해야 header와 footer가 같이 보인다.! insert_form.jsp을 요청하는것이 아니라,
definition이 호출되는것. -->
<tiles-definitions>
<definition name="base_layout" template="/view/module/template.jsp">
<put-attribute name="header" value="/view/module/header.jsp" />
<put-attribute name="footer" value="/view/module/footer.jsp" />
</definition>
<!-- base_layout을 extends하므로.. body만 바꿔준다. -->
<definition name="insert_form" extends="base_layout">
<put-attribute name="body" value="/view/inesrt_form.jsp" />
</definition>
<definition name="list" extends="base_layout">
<put-attribute name="body" value="/view/list.jsp" />
</definition>
<definition name="detail" extends="base_layout">
<put-attribute name="body" value="/view/detail.jsp" />
</definition>
</tiles-definitions>
//그렇다면 의문이 생긴다. 기존에 viewResolver에서는 View에 prefix로 /view/를 붙여주고 suffix로는 .jsp를 붙여줘서
자동으로 /view/insertform.jsp로 연결해줬는데. tiles로 연결하려면 어떻게 해야 하는걸까
=> 답은 ViewResolver를 새로 하나 만드는것이다.
//springapp-servlet.xml에 tiles setting하기 (xml파일 세팅)
//list이므로 여러개 가능.
<!-- Tiles Setting -->
<bean id="tilesConfigurer"
class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
<property name="definitions">
<list>
<value>/WEB-INF/tiles2def.xml</value>
</list>
</property>
</bean>
//새로운 viewResolver를 생성한다. UrlBasedViewResolver를 사용한다.
하지만 여기서 새로운 의문이 생긴다.
tiles를 사용하지 않는 요청은 어떻게 처리해야 하는걸까?
tiles에서 매칭되지 않은 요청은 404에러로 빠지는걸까?
=> 답은 property order를 사용하여 우선순위를 주는것이다.
1순위로 tiles에서 해당하는 요청에 대한 definition이 있는지 확인 후,
<!--tiles를 사용하는 View Resolver = URlBasedViewResolver-->
<bean id="viewResolver2"
class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<property name="viewClass"
value="org.springframework.web.servlet.view.tiles3.TilesView" />
<property name="order" value="1"/>
</bean>
2순위로 기존의 internalResourceViewResolver를 매칭되게 한다.
그러니까 정리하자면,
View를 return할때 tiles에 매칭되는것이 있는지 찾고-
없으면 2순위로 InternalResourceViewResolver로 prefix, suffix를붙여준다.
<!-- jsp를 쓸때는 internalResourceViewResolver를 사용한다. -->
<!-- InternalResourceViewResolver 우선순위를 줘야한다. -->
<!-- 그러니까, tiles Resolver에 매칭되는것이 없으면 internal-을 타게 우선순위를 준다. -->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/view/" />
<property name="suffix" value=".jsp" />
<property name="order" value="2"/>
</bean>
*FileUPload
//insert_form을 수정해서, file을 업로드 할 수 있도록 input태그를 추가한다. AttributeName은 uploadFile.
//form의 enctype을 multipart/form-data로 지정해준다.
<form:form action="board_insert" method="post"
commandName="boardCommand"
enctype="multipart/form-data">
작성자 : <form:input type="text" path="writer"/>
<form:errors path="writer" cssClass="error"/><br>
제목 : <form:input type="text" path="title"/>
<form:errors path="title" cssClass="error"/><br>
파일 : <input type="file" name="uploadFile"/><br>
내용 <br>
<form:textarea rows="6" cols="70" path="contents"/>
<br>
<input type="submit" value="등록">
</form:form>
//servlet xml에 MultipartResolver 설정
Multipart 지원 기능을 사용하려면 먼저 MultipartResolver를 servlet xml파일에 등록해야 한다.
<!주의 스프링에서 기본으로 제공하는 MultipartResolver는 CommonsMultipartResolver이다. CommonsMultipartResolver를 MultipartResolver로 사용하려면 다음과 같이 빈 이름으로 "multipartResolver"를 사용해서 등록.>
※ DispatcherServlet은 이름이 "multipartResolver"인 빈을 사용하기 때문에 다른 이름(아이디)을 지정할경우 MultipartResolver로 사용되지 않음.
=> 출처(자바킹 IT정복님 블로그)
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver"></bean>
//Form에서 Controller에 업로드할 파일을 전달 위해서
command 객체인 Board클래스에도 MultipartFile필드와,
DB에 파일이름을 저장하기 위해 StringType의 fname 필드를 선언해준다.
private MultipartFile uploadFile;
private String fname;
//Controller에 업로드 파일이 위치할 Path를 정해준다.
private String uploadDir = "C:/Tomcat 8.0/upload";
//기존 board_insert 기능을 하는 메서드를 수정해서,
커맨드객체에(Form데이터를 객체로 받음) 있는 uploadFile(multipartFile)을 가져와서,
1)filename을 multipartFile객체의 getOriginalFilename메서드로 가져와서 board객체에 지정 해 준 후,
multipartFile객체의 transferTo메서드를 이용해
File객체를 만들어서 최종 업로드 하게 된다(uploadPath와 fileName을 파라미터로 받음)
@RequestMapping(value="/board_insert", method=RequestMethod.POST)
public String board_insert(@ModelAttribute("boardCommand") @Valid Board board, BindingResult errors){
if(errors.hasErrors()){
System.out.println("에러 발생");
return "insert_form";
}
MultipartFile mutipartFile = board.getUploadFile();
if(mutipartFile != null){
String filename = mutipartFile.getOriginalFilename();
board.setFname(filename);
try {
mutipartFile.transferTo(new File(uploadDir, filename));
} catch (Exception e) {
e.printStackTrace();
}
}
dao.insert(board);
return "redirect:board_list";
}
*FileDownload
1)detail.jsp board_download컨트롤러에 파라미터를 fname으로 전달한다.
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!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>Insert title here</title>
</head>
<body>
<ul>
<li>번호: ${board.seq }</li>
<li>제목: ${board.title }</li>
<li>작성자: ${board.writer }</li>
<li>파일: <a href="board_download?fname=${board.fname }">${board.fname }</a></li>
<li>내용: ${board.contents }</li>
</ul>
</body>
</html>
2)컨트롤러
detail.jsp에서 fname으로 파일 이름을 넘긴다. 그것을 RequestParam을 이용해서 filename으로 받는다.
File객체를 생성한다(uploadPath, 파라미터로 넘겨받은 파일이름을 전달인자로 사용)
File객체는 downloadFile라는 이름으로 Model에 추가하고
=>downloadView로 이동하게 된다.
@RequestMapping("/board_download")
public String board_download(@RequestParam("fname") String filename,
Model model)throws Exception{
File file = new File(uploadDir, filename);
model.addAttribute("downloadFile", file);
return "downloadView";
}
3)Download 뷰.
AbstractView를 이용하면 새로운 뷰를 만들 수 있다.
//파일의 다운로드를 위해 컨텐츠 타입을 application/download로 설정
//model에서 downloadFile을 가져와서(Controller가 저장함), file객체로 만든다.
//그리고 file의 length를 가져와서 reponse컨텐츠의 크기를 지정
//전송되는 데이터의 인코딩은 바이너리 타입이라는것을 명시
=> 출처(자바킹 IT정복님 블로그)
...
import org.springframework.util.FileCopyUtils;
import org.springframework.web.servlet.view.AbstractView;
public class DownloadView extends AbstractView {
public DownloadView() {
setContentType("application/download; charset=utf-8");
}
@Override
protected void renderMergedOutputModel(Map<String, Object> model,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
File file = (File) model.get("downloadFile");
System.out.println("file: " + file.getName());
response.setContentType(getContentType());
response.setContentLength((int) file.length());
String userAgent = request.getHeader("User-Agent");
boolean ie = userAgent.indexOf("MSIE") > -1;
String fileName = null;
if (ie) {
fileName = URLEncoder.encode(file.getName(), "utf-8");
} else {
fileName = new String(file.getName().getBytes("utf-8"),
"iso-8859-1");
}
response.setHeader("Content-Disposition", "attachment; filename=\""
+ fileName + "\";");
response.setHeader("Content-Transfer-Encoding", "binary");
OutputStream out = response.getOutputStream();
FileInputStream fis = null;
try {
fis = new FileInputStream(file);
FileCopyUtils.copy(fis, out);
} finally {
if (fis != null)
try {
fis.close();
} catch (IOException ex) {
}
}
out.flush();
}
}
4) java파일을 View로 쓰기위해선 우선
- downloadView를 객체로 등록하기(bean태그 이용)
- BeanNameViewResolver를 새로운 viewResolver로 등록하기
- 우선순위 조정
<bean id="downloadView" class="kosta.view.DownloadView"/>
<bean id="viewResolver3"
class="org.springframework.web.servlet.view.BeanNameViewResolver">
<property name="order" value="1"/>
</bean>
*JSON데이터를 처리하는 전용 컨트롤러 구현
//boardAjax를 요청한다.
<!DOCTYPE html>
<html>
<head>
<meta charset="EUC-KR">
<title>Insert title here</title>
</head>
<script type="text/javascript">
location.href ="boardAjax";
</script>
<body>
</body>
</html>
//boardAjax.jsp로 이동한다.
@RequestMapping("/boardAjax")
public String client() {
return "boardAjax";
}
//boardAjax.jsp.
//board_json을 요청한다.
*이때 요청은 JSON데이터를 처리하는 컨트롤러에 있다.
<%@ page language="java" contentType="text/html; charset=EUC-KR"
pageEncoding="EUC-KR"%>
<!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=EUC-KR">
<title>Insert title here</title>
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script type="text/javascript">
$(function(){
$.getJSON("board_json", function(data){
//var data = responseData.list;
//alert(data);
$.each(data, function(index, board){
$('#result').append('<tr>' +
'<td>' + board.seq + '</td>' +
'<td>' + board.title + '</td>'+
'<td>' + board.writer + '</td>'+
'<td>' + board.hitcount + '</td>'+
'<td>' + board.regdate + '</td>'+
'</tr>');
});
});
});
</script>
</head>
<body>
<table id="result" border="1">
<tr>
<td>글번호</td>
<td>글제목</td>
<td>작성자</td>
<td>조회수</td>
<td>등록일</td>
</tr>
</table>
</body>
</html>
//클래스명 위에@RestController 어노테이션을 추가해준다.
추가로, jackson Databind 라이브러리를 pom.xml에 추가해준다.
//list를 return해주면 boardAjax.jsp에 전달된다.
그러면 최종적으로 boardAjax.jsp는 data를 받아서 each로 각각 처리 한 후
DOM element를 추가해준다-
//JSON을 만들어주는 전문 컨트롤러 + jackson Databind 라이브러리 추가.
@RestController
public class JsonController {
private BoardDao dao;
public JsonController(){};
public BoardDao getDao() {
return dao;
}
@Autowired
public void setDao(BoardDao dao) {
this.dao = dao;
}
@RequestMapping("board_json")
public List<Board> board_json() {
List<Board> list = dao.listBoard();
return list;
}
}
*Web기반 AOP
//라이브러리 추가
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.3.2.RELEASE</version>
</dependency>
//AOP를 이용해서
세션관리 해보자.
//최초 실행 view
<%@ page language="java" contentType="text/html; charset=EUC-KR"
pageEncoding="EUC-KR"%>
<!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=EUC-KR">
<title>Insert title here</title>
</head>
<body>
<ul>
<!-- session 체크 -->
<li><a href="session.do">No Session</a></li>
<li><a href="session_add.do">Yes Session</a></li>
</ul>
</body>
</html>
//Session Aspect
공통관심사항으로 등록하기 위해 Aspect를 등록한다.
클래스 위에 @Aspect 선언
@Around를 사용하면 핵심관심사항 전 후로 실행하게 된다.
execution 안의 의미는.. kosta.controller안의 모든 클래스에서.. 메서드가 exe로 끝나는 애들은 전부 핵심 관심사항으로 보겠다는 의미!
핵심 관심사항이 전달받은 파라미터들은 ProceedingjoinPoint객체에서 전달 받을 수 있다.
//핵심관심사항 메서드는 HttpServletRequest 객체를 갖는다!
@RequestMapping("/session.do")
public String session_exe(HttpServletRequest request) {
System.out.println("핵심");
return "session/session_success";
}
우리는 세션에 사용자 정보가 있는지 확인해야한다.
우선 ProceedingJoinPoint의 getArgs메서드를 사용하면 파라미터를 Object타입으로 배열형식으로 가져 올 수 있다.
(하지만 파라미터는 HttpServletRequest하나이므로, 0번째 요소를 가져온다.)
가져온 Session객체에서, 특정 Attribute가 있는지 여부를 확인하고,
try Catch문을 이용해서 각 다른 View를 지정해준다.
이때, try가 무사히 실행되면 ProceedingJoinPoint 객체의 proceed메서드를 호출하는데, 이때 비로소 핵심관심사항을 실행하는것이다!
(catch를 타게되면 실행하지 않음.)
//session체크용 공통관심사항 Aspect
//공통관심사항: 세션체크
@Aspect
public class SessionAspect {
//pointCut 설정
//어떤것을 포함시키고, 포함시키지 않을지..
//exe로 끝나는애만 공통관심사항 체크를 한다.
//모든 클래스 중, exe로 끝나는 메서드가 실행될때 실행한다!
@Around("execution(public * kosta.controller.*.*exe(..))") //이전과 이후로 실행한다.
public String sessionCheck(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("aspect를 타고 있습니다.");
Object[] obj = joinPoint.getArgs(); // 파라미터를 배열 형식으로 가져온다.
HttpServletRequest request = (HttpServletRequest) obj[0];//베열형식인데 0번째 index
HttpSession session = request.getSession();
String name = (String)session.getAttribute("name");
String view = "session/session_fail"; //실패시..
try {
if(name == null) {
throw new Exception("no Session");
}
view = (String)joinPoint.proceed(); // session_exe()를 호출하는것과 같다. 핵심관심사항을 호출하는것.
}
//원래 호출하고자 하는 핵심 관심사항임.!
//핵심관심사항 호출!
//success_return을 대입.
catch (Exception e) {
return view;
}
return view;
}
}
//Aspect클래스 선언 후 객체화 해주고 Proxy 설정을 해준다.
<!-- AOP Setting -->
<aop:aspectj-autoproxy/>
<bean id="sessionAspect" class="kosta.controller.SessionAspect"></bean>
//Session controller
package kosta.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
//annotaion을 이용한 AOP 처리.
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class SessionController {
@RequestMapping("/session_req.do")
public String session_req() {
return "session/session_req";
}
//핵심 관심사항.
//세션체크가 필요한 메서드 이름은 _exe를 붙인다.
//공통관심사항을 거쳐야 한다. => Aspect
@RequestMapping("/session.do")
public String session_exe(HttpServletRequest request) {
System.out.println("핵심");
return "session/session_success";
}
@RequestMapping("/session_add.do")
public String session_add() {
return "session/session_add";
}
}
*트랜잭션
업무로직(비즈니스 로직)의 한 단위
Spring Transaction 개요
-트랜잭션은 성공적으로 처리되거나 또는 하나라도 실패하면 완전히 실패처리를 해야 하는 경우에 사용.
-스프링에서 트랜잭션 관리기능을 지원하고, 간단한 설정으로 트랜잭션 처리가 가능함.
스프링 3.x에서는 Annotaion기반이 많아짐.
1)JDBC 기반 트랜잭션 관리자 설정
트랜잭션의 단위는 서비스 메서드 기준이다.
//롤백 조건: Exception 발생시 rollback
<tx:annotation-driven transaction-manager="transactionManager"/>
@Transactional(propagation=Propagation.REQUIRED,
rollbackFor={Exception.class})
public ModelAndView order(int amount2, String id)throws
Exception{
if(item.getAmount() < amount2){
throw new Exception("재고부족");
}
addOrder(order);//order 추가
updateItem(of);//item 재고수정
return mav;
}
예제> 주문테이블과 수량관리 테이블이 있다.
'주문서비스'의 로직은
1)주문정보를 주문테이블에 insert하고,
2)수량을 감소시키는것이다.
둘중 하나라도 실패한다면 전부 롤백해야 한다(주문정보는 들어갔는데, 수량이 충분하지 않아서 수량감소 쿼리가 실패하면 정말 끔찍)
=>따라서 주문서비스를 하나의 트랜잭션으로 관리하고자 한다.
//orderAction 서비스
메서드를 Transaction으로 관리하기 위해
메서드 위에 @Transactional 어노테이션을 선언해주고,
rollback단위는 Exception발생시로 설정해준다.
실패하면 예외발생으로 롤백하는게 Transaction관리의 핵심이라고 할 수 있다.
@Service
public class OrderService {
private OrderDao orderDao;
private ItemDao itemDao;
//트랜잭션은 서비스 메서드 단위임.
//얘가 하나의 트랜잭션이 됨.
//두작업이 모두 성공해야 한다. 하나라도 실패하면 실패.
@Transactional(propagation=Propagation.REQUIRED,
rollbackFor={Exception.class}) //transaction잡기, 트랜잭션 단위가 없으면 새로운 트랜잭션 단위를 만들라는것.
public void orderAction(Order order) throws Exception{
orderDao.addOrder(order);//주문 쿼리넣음
//1)수량을 구해온다.
if(itemDao.findItem(order.getNo()).getAmount() < order.getAmount()) {//재고가 부족한 상황이면!
throw new Exception("재고 부족!");
}
//2)재고를 조정한다.
itemDao.updateItem(order);
}
public OrderDao getOrderDao() {
return orderDao;
}
@Autowired
public void setOrderDao(OrderDao orderDao) {
this.orderDao = orderDao;
}
public ItemDao getItemDao() {
return itemDao;
}
@Autowired
public void setItemDao(ItemDao itemDao) {
this.itemDao = itemDao;
}
}
'Today I learned' 카테고리의 다른 글
1월29일 (0) | 2019.01.29 |
---|---|
URI와 URL (0) | 2019.01.28 |
1월25일 (0) | 2019.01.25 |
1월24일 봄이오나봄, Spring 프레임워크 (0) | 2019.01.24 |
프런트 컨트롤러 디자인 패턴 (0) | 2019.01.21 |
댓글