Framework

Spring

Bean Scope

Bean의 μ‚¬μš© λ²”μœ„λ₯Ό λ§ν•˜λŠ” Bean Scope의 μ’…λ₯˜μ— λŒ€ν•΄ μ•Œμ•„λ³΄μž

Bean은 μŠ€ν”„λ§μ—μ„œ μ‚¬μš©ν•˜λŠ” POJO 기반 객체닀.

상황과 ν•„μš”μ— 따라 Bean을 μ‚¬μš©ν•  λ•Œ ν•˜λ‚˜λ§Œ λ§Œλ“€μ–΄μ•Ό ν•  μˆ˜λ„ 있고, μ—¬λŸ¬κ°œκ°€ ν•„μš”ν•  λ•Œλ„ 있고, μ–΄λ–€ ν•œ μ‹œμ μ—μ„œλ§Œ μ‚¬μš©ν•΄μ•Όν•  λ•Œκ°€ μžˆμ„ 수 μžˆλ‹€.

이λ₯Ό μœ„ν•΄ Scopeλ₯Ό μ„€μ •ν•΄μ„œ Bean의 μ‚¬μš© λ²”μœ„λ₯Ό κ°œλ°œμžκ°€ μ„€μ •ν•  수 μžˆλ‹€.

μš°μ„  λ”°λ‘œ 섀정을 해주지 μ•ŠμœΌλ©΄, Springμ—μ„œ Bean은 Singleton으둜 μƒμ„±λœλ‹€. 싱글톀 νŒ¨ν„΄μ²˜λŸΌ νŠΉμ • νƒ€μž…μ˜ Bean을 λ”± ν•˜λ‚˜λ§Œ λ§Œλ“€κ³  λͺ¨λ‘ κ³΅μœ ν•΄μ„œ μ‚¬μš©ν•˜κΈ° μœ„ν•¨μ΄λ‹€. 보톡은 Bean을 μ΄λ ‡κ²Œ ν•˜λ‚˜λ§Œ λ§Œλ“€μ–΄ μ‚¬μš©ν•˜λŠ” κ²½μš°κ°€ λŒ€λΆ€λΆ„μ΄μ§€λ§Œ, μš”κ΅¬μ‚¬ν•­μ΄λ‚˜ κ΅¬ν˜„μ— 따라 아닐 μˆ˜λ„ μžˆμ„ 것이닀.

λ”°λΌμ„œ Bean ScopeλŠ” 싱글톀 말고도 μ—¬λŸ¬κ°€μ§€λ₯Ό 지원해쀀닀.

✨ Scope μ’…λ₯˜

  • singleton

    ν•΄λ‹Ή Bean에 λŒ€ν•΄ IoC μ»¨ν…Œμ΄λ„ˆμ—μ„œ 단 ν•˜λ‚˜μ˜ 객체둜만 μ‘΄μž¬ν•œλ‹€.

  • prototype

    ν•΄λ‹Ή Bean에 λŒ€ν•΄ λ‹€μˆ˜μ˜ 객체가 μ‘΄μž¬ν•  수 μžˆλ‹€.

  • request

    ν•΄λ‹Ή Bean에 λŒ€ν•΄ ν•˜λ‚˜μ˜ HTTP Request의 λΌμ΄ν”„μ‚¬μ΄ν΄μ—μ„œ 단 ν•˜λ‚˜μ˜ 객체둜만 μ‘΄μž¬ν•œλ‹€.

  • session

    ν•΄λ‹Ή Bean에 λŒ€ν•΄ ν•˜λ‚˜μ˜ HTTP Session의 λΌμ΄ν”„μ‚¬μ΄ν΄μ—μ„œ 단 ν•˜λ‚˜μ˜ 객체둜만 μ‘΄μž¬ν•œλ‹€.

  • global session

    ν•΄λ‹Ή Bean에 λŒ€ν•΄ ν•˜λ‚˜μ˜ Global HTTP Session의 λΌμ΄ν”„μ‚¬μ΄ν΄μ—μ„œ 단 ν•˜λ‚˜μ˜ 객체둜만 μ‘΄μž¬ν•œλ‹€.

request, session, global session은 MVC μ›Ή μ–΄ν”Œλ¦¬μΌ€μ΄μ…˜μ—μ„œλ§Œ μ‚¬μš©ν•¨

Scope듀은 Bean으둜 λ“±λ‘ν•˜λŠ” ν΄λž˜μŠ€μ— μ–΄λ…Έν…Œμ΄μ…˜μœΌλ‘œ 섀정해쀄 수 μžˆλ‹€.

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
 
@Scope("prototype")
@Component
public class UserController {
}

MVC Framework

μŠ€ν”„λ§ MVC ν”„λ ˆμž„μ›Œν¬κ°€ λ™μž‘ν•˜λŠ” 원리λ₯Ό μ΄ν•΄ν•˜κ³  μžˆμ–΄μ•Ό ν•œλ‹€

ν΄λΌμ΄μ–ΈνŠΈκ°€ μ„œλ²„μ—κ²Œ url을 톡해 μš”μ²­ν•  λ•Œ μΌμ–΄λ‚˜λŠ” μŠ€ν”„λ§ ν”„λ ˆμž„μ›Œν¬μ˜ λ™μž‘μ„ 그림으둜 ν‘œν˜„ν•œ 것이닀.

