Search

Spring Boot General

1. 프로젝트 환경설정

가. 프로젝트 설정

1) 기본 설정

start.spring.io 프로젝트 기본 설정
→ Project는 Gradle 선택
build tool(gradle)은 위의 두 가지 의존성이 의존하고 있는 여러 의존성에 대해 자동으로 가져옴
→ build tool에 의해 가져온 의존성들은 build.gradle에 나열되어 있음

2) 주요 라이브러리

스프링부트 라이브러리
→ spring-boot-starter-web → spring-boot-starter-tomcat: 톰캣 (웹서버) → spring-webmvc: 스프링 웹 MVC
→ spring-boot-starter-thymeleaf: 타임리프 템플릿 엔진(View)
→ spring-boot-starter(공통): 스프링 부트 + 스프링 코어 + 로깅
→ spring-boot: spring-core
→ spring-boot-starter-logging: logback, slf4j
테스트 라이브러리: spring-boot-starter-test → junit: 테스트 프레임워크 → mockito: 목 라이브러리 → assertj: 테스트 코드를 좀 더 편하게 작성하게 도와주는 라이브러리 → spring-test: 스프링 통합 테스트 지원

3) logging

현업에서 실 서버에서 작업할 때, system.out.println을 사용하지 않고 log로 확인한다
관련 종속성은 'slf4j'
스프링부트에서 'logback'으로 가져와서 표준으로 사용함

4) 필요한 기능 찾아 쓰기

스프링 자체가 자바의 대부분의 기능을 구현하는 만큼 그 기능이 방대함
필요한 기능이 있다면 공식 사이트에서 검색을 통해 찾아서 사용해야 함
이하 Reference Document의 Spring Boot Features를 중점으로 관련 키워드를 검색하여 필요 기능을 찾는다.

5) Using Muliple Schema or Catalog

MySQL에서 Table의 상위 개념을 Schema로 정의하지만 SpringBoot에서 해당 개념을 Catalog라는 개념으로 맵핑한다. 따라서 Entity에 대해 맵핑된 테이블을 Default Schema가 아니라 다른 Schema에서 찾을 경우 @Table의 Catalog 옵션을 활용해야 한다.
→ RDBMS의 계층 구조는 다음과 같은 4계층 구조다. 인스턴스 <- 데이터베이스 <- 스키마 <- 테이블
→ MySQL의 경우 데이터베이스와 스키마를 합쳐서 3계층 구조로 구현되었다.
Entity @Table(name = "om_log_image_view", catalog = "bghd_log") public class OmImageViewLog { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; }
Java
복사

5) Setting Multiple DataSources

참고) properties or yaml
참고) Database Config
각각의 datasource에 대한 환경변수 주입
주입한 환경변수를 DataSourceProperties에 생성하여 연관 빈 생성
DataSourceProperties를 바탕으로 DataSource 빈 생성
Entity Manager 설정
→ Data Source 맵핑
→ Entity가 정의된 package 맵핑
ex) 위의 Database Config 코드 참고, entityManagerFactoryBean.setPackagesToScan("me.om.omcommon.domain")
or entityManagerFactoryBean.setPackagesToScan(new String[]{"mj.api.domain", "mj.core.domain"})
Transactional Manager 설정
→ 하나의 트랜잭션으로 묶여서 처리할 엔티티매니저를 등록
ChainedTransactionManager 설정
→ 두 개 이상의 트랜잭션 매니저가 설정된 경우, 두 개의 트랜잭션 매니저를 연결한 ChainedTransactionManager 설정하여 Transaction 적용해야 함
// DatabaseConfig.java @Bean(name = "chainedTransactionManager") public ChainedTransactionManager getChainedTransactionManager( @Qualifier("logTransactionManager") PlatformTransactionManager logTm, @Qualifier("transactionManager") PlatformTransactionManager tm) { return new ChainedTransactionManager(logTm, tm); } // LogService.java @Transactional(value = "chainedTransactionManager") public void aggregateDownloadLogBeforeNowAndAfter(final long days) { List<Long> productIds = readLogsBeforeNowAndAfter(days); for (Long productId : productIds) { Optional<OmImageDownloadCount> downloadCount = loadDownloadCountWith(productId); int countUpdating = countBy(productId); if (downloadCount.isPresent()) { updateOldDownloadCountLog(downloadCount.get(), countUpdating); continue; } createNewDownloadCountLog(productId, countUpdating); } }
Java
복사

