miniblog
Search
๋ณธ ํ๋ก์ ํธ๋ โ์คํ๋ง๋ถํธ 3 ๋ฐฑ์๋ ๊ฐ๋ฐ์ ๋๊ธฐโ ์์ ์ ์ฐธ๊ณ ํ์ฌ ์งํํ์์
OAuth ์ธ์ฆ ์ ์ฉ ์ํ ํ ์คํธ ์ฝ๋ ์์ฑ
โข
ํ
์คํธ์ฝ๋ ์์ - ๊ตต์ ๊ธ์จ๋ก ํ์ํ ๋ถ๋ถ ์ฐธ๊ณ
package com.example.msblog.controller;
import com.example.msblog.domain.Article;
import com.example.msblog.domain.User;
import com.example.msblog.dto.AddArticleRequest;
import com.example.msblog.dto.UpdateArticleRequest;
import com.example.msblog.repository.BlogRepository;
import com.example.msblog.repository.UserRepository;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import java.security.Principal;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest // ํ
์คํธ์ฉ ์ ํ๋ฆฌ์ผ์ด์
์ปจํ์คํธ
@AutoConfigureMockMvc // MockMvc ์์ฑ ๋ฐ ์๋ ๊ตฌ์ฑ
class BlogApiControllerTest {
@Autowired
protected MockMvc mockMvc;
@Autowired
protected ObjectMapper objectMapper; // ์ง๋ ฌํ, ์ญ์ง๋ ฌํ๋ฅผ ์ํ ํด๋์ค
@Autowired
private WebApplicationContext context;
@Autowired
BlogRepository blogRepository;
// ์ถ๊ฐ
@Autowired
UserRepository userRepository;
// ์ถ๊ฐ
User user;
// ์ถ๊ฐ
@BeforeEach
@Test
void setSecurityContext() {
userRepository.deleteAll();
user = userRepository.save(User.builder()
.email("user@gmail.com")
.password("test")
.build());
// ์ธ์ฆ ๊ฐ์ฒด๋ฅผ ์ ์ฅํ๋ ์ํ๋ฆฌํฐ ์ฝํ
์คํธ์ setAuthentication() ๋ฉ์๋๋ฅผ ์ฌ์ฉํด ํ
์คํธ ์ ์ ๋ฅผ ์ง์ ํ๋ค.
SecurityContext context = SecurityContextHolder.getContext();
context.setAuthentication(new UsernamePasswordAuthenticationToken(user,
user.getPassword(), user.getAuthorities()));
}
@BeforeEach // ํ
์คํธ ์คํ ์ ์คํํ๋ ๋ฉ์๋
public void mockMvcSetUp() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
blogRepository.deleteAll();
}
@DisplayName("addArticle: ๋ธ๋ก๊ทธ ๊ธ ์ถ๊ฐ์ ์ฑ๊ณตํ๋ค.")
@Test
public void addArticle() throws Exception {
// given
final String url = "/api/articles";
final String title = "title";
final String content = "content";
final AddArticleRequest userRequest = new AddArticleRequest(title, content);
// ๊ฐ์ฒด JSON์ผ๋ก ์ง๋ ฌํํ๊ธฐ
final String requestBody = objectMapper.writeValueAsString(userRequest);
// ์ถ๊ฐ - ๊ธ์ ์์ฑํ๋ API์์๋ ํ๋ผ๋ฏธํฐ๋ก Principal ๊ฐ์ฒด๋ฅผ ๋ฐ๊ณ ์๋๋ฐ ์ด ๊ฐ์ฒด์ ํ
์คํธ ์ ์ ๊ฐ ๋ค์ด๊ฐ๋๋ก ๋ชจํนํ๋ค.
// ํด๋น ํ
์คํธ ์ฝ๋์์๋ Principal ๊ฐ์ฒด๋ฅผ ๋ชจํนํด์ ์คํ๋ง ๋ถํธ ์ ํ๋ฆฌ์ผ์ด์
์์ getName() ๋ฉ์๋๋ฅผ ํธ์ถํ๋ฉด "userName" ์ด๋ผ๋ ๊ฐ์ ๋ฐํํ๋ค.
Principal principal = Mockito.mock(Principal.class);
Mockito.when(principal.getName()).thenReturn("username");
// when
// mockMvc ํ์ฉํ์ฌ HTTP ๋ฉ์๋, URL, ์์ฒญ ๋ณธ๋ฌธ, ์์ฒญ ํ์
๋ฑ์ ์ค์ ํ ๋ค ์ค์ ํ ๋ด์ฉ์ ๋ฐํ์ผ๋ก ํ
์คํธ ์์ฒญ์ ๋ณด๋
// ์ค์ ํ ๋ด์ฉ ๋ฐํ์ผ๋ก ์์ฒญ ์ ์ก
ResultActions result = mockMvc.perform(post(url)
.contentType(MediaType.APPLICATION_JSON_VALUE)
.principal(principal) // ์ถ๊ฐ
.content(requestBody));
// then
result.andExpect(status().isCreated());
List<Article> articles = blogRepository.findAll();
assertThat(articles.size()).isEqualTo(1); // ํฌ๊ธฐ๊ฐ 1์ธ์ง ๊ฒ์ฆ
assertThat(articles.get(0).getTitle()).isEqualTo(title);
assertThat(articles.get(0).getContent()).isEqualTo(content);
}
@DisplayName("findAllAritcles: ๋ธ๋ก๊ทธ ๊ธ ๋ชฉ๋ก ์กฐํ์ ์ฑ๊ณตํ๋ค.")
@Test
public void findAllArticles() throws Exception {
// given
final String url = "/api/articles";
Article savedArticle = createDefaultArticle(); // ์ถ๊ฐ
// when
final ResultActions resultActions = mockMvc.perform(get(url)
.accept(MediaType.APPLICATION_JSON));
// then
resultActions
.andExpect(status().isOk())
.andExpect(jsonPath("$[0].content").value(savedArticle.getContent())) // ์์
.andExpect(jsonPath("$[0].title").value(savedArticle.getTitle())); // ์์
}
@DisplayName("findArticle: ๋ธ๋ก๊ทธ ๊ธ ์กฐํ์ ์ฑ๊ณตํ๋ค.")
@Test
public void findArticle() throws Exception {
// given
final String url = "/api/articles/{id}";
Article savedArticle = createDefaultArticle(); // ์ถ๊ฐ
// when
final ResultActions resultActions = mockMvc.perform(get(url, savedArticle.getId()));
// then
resultActions
.andExpect(status().isOk())
.andExpect(jsonPath("$.content").value(savedArticle.getContent())) // ์์
.andExpect(jsonPath("$.title").value(savedArticle.getTitle())); // ์์
}
@DisplayName("deleteArticle: ๋ธ๋ก๊ทธ ๊ธ ์ญ์ ์ ์ฑ๊ณตํ๋ค.")
@Test
public void deleteArticle() throws Exception {
// given
final String url = "/api/articles/{id}";
Article savedArticle = createDefaultArticle(); // ์ถ๊ฐ
// when
mockMvc.perform(delete(url, savedArticle.getId()))
.andExpect(status().isOk());
// then
List<Article> articles = blogRepository.findAll();
assertThat(articles).isEmpty();
}
@DisplayName("updateArticle: ๋ธ๋ก๊ทธ ๊ธ ์์ ์ ์ฑ๊ณตํ๋ค.")
@Test
public void updateArticle() throws Exception {
// given
final String url = "/api/articles/{id}";
Article savedArticle = createDefaultArticle(); // ์ถ๊ฐ
final String newTitle = "new title";
final String newContent = "new content";
UpdateArticleRequest request = new UpdateArticleRequest(newTitle, newContent);
// when
ResultActions result = mockMvc.perform(put(url, savedArticle.getId())
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(objectMapper.writeValueAsString(request)));
// then
result.andExpect(status().isOk());
Article article = blogRepository.findById(savedArticle.getId()).get();
assertThat(article.getTitle()).isEqualTo(newTitle);
assertThat(article.getContent()).isEqualTo(newContent);
}
// ์ค๋ณต ์ฝ๋ ์ ๊ฑฐํ๊ธฐ ์ํด ๊ธ์ ๋ง๋๋ ๋ก์ง์ ํด๋น ๋ฉ์๋๋ก ์ถ์ถํ๋ค.
private Article createDefaultArticle() {
return blogRepository.save(Article.builder()
.title("title")
.author(user.getUsername())
.content("content")
.build());
}
}
ํต์ฌ ์์ฝ