MVC 진행 κ³Όμ •

  • ν΄λΌμ΄μ–ΈνŠΈκ°€ url을 μš”μ²­ν•˜λ©΄, μ›Ή λΈŒλΌμš°μ €μ—μ„œ μŠ€ν”„λ§μœΌλ‘œ requestκ°€ 보내진닀.

  • Dispatcher Servlet이 requestλ₯Ό λ°›μœΌλ©΄, Handler Mapping을 톡해 ν•΄λ‹Ή url을 λ‹΄λ‹Ήν•˜λŠ” Controllerλ₯Ό 탐색 ν›„ μ°Ύμ•„λ‚Έλ‹€.

  • μ°Ύμ•„λ‚Έ Controller둜 requestλ₯Ό 보내주고, 보내주기 μœ„ν•΄ ν•„μš”ν•œ Model을 κ΅¬μ„±ν•œλ‹€.

  • Modelμ—μ„œλŠ” νŽ˜μ΄μ§€ μ²˜λ¦¬μ— ν•„μš”ν•œ 정보듀을 Database에 μ ‘κ·Όν•˜μ—¬ 쿼리문을 톡해 κ°€μ Έμ˜¨λ‹€.

  • 데이터λ₯Ό 톡해 얻은 Model 정보λ₯Ό Controllerμ—κ²Œ response ν•΄μ£Όλ©΄, ControllerλŠ” 이λ₯Ό λ°›μ•„ Model을 μ™„μ„±μ‹œμΌœ Dispatcher Servletμ—κ²Œ 전달해쀀닀.

  • Dispatcher Servlet은 View Resolverλ₯Ό 톡해 request에 ν•΄λ‹Ήν•˜λŠ” view νŒŒμΌμ„ 탐색 ν›„ λ°›μ•„λ‚Έλ‹€.

  • λ°›μ•„λ‚Έ View νŽ˜μ΄μ§€ νŒŒμΌμ— Model을 보낸 ν›„ ν΄λΌμ΄μ–ΈνŠΈμ—κ²Œ 보낼 νŽ˜μ΄μ§€λ₯Ό μ™„μ„±μ‹œμΌœ λ°›μ•„λ‚Έλ‹€.

  • μ™„μ„±λœ View νŒŒμΌμ„ ν΄λΌμ΄μ–ΈνŠΈμ— responseν•˜μ—¬ 화면에 좜λ ₯ν•œλ‹€.

ꡬ성 μš”μ†Œ


✨ Dispatcher Servlet

λͺ¨λ“  requestλ₯Ό μ²˜λ¦¬ν•˜λŠ” 쀑심 컨트둀러라고 μƒκ°ν•˜λ©΄ λœλ‹€. μ„œλΈ”λ¦Ώ μ»¨ν…Œμ΄λ„ˆμ—μ„œ http ν”„λ‘œν† μ½œμ„ 톡해 λ“€μ–΄μ˜€λŠ” λͺ¨λ“  request에 λŒ€ν•΄ 제일 μ•žλ‹¨μ—μ„œ μ€‘μ•™μ§‘μ€‘μ‹μœΌλ‘œ μ²˜λ¦¬ν•΄μ£ΌλŠ” 핡심적인 역할을 ν•œλ‹€.

κΈ°μ‘΄μ—λŠ” web.xml에 λͺ¨λ‘ λ“±λ‘ν•΄μ€˜μ•Ό ν–ˆμ§€λ§Œ, λ””μŠ€νŒ¨μ²˜ μ„œλΈ”λ¦Ώμ΄ λͺ¨λ“  requestλ₯Ό ν•Έλ“€λ§ν•˜λ©΄μ„œ μž‘μ—…μ„ νŽΈλ¦¬ν•˜κ²Œ ν•  수 μžˆλ‹€.

✨ Handler Mapping

ν΄λΌμ΄μ–ΈνŠΈμ˜ request url을 μ–΄λ–€ μ»¨νŠΈλ‘€λŸ¬κ°€ μ²˜λ¦¬ν•΄μ•Ό ν•  지 μ°Ύμ•„μ„œ Dispatcher Servletμ—κ²Œ μ „λ‹¬ν•΄μ£ΌλŠ” 역할을 λ‹΄λ‹Ήν•œλ‹€.

컨트둀러 μƒμ—μ„œ url을 λ§€ν•‘μ‹œν‚€κΈ° μœ„ν•΄ @RequestMapping을 μ‚¬μš©ν•˜λŠ”λ°, ν•Έλ“€λŸ¬κ°€ 이λ₯Ό μ°Ύμ•„μ£ΌλŠ” 역할을 ν•œλ‹€.

✨ Controller

μ‹€μ§ˆμ μΈ μš”μ²­μ„ μ²˜λ¦¬ν•˜λŠ” 곳이닀. Dispatcher Servlet이 ν”„λ‘ νŠΈ 컨트둀러라면, 이 곳은 λ°±μ—”λ“œ 컨트둀러라고 λ³Ό 수 μžˆλ‹€.

λͺ¨λΈμ˜ 처리 κ²°κ³Όλ₯Ό λ‹΄μ•„ Dispatcher Servletμ—κ²Œ λ°˜ν™˜ν•΄μ€€λ‹€.

✨ View Resolver

컨트둀러의 처리 κ²°κ³Όλ₯Ό λ§Œλ“€ viewλ₯Ό κ²°μ •ν•΄μ£ΌλŠ” 역할을 λ‹΄λ‹Ήν•œλ‹€. λ‹€μ–‘ν•œ μ’…λ₯˜κ°€ 있기 λ•Œλ¬Έμ— 상황에 맞게 ν™œμš©ν•˜λ©΄ λœλ‹€.

SpringApplication

μŠ€ν”„λ§ λΆ€νŠΈλ‘œ ν”„λ‘œμ νŠΈλ₯Ό μ‹€ν–‰ν•  λ•Œ Application 클래슀λ₯Ό λ§Œλ“ λ‹€.