나. thymeleaf 동작 흐름

Client(web browser)에서 Get 방식으로 요청(localhost:8080/hello)
내장 톰켓 서버가 해당 요청에 대해 helloController::hello로 연결
→ @GetMapping("hello") 어토테이션에 따라 위와 같이 스프링부트가 연결할 수 있음
helloController는 스프링부터로부터 전달받은 매개변수 모델에 대해 데이터 처리
→ data라는 변수에 "hello!!!" 문자열 할당
viewResolver는 웹 브라우저 요청에 대한 응답으로 helloController의 반환값으로 관련 템플릿을 찾아서 전달함(resources:templates/hello.html)
→ 스프링 부트 템플릿엔진 기본 viewName 매핑
resources:templates/ +{ViewName}+ .html

다. Gradle

1) Gradle vs Maven

최근 빌드 자동화툴은 Java와의 호환성이 좋은 Grooby 기반의 Gradle을 주로 사용
Gradle은 Grooby 기반 빌드 자동화툴
Maven은 XML 기반 빌드 자동화툴
Grooby는 JVM 위에서 동작하는 스크립트 언어로, Java 문법과 유사하여 Java와의 호환성이 좋음

2) Gradle 실행

실행명령: gradle init
실행 결과

3) Gradle 설정

settings.gradle: 프로젝트 수준의 빌드 설정
build.gradle: 어플리케이션 수준의 빌드 설정

4) Gradle Build

./gradlew run

라. 빌드 & 실행

1) 빌드

프로젝트 폴더(hello-spring)에서 이하 gralew 파일 빌드하기
./gradlew build
빌드 성공 시 /build/libs 이하의 스냅샷이 생성됨
스냅샷으로 프로그램 실행
→ cd /build/libs
→ java -jar hello-spring-0.0.1-SNAPSHOT.jar
→ .jar 파일은 스냅샷이므로 libs 이하에 생성된 파일명 확인할 것
빌드 실패 원인 해결
→ 에러 메시지: Please check that /Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home contains a valid JDK installation.
→ 자바 파일에 대해 빌드를 수행하는 tools.jar가 해당 경로에 없기 때문에 발생함
→이하 스택오버플로우의 가장 많은 지지를 얻은 답변 참고하여 해결https://stackoverflow.com/questions/64968851/could-not-find-tools-jar-please-check-that-library-internet-plug-ins-javaapple
→ .zshrc의 경우 default로 제공이 안되므로 홈디렉토리에서 해당 파일을 생성해서 경로를 재설정해야 함
→ vim ~/.zshrc

2) 환경별 설정값 주입

(Community Version) IntelliJ IDE에 주입
→ Run → Edit Configurations
→ 환경변수(E) 부분에 SPRING_PROFILES_ACTIVE 부분에 입력
→ 스크린샷의 경우 application-test.properties 설정값 주입
(Ultimate version) IntelliJ IDE 주입
→ Run → Edit Configurations
→ Active profiles에 로컬 환경값 주입(apllication-dev.yml 설정값 주입)

2. 스프링 웹 개발 기초

가. 웹 개발 방식

정적 컨텐츠
→ 이미 작성된 정적 컨텐츠를 서버에서 가공하지 않고 그대로 클라이언테에게 전달
→ 정적으로 HTML 전달
MVC와 템플릿 엔진
→ HTML의 내부를 서버에서 가공하여 클라이언트에게 전달
→ 동적으로 HTML 전달
API
→ JSON 방식으로 전달하여 클라이언트 사이드에서 작업이 이루어지도록
→ 리액트, 뷰가 API 방식으로
→ 서버에서 서버로 통신할 때, HTML이 필요 없으므로 API 방식 이용
→ JSON 형식의 데이터 전달