18์ผ์ฐจ
๋ณธ ํ๋ก์ ํธ๋ โ์คํ๋ง๋ถํธ 3 ๋ฐฑ์๋ ๊ฐ๋ฐ์ ๋๊ธฐโ ์์ ์ ์ฐธ๊ณ ํ์ฌ ์งํํ์์
์คํ๋ง ์ํ๋ฆฌํฐ๋ก OAuth2๋ฅผ ๊ตฌํํ๊ณ ์ ์ฉํ๊ธฐ
โข
์คํ๋ง ์ํ๋ฆฌํฐ๋ฅผ ์ฌ์ฉํ์ฌ OAuth2 ๊ตฌํ ์์
โข
๊ฐ์ฅ ๋จผ์ ์ฟ ํค ๊ด๋ฆฌ ํด๋์ค๋ฅผ ๊ตฌํํ๊ณ , OAuth2์์ ์ ๊ณต๋ฐ์ ์ธ์ฆ ๊ฐ์ฒด๋ก ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ค๋ ์ญํ ์ ํ๋ ์๋น์ค๋ฅผ ๊ตฌํํ๊ธฐ
โข
๊ธฐ์กด โ์ํ๋ฆฌํฐ ์ค์ ํ๊ธฐโ ์์ ๊ตฌํํ๋ WebSecurityConfig ํด๋์ค ๋์ ์ฌ์ฉํ OAuth2 ์ค์ ํ์ผ์ ๊ตฌํํ๊ธฐ
โข
๋ง์ง๋ง์๋ ์ง์ ํ
์คํธํ ์ ์๋๋ก ๋ทฐ๋ฅผ ๊ตฌ์ฑ