클래슀λͺ…은 κ°œλ°œμžκ°€ ν”„λ‘œμ νŠΈμ— 맞게 μ„€μ •ν•  수 μžˆμ§€λ§Œ, 큰 틀은 μ•„λž˜μ™€ κ°™λ‹€.

@SpringBootApplication
public class Application {

	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);
	}

}

@SpringBootApplication μ–΄λ…Έν…Œμ΄μ…˜μ„ 톡해 μŠ€ν”„λ§ Bean을 읽어와 μžλ™μœΌλ‘œ 생성해쀀닀.

이 μ–΄λ…Έν…Œμ΄μ…˜μ΄ μžˆλŠ” 파일 μœ„μΉ˜λΆ€ν„° 섀정듀을 μ½μ–΄κ°€λ―€λ‘œ, λ°˜λ“œμ‹œ ν”„λ‘œμ νŠΈμ˜ μ΅œμƒλ‹¨μ— λ§Œλ“€μ–΄μ•Ό ν•œλ‹€.

SpringApplication.run()으둜 ν•΄λ‹Ή 클래슀λ₯Ό runν•˜λ©΄, λ‚΄μž₯ WASλ₯Ό μ‹€ν–‰ν•œλ‹€. λ‚΄μž₯ WAS의 μž₯μ μœΌλ‘œλŠ” κ°œλ°œμžκ°€ λ”°λ‘œ ν†°μΊ£κ³Ό 같은 μ™ΈλΆ€ WASλ₯Ό μ„€μΉ˜ ν›„ 섀정해두지 μ•Šμ•„λ„ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ„ μ‹€ν–‰ν•  수 μžˆλ‹€.

λ˜ν•œ, μ™Έμž₯ WASλ₯Ό μ‚¬μš©ν•  μ‹œ 이 ν”„λ‘œμ νŠΈλ₯Ό μ‹€ν–‰μ‹œν‚€κΈ° μœ„ν•œ μ„œλ²„μ—μ„œ λͺ¨λ‘ μ™Έμž₯ WAS의 μ’…λ₯˜μ™€ 버전, 섀정을 μΌμΉ˜μ‹œμΌœμ•Όλ§Œ ν•œλ‹€. λ”°λΌμ„œ λ‚΄μž₯ WASλ₯Ό μ‚¬μš©ν•˜λ©΄ 이런 신경은 쓰지 μ•Šμ•„λ„ 되기 λ•Œλ¬Έμ— 맀우 νŽΈλ¦¬ν•˜λ‹€.

μ‹€μ œλ‘œ λ§Žμ€ νšŒμ‚¬λ“€μ΄ 이런 μž₯점을 μ‚΄λ € λ‚΄μž₯ WASλ₯Ό μ‚¬μš©ν•˜κ³  있고, μ „ν™˜ν•˜κ³  μžˆλ‹€.

Test Code

✨ ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό μž‘μ„±ν•΄μ•Ό ν•˜λŠ” 이유

  • κ°œλ°œλ‹¨κ³„ μ΄ˆκΈ°μ— 문제λ₯Ό λ°œκ²¬ν•  수 있음

  • λ‚˜μ€‘μ— μ½”λ“œλ₯Ό λ¦¬νŒ©ν† λ§ν•˜κ±°λ‚˜ 라이브러리 μ—…κ·Έλ ˆμ΄λ“œ μ‹œ κΈ°μ‘΄ κΈ°λŠ₯이 잘 μž‘λ™ν•˜λŠ” 지 확인 κ°€λŠ₯함

  • κΈ°λŠ₯에 λŒ€ν•œ λΆˆν™•μ‹€μ„± κ°μ†Œ

개발 μ½”λ“œ 이외에 ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό μž‘μ„±ν•˜λŠ” 일은 개발 μ‹œκ°„μ΄ λŠ˜μ–΄λ‚  것이라고 생각할 수 μžˆλ‹€. ν•˜μ§€λ§Œ λ‚΄ μ½”λ“œμ— 였λ₯˜κ°€ μžˆλŠ” 지 검증할 λ•Œ, ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό μž‘μ„±ν•˜μ§€ μ•Šκ³  μ§„ν–‰ν•œλ‹€λ©΄ 더 μ‹œκ°„ μ†Œλͺ¨κ°€ 클 것이닀.

1. μ½”λ“œλ₯Ό μž‘μ„±ν•œ λ’€ ν”„λ‘œκ·Έλž¨μ„ μ‹€ν–‰ν•˜μ—¬ μ„œλ²„λ₯Ό 킨닀. 2. API ν”„λ‘œκ·Έλž¨(ex. Postman)으둜 HTTP μš”μ²­ ν›„ κ²°κ³Όλ₯Ό Print둜 μ°μ–΄μ„œ ν™•μΈν•œλ‹€. 3. κ²°κ³Όκ°€ μ˜ˆμƒκ³Ό λ‹€λ₯΄λ©΄, λ‹€μ‹œ ν”„λ‘œκ·Έλž¨μ„ μ’…λ£Œν•œ λ’€ μ½”λ“œλ₯Ό μˆ˜μ •ν•˜κ³  λ°˜λ³΅ν•œλ‹€.

μœ„μ™€ 같은 방식이 μ–Όλ§ˆλ‚˜ 반볡될 지 λͺ¨λ₯Έλ‹€. 그리고 ν•˜λ‚˜μ˜ κΈ°λŠ₯λ§ˆλ‹€ μ €λ ‡κ²Œ ν…ŒμŠ€νŠΈλ₯Ό ν•˜λ©΄ μ„œλ²„λ₯Ό ν‚€κ³  λ„λŠ” μž‘μ—… λ˜ν•œ λ„ˆλ¬΄ λΉ„νš¨μœ¨μ μ΄λ‹€.