나. 정적 컨텐츠 동작 흐름

정적 컨텐츠 동작 흐름
웹브라우저에서 localhost:8080/hello-static.html 요청
톰캣서버가 해당 요청을 스프링 컨테이너 내부의 Controller에게 전달함
→ 요청처리 우선순위는 Controller에게 있음
Controller에 hello-static.html 처리에 대한 책임이 없음 확인
톰켓 서버는 resources/static 내 관련 데이터가 있는 지 확인
내부에 관련 데이터 존재함에 따라 해당 .html을 웹 브라우저에 응답으로 전송함

다. MVC 패턴

1) MVC 패턴 기초

MVC(Model View Controller): 사용자 인터페이스에서 비지니스로직을 분리한 디자인 패턴
→ Model: 어플리케이션의 정보(데이터), 어떠한 동작을 수행하는 코드
→ View: 사용자 인터페이스 요소, 모델로부터 값을 가져와서 사용자에게 보여줌
→ Controller: 데이터와 비지니스 로직 간 상호동작 관리, 모델의 상태를 바꿀 때 사용함
→ 장점: 어플리케이션의 유지보수성, 확장성, 유연성이 증가
MVC 간 상호작용
→ 사용자는 컨트롤러를 사용하여 모델의 상태 변경
→ 모델은 최신의 상태를 뷰에게 전달
→ 뷰는 최신의 상태를 사용자에게 보여줌
과거 jsp로 개발하던 시절에는 MVC로 나뉘지 않고 통합
→사용자 인터페이서에 비지니스 로직과 시각적 요소가 혼재
→ 책임 역할 분배 X
// controller @GetMapping("hello-mvc") public String helloMvc(@RequestParam("name") String name, Model model) { model.addAttribute("name", "helloMVC!!!!!"); return "hello-mvc-mvc"; }
Java
복사
// hello-mvc-mvc.html <html xmlns:th="http://www.thymeleaf.org"> <body> <p th:text="'hello ' + ${name}">hello! empty</p> </body> </html>
HTML
복사

2) MVC 동작 흐름

웹브라우저에서 localhost:8080/hello-mvc?name=yayaya 요청 들어옴
웹서버는 해당 요청이 컨트롤러에 책임이 있는 지 확인
컨트롤러의 책임이라면 Mapping된 메소드를 실행하고 템플릿 엔진이 처리할 html의 이름을 반환함
ViewResolver는 컨트롤러가 반환한 문자열을 바탕으로 관련 템플릿파일(.html 파일)을 찾아서 템플릿 엔진에 전달함
해당 템플릿 엔진은 .html 파일을 변환하여 웹 브라우저에 전송함
→ 정적 컨텐츠의 경우 변환과정이 없음

라. HTTP Request 처리 흐름

1) Controller 중심 HTTP 요청 및 응답 처리

웹브라우저의 요청을 웹 서버가 받아서 스프링 컨테이너 내 helloController에게 전달
helloController는 hello-api와 맵핑된 메소드에 해당 요청에 대한 처리책임을 넘김
hello-api와 맵핑된 메소드는 관련 연산을 수행하고 값을 반환함
위의 메소드에 @ResponseBody 어노테이션이 붙어있다면 HttpMessageConverter가 맡아서 처리함
→ 위의 @ResponseBody와 더불어 HTTP Accept Header의 정보, 둘의 합쳐서 HttpMessageConverter가 해당 요청을 처리할 지 결정됨.
→ MVC 중 V(View) 방식으로 출력할 경우, HttpMessageConverter가 아니라 viewResolver가 해당 역할을 맡음
위의 메소드의 반환값이 객체이면 HttpMessageConverter 내부의 JsonConverter가 맡아서 웹 브라우저에 응답함
→ 반면, 반환값이 문자열이라면 HttpMessageConverter 내부의 StringConverter가 맡아서 처리함

