Spring Framework

jojaljaejal·2025년 1월 23일

spring

목록 보기
5/5

Spring Framework 는 엔터프라이즈 애플리케이션 개발을 위한 포괄적인 프로그래밍과 구성 모델을 제공하는 자바 기반의 프레임워크로, 어떤 종류의 배포 플랫폼에서도 적용이 가능합니다.
스프링은 애플리케이션 수준에서의 인프라 지원으로, 특정 배포 환경에 불필요하게 얽히지 않고, 애플리케이션 수준의 비즈니스 로직에 집중할 수 있도록 도움을 준다.

스프링 프레임워크의 핵심 기술

IOC(Inversion of Control) = 제어의 역전

  • 객체의 생명 주기(생성, 초기화, 소멸, 호출 등)을 개발자가 결정하는 것이 아니라, 스프링 프레임워크가 객체의 생명 주기를 관리

DI(Dependency Injection) = 의존성 주입

  • A객체와 B객체가 관계가 있는 경우, 스프링 컨테이너가 의존성을 주입하여,
    개발자가 코드 상에서 객체의 생성과 관계에 대해 관여하지 않아도 되기 때문에, 객체의 의존도와 겹합도를 낮추고 유연성을 확보할 수 있다.

AOP(Aspect Oriented Programming) = 관점 지향 프로그래밍

  • 핵심 로직과는 별도로 부가적인 기능(로깅, 트랜잭션, 예외처리, 보안 등)을 모듈화하여 관리할 수 있게 해주는 프로그래밍 기법.

스프링이 객체를 관리하는 방법

스프링 컨테이너


스프링 컨테이너는 BeanFactoryApplicationContext 두 종류의 인터페이스로 구현되어 있다.

  • BeanFactory
    스프링의 가장 기본적인 IoC 컨테이너로, 객체(Bean)의 라이프사이클(초기화, 소멸 등)과 의존성을 주입, 지연 로딩(Lazy Loading) 방식으로 동작하며, 임베디드 시스템이나 매우 간단한 테스트 환경에서 주로 사용된다.

  • ApplicationContext
    BeanFactory를 확장한 인터페이스로, 더 많은 고급 기능(국제화, 이벤트 처리, AOP, 트랜잭션, 환경 설정 등)을 제공, 즉시 로딩(Eager Loading) 방식으로 동작하며, 대규모 엔터프라이즈 애플리케이션이나 복잡한 기능을 요구하는 경우 사용된다.


스프링 프레임워크로 객체 관리하기

스프링 어노테이션

  • 스프링 프레임워크에서 어노테이션은 클래스, 메서드, 필드, 매개변수 등에 메타데이터를 제공하는 마킹 기능으로 어노테이션을 사용함으로써 코드에 부가적인 정보나 설정을 주입할 수 있으며, 코드 간결화, 가독성 향상, 유지보수성, 개발 생산성이 증가한다. 스프링에서 어노테이션은 @[어노테이션명] 으로 사용된다.

객체를 관리하는데에는 결합도에 따라 Java Bean, POJO(Plain Old Java Object), Spring Bean 으로 나눌 수 있지만 스프링 컨테이너가 관리하는 모든 객체는 Spring Bean으로 new 를 사용하여 생성된 것이 아니라, 스프링에 의해 생성되고 관리된다.


스프링 프레임워크의 의존성 관리

스프링 프레임워크를 사용하면 객체가 아닌 의존성과 와이어링에 집중할 수 있다. 개발자는 애플리케이션의 비즈니스 로직에 집중할 수 있고, 스프링 프레임워크는 객체의 생명 주기를 관리하므로, @Component, @Autowired 어노테이션을 사용해서 의존성을 표시하기만 하면 된다.
그러면 스프링 프레임워크는 우리를 위해 전체 시스템의 준비를 갖춘다.

스프링 프레임워크가 객체를 관리해주지만 스프링의 기본 객체 로딩 방식은 즉시 로딩이다.
스프링 컨테이너는 애플리케이션이 시작될 때 객체(Bean)를 생성하고 로딩 시점을 신경 쓸 필요가 없기 때문에, 코드를 단순하게 작성할 수 있지만, 애플리케이션 실행 시점에 모두 생성되기 때문에, 초기 성능이 저하될 수 있다. 따라서 특정 요청에서만 사용되는 객체는 지연 로딩을 사용하는 것이 최적화에 좋다.