17์ผ์ฐจ


16์ผ์ฐจ
๋ณธ ํ๋ก์ ํธ๋ โ์คํ๋ง๋ถํธ 3 ๋ฐฑ์๋ ๊ฐ๋ฐ์ ๋๊ธฐโ ์์ ์ ์ฐธ๊ณ ํ์ฌ ์งํํ์์
OAuth๋?
OAuth๋ ์ 3์ ์๋น์ค์ ๊ณ์ ๊ด๋ฆฌ๋ฅผ ๋งก๊ธฐ๋ ๋ฐฉ์์ด๋ค. ํํ ๋ณผ ์ ์๋ ๋ค์ด๋ฒ๋ก ๋ก๊ทธ์ธํ๊ธฐ, ๊ตฌ๊ธ๋ก ๋ก๊ทธ์ธํ๊ธฐ ๊ฐ์ ๊ฒ์ด๋ค.
โข
๋ฆฌ์์ค ์ค๋(resource owner)
โข
๋ฆฌ์์ค ์๋ฒ(resource server)

15์ผ์ฐจ
๋ณธ ํ๋ก์ ํธ๋ โ์คํ๋ง๋ถํธ 3 ๋ฐฑ์๋ ๊ฐ๋ฐ์ ๋๊ธฐโ ์์ ์ ์ฐธ๊ณ ํ์ฌ ์งํํ์์
JWTย ์๋น์ค ๊ตฌํ - ํ ์คํธ ์ฝ๋
โข
ํ
์คํธ ์ฝ๋ ์์ฑ

14์ผ์ฐจ
๋ณธ ํ๋ก์ ํธ๋ โ์คํ๋ง๋ถํธ 3 ๋ฐฑ์๋ ๊ฐ๋ฐ์ ๋๊ธฐโ ์์ ์ ์ฐธ๊ณ ํ์ฌ ์งํํ์์
JWTย ์๋น์ค ๊ตฌํ
โข
์์กด์ฑ ์ถ๊ฐ
// ์๋ฐ JWT ๋ผ์ด๋ธ๋ฌ๋ฆฌ
implementation 'io.jsonwebtoken:jjwt:0.9.1'
// XML ๋ฌธ์์ Java ๊ฐ์ฒด ๊ฐ ๋งคํ ์๋ํ
implementation 'javax.xml.bind:jaxb-api:2.3.1'

