스프링부트 with JPA

스프링부트 with JPA 블로그 2(21~40강)

yukuda 2024. 2. 22. 20:21
728x90

21강 Reply 테이블 생성

package com.cos.blog.model;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.CreationTimestamp;

import javax.persistence.*;
import java.sql.Timestamp;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
public class Reply {	// 답변

	@Id //Primary key
	@GeneratedValue(strategy = GenerationType.IDENTITY) // 프로젝트에서 연결된 DB의 넘버링 전략을 따라간다
	private int id; // 시퀀스, auto_increment

	@Column(nullable = false, length = 200)
	private String content;

	@ManyToOne
	@JoinColumn(name="boardId") // 필드명
	private Board board;	// 게시판

	@ManyToOne
	@JoinColumn(name="userId")
	private User user; // 답변을 누가 적었는지

	@CreationTimestamp
	private Timestamp createDate;
}

22강 연관관계의 주인

연관관계의 주인 = FK를 가진 오브젝트

23강 JSON 사용법

json이 yml보다 먼저 나왔고 표준임

24강 회원가입 위한 insert 테스트

UserRepository.java

package com.cos.blog.repository;

import com.cos.blog.model.User;
import org.springframework.data.jpa.repository.JpaRepository;

// DAO
// 자동으로 bean등록이 된다.
// @Repository 생략 가능하다.
public interface UserRepository extends JpaRepository<User,Integer> {
	
}

DummyControllerTest.java

package com.cos.blog.test;

import com.cos.blog.model.User;
import com.cos.blog.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController // 데이터만 return해주는, 응답만 해주는
public class DummyControllerTest {

	@Autowired  // 의존성 주입(DI)
	private UserRepository userRepository;

	// http://localhost:8000/blog/dummy/join (요청)
	// http의 body에 username, password, eamil 데이터를 가지고 (요청)
	@PostMapping("/dummy/join") // 회원가입을 할꺼기 때문에, insert할꺼니깐
	public String join(User user) { // key=value (약속된 규칙)
		System.out.println("id: "+user.getId());
		System.out.println("username: "+user.getUsername());
		System.out.println("password: "+user.getPassword());
		System.out.println("email: "+user.getEmail());
		System.out.println("role: "+user.getRole());
		System.out.println("createDate: "+user.getCreateDate());

		userRepository.save(user);
		return "회원가입이 완료되었습니다.";
	}
}

25강 회원가입을 위한 enum사용법

role 값이 안나옴 →

User.java 에 role 수정

DummyControllerTest.java role 수정

26강 id로 select 테스트

Postman으로 3개를 만듬

DummyControllerTest.java 수정

package com.cos.blog.test;

import com.cos.blog.model.RoleType;
import com.cos.blog.model.User;
import com.cos.blog.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.function.Supplier;

// html파일이 아니라 data를 리턴해주는 controller = RestController
@RestController // 데이터만 return해주는, 응답만 해주는
public class DummyControllerTest {

	@Autowired  // 의존성 주입(DI)
	private UserRepository userRepository;

	// {id} 주소로 파라메터를 전달 반들 수 있음.
	// http://localhost:8000/blog/dummy/user/3
	@GetMapping("/dummy/user/{id}")
	public User detail(@PathVariable int id) {
		// user/4를 찾으면 내가 데이터베이스에서 못찾아오게 되면 user가 null이 될 것 아냐?
		// 그럼 return null 이 리턴이 되잖아.. 그럼 프로그램에 문제가 있지 않겠니?
		// Optional로 너의 User 객체를 감싸서 가져올테니 null인지 아닌지 판단해서 return해!

//		람다식
//		User user = userRepository.findById(id).orElseThrow(()-> {
//				return new IllegalArgumentException("해당 유저는 없습니다.");
//		});

		User user = userRepository.findById(id).orElseThrow(new Supplier<IllegalArgumentException>() {
			@Override
			public IllegalArgumentException get() {
				return new IllegalArgumentException("해당 유저는 없습니다. id : "+id);
			}
		});
		// 요청 : 웹브라우저
		// user 객체 = 자바 오브젝트
		// 변환 ( 웹브라우저가 이해할 수 있는 데이터) -> json (Gson 라이브러리)
		// 스프링부트 = MessageConverter라는 애가 응답시에 자동 작동
		// 만약에 자바 오브젝트를 리턴하게 되면 MessageConverter가 Jackson 라이브러리를 호출해서
		// user 오브젝트를 json으로 변환해서 브라우저에게 던져줍니다.
		return user;
	}

