Spring Boot 요약정리
Posted by Albert 10Day 9Hour 11Min 8Sec ago [2025-12-01]
Spring Boot는 Spring 프레임워크 기반의 애플리케이션을 빠르고 쉽게 개발할 수 있게 해주는 프레임워크입니다. 복잡한 설정 없이도 바로 실행 가능한 독립형 애플리케이션을 만들 수 있습니다.
1. Spring Boot 기본 개념
Spring Boot는 "관례 우선 설정(Convention over Configuration)" 원칙을 따릅니다. 기본 설정을 자동으로 제공하여 개발자가 비즈니스 로직에 집중할 수 있게 합니다.
기본 애플리케이션 구조
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
@SpringBootApplication 어노테이션은 세 가지 어노테이션을 합친 것입니다:
- @Configuration: 스프링 설정 클래스
- @EnableAutoConfiguration: 자동 설정 활성화
- @ComponentScan: 컴포넌트 스캔
2. 의존성 관리 (Dependencies)
Maven 예시 (pom.xml)
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
</parent>
<dependencies>
<!-- 웹 애플리케이션 개발 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- JPA/Hibernate -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- 개발 도구 (자동 재시작) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
3. REST API 개발
기본 컨트롤러
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
// GET: 모든 사용자 조회
@GetMapping
public List<User> getAllUsers() {
return userService.findAll();
}
// GET: 특정 사용자 조회
@GetMapping("/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
return userService.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
// POST: 사용자 생성
@PostMapping
public ResponseEntity<User> createUser(@RequestBody @Valid User user) {
User savedUser = userService.save(user);
return ResponseEntity.status(HttpStatus.CREATED).body(savedUser);
}
// PUT: 사용자 수정
@PutMapping("/{id}")
public ResponseEntity<User> updateUser(
@PathVariable Long id,
@RequestBody @Valid User user) {
return userService.update(id, user)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
// DELETE: 사용자 삭제
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
userService.delete(id);
return ResponseEntity.noContent().build();
}
}
4. 데이터베이스 연동 (JPA)
Entity 클래스
@Entity
@Table(name = "users")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
@Email
private String email;
@Column(nullable = false)
@Size(min = 2, max = 50)
private String name;
@Column(nullable = false)
private String password;
@CreatedDate
@Column(nullable = false, updatable = false)
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
}
Repository 인터페이스
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// 메서드 이름으로 쿼리 자동 생성
Optional<User> findByEmail(String email);
List<User> findByNameContaining(String name);
// JPQL 쿼리
@Query("SELECT u FROM User u WHERE u.createdAt > :date")
List<User> findRecentUsers(@Param("date") LocalDateTime date);
// Native SQL 쿼리
@Query(value = "SELECT * FROM users WHERE email LIKE %:domain",
nativeQuery = true)
List<User> findByEmailDomain(@Param("domain") String domain);
}
Service 계층
@Service
@Transactional
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private PasswordEncoder passwordEncoder;
public List<User> findAll() {
return userRepository.findAll();
}
public Optional<User> findById(Long id) {
return userRepository.findById(id);
}
public User save(User user) {
// 비밀번호 암호화
user.setPassword(passwordEncoder.encode(user.getPassword()));
return userRepository.save(user);
}
public Optional<User> update(Long id, User userDetails) {
return userRepository.findById(id)
.map(user -> {
user.setName(userDetails.getName());
user.setEmail(userDetails.getEmail());
return userRepository.save(user);
});
}
public void delete(Long id) {
userRepository.deleteById(id);
}
}
5. 설정 파일 (application.properties / application.yml)
application.yml 예시
spring:
application:
name: my-application
' 데이터베이스 설정
datasource:
url: jdbc:mysql://localhost:3306/mydb
username: root
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
' JPA 설정
jpa:
hibernate:
ddl-auto: update ' none, validate, update, create, create-drop
show-sql: true
properties:
hibernate:
format_sql: true
dialect: org.hibernate.dialect.MySQL8Dialect
' 서버 설정
server:
port: 8080
servlet:
context-path: /api
' 로깅 설정
logging:
level:
root: INFO
com.myapp: DEBUG
org.hibernate.SQL: DEBUG
6. 예외 처리 (Exception Handling)
커스텀 예외
public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String message) {
super(message);
}
}
전역 예외 핸들러
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleResourceNotFound(
ResourceNotFoundException ex) {
ErrorResponse error = new ErrorResponse(
HttpStatus.NOT_FOUND.value(),
ex.getMessage(),
LocalDateTime.now()
);
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> handleValidationErrors(
MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error ->
errors.put(error.getField(), error.getDefaultMessage())
);
return ResponseEntity.badRequest().body(errors);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGenericException(
Exception ex) {
ErrorResponse error = new ErrorResponse(
HttpStatus.INTERNAL_SERVER_ERROR.value(),
"Internal server error",
LocalDateTime.now()
);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(error);
}
}
@Data
@AllArgsConstructor
class ErrorResponse {
private int status;
private String message;
private LocalDateTime timestamp;
}
7. 유효성 검증 (Validation)
@RestController
@RequestMapping("/api/products")
public class ProductController {
@PostMapping
public ResponseEntity<Product> createProduct(
@RequestBody @Valid ProductRequest request) {
// 검증 통과 시 실행
return ResponseEntity.ok(productService.create(request));
}
}
@Data
public class ProductRequest {
@NotBlank(message = "제품명은 필수입니다")
@Size(min = 2, max = 100, message = "제품명은 2-100자여야 합니다")
private String name;
@NotNull(message = "가격은 필수입니다")
@Min(value = 0, message = "가격은 0 이상이어야 합니다")
private BigDecimal price;
@Email(message = "올바른 이메일 형식이 아닙니다")
private String contactEmail;
@Pattern(regexp = "^\\d{3}-\\d{4}-\\d{4}$",
message = "전화번호 형식: 010-1234-5678")
private String phone;
}
8. Security (보안)
기본 Security 설정
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private UserDetailsService userDetailsService;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers("/api/user/**").hasAnyRole("USER", "ADMIN")
.anyRequest().authenticated()
)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.httpBasic();
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(
AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
}
JWT 인증 필터
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private JwtTokenProvider jwtTokenProvider;
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String token = getTokenFromRequest(request);
if (token != null && jwtTokenProvider.validateToken(token)) {
String username = jwtTokenProvider.getUsernameFromToken(token);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
}
private String getTokenFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}
9. 테스팅 (Testing)
단위 테스트
@SpringBootTest
class UserServiceTest {
@Autowired
private UserService userService;
@MockBean
private UserRepository userRepository;
@Test
void testFindUserById() {
// Given
User user = new User(1L, "test@example.com", "Test User", "password", null, null);
when(userRepository.findById(1L)).thenReturn(Optional.of(user));
// When
Optional<User> result = userService.findById(1L);
// Then
assertTrue(result.isPresent());
assertEquals("test@example.com", result.get().getEmail());
}
}
통합 테스트
@SpringBootTest
@AutoConfigureMockMvc
class UserControllerIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@Test
void testCreateUser() throws Exception {
User user = new User(null, "new@example.com", "New User", "password", null, null);
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(user)))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.email").value("new@example.com"));
}
}
10. 고급 기능
스케줄링
@Configuration
@EnableScheduling
public class SchedulingConfig {
}
@Component
public class ScheduledTasks {
private static final Logger logger = LoggerFactory.getLogger(ScheduledTasks.class);
// 5초마다 실행
@Scheduled(fixedRate = 5000)
public void reportCurrentTime() {
logger.info("현재 시간: {}", LocalDateTime.now());
}
// Cron 표현식: 매일 오전 2시 실행
@Scheduled(cron = "0 0 2 * * ?")
public void performDailyTask() {
logger.info("일일 작업 실행");
}
}
비동기 처리
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(5);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("async-");
executor.initialize();
return executor;
}
}
@Service
public class EmailService {
@Async
public CompletableFuture<String> sendEmail(String to, String subject) {
// 이메일 발송 로직
try {
Thread.sleep(3000); // 시뮬레이션
return CompletableFuture.completedFuture("이메일 발송 완료: " + to);
} catch (InterruptedException e) {
return CompletableFuture.failedFuture(e);
}
}
}
캐싱
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("users", "products");
}
}
@Service
public class ProductService {
@Cacheable(value = "products", key = "'id")
public Product findById(Long id) {
// 데이터베이스 조회
return productRepository.findById(id).orElse(null);
}
@CachePut(value = "products", key = "'product.id")
public Product update(Product product) {
return productRepository.save(product);
}
@CacheEvict(value = "products", key = "'id")
public void delete(Long id) {
productRepository.deleteById(id);
}
}
이 가이드는 Spring Boot의 핵심 개념부터 실무에서 자주 사용되는 고급 기능까지 포괄합니다. 각 코드 예시는 실제 프로젝트에서 바로 활용할 수 있으며, 초보자는 기본 개념부터 순차적으로 학습하고, 숙련자는 필요한 부분을 참고하여 프로젝트에 적용할 수 있습니다.