여러 단계에 걸친 Form 입력 처리하기
public class OrderForm { // 주문상품 정보 private int productId; private int qty; // 배송지 정보 private String postalCode; private String address1; private String address2; private String tel; private String memo; // 결재 정보 private String payType; private String payNumber; private String cardMonth; private String cardYear; private String months; private String payAmount; ... } // 요청핸들러 메소드에서 Model객체에 "orderForm"이라는 이름으로 저장되는 객체가 있으면, 그 객체는 세션에 "orderForm"이라는 속성명으로 저장시킨다. @SessionAttributes({"orderForm"}) public class OrderController { @PostMapping("/order/step-0") public String step0(Model model) { // OrderForm객체를 생성해서 model객체에 "orderForm"이라는 이름으로 저장한다. model.addAttribute("orderForm", new OrderForm()); return "order/form-1"; } } // 주문상품정보를 입력하고 서버로 제출한다. order/form-1.jsp <form method="post" action="/order/step-1"> <input type="number" name="productId" value="100"> <input type="number" name="qty" value="3"> <button type="submit">다음</button> </form> @SessionAttributes({"orderForm"}) public class OrderController { @PostMapping("/order/step-0") public String step0(Model model) { model.addAttribute("orderForm", new OrderForm()); return "order/form-1"; } // 주문상품 정보를 전달받는다. @PostMapping("/order/step-1") // 요청객체나 세션객체에 "orderForm"이라는 속성명으로 저장된 객체를 조회한다. // 앞 단계에서 세션객체 "orderForm"이라는 속성명으로 저장된 OrderForm객체를 가져온다. // 입력폼에서 전달한 주문상품정보를 orderForm객체의 멤버변수에 저장한다. public String step1(@ModelAttribute("orderForm") OrderForm orderForm) { return "order/form-2"; } } order/form-2.jsp <form method="post" action="/order/step-2"> <input type="text" name="postalCode" value="12345"> <input type="text" name="address1" value="서울특별시 종로구 봉익동"> <input type="text" name="address2" value="디아망빌딩 401호"> <input type="text" name="tel" value="010-1111-1111"> <input type="text" name="memo" value="사무실에 맡겨주세요"> <button type="submit">다음</button> </form> @SessionAttributes({"orderForm"}) public class OrderController { @PostMapping("/order/step-0") public String step0(Model model) { model.addAttribute("orderForm", new OrderForm()); return "order/form-1"; } @PostMapping("/order/step-1") public String step1(@ModelAttribute("orderForm") OrderForm orderForm) { return "order/form-2"; } // 배송지 정보를 전달받는다. @PostMapping("/order/step-2") // 요청객체나 세션객체에 "orderForm"이라는 속성명으로 저장된 객체를 조회한다. // 앞/앞 단계에서 세션객체 "orderForm"이라는 속성명으로 저장된 OrderForm객체를 가져온다. // 입력폼에서 전달한 배송정보를 orderForm객체의 멤버변수에 저장한다. // 현재 OrderForm객체에는 주문상품정보와 배송지 정보가 저장되어 있다. public String step2(@ModelAttribute("orderForm") OrderForm orderForm) { return "order/form-3"; } } order/form-3.jsp <form method="post" action="/order/step-3"> <input type="radio" name="payType" value="card" checked> <input type="text" name="payNumber" value="1111-2222-3333-4444"> <input type="text" name="cardMonth" value="04"> <input type="text" name="cardYear" value="27"> <input type="text" name="months" value="0"> <input type="text" name="payAmount" value="7000"/> <button type="submit">주문완료</button> </form> @SessionAttributes({"orderForm"}) public class OrderController { @PostMapping("/order/step-0") public String step0(Model model) { model.addAttribute("orderForm", new OrderForm()); return "order/form-1"; } @PostMapping("/order/step-1") public String step1(@ModelAttribute("orderForm") OrderForm orderForm) { return "order/form-2"; } // 결재정보를 전달받는다. @PostMapping("/order/step-3") // 요청객체나 세션객체에 "orderForm"이라는 속성명으로 저장된 객체를 조회한다. // 앞/앞/앞 단계에서 세션객체 "orderForm"이라는 속성명으로 저장된 OrderForm객체를 가져온다. // 입력폼에서 전달한 결재정보를 orderForm객체의 멤버변수에 저장한다. // 현재 OrderForm객체에는 주문상품정보, 배송지 정보, 결재정보가 저장되어 있다. public String step3(@ModelAttribute("orderForm") OrderForm orderForm, SessionStatus sessionStatus) { // 주문상품정보, 배송지정보, 결재정보가 저장된 OrderForm객체를 서비스에 전달하고, 업무로직을 호출해서 주문처리를 수행시킨다. orderService.createOrder(orderForm); // SessionStatus객체는 세션에 저장된 객체를 삭제한다. // 단, SessionStatus는 이 컨트롤러의 @SessionAttribues 어노테이션에서 지정한 속성명으로 저장된 객체만 삭제한다. sessionStatus.setComplete(); return "redirect:success"; } }
- 여러 개의 Form을 하나의 Form 안에 저장시킬 수 있다.
### 게시글 수정정보: PostModifyForm [no=97, userId=null, title=수정 연습, content=수정 연습 내용, readCount=0, commentCount=0, deleted=null, createdDate=null, updatedDate=null]
수정하기
PostController @GetMapping("/modify") public String modifyform(@RequestParam("postNo") int postNo, Model model) { PostDetailDto dto = postService.getPostDetail(postNo); PostModifyForm form = new PostModifyForm(); BeanUtils.copyProperties(dto, form); model.addAttribute("modifyPost", form); return "post/modify-form"; } @PostMapping("/modify") public String modify(@ModelAttribute("modifyPost") PostModifyForm postModifyForm) { System.out.println("### 게시글 수정정보: " + postModifyForm); postService.updatePost(postModifyForm); return "redirect:detail?postNo=" + postModifyForm.getNo(); }
PostModifyForm.java package com.sample.web.request; import java.sql.Date; public class PostModifyForm { private int no; private String userId; private String title; private String content; private int readCount; private int commentCount; private String deleted; private Date createdDate; private Date updatedDate; public int getNo() { return no; } public void setNo(int no) { this.no = no; } public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public int getReadCount() { return readCount; } public void setReadCount(int readCount) { this.readCount = readCount; } public int getCommentCount() { return commentCount; } public void setCommentCount(int commentCount) { this.commentCount = commentCount; } public String getDeleted() { return deleted; } public void setDeleted(String deleted) { this.deleted = deleted; } public Date getCreatedDate() { return createdDate; } public void setCreatedDate(Date createdDate) { this.createdDate = createdDate; } public Date getUpdatedDate() { return updatedDate; } public void setUpdatedDate(Date updatedDate) { this.updatedDate = updatedDate; } @Override public String toString() { return "PostModifyForm [no=" + no + ", userId=" + userId + ", title=" + title + ", content=" + content + ", readCount=" + readCount + ", commentCount=" + commentCount + ", deleted=" + deleted + ", createdDate=" + createdDate + ", updatedDate=" + updatedDate + "]"; } }
PostService.java public void updatePost(PostModifyForm postModifyForm) { Post post = new Post(); BeanUtils.copyProperties(postModifyForm, post); postMapper.updatePost(post); }
modify-form.jsp <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.2/font/bootstrap-icons.css"> <title>애플리케이션</title> </head> <body> <c:set var="menu" value="post" /> <%@ include file="../common/navbar.jsp" %> <div class="container my-3"> <div class="row mb-3"> <div class="col"> <h1 class="fs-4 border p-2 bg-light">게시글 수정</h1> </div> </div> <div class="row mb-3"> <div class="col"> <p>제목과 내용을 입력하세요</p> <form class="border bg-light p-3" method="post" action="modify"> <!-- 히든필드에 게시글 번호를 설정한다. --> <input type="hidden" name="no" value="${modifyPost.no }" /> <div class="mb-2"> <label class="form-label">제목</label> <input type="text" class="form-control" name="title" value="${modifyPost.title }"/> </div> <div class="mb-2"> <label class="form-label">내용</label> <textarea rows="4" class="form-control" name="content">${modifyPost.content }</textarea> </div> <div class="text-end"> <a href="detail?postNo=100" 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.2.3/dist/js/bootstrap.bundle.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script> </body> </html>
실행결과(수정 전)
실행결과(수정 중)
실행결과(수정 후)
자바 엔터프라이즈 애플리케이션 개발을 지원하는 오픈소스 프레임워크다.
특징
* POJO(Plain Old Java Object)을 사용해서 자바엔터프라이즈 애플리케이션 개발을 개발한다.
POJO - 순수 자바객체
특별한 외부 라이브러리를 가져와서 사용하지 않는 자바객체
특별한 실행환경에 종속되지 않는 자자객체
POJO는 재사용하기 쉽다.
POJO는 테스트하기 쉽다.
POJO는 코드가 단순하고, 디버깅하기 쉽다.
Service/ Vo 객체등이 POJO다.
순수 자바객체 = EJB가 제공하는 기능들을 사용하는데 그 기능을 사용하면 Old가 아니므로 스프링을 통해 완전 Old로 돌아가보자는 취지
IoC/DI의 구현체다.
IoC(Inversion of control:제어역전)
자신이 사용할 객체를 스스로 생성하지 않고, 외부로부터 전달받거나, 생성된 객체를 검색해서 사용하는 것을 말한다.
제어역전은 자신이 사용할 객체를 획득하는 방법이 역전(뒤바꼈다)되었다는 의미다.
Spring Container는 IoC의 구현체다.
IoC의 종류
의존성주입 - DI(Dependency Injection)
의존성검색 - DP(Dependency Pull), DL(Dependency Lookup)
DI(Dependency Injection:의존성주입)
자신이 사용할 객체를 스스로 생성하지 않고, 제 3자(Spring Container)로부터 제공(주입)받는 것이다.
스프링에서는 @Autowired를 이용해서 자동 의존성 주입을 지원한다.
스프링과 스프링 컨테이너
스프링은 프레임워크다.
스프링 컨테이너는 구현 클래스다.
스프링 컨테이너는 스프링 빈 설정정보를 읽어서 객체를 생성하고, 객체간의 의존관계를 파악해서 의존성 주입작업을 수행하는 객체다.
AOP의 구현체다.
AOP(Aspect Oriented Programming)는 관점지향 프로그래밍이다.
AOP는 기능을 공통관심사항(로깅, 트랜잭션처리, 예외처리)과 핵심관심사항(업무로직)으로 구분해서 개발하는 것이다.
AOP는 공통관심사항을 별도의 클래스로 구현하고, 핵심관심사항이 구현된 메소드가 실행될 때 지정된 공통관심사항이 같이 실행되게 하는 것이다.
AOP를 사용하면 개발자가 구현하는 메소드에는 핵심관심사항만 남아있게 된다.(개발자는 업무로직의 구현에만 집중하면 된다.)
* 선언적 트랜잭션처리는 AOP를 이용한 것이다.
AOP는 코드의 중복을 제거한다.
AOP는 코드가 간결해지고, 유지보수를 쉽게한다.
다양한 모듈로 구성되어 있다.
Test 모듈
spring-test : spring application에 대한 테스트를 지원한다.
Core Container 모듈
spring-beans, spring-core, spring-context : 객체 생성, 의존성 주입, 국제화처리 등을 지원하는 spring container를 제공한다.
spring-expression : spring EL 지원한다.
AOP 모듈
spring-aop : 관점지향프로그래밍을 지원한다.
DataAccess 모듈
spring-jdbc : jdbc를 활용한 데이터베이스 엑세스를 지원한다.
spring-tx : 트랜잭션처리를 지원한다.
spring-orm : 객체-관계 매핑프레임워크(ORM:Object-Reational Mapping)와의 연동을 지원한다.
객체와 테이블을 매핑해서 데이터베이스 엑세스 작업을 수행한다.
Web 모듈
spring-web : 웹 환경에 최적화된 spring container를 제공한다.
spring-webmvc : MVC 패턴의 웹 애플리케이션개발을 지원한다.
spring-websocket : 웹소켓기능을 지원한다.
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>
<div class="container">
<h1>홈</h1>
</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>
실행결과
- spring과 달리 localhost 뒤에 /home를 붙이지 않아도 연결이 가능하다.
Mapper파일 생성
UserMapper.java package com.example.mapper; import org.apache.ibatis.annotations.Mapper; @Mapper public interface UserMapper { }
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.example.mapper.UserMapper"> </mapper>
application.yml server: port: 80 # 톰캣서버의 port번호를 지정한다. 기본값은 8080이다. 0으로 지정하면 랜덤으로 지정된다.
logging:
level:
root: info # 애플리케이션의 기본 로그출력 레벨을 info로 지정한다.
'[com.example.mapper]': trace # com.example.mapper 패키지의 로그출력 레벨을 trace로 지정한다.
# mybatis가 실행하는 sql과 실행결과를 출력하게 한다.
spring:
datasource: # 커넥션 풀 설정을 지정한다.
url: jdbc:oracle:thin:@localhost:1521:xe
driver-class-name: oracle.jdbc.OracleDriver
username: hr
password: zxcv1234
mvc: # spring mvc 관련 설정을 지정한다.
view: # 뷰 페이지의 경로를 지정한다.
prefix: /WEB-INF/views/ # 뷰 이름 앞에 붙는 경로를 지정한다.
suffix: .jsp # 뷰 이름 뒤에 붙는 확장자를 지정한다.
mybatis:
mapper-locations:
- Mapper 파일 비생성시 오류가 발생하므로 생성한다.
- yml과 properties가 많이 사용되지만 그 중에서도 yml이 많이 사용된다.