	// http://localhost:8000/blog/dummy/join (요청)
	// http의 body에 username, password, eamil 데이터를 가지고 (요청)
	@PostMapping("/dummy/join") // 회원가입을 할꺼기 때문에, insert할꺼니깐
	public String join(User user) { // key=value (약속된 규칙)
		System.out.println("id: "+user.getId());
		System.out.println("username: "+user.getUsername());
		System.out.println("password: "+user.getPassword());
		System.out.println("email: "+user.getEmail());
		System.out.println("role: "+user.getRole());
		System.out.println("createDate: "+user.getCreateDate());

		user.setRole(RoleType.USER);
		userRepository.save(user);
		return "회원가입이 완료되었습니다.";
	}
}

27강 전체 select 및 paging 테스트

DummyControllerTest.java 수정

28강 update 테스트

DummyControllerTest.java 수정

save함수는 권장하지 않음 @Transactional 어노테이션 이용

DummyControllerTest.java 수정

@Transactional 어노테이션을 사용하면, userRepository.save(user); 를 쓰지 않더라도 값을 변경하면 데이터가 update된다. 이것을 '더티체킹'이라 한다.

29강 영속성 컨텍스트와 더티체킹

30강 삭제하기 테스트

DummyControllerTest.java 수정

 

31강 Exception처리하기

GlobalExceptionHandler.java

package com.cos.blog.handler;

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;

@ControllerAdvice
@RestController
public class GlobalExceptionHandler {

	@ExceptionHandler(value =Exception.class)
	public String handleArgumentException(IllegalArgumentException e) {
		return "<h1>"+e.getMessage()+"</h1>";
	}
}

32강 스프링 기본파싱전략과 json통신

1. Get요청 (select)

ex) http://localhost:8000/blog/user?username=ssar

특징 : body로 데이터를 담아 보내지 않음.

2. Post, Put, Delete 요청 (데이터를 변경)

  • 데이터를 담아보내야 할 것이 많음.
  • username, password, email, address, createDate
  • form 태그 method='Post'
  • form 태그 → get요청, post요청 (key=value)
  • 그래서 자바스크립트로 요청을 해야함.

 

통일 : 자바스크립트로 ajax요청 + 데이터는 json으로 통일!! - 사용O

form:form 태그 → post요청, put요청, delete요청, get요청 가능 - 사용X

3. 스프링 컨트롤러의 파싱 전략 1

스프링 컨트롤러는 key=value 데이터를 자동으로 파싱하여 변수에 담아준다.

가령 get요청은 key=value이고 post요청중에 x-www-form-urlencoded (form태그를 만들어서 데이터 전송) 시에도 key=value 이기 때문에 이러한 데이터는 아래와 같이 함수의 파라메터로 받을 수 있다.

PostMapping("/home")
public String home(String username, String email){
	
	return "home";
}

4. 스프링 컨트롤러의 파싱 전략 2

스프링은 key=value 형태의 데이터를 오브젝트로 파싱해서 받아주는 역할도 한다.

  • * 이때 주의할 점은 setter가 없으면 key=value 데이터를 스프링이 파싱해서 넣어주지 못한다.
class User {
	private String username;
	private String password;
	
	public String getUsername(){
		return username;
	}
	
	public String getPassword(){
		return password;
	}
	
	public void setUsername(String username){
		this.username = username;
	}
	
	public void setPassword(String password){
		this.password = password;
	}
	
}
PostMapping("/home")
public String home(User user){
	
	return "home";
}

5. key=value가 아닌 데이터는 어떻게 파싱할까?

json 데이터나 일반 text데이터는 스프링 컨트롤러에서 받기 위해서는 @RequestBody 어노테이션이 필요하다.

  • * 기본전략이 스프링 컨트롤러는 key=value 데이터를 파싱해서 받아주는 일을 하는데 다른 형태의 데이터 가령 json 같은 데이터는 아래와 같이 생겼다.
{
	"username":"ssar",
	"password":"1234"
}

이런 데이터는 스프링이 파싱해서 오브젝트로 받지 못한다. 그래서 @RequestBody 어노테이션을 붙이면 MessageConverter 클래스를 구현한 Jackson 라이브러리가 발동하면서 json 데이터를 자바 오브젝트로 파싱하여 받아준다.