지연 로딩(Lazy Loading)은 스프링 컨테이너가 객체(Bean)가 필요한 시점에 로딩하는 방식이다. 초기 성능을 향상시키고 리소스를 절약할 수 있는 장점이 있지만, 로딩 시점이 애플리케이션이 실행된 이후(Runtime)이므로, 예상치 못한 문제를 유발할 수 있고, 디버깅이 어려운 문제가 있으니, 특정 상황에 맞게 주의하여 사용하는 것이 좋다.


Spring Bean Scope

스프링에서 객체의 생성과 소멸의 범위를 개발자는 간단하게 어노테이션으로 관리할 수 있다.
@Scope(”singletin”) 또는 생략 --- 스프링 컨테이너 당 하나의 인스턴스
@Scope(”prototype”) --- 요청할 때마다 새로운 인스턴스
@Scope(”request”) --- HTTP 요청 당 하나의 인스턴스
@Scope(”session”) --- HTTP 세션 당 하나의 인스턴스
@Scope(”application”) --- 애플리케이션 범위에서 하나의 인스턴스
@Scope(”websocket”) --- 웹 소켓 연결 당 하나의 인스턴스


스테레오타입 어노테이션

  • 객체(Bean)를 정의하기 위해 @component 어노테이션을 사용하였지만 이를 더욱 명확하게하여
    유지보수 및 가독성을 높일 수 있다. 물론 @component으로 모든 클래스에 정의내려 쓸 수 있지만 각 클래스들이 무슨 역할을 하는지 의도를 명확하게 파악하기 어려워진다.

스테레오타입 어노테이션 종류

(스테레오타입 어노테이션은 @Component 어노테이션을 기본으로 하여, 플리케이션 구조를 명확하게 하기 위해 파생된 어노테이션이다)

@Controller

해당 클래스가 웹 요청을 처리하는 컨트롤러임을 나타내며, MVC 패턴의 컨트롤러 역할을 수행하고, 이 어노테이션이 붙은 객체는 DispatcherServlet 이 핸들링 하는 객체가 되며, 뷰를 반환하거나, Restful API의 엔드포인트로 동작한다.

@Service

해당 클래스가 비즈니스 로직을 처리하는 클래스임을 나타냄
애플리케이션의 비즈니스 로직, 트랜잭션, Presentation 레이어와 Data Access 레이어의 중간다리 역할 등을 수행.

@Repository

해당 클래스가 데이터 접근 객체(Data Access Obejct)임을 나타냄.
데이터베이스, 파일 시스템, 3rd-party 등 데이터 저장소와 통신하는 역할을 수행.

@Configuration

해당 클래스가 스프링 설정 정보를 담고있는 객체임을 나타냅니다.
@Bean 어노테이션으로 애플리케이션에 필요한 설정 객체를 등록할 수 있습니다(데이터베이스 연결 설정, 3rd-party 설정 등).


함수형 프로그래밍

함수형 프로그래밍은 자료 처리를 수학적 함수의 계산으로 취급하고,
상태와 가변 데이터를 멀리하는 프로그래밍 패터다임의 하나. 함수형 프로그래밍은 병렬 처리에 유리하여 2010년, 빅데이터와 분산 시스템이 발전하면서 많은 인기를 얻기 시작했다. 현재 많은 프로그래밍 언어들이 함수형을 지원하고 있으며, 자바는 8버전 이후부터 람다 표현식(Lambda Expression) 과 Stream API 가 도입되어 함수형 프로그래밍을 작성할 수 있게 되었되었다.

람다 표현식(Lambda Expression)

함수형 프로그래밍을 구성하기 위한 함수식을 말합니다.
메서드를 하나의 식으로 표현한 것으로, 익명 함수(Anonymous Function)입니다.
자바에서 람다 표현식은 화살표로 표현합니다.

(int a, int b) -> { 
	int result = a * b; 
	return result; 
}

명령형과 선언형 프로그래밍

  • 명령형 프로그래밍

어떻게(How) 문제를 해결할지를 기술하는 방식입니다.
명령어가 순차적으로 실행되며, 조건문, 반복문 등의 실행 흐름을 명시적으로 정의합니다.
프로그램이 실행됨에 따라 변수를 변경 또는 데이터 상태가 변하는 방식으로 동작합니다.