이 밖에도 Print둜 눈으둜 κ²€μ¦ν•˜λŠ” 것도 μ–΄λŠμ •λ„ μ„ μ—μ„œ ν•œκ³„κ°€ μžˆλ‹€. ν…ŒμŠ€νŠΈ μ½”λ“œλŠ” μžλ™μœΌλ‘œ 검증을 ν•΄μ£ΌκΈ° λ•Œλ¬Έμ— μ„±κ³΅ν•œλ‹€λ©΄ μˆ˜λ™μœΌλ‘œ 검증할 ν•„μš” μžμ²΄κ°€ 없어진닀.

μƒˆλ‘œμš΄ κΈ°λŠ₯이 μΆ”κ°€λ˜μ—ˆμ„ λ•Œλ„ ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό 톡해 λ§Œμ•½ 기쑴의 μ½”λ“œμ— 영ν–₯이 κ°”λ‹€λ©΄ μ–΄λ–€ 뢀뢄을 μˆ˜μ •ν•΄μ•Ό ν•˜λŠ” 지 μ•Œ 수 μžˆλŠ” μž₯점도 μ‘΄μž¬ν•œλ‹€.

λ”°λΌμ„œ ν…ŒμŠ€νŠΈ μ½”λ“œλŠ” κ°œλ°œν•˜λŠ” 데 μžˆμ–΄μ„œ ν•„μˆ˜μ μΈ 뢀뢄이며 λ°˜λ“œμ‹œ ν™œμš©ν•΄μ•Ό ν•œλ‹€.

✨ ν…ŒμŠ€νŠΈ μ½”λ“œ 예제

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;

@RunWith(SpringRunner.class)
@WebMvcTest(controllers = HomeController.class)
public class HomeControllerTest {

    @Autowired
    private MockMvc mvc;

    @Test
    public void home_return() throws Exception {
        //when
        String home = "home";

        //then
        mvc.perform(get("/home"))
                .andExpect(status().isOk())
                .andExpect(content().string(home));
    }
}
  1. @RunWith(SpringRunner.class)

ν…ŒμŠ€νŠΈλ₯Ό 진행할 λ•Œ JUnit에 λ‚΄μž₯된 μ‹€ν–‰μž 외에 λ‹€λ₯Έ μ‹€ν–‰μžλ₯Ό μ‹€ν–‰μ‹œν‚¨λ‹€.

μŠ€ν”„λ§ λΆ€νŠΈ ν…ŒμŠ€νŠΈμ™€ JUnit μ‚¬μ΄μ˜ μ—°κ²°μž 역할을 ν•œλ‹€κ³  μƒκ°ν•˜λ©΄ λœλ‹€.

  1. @WebMvcTest

컨트둀러만 μ‚¬μš©ν•  λ•Œ 선언이 κ°€λŠ₯ν•˜λ©°, Spring MVC에 집쀑할 수 μžˆλŠ” μ–΄λ…Έν…Œμ΄μ…˜μ΄λ‹€.

  1. @Autowired

μŠ€ν”„λ§μ΄ κ΄€λ¦¬ν•˜λŠ” Bean을 μ£Όμž…μ‹œμΌœμ€€λ‹€.

  1. MockMvc

μ›Ή APIλ₯Ό ν…ŒμŠ€νŠΈν•  λ•Œ μ‚¬μš©ν•˜λ©°, 이λ₯Ό 톡해 HTTP GET, POST, DELETE 등에 λŒ€ν•œ API ν…ŒμŠ€νŠΈκ°€ κ°€λŠ₯ν•˜λ‹€.

  1. mvc.perform(get("/home"))

/home μ£Όμ†Œλ‘œ HTTP GET μš”μ²­μ„ ν•œ 상황이닀.

  1. .andExpect(status().isOk())

κ²°κ³Όλ₯Ό κ²€μ¦ν•˜λŠ” andExpect둜, μ—¬λŸ¬κ°œλ₯Ό λΆ™μ—¬μ„œ μ‚¬μš©μ΄ κ°€λŠ₯ν•˜λ‹€. status()λŠ” HTTP Headerλ₯Ό κ²€μ¦ν•˜λŠ” κ²ƒμœΌλ‘œ 결과에 λŒ€ν•œ HTTP Status μƒνƒœλ₯Ό 확인할 수 μžˆλ‹€. ν˜„μž¬ isOK()λŠ” 200 μ½”λ“œκ°€ λ§žλŠ”μ§€ ν™•μΈν•˜κ³  μžˆλ‹€.

ν”„λ‘œμ νŠΈλ₯Ό λ§Œλ“€λ©΄μ„œ λ‹€μ–‘ν•œ κΈ°λŠ₯듀을 κ΅¬ν˜„ν•˜κ²Œ λ˜λŠ”λ°, 이처럼 ν…ŒμŠ€νŠΈ μ½”λ“œλ‘œ κ²¬κ³ ν•œ ν”„λ‘œμ νŠΈλ₯Ό λ§Œλ“€κΈ° μœ„ν•œ κΈ°λŠ₯별 λ‹¨μœ„ ν…ŒμŠ€νŠΈλ₯Ό μ§„ν–‰ν•˜λŠ” μŠ΅κ΄€μ„ κΈΈλŸ¬μ•Ό ν•œλ‹€.

JPA

Java Persistence API

κ°œλ°œμžκ°€ 직접 SQL을 μž‘μ„±ν•˜μ§€ μ•Šκ³ , JPA APIλ₯Ό ν™œμš©ν•΄ DBλ₯Ό μ €μž₯ν•˜κ³  관리할 수 μžˆλ‹€.