6. form 태그로 json데이터 요청방법

key=value 데이터가 아니라 json데이터를 어떻게 전송할 수 있을까?

join.jsp

<div class="container">

	<form>
		<div class="form-group">
			<label for="username">유저네임</label> 
			<input type="text" id="username">
		</div>
		<div class="form-group">
			<label for="password">패스워드</label> 
			<input type="password" id="password">
		</div>
		
		<div class="form-group">
			<label for="email">이메일</label> 
			<input type="email" id="email">
		</div>
	</form>
	
	<button id="join--submit" class="btn btn-primary">회원가입</button>

</div>

<script src="/js/join.js"></script>

join.js

<script>
$('#join--submit').on('click', function() {
	var data = {
		username : $('#username').val(),
		password : $('#password').val(),
		email : $('#email').val()
	};

	$.ajax({
		type : 'POST',
		url : '/user/join',
		data : JSON.stringify(data),
		contentType : 'application/json; charset=utf-8',
		dataType : 'json'
	}).done(function(r) {
		if (r.statusCode == 200) {
			console.log(r);
			alert('회원가입 성공');
			location.href = '/user/login';
		} else {
			if (r.msg == '아이디중복') {
				console.log(r);
				alert('아이디가 중복되었습니다.');
			} else {
				console.log(r);
				alert('회원가입 실패');
			}
		}
	}).fail(function(r) {
		var message = JSON.parse(r.responseText);
		console.log((message));
		alert('서버 오류');
	});
});
</script>

33강 메인화면 만들기

BoadrController.java

package com.cos.blog.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class BoardController {

	@GetMapping({"","/"})
	public String index() {
		// WEB-INF/views/index.jsp
		return "index";
	}
}

index.jsp

<!DOCTYPE html>
<html lang="en">
<head>
	<title>Bootstrap Example</title>
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
	<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
	<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
	<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
</head>
<body>

<nav class="navbar navbar-expand-md bg-dark navbar-dark">
	<a class="navbar-brand" href="/blog">Cos</a>
	<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#collapsibleNavbar">
		<span class="navbar-toggler-icon"></span>
	</button>
	<div class="collapse navbar-collapse" id="collapsibleNavbar">
		<ul class="navbar-nav">
			<li class="nav-item">
				<a class="nav-link" href="/user/login">로그인</a>
			</li>
			<li class="nav-item">
				<a class="nav-link" href="/user/join">회원가입</a>
			</li>
		</ul>
	</div>
</nav>
<br>

<div class="container">

	<div class="card m-2">
		<div class="card-body">
			<h4 class="card-title">제목 적는 부분</h4>
			<p class="card-text">내용 적는 부분</p>
			<a href="#" class="btn btn-primary">상세보기</a>
		</div>
	</div>

	<div class="card m-2">
		<div class="card-body">
			<h4 class="card-title">제목 적는 부분</h4>
			<p class="card-text">내용 적는 부분</p>
			<a href="#" class="btn btn-primary">상세보기</a>
		</div>
	</div>

	<div class="card m-2">
		<div class="card-body">
			<h4 class="card-title">제목 적는 부분</h4>
			<p class="card-text">내용 적는 부분</p>
			<a href="#" class="btn btn-primary">상세보기</a>
		</div>
	</div>

</div>


<div class="jumbotron text-center" style="margin-bottom:0">
	<p>Created by Cos</p>
	<p>📞 010-2222-7777</p>
	<p>🏴 경기 김포시 XX동</p>
</div>
</div>

</body>
</html>

34강 로그인,회원가입 화면 만들기

index.jsp를 header.jsp와 footer.jsp로 나눔

index.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>

<%@ include file="layout/header.jsp"%>

<div class="container">

	<div class="card m-2">
		<div class="card-body">
			<h4 class="card-title">제목 적는 부분</h4>
			<a href="#" class="btn btn-primary">상세보기</a>
		</div>
	</div>

	<div class="card m-2">
		<div class="card-body">
			<h4 class="card-title">제목 적는 부분</h4>
			<a href="#" class="btn btn-primary">상세보기</a>
		</div>
	</div>

	<div class="card m-2">
		<div class="card-body">
			<h4 class="card-title">제목 적는 부분</h4>
			<a href="#" class="btn btn-primary">상세보기</a>
		</div>
	</div>