int[] numbers = {1, 2, 3, 4, 5};
for (int i = 0; i < numbers.length; i++) {
    numbers[i] = numbers[i] * 2;
}
  • 선언형 프로그래밍

무엇을(What) 해결할지를 기술하는 방식입니다.
결과를 정의하는 데 중점을 두며, 실행 흐름을 직접적으로 명시하지 않습니다.

int[] numbers = {1, 2, 3, 4, 5};
int[] doubledNumbers = Arrays.stream(numbers).map(n -> n * 2).toArray();
// 어떻게 map 함수가 내부적으로 동작하는지 신경쓰지 않고, 해당 배열의 요소를 2배로 만들겠다는 결과만 정의

스프링 부트 시작하기

  • 스프링 부트는 대부분의 설정을 자동으로 처리하여 초기 설정의 복잡함을 줄이고,
    의존성 관리를 간소화하여 버전 관리와 호환성 문제를 해결하고 배포를 위한 내장 웹 서버(Tomcat, Jetty, Undertow 등)도 가지고 있어, 외부 웹 서버 필요 없이 독립적으로 실행할 수 있게 되었다.

📁 src/main/java
Java 소스 코드가 위치하는 디렉토리.

📁 src/main/resources
애플리케이션 리소스 파일들(설정 파일, 정적 파일, 뷰 템플릿)이 위치하는 디렉토리.

📁 static
HTML, CSS, JS, 이미지 같은 정적 리소스 파일이 위치하는 디렉토리입니다. 이곳에 위치한 파일들은 /static 경로 없이 제공

📁 templates
Thymeleaf, Freemarker, Mustache 와 같은 서버 사이드 템플릿 파일을 저장하는 디렉토리.

📁 src/test/java
테스트 코드가 위치하는 디렉토리 입니다. JUnit, Mockito 등을 활용하여 테스트 코드를 작성할 수 있다.

📄 application.properties / application.yml
애플리케이션 환경 설정 파일입니다. 데이터베이스, 포트, 로깅 등 다양한 설정을 이 곳에서 정의할 수 있다.

📄 pom.xml (Maven) / build.gradle (Gradle)
Maven 프로젝트, Gradle 프로젝트에 따라 생성되는 프로젝트 빌드 설정 파일입니다.
의존성 관리와 빌드 설정을 정의하는 데 사용.


JDBC 이해하기

Java DataBase Connectivity 의 약자로, 자바에서 데이터베이스에 접속할 수 있도록 하는 표준화된 API를 말한다. JDBC는 JDK1.1 버전에 출시된 아주 오래된 기술이며, 요즘은 직접 사용하는 일은 거의 없고, JDBC를 기반으로 동작하는 기술들(Spring JDBC, Hibernate, Spring Data JPA, MyBatis 등)을 사용하고 있다.

MyBatis

MyBatis는 SQL을 직접 작성할 수 있도록 지원하는 SQL 기반의 프레임워크입니다. 기존 JDBC의 불편함을 해결하면서도, SQL의 강력한 제어를 유지할 수 있다.

JPA

JPA(Java Persistence API)는 자바의 표준 ORM(객체-관계 매핑) 프레임워크로, SQL을 직접 작성하지 않고 객체 중심적인 방식으로 데이터베이스 작업을 수행한다.


MVC 디자인 패턴과 Dispatcher Servlet

모든 흐름의 제어는 Front Controller 가 담당하고, 일부 로직은 컨트롤러가
처리하도록 위임(Delegation)하는 구조가 특징이며, Spring MVC의 아키텍처이다.

💡Dispatcher Servlet

Spring MVC 에서 핵심적인 역할을 하는 서블릿으로, 모든 웹 요청은 Dispatcher Servlet 을 통해 처리되고 라이언트 요청을 컨트롤러로 전달하고, 결과를 View 로 반환하는 일을 수행한다.


