Cute Blinking Unicorn

백엔드/스프링부트

스프링부트 배우기

민밥통 2023. 12. 15. 15:05

https://wikidocs.net/161164

 

2-03 JPA

* `[완성 소스]` : [https://github.com/pahkey/sbb3/tree/2-03](https://github.com/pahkey/sbb3/tree/2-03) …

wikidocs.net

JPA

우리가 만들 SBB는 질문 답변 게시판이다. 질문이나 답변을 작성하면 데이터가 생성된다. 그러므로 데이터를 저장하거나 조회하거나 수정하는 등의 기능을 구현해야 한다. 웹 서비스는 데이터를 처리할 때 대부분 데이터베이스를 사용한다.

그런데 데이터베이스를 사용하려면 SQL 쿼리(query)라는 구조화된 질의를 작성하고 실행하는 등의 복잡한 과정이 필요하다. 이때 ORM(object relational mapping)을 이용하면 자바 문법만으로도 데이터베이스를 다룰 수 있다. 즉, ORM을 이용하면 개발자가 쿼리를 직접 작성하지 않아도 데이터베이스의 데이터를 처리할 수 있다.

 ORM은 데이터베이스에 데이터를 저장하는 테이블을 자바 클래스로 만들어 관리하는 기술로 이해해도 좋다.

 


ORM

SQL 쿼리와 ORM을 비교해 보자. 다음과 같은 형태로 구성된 질문 테이블에 데이터를 입력한다고 가정해 보자.

[question 테이블 구성 예]

idsubjectcontent

1 안녕하세요 가입 인사드립니다 ^^
2 질문 있습니다 ORM이 궁금합니다
... ... ...

표에서 id는 각 데이터를 구분하는 고윳값이다. 데이터베이스의 설정을 통해 값이 자동으로 증가되어 저장되도록 할수 있다.

이렇게 구성된 question 테이블에 새로운 데이터를 삽입하는 쿼리는 보통 다음처럼 작성한다.

insert into question (subject, content) values ('안녕하세요', '가입 인사드립니다 ^^');
insert into question (subject, content) values ('질문 있습니다', 'ORM이 궁금합니다');

하지만 ORM을 사용하면 쿼리 대신 자바 코드로 다음처럼 작성할 수 있다.

다음코드는 작성할 필요없이 눈으로만 확인하자.

Question q1 = new Question();
q1.setSubject("안녕하세요");
q1.setContent("가입 인사드립니다 ^^");
this.questionRepository.save(q1);

Question q2 = new Question();
q2.setSubject("질문 있습니다");
q2.setContent("ORM이 궁금합니다");
this.questionRepository.save(q2);

위와 같이 ORM을 이용한 데이터의 삽입 예제는 코드 자체만 놓고 보면 양이 많아 보이지만 별도의 SQL 문법을 배우지 않아도 된다는 장점이 있다.

코드에서 Question은 자바 클래스이며, 이처럼 데이터를 관리하는 데 사용하는 ORM 클래스를 엔티티(Entity)라고 한다. ORM을 사용하면 내부에서 SQL 쿼리를 자동으로 생성해 주므로 직접 작성하지 않아도 된다. 즉, 자바만 알아도 데이터베이스에 질의할 수 있다.

 

점프 투 스프링부트ORM의 장점을 더 알아보자

ORM을 이용하면 데이터베이스 종류에 상관 없이 일관된 코드를 유지할 수 있어서 프로그램을 유지·보수하기가 편리하다.(개발자 측면에서)  또한 내부에서 안전한 SQL 쿼리를 자동으로 생성해 주므로 (일반 사람이 개발자 SQL을 만들담보면 실수 오류가 발생할 수 있는데 함수만 잘 써주면 SQL걱정안해도 된다는 것) 개발자가 달라도 통일된 쿼리를 작성할 수 있고 오류 발생률도 줄일 수 있다. 

 

JPA 란?

스프링부트는 JPA(Java Persistence API)를 사용하여 데이터베이스를 처리한다. JPA는 자바 진영에서 ORM(Object-Relational Mapping)의 기술 표준으로 사용하는 인터페이스의 모음이다.

JPA는 인터페이스이다. 따라서 인터페이스를 구현하는 실제 클래스가 필요하다. JPA를 구현한 대표적인 실제 클래스에는 하이버네이트(Hibernate)가 있다. SBB도 JPA + 하이버네이트 조합을 사용한다.

 

추상 메서드 > 껍데기만 있고 구현은 안됨


 

https://wikidocs.net/160047

 

1-01 필자가 생각하는 스프링부트란?

스프링부트(Spring Boot)는 자바의 웹 프레임워크로 기존 [[스프링 프레임워크]](Spring Framework)에 [[톰캣]] 서버를 내장하고 여러 편의 기능들을…

wikidocs.net


스프링부트는 웹 프로그램을 쉽고 빠르게 만들어 주는 웹 프레임워크다

스프링부트는 웹 프로그램을 쉽고 빠르게 만들 수 있도록 도와주는 웹 프레임워크이다. 웹 프레임워크라는 표현을 처음 듣는 사람을 위해 잠시 웹 프레임워크를 설명해 보겠다.

웹 프레임워크란?

웹 프로그램을 만들어 본 경험이 있는가? 만약 그런 경험이 있다면 웹 프로그램을 위해 얼마나 많은 기능을 만들어야 하는지 잘 알고 있을 것이다.

예를 들어 쿠키나 세션 처리, 로그인/로그아웃 처리, 권한 처리, 데이터베이스 처리 등 웹 프로그램을 위해 만들어야 할 기능이 정말 산더미처럼 많다. 하지만 웹 프레임워크를 사용하면 이런 기능들을 여러분이 일일이 만들 필요가 없다. 왜냐하면 웹 프레임워크에는 그런 기능들이 이미 만들어져 있기 때문이다. 그저 웹 프레임워크에 있는 기능을 익혀서 사용하기만 하면 된다. 쉽게 말해 웹 프레임워크는 웹 프로그램을 만들기 위한 스타터 키트라고 생각하면 된다. 그리고 자바로 만들어진 웹 프레임워크 중 하나가 바로 스프링부트이다.

얼마나 빨리 만들 수 있나?

스프링부트의 몇 가지 규칙만 익히면 누구나 빠르게 웹 프로그램을 만들 수 있다. 웹 브라우저에 'Hello World'를 출력하려면 다음과 같은 클래스 하나만 작성하면 된다.

[스프링부트의 빠른 개발 속도를 보여 주는 예]

@Controller
public class HelloController {
    @GetMapping("/")
    @ResponseBody
    public String hello() {
        return "Hello World";
    }
}

스프링부트는 튼튼한 웹 프레임워크이다

개발자가 웹 프로그램을 만들 때 가장 어렵게 느끼는 기능 중 하나는 바로 보안 기능이다. 이 세상에는 기상천외한 방법으로 웹 사이트를 괴롭히는 사람들이 있다. 이런 공격에 개발자 홀로 신속하게 대응하기는 무척 어려운 일이다. 하지만 걱정할 필요는 없다. 스프링부트는 이런 보안 공격을 기본으로 아주 잘 막아 준다. 그만큼 스프링부트는 튼튼한 웹 프레임워크다. 예를 들어 SQL 인젝션, XSS(cross-site scripting), CSRF(cross-site request forgery), 클릭재킹(clickjacking)과 같은 보안 공격을 기본으로 막아 준다. 즉, 스프링부트를 사용하면 이런 보안 공격에 대한 코드를 여러분이 짤 필요가 없다.

- SQL 인젝션은 악의적인 SQL을 주입하여 공격하는 방법이다.
- XSS는 자바스크립트를 삽입해 공격하는 방법이다.
- CSRF는 위조된 요청을 보내는 공격 방법이다.
- 클릭재킹은 사용자의 의도하지 않은 클릭을 유도하는 공격 방법이다.

스프링부트에는 여러 기능이 준비되어 있다

스프링부트는 2012년에 등장하여 10년 이상의 세월을 감내한 베테랑 웹 프레임워크이다. 그동안 정말 무수히 많은 기능이 추가되고 또 다듬어졌다. 혹시 로그인 기능을 원하는가? 페이징 기능을 원하는가? 이미 스프링부트에 있다. 이미 있을 뿐 아니라 너무나도 잘 만들어져 있다. 한마디로 스프링부트에는 여러분이 필요로 하는 웹 프로그램 개발을 위한 도구와 기능이 대부분 준비되어 있다. 필자는 스프링부트를 공부할 여러분에게 '이미 만들어져 있는 기능을 새로 만드느라 애써 고생하지 말라'는 이야기를 꼭 해 주고 싶다.

스프링부트는 WAS가 따로 필요없다.

스프링부트 대신 스프링만 사용하여 웹 애플리케이션을 개발한다면 웹 애플리케이션을 실행할 수 있는 톰캣과 같은 WAS(Web Application Server)가 필요하다. WAS의 종류(Tomcat, NGINX, Weblogic, WebSphere, JBoss, Jeus 등)는 매우 다양하며 설정 방식도 제각각이어서 WAS에 대해 공부해야할 내용도 상당하다. 하지만 스프링부트에는 톰캣 서버가 내장되어 있고 설정도 자동 적용되기 때문에 여러분은 WAS에 대해서 전혀 신경쓸 필요가 없다. 심지어 배포되는 jar 파일에도 톰캣서버가 내장되어 실행되므로 서로 다른 WAS들로 인해 발생되는 문제들도 사라진다.

스프링부트로 작성하더라도 톰캣 대신 다른 WAS를 사용할수 있다.

스프링부트는 설정이 쉽다.

스프링부트 이전에 스프링만을 사용하여 웹 애플리케이션을 만들어 보았다면 상당히 복잡한 설정들로 인해 많은 어려움을 겪었을 것이다. 심지어 한번 설정한 기능들이 스프링 버전업으로 인해 변경되고 없어지는 일도 비일비재 하였다. 하지만 스프링부트는 스프링의 복잡한 설정을 자동화하고 단순화 하여 누구나 스프링을 쉽게 사용할 수 있게 만들었다. 스프링으로 웹 애플리케이션을 만들 때 다른 언어로 작성된 프레임워크(Django, Rails 등)의 간결함을 많이 부러워 했는데 스프링부트의 등장과 꾸준한 발전으로 인해 이제는 더이상 부러워할 이유가 없을듯 하다.

스프링부트는 재미있다

스프링부트로 웹 프로그램을 만드는 것이 게임을 하는 것보다 재밌다고 하면 믿겠는가? 약간 과장이긴 하지만 무언가에 홀린 듯이 코딩을 하고 있는 필자 자신을 발견할 때가 있었는데, 그때가 바로 웹 프로그램을 만들고 있을 때였다. 정말이니 의심하지 말고 지금 당장 스프링부트로 웹 프로그래밍을 시작해 보자.


https://wikidocs.net/160444

 

1-04 스프링부트 맛보기

* `[완성 소스]` : [https://github.com/pahkey/sbb3/tree/1-04](https://github.com/pahkey/sbb3/tree/1-04) …

wikidocs.net


1-04 스프링부트 맛보기

이번에는 브라우저 주소창에 http://localhost:8080/hello 라는 URL을 입력했을 때 브라우저 화면에 "Hello World"라는 문구를 출력하는 웹 프로그램을 작성해 볼 것이다.

이 프로그램이 동작하기 위해서는 여러분의 컴퓨터(localhost)가 웹 서버가 되어 8080 포트에서 실행되어야 하고 http://localhost:8080/hello 라는 URL을 통해 서버에 요청이 발생하면 "Hello World" 라는 문구를 브라우저 화면으로 출력해야 한다.

스프링부트로 얼마나 간편하게 할 수 있는지 지금부터 알아보자.

HelloController

http://localhost:8080/hello 와 같은 브라우저의 요청을 처리하기 위해서는 컨트롤러(Controller)가 필요하다. 컨트롤러는 서버에 전달된 클라이언트의 요청(URL과 전달된 파라미터 등)을 처리하는 자바 클래스이다.

컨트롤러를 만들어 보자.

위 그림처럼 순서대로 com.mysite.sbb 위치에서 마우스 우측 버튼을 누르고 New, Class를 선택한다. 그러면 다음과 같은 화면이 나타난다.

Name 항목에 HelloController라고 입력한후 "Finish"를 누르자.

그러면 다음과 같은 HelloController.java 파일이 생성된다.

package com.mysite.sbb;

public class HelloController {

}

하지만 지금 작성한 HelloController는 껍데기 클래스이므로 컨트롤러의 기능을 갖추려면 다음과 같이 수정해야 한다.

package com.mysite.sbb;

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

@Controller
public class HelloController {
    @GetMapping("/hello")
    @ResponseBody
    public String hello() {
        return "Hello World";
    }
}

프로젝트를 진행하며 더 자세히 설명하겠지만 여기서 사용된 것들에 대해서 간단히 알아보자. 클래스명 위에 적용된 @Controller 애너테이션은 HelloController 클래스가 컨트롤러의 기능을 수행한다는 의미이다. 이 애너테이션이 있어야 스프링부트 프레임워크가 컨트롤러로 인식한다. hello 메서드에 적용된 @GetMapping("/hello") 애너테이션은 http://localhost:8080/hello URL 요청이 발생하면 hello 메서드가 실행됨을 의미한다. 즉, /hello URL과 hello 메서드를 매핑하는 역할을 한다.

  • URL명과 메서드명은 동일할 필요는 없다. 즉 /hello URL일 때 메서드명을 hello가 아닌 hello2와 같이 해도 상관없다.
  • Get 방식의 URL 요청은 GetMapping을 사용하고 Post 방식의 URL 요청은 PostMapping을 사용한다.

그리고 @ResponseBody 애너테이션은 hello 메서드의 응답 결과가 문자열 그 자체임을 나타낸다. hello 메서드는 "Hello World" 라는 문자열을 리턴하므로 출력으로 "Hello World" 문자열이 나갈 것이다.

나중에 공부하겠지만 응답 결과는 이처럼 단순한 문자열 보다는 HTML 파일과 같은 템플릿을 주로 사용한다.

로컬서버 실행하기

이제 작성한 HelloController가 정상 동작하는지 확인해 보자. HelloController의 동작을 확인하기 위해서는 로컬서버를 실행해야 한다. 로컬서버는 다음과 같은 순서로 실행한다.

  1. 먼저 위 그림의 좌측 하단에 Boot Dashboard가 보이지 않는다면 STS 상단의 툴바에서 "Boot Dashboard" 아이콘을 눌러서 실행한다.
  2. Boot Dashboard에서 local 을 한번 누르면 그림처럼 "sbb" 라는 프로젝트명이 보인다. 여기서 "sbb"를 마우스로 선택하자.
  3. "sbb"를 선택하면 로컬서버를 실행할 수 있는 버튼들이 보인다. 가장 좌측의 버튼을 눌러서 서버를 실행한다.

순서대로 진행하면 로컬서버가 실행되고 STS 콘솔창에 다음과 같은 메시지들이 출력될 것이다.

브라우저로 확인하기

서버가 실행되었으니 이제 HelloController의 동작을 확인해 보자. 브라우저를 실행하고 주소창에 http://localhost:8080/hello라고 입력해 보자.

로컬서버는 디폴트로 8080 포트로 실행된다.

위와 같이 /hello URL이 요청되면 컨트롤러인 HelloController의 hello 메서드와 매핑된 hello 메서드가 호출되고 "Hello World"라는 문자열이 브라우저에 출력되는 것을 확인할 수 있다.

스프링부트의 세계에 온 것을 축하한다 ^^


직접 실행


package com.mysite.sbb;

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

// @Controller 애너테이션은 HelloController 클래스가 컨트롤러의 기능을 수행한다는 의미
@Controller
public class HelloController {
	
	// hello 메서드에 적용된 @GetMapping("/hello") 애너테이션은 
	//http://localhost:8080/hello URL 요청이 발생하면 hello 메서드가 실행됨을 의미한다. 
	//즉, /hello URL과 hello 메서드를 매핑하는 역할을 한다.
    @GetMapping("/hello")
    
    //@ResponseBody 애너테이션은 hello 메서드의 응답 결과가 문자열 그 자체임을 나타낸다.
    @ResponseBody
    public String hello() {
        return "Hello World";
    }
}


골머리 많이 때린 lombok......

https://wikidocs.net/160957

 

1-05 스프링부트 도구 설치하기

* `[완성 소스]` : [https://github.com/pahkey/sbb3/tree/1-05](https://github.com/pahkey/sbb3/tree/1-05) …

wikidocs.net


src/main/resources 디렉터리

src/main/resources 디렉터리는 자바 파일을 제외한 HTML, CSS, Javascript, 환경파일 등을 작성하는 공간이다.

 


컨트롤러

URL 매핑을 추가하기 위해 MainController.java 파일을 다음과 같이 작성하자.

[파일명:/sbb/src/main/java/com/mysite/sbb/MainController.java]

package com.mysite.sbb;

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

@Controller
public class MainController {

    @GetMapping("/sbb")
    public void index() {
        System.out.println("index");
    }
}

MainController 클래스에 @Controller 애너테이션을 적용하면 MainController 클래스는 스프링부트의 컨트롤러가 된다. 그리고 메서드의 @GetMapping 애너테이션은 요청된 URL과의 매핑을 담당한다. 서버에 요청이 발생하면 스프링부트는 요청 페이지와 매핑되는 메서드를 컨트롤러를 대상으로 찾는다. 즉, 스프링부트는 http://localhost:8080/sbb 요청이 발생하면 /sbb URL과 매핑되는 index 메서드를 MainController 클래스에서 찾아 실행한다.

@GetMapping에 http://localhost:8080 과 같은 도메인명과 포트는 적지 않는다. 왜냐하면 도메인명과 포트는 서버 설정에 따라 변하기 때문이다.

이제 다시 http://localhost:8080/sbb URL을 호출해 보자.

이번에도 오류가 발생한다. 하지만 404 오류가 아닌 500 오류코드로 바뀐것을 확인할 수 있다. http://localhost:8080/sbb 호출시 MainController의 index 함수는 호출되었지만 오류가 발생한 것이다. URL과 매핑된 함수는 결괏값을 리턴해야 하는데 아무런 값도 리턴하지 않기 때문에 오류가 발생한 것이다. 오류를 해결하려면 클라이언트(브라우저)로 응답을 리턴해야 한다.

콘솔 로그를 보면 index 메스드에서 실행한 System.out.println("index");가 실행되어 "index"라는 문자열이 출력된 것을 확인할 수 있다.

다음과 같이 MainController를 수정하자.

package com.mysite.sbb;

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

@Controller
public class MainController {

    @GetMapping("/sbb")
    @ResponseBody
    public String index() {
        return "index";
    }
}

응답으로 "index"라는 문자열을 브라우저에 출력하기 위해 index 함수의 리턴값을 String으로 변경하고 "index"라는 문자열을 리턴했다. @ResponseBody 애너테이션은 URL 요청에 대한 응답으로 문자열을 리턴하라는 의미이다.

만약 @ResponseBody 애너테이션을 생략한다면 "index"라는 이름의 템플릿 파일을 찾게 되는데 이에 대한 내용은 나중에 자세히 알아본다.

오류가 해결되었는지 http://localhost:8080/sbb URL을 호출해 확인해 보자.

이번에는 MainController의 출력 문자열 "index"를 "안녕하세요 sbb에 오신것을 환영합니다."로 바꾸어보자.

package com.mysite.sbb;

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

@Controller
public class MainController {

    @GetMapping("/sbb")
    @ResponseBody
    public String index() {
        return "안녕하세요 sbb에 오신것을 환영합니다.";
    }
}

그리고 브라우저에서 변경한 문자열이 잘 출력되었는지 확인해 보자.

Spring Boot Devtools와 Live Reload 기능에 의해 서버는 물론 브라우저도 리로딩되어 변경된 사항을 추가 작업 없이 즉시 확인할 수 있을 것이다.

 

다.

  1. 500 Internal Server Error:
    • 이 오류는 서버 측에서 에러가 발생했을 때 반환됩니다. 즉, 서버가 클라이언트의 요청을 처리하는 동안 예상치 못한 상황이 발생했음을 나타냅니다.
    • 예를 들어, 서버에서 코드 버그가 발생하거나 필요한 자원에 접근할 수 없는 경우에 500 Internal Server Error가 발생할 수 있습니다.
    • 이 오류는 클라이언트 측의 문제가 아니라 서버에서 발생한 문제를 나타냅니다.
  2. 404 Not Found:
    • 404 오류는 클라이언트가 요청한 리소스(일반적으로 웹 페이지나 파일)를 찾을 수 없을 때 발생합니다.
    • 이는 클라이언트가 잘못된 URL을 요청하거나 서버에 존재하지 않는 페이지를 요청했을 때 발생할 수 있습니다.
    • 예를 들어, 사용자가 존재하지 않는 페이지에 접근하려고 할 때 404 Not Found 오류가 발생합니다.

간단하게 말하면, 500 오류는 서버에서의 문제를 나타내며, 404 오류는 클라이언트가 요청한 리소스를 찾을 수 없는 상황을 나타냅니다. 개발자들은 이러한 오류를 디버깅하고 해결하기 위해 서버 로그 및 클라이언트 요청을 확인하여 원인을 파악합니다.


H2 데이터베이스

JPA를 사용하기 전에 데이터를 저장할 데이터베이스를 설치해 보자. 개발시에는 Oracle, MSSQL 등의 굵직한 데이터베이스 보다는 설치도 쉽고 사용도 편리한 H2 데이터베이스를 많이 사용한다.

점프 투 스프링부트H2 데이터베이스

H2 데이터베이스는 주로 개발용이나 소규모 프로젝트에서 사용되는 파일 기반의 경량 데이터베이스이다. 개발시에는 H2를 사용하여 빠르게 개발하고 실제 운영시스템은 좀 더 규모있는 DB를 사용하는 것이 일반적인 개발 패턴이다.

다음과 같이 H2 데이터베이스를 설치하자.

[파일명: /sbb/build.gradle]

(... 생략 ...)

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    runtimeOnly 'com.h2database:h2'
}

(... 생략 ...)

그리고 "Refresh Gradle Project"를 실행하여 필요한 라이브러리를 설치하자.

점프 투 스프링부트runtimeOnly

build.gradle 파일의 runtimeOnly는 해당 라이브러리가 런타임(Runtime)시에만 필요한 경우에 사용한다. 컴파일(Compile)시에만 필요한 경우에는 runtimeOnly 대신 compileOnly를 사용한다.

설치한 H2 데이터베이스를 사용하기 위해서는 설정을 해야 한다. 다음과 같이 application.properties 파일을 수정하자.

현재 application.properties 파일에는 아무런 내용이 없을 것이다.

[파일명: /sbb/src/main/resources/application.properties]

# DATABASE
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
spring.datasource.url=jdbc:h2:~/local
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=

각각의 항목에 대해서 알아보자.

  • spring.h2.console.enabled - H2 콘솔의 접속을 허용할지의 여부이다. true로 설정한다.
  • spring.h2.console.path - 콘솔 접속을 위한 URL 경로이다.
  • spring.datasource.url - 데이터베이스 접속을 위한 경로이다.
  • spring.datasource.driverClassName - 데이터베이스 접속시 사용하는 드라이버이다.
  • spring.datasource.username - 데이터베이스의 사용자명이다. (사용자명은 기본 값인 sa로 설정한다.)
  • spring.datasource.password - 데이터베이스의 패스워드이다. 로컬 개발 용도로만 사용하기 때문에 패스워드를 설정하지 않았다.

'백엔드 > 스프링부트' 카테고리의 다른 글

jsp, 스프링부트 간단한 게시판 만들기  (0) 2023.12.27
mysql 연동하기  (1) 2023.12.22
강합 결합과 약한 결합  (0) 2023.12.14
스프링부트 시작  (0) 2023.12.14
스프링 프레임워크와 스프링 부트  (0) 2023.12.12