JPAλŠ” μ˜€λŠ˜λ‚  μŠ€ν”„λ§μ—μ„œ 많이 ν™œμš©λ˜κ³  μžˆμ§€λ§Œ, μŠ€ν”„λ§μ΄ μ œκ³΅ν•˜λŠ” APIκ°€ μ•„λ‹Œ μžλ°”κ°€ μ œκ³΅ν•˜λŠ” APIλ‹€.

μžλ°” ORM κΈ°μˆ μ— λŒ€ν•œ ν‘œμ€€ λͺ…μ„Έλ‘œ, μžλ°” μ–΄ν”Œλ¦¬μΌ€μ΄μ…˜μ—μ„œ κ΄€κ³„ν˜• λ°μ΄ν„°λ² μ΄μŠ€λ₯Ό μ‚¬μš©ν•˜λŠ” 방식을 μ •μ˜ν•œ μΈν„°νŽ˜μ΄μŠ€λ‹€.

✨ ORM(Object Relational Mapping)

ORM ν”„λ ˆμž„μ›Œν¬λŠ” μžλ°” 객체와 κ΄€κ³„ν˜• DBλ₯Ό λ§€ν•‘ν•œλ‹€. 즉, 객체가 DB ν…Œμ΄λΈ”μ΄ λ˜λ„λ‘ λ§Œλ“€μ–΄μ£ΌλŠ” 것이닀. ORM을 μ‚¬μš©ν•˜λ©΄, SQL을 μž‘μ„±ν•˜μ§€ μ•Šμ•„λ„ 직관적인 λ©”μ†Œλ“œλ‘œ 데이터λ₯Ό μ‘°μž‘ν•  수 μžˆλ‹€λŠ” μž₯점이 μžˆλ‹€. ( κ°œλ°œμžμ—κ²Œ 생산성을 ν–₯μƒμ‹œμΌœμ€„ 수 있음 )

μ’…λ₯˜λ‘œλŠ” Hibernate, EclipseLink, DataNucleus 등이 μžˆλ‹€.

μŠ€ν”„λ§ λΆ€νŠΈμ—μ„œλŠ” spring-boot-starter-data-jpa둜 νŒ¨ν‚€μ§€λ₯Ό 가져와 μ‚¬μš©ν•˜λ©°, μ΄λŠ” Hibernate ν”„λ ˆμž„μ›Œν¬λ₯Ό ν™œμš©ν•œλ‹€.

JPAλŠ” μ• ν”Œλ¦¬μΌ€μ΄μ…˜κ³Ό JDBC μ‚¬μ΄μ—μ„œ λ™μž‘ν•˜λ©°, κ°œλ°œμžκ°€ JPAλ₯Ό ν™œμš©ν–ˆμ„ λ•Œ JDBC APIλ₯Ό 톡해 SQL을 ν˜ΈμΆœν•˜μ—¬ λ°μ΄ν„°λ² μ΄μŠ€μ™€ ν˜ΈμΆœν•˜λŠ” μ „κ°œκ°€ 이루어진닀.

즉, κ°œλ°œμžλŠ” JPA의 ν™œμš©λ²•λ§Œ 읡히면 DB 쿼리 κ΅¬ν˜„μ—†μ΄ λ°μ΄ν„°λ² μ΄μŠ€λ₯Ό 관리할 수 μžˆλ‹€.

✨ JPA νŠΉμ§•

  1. 객체 쀑심 개발 κ°€λŠ₯

    SQL 쀑심 개발이 이루어진닀면, CRUD μž‘μ—…μ΄ λ°˜λ³΅ν•΄μ„œ μ΄λ£¨μ–΄μ Έμ•Όν•œλ‹€.

    ν•˜λ‚˜μ˜ ν…Œμ΄λΈ”μ„ 생성해야할 λ•Œ 이에 ν•΄λ‹Ήν•˜λŠ” CRUDλ₯Ό μ „λΆ€ λ§Œλ“€μ–΄μ•Ό ν•˜λ©°, 좔후에 컬럼이 μƒμ„±λ˜λ©΄ κ΄€λ ¨ SQL을 λͺ¨λ‘ μˆ˜μ •ν•΄μ•Ό ν•˜λŠ” λ²ˆκ±°λ‘œμ›€μ΄ μžˆλ‹€. λ˜ν•œ 개발 κ³Όμ •μ—μ„œ μ‹€μˆ˜ν•  κ°€λŠ₯성도 높아진닀.

  2. 생산성 증가

    SQL 쿼리λ₯Ό 직접 μƒμ„±ν•˜μ§€ μ•Šκ³ , λ§Œλ“€μ–΄μ§„ 객체에 JPA λ©”μ†Œλ“œλ₯Ό ν™œμš©ν•΄ λ°μ΄ν„°λ² μ΄μŠ€λ₯Ό 닀루기 λ•Œλ¬Έμ— κ°œλ°œμžμ—κ²Œ 맀우 νŽΈλ¦¬μ„±μ„ μ œκ³΅ν•΄μ€€λ‹€.

  3. μœ μ§€λ³΄μˆ˜ 용이

    쿼리 μˆ˜μ •μ΄ ν•„μš”ν•  λ•Œ, 이λ₯Ό λ‹΄μ•„μ•Ό ν•  DTO ν•„λ“œλ„ λͺ¨λ‘ λ³€κ²½ν•΄μ•Ό ν•˜λŠ” μž‘μ—…μ΄ ν•„μš”ν•˜μ§€λ§Œ JPAμ—μ„œλŠ” μ—”ν‹°ν‹° 클래슀 μ •λ³΄λ§Œ λ³€κ²½ν•˜λ©΄ λ˜λ―€λ‘œ μœ μ§€λ³΄μˆ˜μ— μš©μ΄ν•˜λ‹€.

  4. μ„±λŠ₯ 증가

    μ‚¬λžŒμ΄ 직접 SQL을 μ§œλŠ” 것과 λΉ„κ΅ν•΄μ„œ JPAλŠ” λ™μΌν•œ 쿼리에 λŒ€ν•œ μΊμ‹œ κΈ°λŠ₯을 지원해주기 λ•Œλ¬Έμ— 비ꡐ적 높은 μ„±λŠ₯ νš¨μœ¨μ„ κ²½ν—˜ν•  수 μžˆλ‹€.