HTTP Status Codes

  • 1xx: Informational
    100 Continue → 임시 응답으로, 지금까지 상태가 괜찮으니 클라이언트가 계속해서 요청을 하거나, 요청을 완료한 경우 무시해도 되는 것을 알려줍니다.
    101 Switching Protocal → 클라이언트 요청에 따라 서버에서 프로토콜을 변경할 것임을 알려줍니다.
  • 2xx: Success
    200 OK → 요청이 성공적으로 처리되었음을 알려줍니다.
    201 Created → 요청이 성공했고, 새로운 리소스가 생성됐음을 알려줍니다.
    202 Accepted → 요청을 수락했으나, 아직 처리가 완료되지 않음을 알려줍니다.
    204 No Content →요청은 성공했으나 보내줄 수 있는 리소스가 없음을 알려줍니다. PUT 결과가 없는 경우 204 상태를 반환합니다.
  • 3xx: Redirectional
    300 Multiple Choices → 요청한 리소스에 하나 이상의 응답이 가능함을 알려줍니다.
    301 Moved Permanently → 요청한 리소스가 다른 URI로 변경되었음을 알려줍니다.
    302 Found → 요청한 리소스가 일시적으로 URI가 변경되었음을 알려줍니다.
    303 See Other → 요청한 리소스는 다른 URI에서 확인할 수 있음을 알려줍니다.
    304 Not Modified → 캐시 목적으로 사용되며, 응답이 수정되지 않았음을 알려줍니다.
  • 4xx: Client Error
    400 Bad Request → 잘못된 요청 형식임을 알려줍니다. 서버가 요구하는 리소스 형식에 맞지 않는 경우에 발생합니다.
    401 Unauthorized → 인증이 필요하거나, 인증이 실패했음을 알려줍니다.
    403 Forbidden → 리소스에 접근할 권한을 가지고 있지 않음을 알려줍니다.
    404 Not Found → 요청한 리소스를 찾을 수 없음을 알려줍니다.
    405 Method Not Allowed → 요청된 HTTP 메서드가 허용되지 않음을 알려줍니다.
    409 Conflict → 클라이언트 요청이 서버의 현재 상태와 충돌(리소스 중복, 버전 충돌 등)이 발생했음을 알려줍니다.
  • 5xx: Server Error
    500 Internal Server Error → 서버에 오류가 발생했음을 알려줍니다.
    501 Not Implemented → 요청한 기능이 서버에서 지원하지 않음을 알려줍니다.
    502 Bad Gateway → 게이트웨이, 프록시 서버가 잘못된 응답을 수신했음을 알려줍니다.
    503 Service Unavailable → 서버 과부화 또는 유지보수 중으로 현재 요청을 처리할 수 없음을 알려줍니다.
    504 Gateway Timeout → 게이트웨이가 요청을 처리하는데 시간이 초과됐음을 알려줍니다.

--

스프링 RestAPI 개발하기

REST API란?

  • REST API는 API의 한 종류로, REST(Representational State Transfer) 아키텍처 스타일을 따르는 웹 API 즉, HTTP 프로토콜을 기반으로 리소스를 정의하고 조작하는 방식

( HTTP : Hyper Text Transfer Protocal 의 약자로, 웹에서 클라이언트와 서버 간 데이터를 주고받기 위한 통신 규약)

RestAPI 를 설계할 때 유용한 어노테이션

@PathVariable, @RequestParam, @RequestBody

@PatiVariable

URL 경로에 있는 변수를 메서드 파라미터로 바인딩할 수 있습니다.

@RestController
public class UserController {
		// http://localhost:8080/users/james
    @GetMapping("/users/{id}")
    public ResponseEntity<String> getUserById(@PathVariable String id) {
        return ResponseEntity.ok("User ID: " + id);
    }
}

@RequestParam
쿼리 스트링 또는 폼 데이터에 있는 요청 파라미터를 메서드 파라미터로 바인딩할 수 있습니다.

@RestController
public class SearchController {

    // http://localhost:8080/search?keyword=foo
    @GetMapping("/search")
    public ResponseEntity<String> search(@RequestParam String keyword) { 
        return ResponseEntity.ok("Searching for: " + keyword);
    }
}

@RequestBody
HTTP 본문(Body)에 있는 데이터를 메서드 파라미터로 매핑할 수 있습니다.
POST, PUT, PATCH 요청의 데이터를 객체로 변환할 때 사용됩니다.

@RestController
public class UserController {

