result.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<title>Insert title here</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
<link rel="stylesheet" href="/css/main.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/sweetalert2@11"></script>
</head>
<body>
<div id="page">
<header th:replace="/fragments/header.html">
</header>
<nav th:replace="/fragments/nav.html">
</nav>
<div id="main">
<aside th:replace="/fragments/aside.html">
</aside>
<section>
<div th:text="${order}"></div>
<h1>주문이 완료되었습니다! (๑ᵔ⩊ᵔ๑)</h1>
<a class="btn btn-info" href="/order/list">주문 정보 보기</a>
</section>
</div>
<footer th:replace="/fragments/footer.html">
</footer>
</div>
</body>
</html>
src/main/java - com.example.demo.controller.mvc - OrderController
@Secured("ROLE_USER")
@AllArgsConstructor
@Controller
public class OrderController {
private OrderService service;
// 주문 처리 성공 화면으로 forward
@PostMapping("/order/new")
public String order(Integer addressNo, Principal principal, HttpSession session, RedirectAttributes ra) {
OrderDto.View dto = (OrderDto.View)session.getAttribute("dto");
service.add(addressNo, dto, principal.getName());
ra.addFlashAttribute("isNew", true);
return "redirect:/order/result";
}
// 주문 처리 성공 화면
@GetMapping("/order/result")
public String result(HttpServletRequest request) {
Map<String, ?> flashMap = RequestContextUtils.getInputFlashMap(request);
if (flashMap == null)
return "redirect:/";
return "order/result";
}
}
📝 /order/new를 거치지 않고 /order/result로 넘어갈 수 없게 하려고 한다. 이를 위해서 RedirectAttributes를 사용했다. /order/new에서 ra 값을 담아 이동시키고, /order/result에서 ra 값이 존재하는지를 확인한다.
💡 Session과 RedirectAttributes의 차이
Session은 값을 일정 시간 동안 저장해 놓지만 RedirectAttributes는 일회용에 가깝다. 예를 들어 내 정보 보기 화면에 들어가기 전에 비밀번호를 확인한다고 했을 때, Session을 이용하면 한번 비밀번호를 입력해 놓으면 일정 시간 동안은 다시 비밀번호를 확인하지 않지만 RedirectAttributes를 이용하면 내 정보 보기에 들어갈 때마다 비밀번호를 확인한다.
src/main/java - com.example.demo.service - OrderService
package com.example.demo.service;
@RequiredArgsConstructor
@Service
public class OrderService {
private final OrderDao orderDao;
private final OrderItemDao orderItemDao;
@Transactional
public void add(Integer addressNo, OrderDto.View dto, String loginId) {
Order order = new Order(null, loginId, DeliveryStatus.PAY, LocalDate.now(), dto.getTotalprice(), addressNo);
orderDao.save(order);
for (OrderItem orderItem : dto.getOrderItems()) {
orderItem.setOrderNo(order.getOrderNo());
orderItemDao.save(orderItem);
}
}
}
💡 Transactional
add()의 작업을 하나의 트랜잭션으로 묶는다. (모두 성공 아니면 모두 실패) 주로 서비스에서 DAO 작업들을 하나로 묶어 줄 때 사용한다. 예를 들어, Order와 OrderItem은 각각의 SQL이 존재하지만 주문이라는 작업을 위한 하나의 트랜잭션이라고 볼 수 있다. 따라서 Order 저장에는 성공하더라도 OrderItem 저장에 실패했다면 Order 저장까지 모두 날려 준다.
src/main/java - com.example.demo.dao - OrderDao
package com.example.demo.dao;
public interface OrderDao {
@SelectKey(statement="select order_seq.nextval from dual", keyProperty="orderNo", before=true, resultType=Integer.class)
@Insert("insert into orders values(#{orderNo}, #{username}, #{deliveryStatus}, #{orderday}, #{totalPrice}, #{addressNo})")
public Integer save(Order order);
}
src/main/java - com.example.demo.dao - OrderItemDao
package com.example.demo.dao;
public interface OrderItemDao {
@Insert("insert into order_item values(#{orderNo}, #{pno}, #{vendor}, #{name}, #{price}, #{count}, #{orderItemPrice})")
public void save(OrderItem orderItem);
}
list.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<title>Insert title here</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
<link rel="stylesheet" href="/css/main.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/sweetalert2@11"></script>
</head>
<body>
<div id="page">
<header th:replace="/fragments/header.html">
</header>
<nav th:replace="/fragments/nav.html">
</nav>
<div id="main">
<aside th:replace="/fragments/aside.html">
</aside>
<section>
<div th:text="${list}"></div>
</section>
</div>
<footer th:replace="/fragments/footer.html">
</footer>
</div>
</body>
</html>
src/main/java - com.example.demo.dto - OrderDto
package com.example.demo.dto;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class OrderDto {
@Data
@AllArgsConstructor
@Builder
public static class Read {
private Integer orderNo;
private String username;
private String deliveryStatus;
private String orderday;
private Integer totalPrice;
private String address;
}
}
src/main/java - com.example.demo.entity - Order
package com.example.demo.entity;
@Data
@AllArgsConstructor
public class Order {
public OrderDto.Read toRead() {
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
return OrderDto.Read.builder().orderNo(orderNo).username(username).deliveryStatus(deliveryStatus.getKorean())
.orderday(dtf.format(orderday)).totalPrice(totalPrice).build();
}
}
src/main/java - com.example.demo.controller.mvc - OrderController
package com.example.demo.controller.mvc;
@Secured("ROLE_USER")
@AllArgsConstructor
@Controller
public class OrderController {
private OrderService service;
@GetMapping("/order/list")
public void list(Model model, Principal principal) {
model.addAttribute("list", service.readAll(principal.getName()));
}
}
💡 ModelAndView
- Model과 View 모두 사용하는 경우 → 원래대로 사용
- View를 사용하지 않는 경우 → void 리턴
- View만 사용하는 경우 → String 리턴
- Model만 사용하는 경우 → Model 파라미터 받기
src/main/java - com.example.demo.service - OrderService
package com.example.demo.service;
@RequiredArgsConstructor
@Service
public class OrderService {
private final OrderDao orderDao;
private final OrderItemDao orderItemDao;
private final AddressDao addressDao;
private final ProductDao productDao;
@Value("${product.image.path}")
private String imagePath;
// 나의 주문 정보
public List<OrderDto.Read> readAll(String loginId) {
List<Order> list = orderDao.findByUsername(loginId);
List<OrderDto.Read> orderDtos = new ArrayList<>();
for (Order order : list) {
OrderDto.Read dto = order.toRead();
Address address = addressDao.findByUsernameAndAddressNo(loginId, order.getAddressNo());
dto.setAddress(address.getAddress1() + " " + address.getAddress2());
orderDtos.add(dto);
}
return orderDtos;
}
// 내가 주문한 물품 정보
public OrderDto.View orderOne(Integer pno, Integer count, String loginId) {
Product product = productDao.findById(pno);
OrderItem orderItem = new OrderItem(null, pno, product.getVendor(), product.getName(), imagePath+product.getImagename(), product.getPrice(), count, product.getPrice()*count);
return new OrderDto.View(product.getPrice() * count, Arrays.asList(orderItem));
}
}
src/main/java - com.example.demo.dao - OrderDao
package com.example.demo.dao;
public interface OrderDao {
@Select("select * from orders where username=#{username}")
public List<Order> findByUsername(String username);
}