</div>

<%@include file="layout/footer.jsp"%>

header.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html lang="en">
<head>
	<title>Bootstrap Example</title>
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
</head>
<body>

<nav class="navbar navbar-expand-md bg-dark navbar-dark">
	<a class="navbar-brand" href="/blog">Cos</a>
	<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#collapsibleNavbar">
		<span class="navbar-toggler-icon"></span>
	</button>
	<div class="collapse navbar-collapse" id="collapsibleNavbar">
		<ul class="navbar-nav">
			<li class="nav-item">
				<a class="nav-link" href="/blog/user/loginForm">로그인</a>
			</li>
			<li class="nav-item">
				<a class="nav-link" href="/blog/user/joinForm">회원가입</a>
			</li>
		</ul>
	</div>
</nav>
<br />

footer.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<br/>
<div class="jumbotron text-center" style="margin-bottom:0">
	<p>Created by Cos</p>
	<p>📞 010-2222-7777</p>
	<p>🏴 경기 김포시 XX동</p>
</div>
</div>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
</body>
</html>

webapp.WEB-INF.views.user에 joinForm.jsp와 loginForm.jsp를 만듬

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>

<%@ include file="../layout/header.jsp" %>

<div class="container">
	<form action="/action_page.php">
		<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" %>

loginForm.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>

<%@ include file="../layout/header.jsp" %>

<div class="container">
	<form action="/action_page.php">
		<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="password">Password</label>
			<input type="password" class="form-control" placeholder="Enter password" id="password">
		</div>
		<div class="form-group form-check">
			<label class="form-check-label">
				<input class="form-check-input" type="checkbox"> Remember me
			</label>
		</div>
		<button type="submit" class="btn btn-primary">로그인완료</button>
	</form>

</div>

<%@include file="../layout/footer.jsp" %>

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";
	}

}

35강 회원가입을 위한 기초세팅

static.js에 user.js를 만듬

let index = {
	init: function(){
		$("#btn-save").on("click", ()=>{
			this.save();
		});
	},

	save:function(){
		//alert('user의 save함수 호출됨');
		let data = {
			username: $("#username").val(),
			password: $("#password").val(),
			email: $("#email").val(),
		}

		//console.log(data);

		$.ajax().done().fail(); // ajax 통신을 이용해서 3개의 데이터를 json으로 변경하여 insert 요청!!
	}
}

index.init();

static 폴더는 /만 해줘도 경로 이동 가능

36강 Ajax를 사용하는 이유 첫번째

  1. 요청에 대한 응답을 html이 아닌 Data(Json)를 받기 위하여

웹 프로그램과 앱 프로그램에서 하나의 서버를 공유해서 사용할 수 있다.

Ajax를 사용하는 첫번째 이유 : 고객이 사용하는 클라이언트는 크게 2가지로 웹과 앱이 있으며, 일반적으로 서버로 부터 응답받을때 웹은 HTML파일을 받고 앱은 데이터(JSON)를 받는다. 이것은 서버를 이원화하여 구축한 것을 의미하며 이때 '서버를 통합하여 각각의 클라이언트에게 응답해줄순 없을까?'라는 의문점에서 고안된 방법이 Ajax통신이다. Ajax통신을 사용하면 웹은 서버로부터 데이터(JSON)를 리턴받을 수 있으며 그렇게 되면 서버의 분리 필요없이 하나의 서버로 각각의 클라이언트 요청을 받아 응답해줄수 있게된다. 대신 웹클라이언트는 추가적인 요청을 통해 HTML파일을 받아야한다.

37강 Ajax를 사용하는 이유 두번째

  1. 비동기 통신을 하기 위해서이다.

38강 회원가입 하기 Ajax요청

user.js