		// {
		//  "username": "john",
		//  "email": "john@example.com"
		// }
    @PostMapping("/users")
    public ResponseEntity<String> createUser(@RequestBody User user) {
        return ResponseEntity.ok("User created: " + user.getUsername());
    }
}

JsonIgnore
해당 어노테이션이 붙은 필드는 요청/응답 데이터에서 제외시킬 수 있다.

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;

import java.time.LocalDate;


@Data
public class UserDto {
    private String id;

		// 비밀번호는 응답 데이터에서 제외
    @JsonIgnore
    private String password;

    private String username;
    private int age;
    private LocalDate createdAt;
}

ResponseEntity

HTTP Response 를 표현하는데 사용하는 클래스로, 단순 객체를 return 하는 것 보다 Header, Body, HTTP Status Code 를 직접 제어할 수 있어 유용하다.


import com.example.lecture014.user.dto.UserDto;
import com.example.lecture014.user.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1")
public class UserController {
    private final UserService userService;

    @GetMapping("/users")
    public ResponseEntity<List<UserDto>> getAllUsers() {
        return ResponseEntity.ok(userService.getAllUsers());
    }
}

RestAPI 예시 시나리오

💡Resource 가 같아도 HTTP Method 가 다르면 같은 API가 아니다.


데이터 유효성 검증하기

Rest API 의 POST, PUT, PATCH 요청에 포함된 데이터가 유효한 값인지 검증하는 로직이 없을 경우 spring-boot-starter-validation 을 사용하여 데이터 유효성 검증을 통해 올바른 요청을 했는지 확인할 수 있다.

Validation Annotations 간단정리

  • @Valid
    선언된 매개 변수/객체에 대한 검증을 자동으로 트리거한다.

  • @Validated
    선언된 매개 변수/객체에 대한 검증을 자동으로 트리거하는데,
    검증 그룹을 지정할 수 있다.

  • @NotNull
    값이 null 이 아니여야 한다.

  • @Null
    값이 null 이여야 한다.

  • @Min(value), @Max(value)
    숫자의 최소/최대 값을 제한한다.

  • @Size(min, max)
    문자열, 배열, 컬렉션의 크기의 범위를 제한한다.

  • @Pattern(regex)
    정규식을 사용하여 문자열 패턴을 검증한다.

  • @Email
    값이 이메일 형식이여야 한다.

  • @Positive, @Negative
    값이 양수/음수여야 한다.

  • @Digits(integer, fraction)
    소수점 자릿수나 정수부 자릴수를 제한한다.

  • @Past, @Future
    값이 현재 날짜보다 이전/미래여야 한다.

  • @AssertTrue, @AssertFalse
    값이 true/false 여야 한다.

import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.Size;
import lombok.Data;


@Data
public class CreateUserDto {
    @NotNull
    @Size(max = 20)
    private String id;

    @NotNull
    @Size(max = 128)
    private String password;

    @NotNull
    @Size(max = 50)
    private String username;

    @NotNull
    @Positive
    private int age;
}

Mybatis ResultMap

Mybatis 의 기본적인 자동 매핑 기능으로 처리가 어려울 때, 복잡한 결과를 매핑할 때, 다양한 매핑 요구 사항을 처리할 때, ResultMap 을 사용하여 유연하게 처리할 수 있다.
객체 필드와 데이터베이스 컬럼 이름이 다른 경우 매핑.
Mybatis 는 기본적으로 SQL 결과의 컬럼과 동일한 객체 필드에 자동으로 매핑해준다.
그러나 컬럼 이름이 객체 필드와 다를 때 자동 매핑이 불가하므로, ResultMap 을 사용하면 좋다.

@Data
class UserDto {
    private String userId;
    private String userName;
    private int userAge;
}
//객체 필드와 DB 컬럼이 다른 경우

<resultMap id="userResultMap" type="com.example.dto.UserDto">
    <id property="userId" column="id"/>
    <result property="userName" column="username"/>
    <result property="userAge" column="age"/>
</resultMap>

<select id="selectUser" resultMap="userResultMap">
    SELECT id, username, age FROM users WHERE id = #{id}
</select>

💡객체 필드와 DB 컬럼이 다른 경우, SQL 을 작성할 때, 컬럼에 Alias 를 지정해서 조회한다면, ResultMap 을 사용하지 않아도 구현이 가능합니다.