13์ผ์ฐจ
๋ณธ ํ๋ก์ ํธ๋ โ์คํ๋ง๋ถํธ 3 ๋ฐฑ์๋ ๊ฐ๋ฐ์ ๋๊ธฐโ ์์ ์ ์ฐธ๊ณ ํ์ฌ ์งํํ์์
ํ์๊ฐ์ , ๋ก๊ทธ์ธ ๋ทฐ ์์ฑํ๊ธฐ
โข
๋ทฐ ์ปจํธ๋กค๋ฌ ๊ตฌํ
package com.example.msblog.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class ViewController {
@GetMapping("/login")
public String login() {
return "login";
}
@GetMapping("/signup")
public String signup() {
return "signup";
}
}

12์ผ์ฐจ
๋ณธ ํ๋ก์ ํธ๋ โ์คํ๋ง๋ถํธ 3 ๋ฐฑ์๋ ๊ฐ๋ฐ์ ๋๊ธฐโ ์์ ์ ์ฐธ๊ณ ํ์ฌ ์งํํ์์
ํ์๊ฐ์ ๊ตฌํํ๊ธฐ
โข
์ฌ์ฉ์ ์ ๋ณด ๋ด๊ณ ์๋ ๊ฐ์ฒด ์์ฑ
package com.example.msblog.dto;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class AddUserRequest {
private String email;
private String password;
}
โข
AddUserRequest ๊ฐ์ฒด๋ฅผ ์ธ์๋ก ๋ฐ๋ ํ์ ์ ๋ณด ์ถ๊ฐ ๋ฉ์๋ ์์ฑ - ์๋น์ค

11์ผ์ฐจ
๋ณธ ํ๋ก์ ํธ๋ โ์คํ๋ง๋ถํธ 3 ๋ฐฑ์๋ ๊ฐ๋ฐ์ ๋๊ธฐโ ์์ ์ ์ฐธ๊ณ ํ์ฌ ์งํํ์์
์ํ๋ฆฌํฐ ์ค์ ํ๊ธฐ
โข
requestMathcers(): ํน์ ์์ฒญ๊ณผ ์ผ์นํ๋ url์ ๋ํ ์ก์ธ์ค๋ฅผ ์ค์ ํ๋ค.
โข
permitAll(): ๋๊ตฌ๋ ์ ๊ทผ์ด ๊ฐ๋ฅํ๊ฒ ์ค์ ํ๋ค.
โข
anyRequest(): ์์์ ์ค์ ํ url ์ด์ธ์ ์์ฒญ์ ๋ํด ์ค์ ํ๋ค.
โข
loginPage(): ๋ก๊ทธ์ธ ํ์ด์ง ๊ฒฝ๋ก ์ค์ ํ๋ค.

10์ผ์ฐจ
๋ณธ ํ๋ก์ ํธ๋ โ์คํ๋ง๋ถํธ 3 ๋ฐฑ์๋ ๊ฐ๋ฐ์ ๋๊ธฐโ ์์ ์ ์ฐธ๊ณ ํ์ฌ ์งํํ์์
ํ์ ๋๋ฉ์ธ ๋ง๋ค๊ธฐ
โข
์์กด์ฑ ์ถ๊ฐํ๊ธฐ
dependencies {
... ์๋ต ...
// ์คํ๋ง ์ํ๋ฆฌํฐ ์ฌ์ฉํ๊ธฐ ์ํ ์คํํฐ ์ถ๊ฐ
implementation 'org.springframework.boot:spring-boot-starter-security'
// ํ์๋ฆฌํ์์ ์คํ๋ง ์ํ๋ฆฌํฐ ์ฌ์ฉํ๊ธฐ ์ํ ์์กด์ฑ ์ถ๊ฐ
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'
// ์คํ๋ง ์ํ๋ฆฌํฐ๋ฅผ ํ
์คํธํ๊ธฐ ์ํ ์์กด์ฑ ์ถ๊ฐ
implementation 'org.springframework.security:spring-security-test'
}