✨ μ œμ•½μ‚¬ν•­

JPAλŠ” λ³΅μž‘ν•œ μΏΌλ¦¬λ³΄λ‹€λŠ” μ‹€μ‹œκ°„ 쿼리에 μ΅œμ ν™”λ˜μ–΄μžˆλ‹€. 예λ₯Ό λ“€μ–΄ 톡계 μ²˜λ¦¬μ™€ 같은 λ³΅μž‘ν•œ μž‘μ—…μ΄ ν•„μš”ν•œ κ²½μš°μ—λŠ” 기쑴의 Mybatis와 같은 Mapper 방식이 더 효율적일 수 μžˆλ‹€.

Springμ—μ„œλŠ” JPA와 Mybatisλ₯Ό 같이 μ‚¬μš©ν•  수 있기 λ•Œλ¬Έμ—, 상황에 λ§žλŠ” 방식을 νƒν•˜μ—¬ κ°œλ°œν•˜λ©΄ λœλ‹€.

[Spring data JPA] 더티 체킹(Dirty Checking)

νŠΈλžœμž­μ…˜ μ•ˆμ—μ„œ Entity의 변경이 일어났을 λ•Œ λ³€κ²½ν•œ λ‚΄μš©μ„ μžλ™μœΌλ‘œ DB에 λ°˜μ˜ν•˜λŠ” 것

ORM κ΅¬ν˜„μ²΄ 개발 μ‹œ 더티 μ²΄ν‚Ήμ΄λΌλŠ” 말을 자주 λ³Ό 수 μžˆλ‹€.

더티 체킹이 μ–΄λ–€ 것을 λœ»ν•˜λŠ” 지 κ°„λ‹¨νžˆ μ‚΄νŽ΄λ³΄μž.

JPA둜 κ°œλ°œν•˜λŠ” 경우 κ΅¬ν˜„ν•œ ν•œ 가지 κΈ°λŠ₯을 예둜 λ“€μ–΄λ³΄μž

✨ ex) μ£Όλ¬Έ μ·¨μ†Œ κΈ°λŠ₯

@Transactional  
public void cancelOrder(Long orderId) {  
    //μ£Όλ¬Έ μ—”ν‹°ν‹° 쑰회  
    Order order = orderRepository.findOne(orderId);  

    //μ£Όλ¬Έ μ·¨μ†Œ  
    order.cancel();  
}

orderIdλ₯Ό 톡해 주문을 μ·¨μ†Œν•˜λŠ” λ©”μ†Œλ“œλ‹€. λ°μ΄ν„°λ² μ΄μŠ€μ— λ°˜μ˜ν•˜κΈ° μœ„ν•΄μ„ , update와 같은 쿼리가 μžˆμ–΄μ•Όν•  것 같은데 μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ”λ‹€.

ν•˜μ§€λ§Œ, μ‹€μ œλ‘œ 이 λ©”μ†Œλ“œλ₯Ό μ‹€ν–‰ν•˜λ©΄ λ°μ΄ν„°λ² μ΄μŠ€μ— updateκ°€ 잘 이루어진닀.

  • νŠΈλžœμž­μ…˜ μ‹œμž‘

  • orderId둜 μ£Όλ¬Έ Entity 쑰회

  • ν•΄λ‹Ή Entity μ£Όλ¬Έ μ·¨μ†Œ μƒνƒœλ‘œ Update

  • νŠΈλžœμž­μ…˜ 컀밋

이λ₯Ό κ°€λŠ₯ν•˜κ²Œ ν•˜λŠ” 것이 λ°”λ‘œ '더티 체킹(Dirty Checking)'이라고 보면 λœλ‹€.

κ·Έλƒ₯ 더티 μ²΄ν‚Ήμ˜ λ‹¨μ–΄λ§Œ κ°„λ‹¨νžˆ ν•΄μ„ν•˜λ©΄ λ³€κ²½ κ°μ§€λ‘œ λ³Ό 수 μžˆλ‹€. μ’€ 더 μžμ„Ένžˆ λ§ν•˜λ©΄, Entityμ—μ„œ 변경이 μΌμ–΄λ‚œ κ±Έ κ°μ§€ν•œ λ’€, λ°μ΄ν„°λ² μ΄μŠ€μ— λ°˜μ˜μ‹œμΌœμ€€λ‹€λŠ” μ˜λ―Έλ‹€. (변경은 졜초 쑰회 μƒνƒœκ°€ 기쀀이닀)

Dirty : μƒνƒœμ˜ λ³€ν™”κ°€ 생김

Checking : 검사