Pagination 이해하기

만약 A 라는 사용자의 게시물이 수천, 수만 건까지 쌓여있을 경우, API가 모든 데이터를 응답한다면 성능 이슈가 발생할 수 있다. 이를 Pagination 처리를 통해 데이터를 잘라서 보낼 수 있도록 한다.

Pagination 로직

페이징 처리의 핵심은 데이터를 어느 위치에서부터 가져올지를 구하는 것

offset = (page - 1) * size

예시) page=2, size=10 인 경우, offset 은 (2 - 1) * 10 = 10 이 되며, DB에서는 11번째 데이터부터 가져오게 됩니다.

Pagination 쿼리 예시(오라클 기준)

SELECT 
    T.*
FROM (
    SELECT
        ROW_NUMBER() OVER (ORDER BY order_column ASC) ROW_NUM,
        *
    FROM table_name
) T
WHERE ROW_NUM >= #{offset} AND ROW_NUM <= #{offset} + #{size}

방식1: 전통적인 방법

SELECT *
FROM table_name
ORDER BY order_column
OFFSET #{offset} ROWS FETCH NEXT #{size} ROWS ONLY;

방식2: Oracle 12c 버전 이상

💡OFFSET FETCH 를 사용하면 쿼리가 간결하지만, 레코드를 많이 건너뛸수록(페이지가 커질수록) 성능이 저하될 수 있다.


AOP(Aspect-Oriented Programming)

관점 지향 프로그래밍(Aspect-Oriented Programming)이란, 핵심 기능과는 별도로 분리된 관심사를 모듈화하여 코드 중복을 줄이고, 유지보수성을 높이는 프로그래밍 패러다임 로깅, 보안, 트랜잭션 등 비즈니스 로직에서 부가적인 기능을 분리하는데 중점을 둔다.

대표적인 자바기반 AOP 프레임워크

  • Spring AOP

스프링 프레임워크에서 제공하는 경량 AOP 프레임워크로, 가장 인기가 많다.
프록시(Proxy) 패턴으로 AOP를 구현하며, 스프링 빈(Bean)을 대상으로만 AOP 기능을 제공하므로, 완전한 AOP 솔루션이라고 말할 수 없다.

  • Aspect J

    스프링 AOP보다 더 강력하고 완전한 AOP 프레임워크지만, 거의 사용되지 않는다.
    자바 언어 수준에서 직접 AOP를 구현할 수 있고, 모든 자바 객체에 AOP를 적용할 수 있다.
    AOP를 구현하기에 강력한 프레임워크지만, 설정이 복잡하고 비교적 무거운 프레임워크다.

트랜잭션(Transaction)

트랜잭션(Transaction)이란, 데이터베이스나 시스템에서 하나의 작업 단위를 의미하는데,
그 작업이 완전히 성공하거나, 완전히 실패하여 원래 상태로 돌아가야 하는 특성을 가진다

데이터의 일관성을 보장하기 위해 트랜잭션은 반드시 필요하며, 여러 작업을 하나로 묶어서 처리할 때 사용.

주요 속성(ACID)

  • 원자성(Atomicity)
    트랜잭션 내 모든 작업은 전부 성공하거나 실패해야 합니다(일부만 실행될 수 없음).
  • 일관성(Consistency)
    트랜잭션이 실행되기 전/후에 시스템은 항상 일관된 상태를 유지해야 합니다(트랜잭션 중간에 데이터가 불일치하면 안됨).
  • 고립성(Isolation)
    동시에 여러 트랜잭션이 실행되어도 각 트랜잭션은 독립적으로 실행되어야 합니다(트랜잭션의 중간 결과를 다른 트랙잭션이 참조하면 안됨).
  • 지속성(Durability)
    트랜잭션이 성공했다면, 그 결과는 시스템에 반영되어 오류나 장애가 발생해도 유지되어야 합니다.

처리 흐름

  • BEGIN
    트랜잭션 시작을 알립니다.
  • COMMIT
    트랜잭션이 성공했을 때, 변경된 사항을 확정하고 반영합니다.
  • ROLLBACK
    트랜잭션이 실패했을 때, 변경된 사항을 이전 상태로 되돌립니다.

@Transactional

