Spring Framework 는 엔터프라이즈 애플리케이션 개발을 위한 포괄적인 프로그래밍과 구성 모델을 제공하는 자바 기반의 프레임워크로, 어떤 종류의 배포 플랫폼에서도 적용이 가능합니다.
스프링은 애플리케이션 수준에서의 인프라 지원으로, 특정 배포 환경에 불필요하게 얽히지 않고, 애플리케이션 수준의 비즈니스 로직에 집중할 수 있도록 도움을 준다.
IOC(Inversion of Control) = 제어의 역전
DI(Dependency Injection) = 의존성 주입
AOP(Aspect Oriented Programming) = 관점 지향 프로그래밍
스프링 컨테이너

스프링 컨테이너는 BeanFactory 와 ApplicationContext 두 종류의 인터페이스로 구현되어 있다.
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)이므로, 예상치 못한 문제를 유발할 수 있고, 디버깅이 어려운 문제가 있으니, 특정 상황에 맞게 주의하여 사용하는 것이 좋다.

스프링에서 객체의 생성과 소멸의 범위를 개발자는 간단하게 어노테이션으로 관리할 수 있다.
@Scope(”singletin”) 또는 생략 --- 스프링 컨테이너 당 하나의 인스턴스
@Scope(”prototype”) --- 요청할 때마다 새로운 인스턴스
@Scope(”request”) --- HTTP 요청 당 하나의 인스턴스
@Scope(”session”) --- HTTP 세션 당 하나의 인스턴스
@Scope(”application”) --- 애플리케이션 범위에서 하나의 인스턴스
@Scope(”websocket”) --- 웹 소켓 연결 당 하나의 인스턴스
@component 어노테이션을 사용하였지만 이를 더욱 명확하게하여@component으로 모든 클래스에 정의내려 쓸 수 있지만 각 클래스들이 무슨 역할을 하는지 의도를 명확하게 파악하기 어려워진다.
(스테레오타입 어노테이션은 @Component 어노테이션을 기본으로 하여, 플리케이션 구조를 명확하게 하기 위해 파생된 어노테이션이다)
해당 클래스가 웹 요청을 처리하는 컨트롤러임을 나타내며, MVC 패턴의 컨트롤러 역할을 수행하고, 이 어노테이션이 붙은 객체는 DispatcherServlet 이 핸들링 하는 객체가 되며, 뷰를 반환하거나, Restful API의 엔드포인트로 동작한다.
해당 클래스가 비즈니스 로직을 처리하는 클래스임을 나타냄
애플리케이션의 비즈니스 로직, 트랜잭션, Presentation 레이어와 Data Access 레이어의 중간다리 역할 등을 수행.
해당 클래스가 데이터 접근 객체(Data Access Obejct)임을 나타냄.
데이터베이스, 파일 시스템, 3rd-party 등 데이터 저장소와 통신하는 역할을 수행.
해당 클래스가 스프링 설정 정보를 담고있는 객체임을 나타냅니다.
@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배로 만들겠다는 결과만 정의
📁 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 프로젝트에 따라 생성되는 프로젝트 빌드 설정 파일입니다.
의존성 관리와 빌드 설정을 정의하는 데 사용.
Java DataBase Connectivity 의 약자로, 자바에서 데이터베이스에 접속할 수 있도록 하는 표준화된 API를 말한다. JDBC는 JDK1.1 버전에 출시된 아주 오래된 기술이며, 요즘은 직접 사용하는 일은 거의 없고, JDBC를 기반으로 동작하는 기술들(Spring JDBC, Hibernate, Spring Data JPA, MyBatis 등)을 사용하고 있다.
MyBatis는 SQL을 직접 작성할 수 있도록 지원하는 SQL 기반의 프레임워크입니다. 기존 JDBC의 불편함을 해결하면서도, SQL의 강력한 제어를 유지할 수 있다.
JPA(Java Persistence API)는 자바의 표준 ORM(객체-관계 매핑) 프레임워크로, SQL을 직접 작성하지 않고 객체 중심적인 방식으로 데이터베이스 작업을 수행한다.

모든 흐름의 제어는 Front Controller 가 담당하고, 일부 로직은 컨트롤러가
처리하도록 위임(Delegation)하는 구조가 특징이며, Spring MVC의 아키텍처이다.
💡Dispatcher Servlet
Spring MVC 에서 핵심적인 역할을 하는 서블릿으로, 모든 웹 요청은 Dispatcher Servlet 을 통해 처리되고 라이언트 요청을 컨트롤러로 전달하고, 결과를 View 로 반환하는 일을 수행한다.
PUT 결과가 없는 경우 204 상태를 반환합니다.--
REST API란?
( HTTP : Hyper Text Transfer Protocal 의 약자로, 웹에서 클라이언트와 서버 간 데이터를 주고받기 위한 통신 규약)
RestAPI 를 설계할 때 유용한 어노테이션
@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 는 기본적으로 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 을 사용하지 않아도 구현이 가능합니다.
만약 A 라는 사용자의 게시물이 수천, 수만 건까지 쌓여있을 경우, API가 모든 데이터를 응답한다면 성능 이슈가 발생할 수 있다. 이를 Pagination 처리를 통해 데이터를 잘라서 보낼 수 있도록 한다.
페이징 처리의 핵심은 데이터를 어느 위치에서부터 가져올지를 구하는 것
offset = (page - 1) * size
예시) page=2, size=10 인 경우, offset 은 (2 - 1) * 10 = 10 이 되며, DB에서는 11번째 데이터부터 가져오게 됩니다.
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 를 사용하면 쿼리가 간결하지만, 레코드를 많이 건너뛸수록(페이지가 커질수록) 성능이 저하될 수 있다.
관점 지향 프로그래밍(Aspect-Oriented Programming)이란, 핵심 기능과는 별도로 분리된 관심사를 모듈화하여 코드 중복을 줄이고, 유지보수성을 높이는 프로그래밍 패러다임 로깅, 보안, 트랜잭션 등 비즈니스 로직에서 부가적인 기능을 분리하는데 중점을 둔다.
대표적인 자바기반 AOP 프레임워크
스프링 프레임워크에서 제공하는 경량 AOP 프레임워크로, 가장 인기가 많다.
프록시(Proxy) 패턴으로 AOP를 구현하며, 스프링 빈(Bean)을 대상으로만 AOP 기능을 제공하므로, 완전한 AOP 솔루션이라고 말할 수 없다.
트랜잭션(Transaction)이란, 데이터베이스나 시스템에서 하나의 작업 단위를 의미하는데,
그 작업이 완전히 성공하거나, 완전히 실패하여 원래 상태로 돌아가야 하는 특성을 가진다
데이터의 일관성을 보장하기 위해 트랜잭션은 반드시 필요하며, 여러 작업을 하나로 묶어서 처리할 때 사용.
@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의 주요 속성을 알아보자.


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

Spring Security Filter Chain 이 문(Door) 역할을 수행할 수 있다.