JPAμ—μ„œλŠ” νŠΈλžœμž­μ…˜μ΄ λλ‚˜λŠ” μ‹œμ μ— λ³€ν™”κ°€ 있던 λͺ¨λ“  μ—”ν‹°ν‹°μ˜ 객체λ₯Ό λ°μ΄ν„°λ² μ΄μŠ€λ‘œ μ•Œμ•„μ„œ λ°˜μ˜μ„ μ‹œμΌœμ€€λ‹€. 즉, νŠΈλžœμž­μ…˜μ˜ λ§ˆμ§€λ§‰ μ‹œμ μ—μ„œ λ‹€λ₯Έ 점을 λ°œκ²¬ν–ˆμ„ λ•Œ λ°μ΄ν„°λ² μ΄μŠ€λ‘œ update 쿼리λ₯Ό λ‚ λ €μ£ΌλŠ” 것이닀.

  • JPAμ—μ„œ Entityλ₯Ό 쑰회

  • 쑰회된 μƒνƒœμ˜ Entity에 λŒ€ν•œ μŠ€λƒ…μƒ· 생성

  • νŠΈλžœμž­μ…˜ 컀밋 ν›„ ν•΄λ‹Ή μŠ€λƒ…μƒ·κ³Ό ν˜„μž¬ Entity μƒνƒœμ˜ λ‹€λ₯Έ 점을 체크

  • λ‹€λ₯Έ 점듀을 update 쿼리둜 λ°μ΄ν„°λ² μ΄μŠ€μ— 전달

μ΄λ•Œ 더티 체킹을 κ²€μ‚¬ν•˜λŠ” λŒ€μƒμ€ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈκ°€ κ΄€λ¦¬ν•˜λŠ” Entity둜만 λŒ€μƒμœΌλ‘œ ν•œλ‹€.

μ€€μ˜μ†, λΉ„μ˜μ† EntityλŠ” 값을 λ³€κ²½ν•  지라도 λ°μ΄ν„°λ² μ΄μŠ€μ— λ°˜μ˜μ‹œν‚€μ§€ μ•ŠλŠ”λ‹€.

기본적으둜 더티 체킹을 μ‹€ν–‰ν•˜λ©΄, SQLμ—μ„œλŠ” λ³€κ²½λœ μ—”ν‹°ν‹°μ˜ λͺ¨λ“  λ‚΄μš©μ„ update 쿼리둜 λ§Œλ“€μ–΄ μ „λ‹¬ν•˜λŠ”λ°, μ΄λ•Œ ν•„λ“œκ°€ λ§Žμ•„μ§€λ©΄ 전체 ν•„λ“œλ₯Ό updateν•˜λŠ”κ²Œ λΉ„νš¨μœ¨μ μΌ μˆ˜λ„ μžˆλ‹€.

μ΄λ•ŒλŠ” @DynamicUpdateλ₯Ό ν•΄λ‹Ή Entity에 μ„ μ–Έν•˜μ—¬ λ³€κ²½ ν•„λ“œλ§Œ λ°˜μ˜μ‹œν‚€λ„λ‘ λ§Œλ“€μ–΄μ€„ 수 μžˆλ‹€.

@Getter
@NoArgsConstructor
@Entity
@DynamicUpdate
public class Order {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String product;
}

Spring Security - 인증 및 κΆŒν•œ λΆ€μ—¬

API에 κΆŒν•œ κΈ°λŠ₯이 μ—†μœΌλ©΄, μ•„λ¬΄λ‚˜ νšŒμ› 정보λ₯Ό μ‘°νšŒν•˜κ³  μˆ˜μ •ν•˜κ³  μ‚­μ œν•  수 μžˆλ‹€. λ”°λΌμ„œ 이λ₯Ό 막기 μœ„ν•΄ 인증된 μœ μ €λ§Œ APIλ₯Ό μ‚¬μš©ν•  수 μžˆλ„λ‘ ν•΄μ•Όν•˜λŠ”λ°, μ΄λ•Œ μ‚¬μš©ν•  수 μžˆλŠ” ν•΄κ²° μ±… 쀑 ν•˜λ‚˜κ°€ Spring Securityλ‹€.

μŠ€ν”„λ§ ν”„λ ˆμž„μ›Œν¬μ—μ„œλŠ” 인증 및 κΆŒν•œ λΆ€μ—¬λ‘œ λ¦¬μ†ŒμŠ€ μ‚¬μš©μ„ 컨트둀 ν•  수 μžˆλŠ” Spring Securityλ₯Ό μ œκ³΅ν•œλ‹€. 이 ν”„λ ˆμž„μ›Œν¬λ₯Ό μ‚¬μš©ν•˜λ©΄, λ³΄μ•ˆ 처리λ₯Ό 자체적으둜 κ΅¬ν˜„ν•˜μ§€ μ•Šμ•„λ„ μ‰½κ²Œ ν•„μš”ν•œ κΈ°λŠ₯을 κ΅¬ν˜„ν•  수 μžˆλ‹€.

Spring SecurityλŠ” μŠ€ν”„λ§μ˜ DispatcherServlet μ•žλ‹¨μ— Filter ν˜•νƒœλ‘œ μœ„μΉ˜ν•œλ‹€. Dispatcher둜 λ„˜μ–΄κ°€κΈ° 전에 이 Filterκ°€ μš”μ²­μ„ κ°€λ‘œμ±„μ„œ, ν΄λΌμ΄μ–ΈνŠΈμ˜ λ¦¬μ†ŒμŠ€ μ ‘κ·Ό κΆŒν•œμ„ ν™•μΈν•˜κ³ , μ—†λŠ” κ²½μš°μ—λŠ” 인증 μš”μ²­ ν™”λ©΄μœΌλ‘œ μžλ™ λ¦¬λ‹€μ΄λ ‰νŠΈν•œλ‹€.

✨ Spring Security Filter

Filter의 μ’…λ₯˜λŠ” μƒλ‹Ήνžˆ λ§Žλ‹€. μœ„μ—μ„œ μ˜ˆμ‹œλ‘œ λ“  ν΄λΌμ΄μ–ΈνŠΈκ°€ λ¦¬μ†ŒμŠ€μ— λŒ€ν•œ μ ‘κ·Ό κΆŒν•œμ΄ 없을 λ•Œ 처리λ₯Ό λ‹΄λ‹Ήν•˜λŠ” ν•„ν„°λŠ” UsernamePasswordAuthenticationFilterλ‹€.