let index = {
	init: function(){
		$("#btn-save").on("click", ()=>{	// function(){}, ()=>{} this를 바인딩하기 위해서!!
			this.save();
		});
	},

	save:function(){
		//alert('user의 save함수 호출됨');
		let data = {
			username: $("#username").val(),
			password: $("#password").val(),
			email: $("#email").val(),
		};

		//console.log(data);

		// ajax호출시 default가 비동기 호출
		// ajax 통신을 이용해서 3개의 데이터를 json으로 변경하여 insert 요청!!
		// ajax가 통신을 성공하고 json을 리턴해주면 자동으로 자바 오브젝트로 변환해주네요.
		$.ajax({
			// 회원가입 수행 요청
			type: "POST",
			url: "/blog/api/user",
			data: JSON.stringify(data), // http body데이터
			contentType: "application/json; charset=utf-8", // body데이터가 어떤 타입인지(MIME)
			dataType: "json" // 요청을 서버로해서 응답이 왔을때 기본적으로 모든 것이 문자열 (생긴게 json이라면) => javascript오브젝트로 변경
		}).done(function(resp){
			alert("회원가입이 완료되었습니다.");
			console.log(resp);
			location.href= "/blog";
		}).fail(function(error) {
			alert(JSON.stringify(error));
		}); // ajax 통신을 이용해서 3개의 데이터를 json으로 변경하여 insert 요청!!
	}
}

index.init();

ResponseDto.java

package com.cos.blog.dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.http.HttpStatus;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ResponseDto<T> {

	HttpStatus status;
	T data;
}

UserApiController.java

package com.cos.blog.controller.api;

import com.cos.blog.dto.ResponseDto;
import com.cos.blog.model.User;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserApiController {

	@PostMapping("/api/user")
	public ResponseDto<Integer> save(@RequestBody User user){
		System.out.println("UserApiController : save 호출됨");
		// 실제로 DB에 insert를 하고 아래에서 return이 되면 되요.
		return new ResponseDto<Integer>(HttpStatus.OK,1); // 자바오브젝트를 JSON으로 변환해서 리턴 (Jackson)
	}
}

39강 회원가입 하기 두번째 완료

서비스 필요 이유

  1. 트랜잭션 관리
  2. 서비스 의미 때문

UserService.java

package com.cos.blog.service;

import com.cos.blog.model.User;
import com.cos.blog.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;

// 스프링이 커포넌트 스캔을 통해서 Bean에 등록을 해줌. IoC를 해준다.
@Service
public class UserService {

	@Autowired
	private UserRepository userRepository;

	@Transactional
	public int 회원가입(User user) {
		try {
			userRepository.save(user);
			return 1;
		} catch (Exception e) {
			e.printStackTrace();
			System.out.println("UserService : 회원가입() :" + e.getMessage());
		}
		return -1;
	}
}

UserApiController.java 수정

package com.cos.blog.controller.api;

import com.cos.blog.dto.ResponseDto;
import com.cos.blog.model.RoleType;
import com.cos.blog.model.User;
import com.cos.blog.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserApiController {

	@Autowired
	private UserService userService;

	@PostMapping("/api/user")
	public ResponseDto<Integer> save(@RequestBody User user){ // username, password, email
		System.out.println("UserApiController : save 호출됨");
		// 실제로 DB에 insert를 하고 아래에서 return이 되면 되요.
		user.setRole(RoleType.USER);
		int result = userService.회원가입(user);
		return new ResponseDto<Integer>(HttpStatus.OK,result); // 자바오브젝트를 JSON으로 변환해서 리턴 (Jackson)
	}
}

40강 ResponseDto 수정

GlobalExceptionHandler.java 수정

package com.cos.blog.handler;

import com.cos.blog.dto.ResponseDto;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;

@ControllerAdvice
@RestController
public class GlobalExceptionHandler {

	@ExceptionHandler(value =Exception.class)
	public ResponseDto<String> handleArgumentException(Exception e) {
		return new ResponseDto<String>(HttpStatus.INTERNAL_SERVER_ERROR.value(),e.getMessage());
	}
}

UserApiController.java 수정

package com.cos.blog.controller.api;

import com.cos.blog.dto.ResponseDto;
import com.cos.blog.model.RoleType;
import com.cos.blog.model.User;
import com.cos.blog.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserApiController {

	@Autowired
	private UserService userService;

	@PostMapping("/api/user")
	public ResponseDto<Integer> save(@RequestBody User user){ // username, password, email
		System.out.println("UserApiController : save 호출됨");
		// 실제로 DB에 insert를 하고 아래에서 return이 되면 되요.
		user.setRole(RoleType.USER);
		int result = userService.회원가입(user);
		return new ResponseDto<Integer>(HttpStatus.OK.value(),result); // 자바오브젝트를 JSON으로 변환해서 리턴 (Jackson)
	}
}

ResponseDto.java 수정

package com.cos.blog.dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.http.HttpStatus;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ResponseDto<T> {

	int status;
	T data;
}