- Spring Boot 프레임워크의 일부로 웹 애플리케이션을 빠르고 쉽게 구축할 수 있도록 도와주는 도구입니다.
- 내장된 웹 서버를 제공하여 웹 애플리케이션을 실행하고 관리하는 데 필요한 모든 설정을 자동으로 처리합니다. (내장된 웹 서버로 Tomcat, Jetty, Undertow와 같은 서버를 사용할 수 있습니다.) - 이를 사용하면 간단한 설정으로 HTTP 엔드포인트를 생성하고 관리할 수 있습니다. 또한, Spring MVC와 같은 웹 프레임워크와 통합되어 효율적인 웹 애플리케이션 개발을 지원합니다. - RESTful API, 웹 서비스, 단일 페이지 애플리케이션 등 다양한 유형의 웹 애플리케이션 개발에 적합합니다.
💡 [참고] Spring Boot Web을 사용하기 위한 의존성 추가
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web' // Spring Boot Web
}
- Java 스프링 프레임워크에서 RESTful API 개발 시 예외 처리와 관련된 작업을 효과적으로 수행할 수 있도록 도와주는 어노테이션입니다.
- @RestController로 선언한 전역 예외 처리를 담당하는 클래스를 정의할 수 있습니다. 이 클래스는 @ExceptionHandler 어노테이션과 함께 사용되어 특정 예외 타입에 대한 처리 로직을 구현할 수 있습니다. - 예를 들어, 특정 예외가 발생할 경우 클라이언트에게 적절한 에러 응답을 반환하거나 로깅하는 등의 작업을 수행할 수 있습니다.
💡 [참고] 해당 어노테이션의 주요 속성 값입니다.
속성
설명
value
적용할 범위를 지정합니다. 특정 패키지나 클래스를 대상으로 할 수 있습니다. 기본값은 모든 패키지가 대상입니다.
💡 @Transactional - 스프링 프레임워크에서 특정 메소드 또는 클래스에서 수행되는 ‘트랜잭션’과 관련되어 관리를 위해서 사용되는 어노테이션입니다. - 메서드 또는 클래스에 해당 어노테이션을 선언하면 코드 실행 중에 트랜잭션에 오류가 발생되면 트랜잭션이 롤백되고 변경 사항이 모두 취소됩니다.
이미 진행 중인 트랜잭션이 있으면 해당 트랜잭션에 참여하고, 없으면 새로운 트랜잭션을 시작합니다.
SUPPORTS
이미 진행 중인 트랜잭션이 있으면 해당 트랜잭션에 참여하고, 없으면 트랜잭션 없이 진행합니다.
MANDATORY
이미 진행 중인 트랜잭션이 있으면 해당 트랜잭션에 참여하고, 없으면 예외를 발생시킵니다.
REQUIRES_NEW
항상 새로운 트랜잭션을 시작합니다.
NOT_SUPPORTED
트랜잭션 없이 진행합니다. 이미 진행 중인 트랜잭션을 일시 중지시킵니다.
NEVER
트랜잭션 없이 진행합니다. 이미 진행 중인 트랜잭션이 있으면 예외를 발생시킵니다.
NESTED
이미 진행 중인 트랜잭션이 있으면 중첩 트랜잭션을 시작합니다.
💡 [참고] isolation 속성 값에 대한 설명
isolation 속성 값
설명
DEFAULT
기본 격리 수준으로 데이터베이스의 기본 설정을 따릅니다.
READ_UNCOMMITTED
트랜잭션에서 수정 중인 데이터를 다른 트랜잭션에서 읽을 수 있습니다.
READ_COMMITTED
트랜잭션이 커밋된 데이터만 읽을 수 있습니다.
REPEATABLE_READ
트랜잭션 내에서 조회한 데이터는 다른 트랜잭션에서 수정할 수 없습니다.
SERIALIZABLE
트랜잭션 동안 다른 트랜잭션에서 데이터를 수정할 수 없습니다.
💡 @Transactional 사용 예시 - 해당 TemplateServiceImpl 클래스는 TemplateService의 인터페이스의 구현체 파일입니다.
- @Transactional의 어노테이션은 메소드 단위로 적용을 하였습니다. - 트랜잭션이 조회 처리를 하는 경우 @Transactional(readOnly = true) 속성을 적용하였고 이외 실제 트렌잭션이 발생하는 곳에는 @Transactional을 선언하였습니다. - 이러한 선언을 통해 잘못된 데이터가 발생하는 경우 처리에 대한 롤백이 수행되도록 구성하였습니다.
/**
* [ 템플릿 설명 ]
* - 해당 파일은 서비스의 비즈니스 로직을 구현하는 곳입니다.
* - 해당 *ServiceImpl 에서는 @Service 어노테이션을 필수적으로 사용합니다.
*/
@Slf4j
@Service
public class TemplateServiceImpl implements TemplateService {
/**
* '생성자 주입 형태'로 사용합니다.
* - Autowired 는 권장되지 않기에 생성자 주입 형태로 구성합니다.
*/
private final SqlSession sqlSession;
public TemplateServiceImpl(SqlSession ss) {
this.sqlSession = ss;
}
/**
* Template 리스트 값 조회
*
* @return List<TemplateVO> 반환값
*/
@Transactional(readOnly = true)
public List<TemplateVO> selectTempList() {
TemplateMapper tm = sqlSession.getMapper(TemplateMapper.class);
return tm.selectTempList();
}
/**
* 키 값을 기반으로 Template 리스트 조회
*
* @param templateId 조회 키 값
* @return TemplateVO 반환 값
*/
@Transactional(readOnly = true)
public TemplateVO selectTempById(Integer templateId) {
TemplateMapper tm = sqlSession.getMapper(TemplateMapper.class);
return tm.selectTempById(templateId);
}
/**
* Template INSERT
*
* @param templateVO 저장 할 값
* @return TemplateVO 결과값 반환
*/
@Transactional
public int insertTemp(TemplateVO templateVO) {
TemplateMapper tm = sqlSession.getMapper(TemplateMapper.class);
return tm.insertTemp(templateVO);
}
/**
* Template Update
*
* @param templateVO Update Value
* @return TemplateVO 결과값 반환
*/
@Transactional
public int updateTemp(TemplateVO templateVO) {
TemplateMapper tm = sqlSession.getMapper(TemplateMapper.class);
return tm.updateTemp(templateVO);
}
/**
* Template Delete
*
* @param tempId 삭제 아이디
* @return TemplateVO 결과값 반환
*/
@Transactional
public int deleteTempById(Integer templateId) {
TemplateMapper tm = sqlSession.getMapper(TemplateMapper.class);
return tm.deleteTempById(templateId);
}
}
💡 @Autowired - Java 스프링 프레임워크에서 사용되는 ‘자동 의존성 주입’을 위한 어노테이션입니다. 주로 컴포넌트 간의 의존성을 자동으로 연결하기 위해 사용하는 어노테이션입니다.
- @Autowired를 필드, 생성자 또는 설정 메서드에 어노테이션으로 달면, Spring은 해당 컴포넌트에 적절한 빈을 자동으로 해결하고 주입합니다. 이를 통해 컴포넌트 간의 느슨한 결합이 가능하며, 애플리케이션의 의존성 관리 과정을 간소화할 수 있습니다.
https://adjh54.tistory.com/298
[ 더 알아보기 ] 💡 의존성 주입을 하지 않으면 어떻게 될까? 예를 들어 Controller에서 Service의 Interface 메서드를 가져오려고 해
- Controller에서 Service 인터페이스를 호출할 때 의존성을 주입하지 않으면 예외나 오류가 발생할 수 있습니다. Controller는 Service 인터페이스가 제공하는 메서드와 기능에 접근할 수 없게 됩니다.
💡 클래스에서 클래스를 호출하는 경우도 있는데 그건 잘 되는데? 예를 들어 Service에서 공통 유틸 클래스를 호출해서 사용하는 것 같이 수행하는것은 잘 되는데?
- 클래스에서 클래스를 호출하여 가져올 수 있는 경우에는 의존성 주입을 사용하지 않을 수 있습니다. 이는 간단한 의존성 관리나 작은 규모의 애플리케이션에서 흔히 사용되는 방식입니다. 그러나 보다 복잡한 애플리케이션 개발이나 테스트 용이성 등을 위해 의존성 주입을 사용하는 것이 좋습니다.
💡 의존성 주입을 하는 이유는 뭘까? 1. 코드의 유지 보수성과 확장성을 향상합니다.
- 의존성 주입을 통해 컴포넌트 간의 결합도를 낮출 수 있고, 의존하는 객체를 쉽게 교체하거나 확장할 수 있습니다. 2. 테스트 용이성을 제공합니다.
- 의존하는 객체를 모의(mock) 객체로 대체하여 단위 테스트를 수행할 수 있으며, 테스트할 때 원하는 동작을 구현한 모의 객체를 주입할 수 있습니다. 3. 코드의 가독성과 이해도를 높여줍니다.
- 클래스가 직접 의존하는 객체를 생성하거나 가져오는 것보다, 외부에서 의존 객체를 주입받는 방식은 코드의 의도를 명확하게 표현할 수 있습니다.
💡 [참고] 해당 어노테이션의 주요 속성 값입니다.
속성
설명
required
의존성이 autowired 되어야 하는지를 지정합니다. 기본값은 true입니다.
value
autowiring에 사용될 빈 이름 또는 빈 클래스입니다.
qualifier
이름에 의한 autowiring을 위한 한정자 값입니다.
annotationType
autowiring에 사용될 주석 유형입니다.
name
autowiring에 사용될 빈 이름입니다.
defaultValue
요청된 유형의 빈이 존재하지 않을 경우 사용할 기본값입니다.
💡 해당 예시에서는 아래와 같은 아키텍처 구조를 가지고 있습니다.
- 아래의 예시에서는 Controller 내에서 @Autowired 어노테이션을 통해서 Service(Interface)의 의존성을 주입받아올 예정입니다.
https://adjh54.tistory.com/105
💡 @Autowired 사용 예시 - CodeController는 코드 정보를 관리하는 API입니다.
- @Autowired 어노테이션을 통해서 서비스의 의존성을 주입하여서 Controller 내에서 사용함을 확인할 수 있습니다.
package com.adjh.multiflexapi.controller;
import com.adjh.multiflexapi.common.codes.SuccessCode;
import com.adjh.multiflexapi.common.response.ApiResponse;
import com.adjh.multiflexapi.model.CodeDto;
import com.adjh.multiflexapi.service.CodeService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
/**
* 코드 정보를 관리하는 Controller
*/
@Slf4j
@RestController
@RequestMapping(value = "/api/v1/code")
@Tag(name = "Code", description = "코드 API")
public class CodeController {
@Autowired
private CodeService codeService;
/**
* [API] 코드 리스트 출력
*
* @param codeDto codeDto
* @return ApiResponseWrapper<ApiResponse> : 응답 결과 및 응답 코드 반환
*/
@PostMapping("/codes")
@Operation(summary = "코드 조회", description = "코드 조회")
public ResponseEntity<ApiResponse<Object>> selectCodeList(@RequestBody @Validated CodeDto codeDto) {
log.debug("코드의 모든 리스트를 조회합니다.");
CodeDto resultDto = codeService.selectCode(codeDto);
ApiResponse<Object> ar = ApiResponse.builder()
.result(resultDto)
.resultCode(SuccessCode.SELECT.getStatus())
.resultMsg(SuccessCode.SELECT.getMessage())
.build();
return new ResponseEntity<>(ar, HttpStatus.OK);
}
/**
* [API] 코드 단건 조회 : 코드 키 값을 기반으로 조회합니다.
*
* @param cd String
* @return ApiResponseWrapper<ApiResponse> : 응답 결과 및 응답 코드 반환
*/
@GetMapping("/code")
@Operation(summary = "코드 값 별 코드 조회", description = "코드 조회")
public ResponseEntity<ApiResponse<Object>> selectCodeByCd(@RequestParam String cd) {
CodeDto codeItem = codeService.selectCodeByCd(cd);
ApiResponse<Object> ar = ApiResponse.builder()
.result(codeItem)
.resultCode(SuccessCode.SELECT.getStatus())
.resultMsg(SuccessCode.SELECT.getMessage())
.build();
return new ResponseEntity<>(ar, HttpStatus.OK);
}
/**
* [API] 코드 단건 조회 : 코드 키 값을 기반으로 조회합니다.
*
* @param codeDto
* @return ApiResponseWrapper<ApiResponse> : 응답 결과 및 응답 코드 반환
*/
@PostMapping("/codeItem")
@Operation(summary = "코드 값 별 코드 조회", description = "코드 조회")
public ResponseEntity<ApiResponse<Object>> selectCode(@RequestBody CodeDto codeDto) {
CodeDto codeItem = codeService.selectCode(codeDto);
ApiResponse<Object> ar = ApiResponse.builder()
.result(codeItem)
.resultCode(SuccessCode.SELECT.getStatus())
.resultMsg(SuccessCode.SELECT.getMessage())
.build();
return new ResponseEntity<>(ar, HttpStatus.OK);
}
}
💡 [참고] @Autowired 방식은 권장하지 않습니다 : IntelliJ - Field Injection is not recommended
- 해당 필드 주입 방식은 아래와 같은 이유로 추천을 하지 않고 있습니다.
1. 테스트 용이성 감소 - 필드 주입은 주입된 의존성을 가진 클래스에 대한 단위 테스트 작성을 어렵게 만듭니다. 테스트 중에 주입된 의존성을 목 객체로 대체하거나 모의 객체로 대체하기 어려울 수 있습니다.
2. 숨겨진 의존성 - 필드 주입을 사용하면 의존성이 생성자나 메서드 매개변수에 명시적으로 선언되지 않습니다. 이로 인해 클래스의 의존성을 이해하기 어려워지며, 숨겨진 의존성이 발생할 수 있습니다.
3. 강한 결합 - 필드 주입은 클래스와 해당 의존성 간에 강한 결합을 만듭니다. 이로 인해 코드베이스가 유연성이 떨어지고 유지보수가 어려워질 수 있습니다. 의존성에 변경이 발생하면 해당 의존성을 사용하는 클래스를 수정해야 할 수도 있습니다.
4. 제어 부족 - 필드 주입은 의존성의 수명 주기를 제어하는 데 제한적인 제어를 제공합니다. 의존성의 생성과 소멸을 관리하기 어려워지며, 이로 인해 메모리 누수나 리소스 관리 문제가 발생할 수 있습니다.
- IntelliJ 추천으로는 필드 의존성 주입보다는 ‘생성자 의존성 주입’ 방식을 추천해주고 있습니다.
@Slf4j
@RestController
@RequestMapping(value = "/api/v1/code")
public class CodeController {
// 적용 이전
@Autowired
private CodeService codeService;
// 적용 이후
private final CodeService codeService;
public CodeController(CodeService codeService) {
this.codeService = codeService;
}
}
2. @Value
💡 @Value - Java 스프링 프레임워크에서 필드, 메서드 또는 생성자 인수에 값을 주입하기 위해 사용됩니다. 일반적으로 ‘구성 파일이나 환경 변수에서 속성 값을 주입’하는 데 사용됩니다.
- 이를 사용하려면 속성에 대한 값을 정의해야 합니다. 기본적으로 Spring Boot 프로젝트를 사용 중이라면 기본은 application.properties 파일을 가리키고 있습니다.
💡 [참고] application.properties 파일에 대해 궁금하시면 아래의 글을 참고하시면 도움이 됩니다.
- 해당 예시에서는 PrimaryExampleBean 클래스와 SecondaryExampleBean 클래스, ExampleService 클래스가 존재합니다.
1. PrimaryExampleBean 클래스에서 @Primary를 추가하여 해당 빈을 기본적으로 주입할 빈으로 지정합니다. 그리고 ExampleService 클래스에서 ExampleBean을 주입받을 때, @Autowired 어노테이션만 사용하여 주입받습니다.
2. 이를 통해 스프링은 기본적으로 PrimaryExampleBean을 주입할 빈으로 선택하며, @Primary로 지정된 빈이 없을 경우에는 동일한 타입의 다른 빈을 주입합니다.
@Component
@Primary
public class PrimaryExampleBean implements ExampleBean {
// ...
}
@Component
public class SecondaryExampleBean implements ExampleBean {
// ...
}
@Component
public class ExampleService {
private ExampleBean exampleBean;
@Autowired
public ExampleService(ExampleBean exampleBean) {
this.exampleBean = exampleBean;
}
// ...
}
5. @Profile
💡 @Profile
- Java 스프링 프레임워크에서 ‘특정 환경이나 조건에서만 동작하는 빈을 지정’할 수 있습니다. 이를 통해 스프링 애플리케이션을 다양한 환경에서 유연하게 구성하고 관리할 수 있습니다.
💡 [참고] 해당 어노테이션의 주요 속성 값입니다.
속성
설명
없음
해당 빈은 모든 환경에서 사용됩니다.
"profile"
지정된 프로파일에 해당하는 환경에서만 해당 빈이 사용됩니다. 여러 개의 프로파일을 지정할 수 있으며, 쉼표로 구분합니다.
"!profile"
지정된 프로파일에 해당하지 않는 환경에서 해당 빈이 사용됩니다.
{"profile1", "profile2"}
지정된 여러 개의 프로파일 중 하나에 해당하는 환경에서 해당 빈이 사용됩니다.
{"!profile1", "!profile2"}
지정된 여러 개의 프로파일에 해당하지 않는 환경에서 해당 빈이 사용됩니다.
💡 @Profile 사용 예시
- 해당 예시에서는 DevelopmentConfiguration 클래스와 ProductionConfiguration 클래스가 존재합니다. 두 개의 클래스는 @Configuration를 통해 설정 클래스임을 지정하였습니다.
1. DevelopmentConfiguration 클래스에 @Profile("development")를 추가하여 해당 빈들이 개발 환경에서만 사용되도록 설정합니다.
2. 마찬가지로 ProductionConfiguration 클래스에 @Profile("production")을 추가하여 해당 빈들이 운영 환경에서만 사용되도록 설정합니다.
3. 이렇게 설정된 빈들은 각각의 프로파일에 해당하는 환경에서만 사용됩니다. 스프링 애플리케이션을 실행할 때, spring.profiles.active 프로퍼티를 설정하여 원하는 프로파일을 활성화할 수 있습니다.
@Configuration
@Profile("development")
public class DevelopmentConfiguration {
@Bean
public LoggingService loggingService() {
return new LoggingService("DEBUG");
}
}
@Configuration
@Profile("production")
public class ProductionConfiguration {
@Bean
public LoggingService loggingService() {
return new LoggingService("INFO");
}
}
💡 @Profile 사용 예시
- 위에서 구성한 내용을 기반으로 Applcation이 시작될 때 setAdditionalProfiles("development"); 지정을 통해서 @Profile("development")로 지정한 Configuration 파일을 로드합니다.
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(MyApplication.class);
app.setAdditionalProfiles("development"); // 개발 환경 프로파일 활성화
app.run(args);
}
}
💡 [참고] Spring Web Annotation에 대해 더 궁금하시면 아래의 글을 참고하시면 도움이 됩니다.