9์ผ์ฐจ
๋ณธ ํ๋ก์ ํธ๋ โ์คํ๋ง๋ถํธ 3 ๋ฐฑ์๋ ๊ฐ๋ฐ์ ๋๊ธฐโ ์์ ์ ์ฐธ๊ณ ํ์ฌ ์งํํ์์
์์ฑ ๊ธฐ๋ฅ ๋ง๋ฌด๋ฆฌ
โข
article.js ์ ์ฝ๋ ์์ฑ
โข
article.js ํ์ผ์ ์ด์ด [๋ฑ๋ก] ๋ฒํผ์ ๋๋ฅด๋ฉด ์
๋ ฅ ์นธ์ ์๋ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ ๊ฒ์๊ธ ์์ฑ API์ ๊ธ ์์ฑ ๊ด๋ จ ์์ฒญ์ ๋ณด๋ด์ค๋ค.
โข
id๊ฐ create-btn์ธ ์๋ฆฌ๋จผํธ๋ฅผ ์ฐพ์ ๊ทธ ์๋ฆฌ๋จผํธ์์ ํด๋ฆญ ์ด๋ฒคํธ ๋ฐ์ ์ id๊ฐ title, content์ธ ์๋ฆฌ๋จผํธ์ ๊ฐ์ ๊ฐ์ ธ์ fetch() ๋ฉ์๋๋ฅผ ํตํด ์์ฑ API๋ก /api/articles/ POST ์์ฒญ์ ๋ณด๋ธ๋ค.

8์ผ์ฐจ


7์ผ์ฐจ
๋ณธ ํ๋ก์ ํธ๋ โ์คํ๋ง๋ถํธ 3 ๋ฐฑ์๋ ๊ฐ๋ฐ์ ๋๊ธฐโ ์์ ์ ์ฐธ๊ณ ํ์ฌ ์งํํ์์
๋ธ๋ก๊ทธ ๊ธ ๋ชฉ๋ก ๋ทฐ ๊ตฌํ
โข
์์ฒญ์ ๋ฐ์ ์ฌ์ฉ์์๊ฒ ๋ทฐ๋ฅผ ๋ณด์ฌ์ฃผ๋ ค๋ฉด ๋ทฐ ์ปจํธ๋กค๋ฌ๊ฐ ํ์ํจ
โข
๋ทฐ ์ปจํธ๋กค๋ฌ ๋ฉ์๋๋ ๋ทฐ์ ์ด๋ฆ์ ๋ฐํํ๊ณ , ๋ชจ๋ธ ๊ฐ์ฒด์ ๊ฐ์ ๋ด์
โข
๋ทฐ์ ์ ๋ฌํ๊ธฐ ์ํ ๊ฐ์ฒด ์์ฑ

6์ผ์ฐจ
๋ณธ ํ๋ก์ ํธ๋ โ์คํ๋ง๋ถํธ 3 ๋ฐฑ์๋ ๊ฐ๋ฐ์ ๋๊ธฐโ ์์ ์ ์ฐธ๊ณ ํ์ฌ ์งํํ์์
ํ์๋ฆฌํ
โข
๋ทฐ ์์ฑ - example.html
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Title</title>
</head>
<body>
<h1>ํ์๋ฆฌํ ์ตํ๊ธฐ</h1>
<!-- LocalDate๋ฅผ yyyy-MM-dd ํฌ๋งท์ผ๋ก ๋ณ๊ฒฝํ๊ธฐ -->
<p th:text="${#temporals.format(today, 'yyyy-MM-dd')}"></p>
<div th:object="${person}"> <!-- person์ ์ ํํ ๊ฐ์ฒด๋ก ์ง์ -->
<p th:text="|์ด๋ฆ : *{name}|"></p> <!-- ๋ณํ๋ฅผ ์ฌ์ฉํด ๋ถ๋ชจ ํ๊ทธ์ ์ ์ฉํ ๊ฐ์ฒด ๊ฐ์ ์ ๊ทผํ ์ ์์ -->
<p th:text="|๋์ด : *{age}|"></p>
<p>์ทจ๋ฏธ</p>
<ul th:each="hobby : *{hobbies}"> <!-- hobbies ๊ฐ์๋งํผ ๋ฐ๋ณต -->
<li th:text="${hobby}"></li>
<!-- ๋ฐ๋ณต ๋์์ด ์ด๋์ผ ๊ฒฝ์ฐ '๋ํ ์ทจ๋ฏธ' ๋ผ๋ ํ์๋ฅผ ์ถ๊ฐํ๊ธฐ -->
<span th:if="${hobby == '์ด๋'}">(๋ํ ์ทจ๋ฏธ)</span>
</ul>
</div>
<!-- 1๋ฒ ๋ธ๋ก๊ทธ ๊ธ์ ๋ณด๋ฌ ์ด๋ํ๊ธฐ -->
<a th:href="@{/api/articles/{id}(id=${person.id})}">๊ธ ๋ณด๊ธฐ</a>
</body>
</html>