스프링 프레임워크에서 제공하는 어노테이션으로, 메서드 또는 클래스 수준에서 트랜잭션 처리를 설정할 수 있도록 해준다.
어노테이션된 메서드 또는 클래스는 하나의 트랙잭션으로 처리되며, ACID 속성을 보장할 수 있다.

만약 @Transactional을 사용하지 않는다면?

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class BankService {

    public void transferMoney(long fromAccountId, long toAccountId, double amount) {
        Connection connection = null;

        try {
            // 1. 데이터베이스 연결
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "user", "password");

            // 2. 트랜잭션 시작
            connection.setAutoCommit(false);

            // 3. A 계좌에서 출금
            try (PreparedStatement withdrawStmt = connection.prepareStatement(
                    "UPDATE account SET balance = balance - ? WHERE id = ?")) {
                withdrawStmt.setDouble(1, amount);
                withdrawStmt.setLong(2, fromAccountId);
                withdrawStmt.executeUpdate();
            }

            // 4. B 계좌에 입금
            try (PreparedStatement depositStmt = connection.prepareStatement(
                    "UPDATE account SET balance = balance + ? WHERE id = ?")) {
                depositStmt.setDouble(1, amount);
                depositStmt.setLong(2, toAccountId);
                depositStmt.executeUpdate();
            }

            // 5. 트랜잭션 커밋
            connection.commit();
        } catch (SQLException e) {
            // 6. 오류 발생 시 롤백
            if (connection != null) {
                try {
                    connection.rollback();
                } catch (SQLException rollbackEx) {
                    rollbackEx.printStackTrace();
                }
            }
            e.printStackTrace();
        } finally {
            // 7. 연결 닫기
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException closeEx) {
                    closeEx.printStackTrace();
                }
            }
        }
    }
}

그렇다면 @Transactional을 사용하기 위해 @Transactional의 주요 속성을 알아보자.

  • propagation
    현재 트랜잭션이 이미 존재하는지 여부에 따라 새로운 트랜잭션을 생성할지, 아니면 참여할지 결정한다.
  • Isolation
    트랜잭션 격리 수준을 설정하며, 동시성 문제를 어떻게 처리할지 결정한다.

파일 업로드/다운로드 기능 구현하기

https://henrykim90.notion.site/018-12620a7966e780ecb7b0d1d2df180663


Spring Security

스프링 기반 웹 애플리케이션, REST API 개발 시 인증(Authentication)과 권한 부여(Authorization) 기능을 제공하는 보안 프레임워크로Spring Security 는 URL 및 메서드 단위로 접근을 제어할 수 있으며, 다양한 인증 및 권한 부여 방식(OAuth2, LDAP, JWT 등)을 지원하고, 요청이 애플리케이션 리소스에 접근하기 전에 여러 보안 필터를 통과하도록 할 수 있다.

주요 보안 원칙

  1. 아무것도 신뢰하지 말고, 들어오는 모든 요청을 검증해야 한다.
  2. 시스템 설계를 시작할 때부터 보안을 염두하여 설계하고, 사용자나 시스템이 필요한 최소한의 권한만을 부여받도록 해야한다.
  3. 모든 사람이 통과하는 문은 하나여야 합니다. Spring Security Filter Chain 이 문(Door) 역할을 수행할 수 있다.
  4. 심층적인 방어구조를 설계해야 합니다. 단일 보안 계층에 의존하지 않고, 공격자가 시스템에 침투하는 것을 어렵게 만들어야 한다(전송 레이어, 네트워크 레이어, 운영체제 레벨 등 심층적으로 보안을 구현).
  5. 보안 아키텍쳐를 가능한 간단하게 설계/유지하는 것이 좋습니다. 시스템이 단순할수록 설계자가 의도한 대로 공격자가 행동할 가능성이 높아지고, 반대로 복잡한 시스템은 공격 표면이 넓어져 취약점이 많아질 수 있다.
  6. 개방형 보안 설계를 구축하고, 개방형 보안 표준(OAuth, JWT, TLS 등)을 사용하세요. 표준화된 프로토콜 및 인증 방식이 아닌, 독자적인 보안 방식은 보안성, 호환성, 개발 속도 저하 등 다양한 문제가 발생할 수 있다.
profile
Ader_Error

0개의 댓글