1. 메뉴
부트스트랩 : https://www.w3schools.com/bootstrap4/bootstrap_get_started.asp
구글에서 만든 디자인 : https://materializecss.com/getting-started.html
여기선 부트 스트랩을 이용할 예정
bootstrap4 - navbar - Collapsing The Navigation Bar 복사 후 src/main/webapp/WEB-INF/views/index.jsp에 붙여넣기
controller 패키지의 BoardController를 만든 후 index 경로 설정
@Controller
public class BoardController {
// http://localhost:8000/blog 와 http://localhost:8000/blog/ 모두
@GetMapping({"", "/"})
public String index() {
return "index";
}
}
application.yml에 설정을 다음과 같이 해두었기 때문에 가능한 일이다. /WEB-INF/views/index.jsp를 보여줄 것이다.
spring:
mvc:
view:
prefix: /WEB-INF/views/
suffix: .jsp
[실행 화면]
Collapsible Navbar는 웹 사이즈가 줄어들면 자동으로 토글식으로 바뀐다.
2. footer
Bootstrap4 templates - demo - footer 부분 요소 복사 후 index.jsp에 붙여넣기
3. 메인화면 - 글 목록 부분
bootstrap4 - Cards - Card Images 복사 후 index.html에 붙여넣기
4. 회원가입 화면
먼저 index.jsp에서 script 구문을 body 마지막 부분으로 옮겨준다.
그 이유는 자바스크립트는 인터프리터 언어로 실행과 동시에 번역이 되기 때문에 순서가 중요하다. 따라서 기본적으로 자바 스크립트는 제일 밑에 적어주는 게 좋다.
로그인, 회원가입 화면에도 마찬가지로 메뉴(header)와 footer가 필요하다. 모든 페이지에서 또 같은 작업을 하는 수고를 덜기 위해 템플릿 작업을 해줘야 한다.
공용으로 사용할 footer와 header(메뉴)를 위해 layout 폴더에 footer.jsp와 header.jsp를 생성해준다.
header.jsp에는 index.jsp에서 header 부분을 footer.jsp에는 index.jsp에서 footer 부분을 분리해서 넣어준다.
그 후 index.jsp 파일에 include를 통해 삽입해주면 된다.
<%@ include file="layout/header.jsp" %>
...
<%@ include file="layout/footer.jsp" %>
만약 여기서 /layout/header.jsp라고 적으면 에러가 뜨면서 경로를 못찾는다.
회원가입 화면
메뉴에서 회원가입을 누르면 http://localhost:8000/user/joinForm 주소로 이동한다.
UserController.java 수정
package com.cos.blog.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class UserController {
@GetMapping("/user/joinForm")
public String joinForm() {
return "user/joinForm";
}
@GetMapping("/user/loginForm")
public String loginForm() {
return "user/loginForm";
}
}
user/joinForm 생성
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!-- user 폴더 입장에서는 layout 폴더는 상위폴더인 views로 올라가야 찾을 수 있으므로 다음과 같이 변경 -->
<%@ include file="../layout/header.jsp"%>
<div class="container">
<!-- 회원가입 폼 -->
</div>
<!-- user 폴더 입장에서는 layout 폴더는 상위폴더인 views로 올라가야 찾을 수 있으므로 다음과 같이 변경 -->
<%@ include file="../layout/footer.jsp"%>
부트스트랩의 BS4 Forms의 Bootstrap 4 Stacked Form을 이용하여 회원가입 폼을 만든다.
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ include file="../layout/header.jsp"%>
<div class="container">
<!-- 회원가입 폼 -->
<form>
<div class="form-group">
<label for="username">Username</label> <input type="text" class="form-control" placeholder="Enter username" id="username">
</div>
<div class="form-group">
<label for="email">Email</label> <input type="email" class="form-control" placeholder="Enter email" id="email">
</div>
<div class="form-group">
<label for="password">Password:</label> <input type="password" class="form-control" placeholder="Enter password" id="password">
</div>
<button type="submit" class="btn btn-primary">회원가입</button>
</form>
</div>
<%@ include file="../layout/footer.jsp"%>
회원가입 버튼을 누르면 실제로 회원가입이 되도록 설정해줘야 한다.
<form action="/user/join" method="POST">
...
</form>
form 태그의 action을 이용해서 할 수도 있지만 최근엔 자바스크립트로 해결한다. 따라서 form 구문 안의 회원가입 버튼을 form 구문 밖으로 빼줘야 한다.
<form>
...
</form>
<button id="btn-save" class="btn btn-primary">회원가입</button>
먼저 joinform에서 script를 걸어준다!
<div class="container">
<form>
...
</form>
<button id="btn-save" class="btn btn-primary">회원가입</button>
</div>
<script src="/js/user.js"></script>
static/js 폴더를 생성 후 user.js 파일을 작성한다. 스프링은 기본적으로 static 폴더에서 먼저 찾기 때문에 여기에 정적인 자원을 위치시키면 된다. (js는 정적인 자원)
let index = {
init: function() {
// jQuery 사용
// btn-save가 클릭이 되면 실행
// on("이벤트명", 동작)
$("#btn-save").on("click", () => {
this.save(); // save함수 이벤트로 호출
});
},
save: function(){
// alert('user의 save함수 호출');
// 자바스크립트 data 오브젝트에 제출된 자원 넣기!
// 제출된 자원은 id 값으로 찾는다,
let data = {
username: $("#username").val(),
password: $("#password").val(),
email: $("#email").val()
}
// console.log(data); // 콘솔에 찍어보기
// ajax 호출
// ajax 통신을 이용해서 3개의 데이터를 json으로 변경하여 insert요청!!
$.ajax().done().fail();
}
}
index.init();
여기서 ajax라는 기술이 사용된다.
Ajax란?
Asynchronous Javascript And Xml(비동기식 자바스크립트와 xml)의 약자로 자바스크립트의 라이브러리 중 하나이며, 브라우저가 가지고 있는 XMLHttpRequest 객체를 이용해 전체 페이지를 새로 고치지 않고도 페이지 일부만을 위한 데이터를 로드하는 기법이다.
즉, 자바스크립트를 사용해 비동기 통신, 클라이언트와 서버 간 XML 데이터를 주고 받을 수 있는 기술 !!
왜 회원가입에서 Ajax를 사용할까?
첫번째 이유는 요청에 대한 응답을 html이 아닌 Data(Json)를 받기 위해서이다.
클라이언트는 브라우저로 서버에게 요청을 하게 되는데 서버는 그에 대한 응답을 html로 한다. 브라우저가 회원가입을 요청했을 때 서버는 회원가입을 수행한 후 완료되었음을 알리기 위해 보통 메인화면(html)을 응답해준다.
여기서 클라이언트가 꼭 브라우저란 법은 없다. 만약 앱이라면 화면 디자인을 자바코드로 하게 되는데 서버가 html을 리턴하게 되면 앱이 이해할 수가 없다... 이런 문제를 해결하기 위해 Data 자체를 응답해주면 된다.
서버가 웹과 앱에 응답해줘야 할 것이 다르다. 이러면 서버를 2개를 만들어야 한다.. 이런 수고를 덜기 위해 앱과 웹의 리턴값을 Data로 통합시킨다.
그냥 웹도 Data를 리턴받고 다시 request를 해서 메인페이지를 요청하고 html을 받는 방식으로 하면 된다.
이러면 앱에서도 웹에서도 동작 가능한 서버를 만들 수 있다.
두번째 이유는 비동기 통신을 하기 위해서이다.
프로그램은 원래 일의 순서에 맞게 절차적으로 처리한다. 그런데 만약 중간에서 다운로드 작업이 오래 걸린다면 뒤의 쉬운 작업들도 다 딜레이된다. 시간이 오래 걸리는 다운로드 작업을 할 때에 비동기 방식으로 처리해서 딜레이를 줄여준다.
Ajax 적용
user.js
let index = {
init: function() {
...
},
save: function(){
...
// ajax 통신을 이용해서 3개의 데이터를 json으로 변경하여 insert요청!!
// ajax 호출시 default가 비동기 호출이라서 회원가입 수행이 오려걸려도 ㄱㅊ!
$.ajax({
// 회원가입 수행 요청
type: "POST",
url: "/blog/api/user",
data: JSON.stringify(data), // data는 자바스크립트 객체라서 자바에 던지면 못알아들으니까 JSON으로 변환해주기!
// data는 http body 데이터이다.
contentType: "application/json; charset=utf-8", // body 데이터가 어떤 타입인지(MIME)
// 요청을 서버로 해서 응답이 왔을 때 기본적으로 String 문자열인데
// 만약 생긴게 json이라면 javascript 오브젝트로 변경해준다.
dataType: "json"
// 사실 dataType: "json"을 안적어줘도 ajax가 통신을 성공하고 나서 json을 리턴해주면 자동으로 자바 오브젝트로 변환해줌
}).done(function(resp){
// 응답의 결과가 성공일 경우 done()
alert("회원가입이 완료되었습니다.");
// console.log(resp);
location.href="/blog";
}).fail(function(error){
// 응답의 결과가 실패일 경우 fail()
alert(JSON.stringify(error));
});
}
}
index.init();
controller/api/UserApiController.java
// 데이터만 리턴해줄 거라서
@RestController
public class UserApiController {
@PostMapping("/api/user")
public ResponseDto<Integer> save(@RequestBody User user) { // 요청받은 게 Json이니까
System.out.println("UserApiController : save 호출");
// 실제로 DB에 insert!
...
return new ResponseDto<Integer>(HttpStatus.OK, 1); // JACKSON 라이브러리가 자바오브젝트를 JSON으로 변환해서 리턴
}
}
dto/ResponseDto.java
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ResponseDto<T> {
HttpStatus status;
T data;
}
service/UserService.java
// 스프링이 컴포넌트 스캔으로 Bean에 등록해줌 (IoC)
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
// 회원가입이 하나의 트랜잭션으로 묶이게 된다.
@Transactional
public Integer 회원가입(User user) {
try {
userRepository.save(user);
// 정상이면 1 return
return 1;
} catch (Exception e) {
e.printStackTrace();
System.out.println("UserService : 회원가입() " + e.getMessage());
}
// 비정상이면 -1 return
return -1;
}
}
Service가 필요한 이유
1. 트랜잭션을 관리하기 위해서
2. 서비스라는 의미 때문
5만원이 있는 A가 2만원이 있는 B에게 3만원을 송금하는 서비스를 생각해보자
이를 위해선 A의 보유 금액을 2만원으로 update해주고 B의 보유 금액을 5만원으로 update해야 한다. 서비스라는 것은 DB에서 한번 update하는 것이 아닌 updqte, delete, insert 등의 로직이 모여서 하나의 기능이 되어야 한다. Repository의 경우는 단순히 CRUD 하나씩 들고 있다고 치면 서비스는 여러개를 들고 있는 하나의 기능이 된다. 만약 하나의 서비스 안 여러 로직 중 하나라도 실패한다면 서비스 장애가 생긴 것이므로 전부다 롤백해줘야 한다. 이 때 하나하나의 로직을 트랜잭션이라고 하는데 여러 트랜잭션들을 하나의 트랜잭션으로 묶어서 서비스화할 수 있는 것이다. 그래서 서비스가 필요하다!!
UserApiController에 UserService 의존성 주입 (DI)
@RestController
public class UserApiController {
// UserService의 @Service로 Bean에 등록된 상태라서 DI 가능
@Autowired
private UserService userService;
@PostMapping("/api/user")
public ResponseDto<Integer> save(@RequestBody User user) { // 요청받은 게 Json이니까
System.out.println("UserApiController : save 호출");
// 실제로 DB에 insert!
user.setRole(RoleType.USER);
int result = userService.회원가입(user);
// JACKSON 라이브러리가 자바오브젝트를 JSON으로 변환해서 리턴
return new ResponseDto<Integer>(HttpStatus.OK, result);
}
}
회원가입 후 workbench 확인해보면 잘 저장된 것을 볼 수 있다!
회원가입 버그 수정
같은 아이디로 회원가입을 시도했을 경우 즉, userRepository.save()에서 오류가 생겼을 경우 이전에 만들어둔 모든 Exception을 처리해주는 GlobalExceptionHandler가 실행된다.
GlobalExceptionHandler 수정
@ControllerAdvice
@RestController
public class GlobalExceptionHandler {
/*
수정 전
@ExceptionHandler(value=Exception.class)
public String handleArgumentException(Exception e) {
return "<h1>" + e.getMessage() + "</h1>";
}
*/
// 수정 후
@ExceptionHandler(value=Exception.class)
public ResponseDto<String> handleArgumentException(Exception e) {
return new ResponseDto<String>(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage());
}
}
이제 여기서 동일한 이름으로 회원가입을 하면 다음과 같이 에러가 뜰 것이다.
출처 : https://www.youtube.com/watch?v=f2IXBWi_8sA&list=PL93mKxaRDidECgjOBjPgI3Dyo8ka6Ilqm&index=35
'Spring > Blog 만들기 with SpringBoot' 카테고리의 다른 글
스프링 JPA의 OSIV 전략 (0) | 2022.07.09 |
---|---|
DB 격리수준, 스프링의 트랜잭션 (0) | 2022.07.09 |
스프링 기본 파싱 전략과 JSON 통신 (0) | 2022.06.13 |
delete 테스트, Exception 처리 (0) | 2022.05.16 |
update 테스트, JPA 영속성 컨텍스트 (0) | 2022.05.16 |