인증 κΆŒν•œμ΄ 없을 λ•Œ 였λ₯˜λ₯Ό JSON으둜 λ‚΄λ €μ£ΌκΈ° μœ„ν•΄ ν•΄λ‹Ή ν•„ν„°κ°€ μ‹€ν–‰λ˜κΈ° μ „ μ²˜λ¦¬κ°€ ν•„μš”ν•  것이닀.

API 인증 및 κΆŒν•œ λΆ€μ—¬λ₯Ό μœ„ν•œ μž‘μ—… μˆœμ„œλŠ” μ•„λž˜μ™€ 같이 ꡬ성할 수 μžˆλ‹€.

  1. νšŒμ› κ°€μž…, 둜그인 API κ΅¬ν˜„

  2. λ¦¬μ†ŒμŠ€ μ ‘κ·Ό κ°€λŠ₯ν•œ ROLE_USER κΆŒν•œμ„ κ°€μž… νšŒμ›μ—κ²Œ λΆ€μ—¬

  3. Spring Security μ„€μ •μ—μ„œ ROLE_USER κΆŒν•œμ„ 가지면 μ ‘κ·Ό κ°€λŠ₯ν•˜λ„λ‘ μ„ΈνŒ…

  4. κΆŒν•œμ΄ μžˆλŠ” νšŒμ›μ΄ 둜그인 μ„±κ³΅ν•˜λ©΄ λ¦¬μ†ŒμŠ€ μ ‘κ·Ό κ°€λŠ₯ν•œ JWT 토큰 λ°œκΈ‰

  5. ν•΄λ‹Ή νšŒμ›μ€ κΆŒν•œμ΄ ν•„μš”ν•œ API μ ‘κ·Ό μ‹œ JWT λ³΄μ•ˆ 토큰을 μ‚¬μš©

이처럼 μ ‘κ·Ό μ œν•œμ΄ ν•„μš”ν•œ APIμ—λŠ” λ³΄μ•ˆ 토큰을 ν†΅ν•΄μ„œ 이 μœ μ €κ°€ κΆŒν•œμ΄ μžˆλŠ”μ§€ μ—¬λΆ€λ₯Ό Spring Securityλ₯Ό 톡해 μ²΄ν¬ν•˜κ³  λ¦¬μ†ŒμŠ€λ₯Ό μš”μ²­ν•  수 μžˆλ„λ‘ ꡬ성할 수 μžˆλ‹€.

✨ Spring Security Configuration

μ„œλ²„μ— λ³΄μ•ˆμ„ μ„€μ •ν•˜κΈ° μœ„ν•΄ Configuration을 λ§Œλ“ λ‹€. κΈ°μ‘΄ μ˜ˆμ‹œμ²˜λŸΌ, USER에 λŒ€ν•œ κΆŒν•œμ„ μ„€μ •ν•˜κΈ° μœ„ν•œ μž‘μ—…λ„ μ—¬κΈ°μ„œ μ§„ν–‰λœλ‹€.

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .httpBasic().disable() // rest api μ΄λ―€λ‘œ κΈ°λ³Έμ„€μ • μ‚¬μš©μ•ˆν•¨. 기본섀정은 λΉ„μΈμ¦μ‹œ 둜그인폼 ν™”λ©΄μœΌλ‘œ λ¦¬λ‹€μ΄λ ‰νŠΈ
                .cors().configurationSource(corsConfigurationSource())
                .and()
                .csrf().disable() // rest apiμ΄λ―€λ‘œ csrf λ³΄μ•ˆμ΄ ν•„μš”μ—†μœΌλ―€λ‘œ disable처리.
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // jwt token으둜 μΈμ¦ν•˜λ―€λ‘œ μ„Έμ…˜μ€ ν•„μš”μ—†μœΌλ―€λ‘œ μƒμ„±μ•ˆν•¨.
                .and()
                .authorizeRequests() // λ‹€μŒ λ¦¬ν€˜μŠ€νŠΈμ— λŒ€ν•œ μ‚¬μš©κΆŒν•œ 체크
                .antMatchers("/*/signin", "/*/signin/**", "/*/signup", "/*/signup/**", "/social/**").permitAll() // κ°€μž… 및 인증 μ£Όμ†ŒλŠ” λˆ„κ΅¬λ‚˜ μ ‘κ·Όκ°€λŠ₯
                .antMatchers(HttpMethod.GET, "home/**").permitAll() // home으둜 μ‹œμž‘ν•˜λŠ” GETμš”μ²­ λ¦¬μ†ŒμŠ€λŠ” λˆ„κ΅¬λ‚˜ μ ‘κ·Όκ°€λŠ₯
                .anyRequest().hasRole("USER") // κ·Έμ™Έ λ‚˜λ¨Έμ§€ μš”μ²­μ€ λͺ¨λ‘ 인증된 νšŒμ›λ§Œ μ ‘κ·Ό κ°€λŠ₯
                .and()
                .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class); // jwt token ν•„ν„°λ₯Ό id/password 인증 ν•„ν„° 전에 λ„£λŠ”λ‹€

    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .httpBasic().disable() 
                .cors().configurationSource(corsConfigurationSource())
                .and()
                .csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) 
                .and()
                .authorizeRequests()
                .antMatchers("/*/signin", "/*/signin/**", "/*/signup", "/*/signup/**", "/social/**").permitAll() 
                .antMatchers(HttpMethod.GET, "home/**").permitAll()
                .anyRequest().hasRole("USER") 
                .and()
                .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider),
                                         UsernamePasswordAuthenticationFilter.class); 

    }

Last updated