2) Spring MVC Request Life Cycle

스프링 어플리케이션은 POJO 클래스와 설정 메타정보를 이용해 IoC 컨테이너가 만들어주는 오브젝트의 조합 - 토비의 스프링 3
웹에서 스프링 어플리케이션의 동작 방식
클라이언트로부터 요청이 발생하면 서블릿 컨테이너는 해당 요청을 받아서 서블릿을 동작시킨다
서블릿은 미리 생성한 WebApplicationContext를 통해 Bean을 호출한다
image from p782, 이일민, 토비의 스프링 3
Dispatcher Servlet
역할: Request의 URL 패턴에 따라 사전 mapping된 Controller로 요청을 전달함. 더불어 Spring IoC container와 통합되어 있어 스프링의 여러 기능을 사용하는데 여러모로 기여함
→ Dispatcher Servlet은 ‘Front Controller’ Design Pattern의 구현체로 볼 수 있음
→ Dispatcher Servlet은 서블릿의 일종으로, 설정 메타정보를 참고하여 ApplicationContext를 생성한다. 요청을 처음으로 받아서 적절한 Bean(or Object or Controller)을 찾아서 실행시킴
Front Controller의 요청 처리 흐름도
Filter
Interceptor
역할: 특정 Controller로 전달되는 HTTP Request에 대한 전처리
주요작업: 인증 및 인가(Authorization, Authentication), Claim Token Parsing 등
조작 대상: HttpServletRequest, HttpServletResponse로 전달 받는 객체 내 데이터 조작 가능
→ Filter와 달리 전달 받는 객체 자체에 대해 조작 불가
Filter vs Interceptor
filter: 웹서버(톰캣)를 거친 후 SpringMVC 집입 전
interceptor: Spring MVC 내 DispatcherServlet을 거친 후 Controller 진입 전

3. Validation

가. Bean Validation

‘Bean Validation’은 자바 생태계에서 유효성 검사의 표준에 가까운 기술임
→ Spring과 Spring Boot에서도 ‘Bean Validation’ 활용하여 유효성 검사할 것을 권장

나. Controller Validation

1) Valid

메소드의 매개변수 또는 필드 수준에서의 유효성 검사에 사용됨
유효하지 않은 데이터 입력 시 MethodArgumentNotValidException 발생
→ Controller에서 해당 예외를 전달 받으면 HTTP status 400 Bad Request 로 응답 메시지 전송
@RestController class ValidateRequestBodyController { @PostMapping("/validateBody") ResponseEntity<String> validateBody(@Valid @RequestBody Input input) { return ResponseEntity.ok("valid"); } }
Java
복사
code from https://reflectoring.io/bean-validation-with-spring-boot/
class Input { @Min(1) @Max(10) private int numberBetweenOneAndTen; @Pattern(regexp = "^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}$") private String ipAddress; // ... }
Java
복사
code from https://reflectoring.io/bean-validation-with-spring-boot/

2) Validated

클래스 수준의 유효성 검사에 사용됨
@RestController @Validated class ValidateParametersController { @GetMapping("/validatePathVariable/{id}") ResponseEntity<String> validatePathVariable( @PathVariable("id") @Min(5) int id) { return ResponseEntity.ok("valid"); } @GetMapping("/validateRequestParameter") ResponseEntity<String> validateRequestParameter( @RequestParam("param") @Min(5) int param) { return ResponseEntity.ok("valid"); } }
Java
복사
code from https://reflectoring.io/bean-validation-with-spring-boot/
유효하지 않은 데이터 입력 시 ConstraintViolationException 발생
MethodArgumentNotValidException과 달리 해당 예외는 자동 응답 메시지 전송하지 않음
HTTP status 400 Bad Request 응답 의도할 경우, 아래와 같이 커스텀해서 관련 기능을 정의해야 함
@RestController @Validated class ValidateParametersController { // request mapping method omitted @ExceptionHandler(ConstraintViolationException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) ResponseEntity<String> handleConstraintViolationException(ConstraintViolationException e) { return new ResponseEntity<>("not valid due to validation error: " + e.getMessage(), HttpStatus.BAD_REQUEST); } }
Java
복사

다. Entity Validation

4. DB 접근 기술

가. DB Connection

1) H2