5์ผ์ฐจ
๋ณธ ํ๋ก์ ํธ๋ โ์คํ๋ง๋ถํธ 3 ๋ฐฑ์๋ ๊ฐ๋ฐ์ ๋๊ธฐโ ์์ ์ ์ฐธ๊ณ ํ์ฌ ์งํํ์์
ํ์๋ฆฌํ
โข
ํ์๋ฆฌํ๋ ํ
ํ๋ฆฟ ์์ง์ด๋ค.
โข
๋ฌธ๋ฒ

4์ผ์ฐจ
๋ณธ ํ๋ก์ ํธ๋ โ์คํ๋ง๋ถํธ 3 ๋ฐฑ์๋ ๊ฐ๋ฐ์ ๋๊ธฐโ ์์ ์ ์ฐธ๊ณ ํ์ฌ ์งํํ์์
๋ธ๋ก๊ทธ ๊ธ ์ญ์ API ๊ตฌํ
โข
BlogService.java
// ์ญ์ ๋ฉ์๋ ์ถ๊ฐ
public void delete(long id) {
blogRepository.deleteById(id);
}

3์ผ์ฐจ
๋ณธ ํ๋ก์ ํธ๋ โ์คํ๋ง๋ถํธ 3 ๋ฐฑ์๋ ๊ฐ๋ฐ์ ๋๊ธฐโ ์์ ์ ์ฐธ๊ณ ํ์ฌ ์งํํ์์
๋ธ๋ก๊ทธ์ ๋ชจ๋ ๊ธ ์กฐํํ๋ API ๊ตฌํ
โข
BlogService.java
package com.example.msblog.service;
import com.example.msblog.domain.Article;
import com.example.msblog.dto.AddArticleRequest;
import com.example.msblog.repository.BlogRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
@RequiredArgsConstructor // final์ด ๋ถ๊ฑฐ๋ @NotNull์ด ๋ถ์ ํ๋์ ์์ฑ์ ์ถ๊ฐ
@Service // ๋น์ผ๋ก ๋ฑ๋ก
public class BlogService {
private final BlogRepository blogRepository;
// ๋ธ๋ก๊ทธ ๊ธ ์ถ๊ฐ ๋ฉ์๋
public Article save(AddArticleRequest request) {
return blogRepository.save(request.toEntity());
}
// ๋ชจ๋ ๊ธ ์กฐํ
public List<Article> findAll() {
return blogRepository.findAll();
}
}
์๋ต์ ์ํ DTO ์์ฑ

2์ผ์ฐจ
๋ณธ ํ๋ก์ ํธ๋ โ์คํ๋ง๋ถํธ 3 ๋ฐฑ์๋ ๊ฐ๋ฐ์ ๋๊ธฐโ ์์ ์ ์ฐธ๊ณ ํ์ฌ ์งํํ์์
์ํฐํฐ ๊ตฌ์ฑ
// domain > Article.java
package com.example.msblog.domain;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Entity // ์ํฐํฐ๋ก ์ง์
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Article {
@Id // Id ํ๋๋ฅผ ๊ธฐ๋ณธํค๋ก ์ง์
@GeneratedValue(strategy = GenerationType.IDENTITY) // ๊ธฐ๋ณธํค๋ฅผ ์๋์ผ๋ก 1์ฉ ์ฆ๊ฐ์ํค๊ธฐ
@Column(name = "id", updatable = false)
private Long id;
@Column(name = "title", nullable = false) // 'title' ์ด๋ผ๋ not null ์ปฌ๋ผ๊ณผ ๋งคํ
private String title;
@Column(name = "content", nullable = false)
private String content;
@Builder // ๋น๋ ํจํด์ผ๋ก ์์ฑ
public Article(String title, String content) {
this.title = title;
this.content = content;
}
}
๋ฆฌํฌ์งํ ๋ฆฌ ๋ง๋ค๊ธฐ

1์ผ์ฐจ