설치 후 h2/bin/h2.sh에 755 권한 부여
→ chmod 755 h2.sh
실행
→ ./h2.sh
home directory에 test.mv.db 생성 확인
이후 JDBC URL 부분에 jdbc:h2:tcp://localhost/~/test로 변경하여 접속
→ 기존 URL로 접속하면 IDE 콘솔과 웹 어플리케이션에서 DB에 동시접속이 안됨
만약 H2 설치과정 중 에러가 발생하면 test.mv.db 파일 삭제 후 재설치

2) MySQL

나. JDBC DB 접근 기술

1) 설정 정보 추가

종속성
implementation 'org.springframework.boot:spring-boot-starter-jdbc' runtimeOnly 'com.h2database:h2'
Java
복사
DB 설정 정보
spring.datasource.url=jdbc:h2:tcp://localhost/~/test spring.datasource.driver-class-name=org.h2.Driver spring.datasource.username=sa
Java
복사

2) JDBC Template 특징

순수 JDBC와 환경설정은 동일함
JDBC Template의 장점은 순수 JDBC에서 발생하는 반복 코드를 제거한다는 것
SQL은 순수 JDBC와 같이 직접 작성해야 함
실무에서 많이 사용하고 있음

다. JPA DB 접근 기술

이하 참고

라. Initializing DB using SQL Script

1) 설정

MySQL 등 Disk 기반 DB의 경우 추가적인 설정이 필요함
→ Script 파일 기반 DB 초기화는 In-Memory DB의 경우에만 기본값으로 지정됨
→ application.properties에 spring.sql.init.mode=always 설정을 추가하여 DB 유형에 관계없이 SQL Script 기반 초기화되도록 작업 필요

2) SQL Script 작성

5. Test

가. 스프링 통합 테스트

1) 유용한 어노테이션

@SpringBootTest: 스프링 컨네이너와 테스트를 함께 실행함
→ 스프링을 동작시켜서 통합적으로 관련 기능을 테스트할 때 사용함
→ 하지만 순수 자바 코드만으로 기능의 단일성을 테스트하는 테스트 코드를 권장함
@Transactional: 테스트 시작 전에 트랜잭션을 시작하고 테스트 케이스가 끝나면 롤백함
→ 테스트가 끝나면 테스트에 사용된 데이터라 자동으로 제거되므로 테스트의 단일성이 보장됨

2) 통합 테스트 환경 기본 설정

환경설정 파일이 기본값이 아닌 경우, 추가 설정이 필요함. 관련 설정이 없으면 주요 의존성 주입이 실패에 따라 에러 발생
두 가지 방법으로 해결할 수 있음. 환경 지정 또는 환경설정 파일 지정
ex) 환경이 dev인 경우
@SpringBootTest @WebAppConfiguration @ActiveProfiles("dev") class CartResourceIntegrationTest { // 생략 }
Java
복사
ex) 환경설정 파일이 application-dev.yml 인 경우
@SpringBootTest @WebAppConfiguration @TestPropertySource(properties = { "spring.config.location=classpath:application-dev.yml" }) class UserResourceTest { // 생략 }
Java
복사
작성한 테스트 코드를 IDE에서 찾을 수 없는 경우, 테스트 실행 환경을 변경해주는 것 권장
ex) ‘Run tests using’ 값을 Gradle → IntelliJ IDEA로 수정 후 정상

3) Spring Security 설정

Reference

이일민, 토비의 스프링 3, 에이콘
http-client로 API 테스트