Spring Boot를 공부하다 보니 Servlet, Spring MVC, JPA, Hibernate, JPQL, JDBC, AOP, Redis, Kafka 등 여러 기술이 등장해서 처음에는 각각이 어떤 관계인지 헷갈렸다.
각 기술을 따로 외우기보다는 "어떤 계층에서 어떤 역할을 담당하는가?"를 기준으로 이해하는 것이 중요하다고 느꼈다.
전체적인 구조는 다음과 같이 볼 수 있다.
Client
↓
Servlet / Spring MVC
↓
Controller
↓
Service
↓
┌───────────────┬───────────────┬───────────────┐
│ Redis │ JPA │ Kafka │
│ Cache │ DB 접근 │ 메시지 │
└───────────────┴───────────────┴───────────────┘
↓
Hibernate
↓
JDBC
↓
Database
Java에서 HTTP 요청과 응답을 처리하기 위한 기본적인 웹 기술이다.
클라이언트가 서버에 요청을 보내면 Servlet이 요청을 받아 처리하고 응답을 전달하는 구조이다.
Client
↓ HTTP Request
Servlet
↓
Server Logic
↓ HTTP Response
Client
Spring MVC는 Servlet 위에서 동작한다.
즉,
Servlet = Java 웹 애플리케이션의 기본적인 HTTP 처리 기반
이라고 이해했다.
Spring은 Java 애플리케이션을 개발하기 위한 프레임워크이다.
대표적인 기능으로
등이 있다.
Spring Boot는 Spring을 쉽게 설정하고 실행할 수 있도록 도와주는 기술이다.
복잡한 설정을 자동으로 처리해주기 때문에 Spring 기반 애플리케이션을 빠르게 개발할 수 있다.
Spring
├── DI / IoC
├── AOP
├── Transaction
├── MVC
└── Security
Spring Boot
└── Spring을 쉽게 설정하고 실행할 수 있도록 지원
처음에는 세 가지가 비슷해 보여서 헷갈렸는데 역할이 다르다.
JPA는 Java에서 객체와 데이터베이스를 매핑하기 위한 표준 명세이다.
Java 객체 ↔ DB 테이블
예를 들어
@Entity
public class User {
@Id
private Long id;
private String name;
}
이런 객체를 데이터베이스 테이블과 연결할 수 있다.
Hibernate는 JPA를 실제로 구현한 대표적인 ORM 프레임워크이다.
JPA
↓
표준 규칙
↓
Hibernate
↓
실제 DB 작업
즉,
JPA = 규칙/표준
Hibernate = JPA를 실제로 구현한 기술
이라고 이해했다.
Spring Data JPA는 JPA를 Spring에서 더 편하게 사용할 수 있도록 도와준다.
예를 들어
public interface UserRepository
extends JpaRepository<User, Long> {
}
이렇게 작성하면 기본적인 CRUD 기능을 직접 구현하지 않아도 사용할 수 있다.
userRepository.save(user);
userRepository.findById(id);
userRepository.delete(user);
JDBC는 Java에서 데이터베이스와 통신하기 위한 기본적인 API이다.
Java
↓
JDBC
↓
Database
JPA/Hibernate를 사용하더라도 내부적으로 결국 DB와 통신하는 과정에서는 JDBC가 사용된다.
따라서 대략적인 관계는
Spring Data JPA
↓
JPA
↓
Hibernate
↓
JDBC
↓
Database
JPQL은 JPA에서 사용하는 객체 중심의 쿼리 언어이다.
일반적인 SQL은 테이블을 대상으로 한다.
SELECT * FROM users;
JPQL은 객체를 대상으로 한다.
SELECT u FROM User u
즉,
SQL = 데이터베이스 테이블 중심
JPQL = Java 객체 중심
이라고 이해했다.
@TransactionalTransaction은 여러 DB 작업을 하나의 작업 단위로 묶는 것이다.
예를 들어 송금이 있다고 생각하면
A 계좌 -10,000원
↓
B 계좌 +10,000원
두 작업이 모두 성공해야 한다.
만약 A에서는 돈이 빠졌는데 B에 돈이 들어가지 않는다면 문제가 발생한다.
그래서 하나의 Transaction으로 묶는다.
Transaction 시작
↓
A 계좌 -10,000
↓
B 계좌 +10,000
↓
성공 → COMMIT
실패 → ROLLBACK
Spring에서는 @Transactional을 사용한다.
@Transactional
public void transfer() {
// DB 작업
}
@Transactional은 어떻게 동작하는가?@Transactional이라는 어노테이션 자체가 직접 Transaction을 시작하는 것은 아니다.
Spring이 Proxy와 AOP를 이용해서 해당 메서드 실행을 가로채고 Transaction 처리를 해준다.
개념적으로는 다음과 같다.
Controller
↓
Spring Proxy
↓
Transaction 시작
↓
Service 메서드 실행
↓
┌─────────────┐
│ │
성공 예외
│ │
↓ ↓
COMMIT ROLLBACK
즉,
@Transactional= "이 메서드를 Transaction 단위로 처리해줘"라는 Spring의 설정 정보
라고 이해했다.
AOP는 여러 곳에서 공통적으로 필요한 기능을 따로 분리하는 방법이다.
예를 들어 모든 관리자 요청에 로그를 남기고 싶다고 하자.
Controller마다 직접 작성하면
log.info(...);
가 계속 반복된다.
AOP를 사용하면 공통 기능을 별도로 분리할 수 있다.
Controller
↓
AOP
↓
로그 기록
↓
실제 Controller 실행
대표적으로
등에 활용할 수 있다.
AOP에서는 "어떤 메서드에 AOP를 적용할 것인가?"를 지정해야 한다.
예를 들어
@After("execution(* org.example.UserAdminController.changeUserRole(..))")
라고 하면
UserAdminController
↓
changeUserRole()
↓
AOP 실행
이런 식으로 특정 메서드를 대상으로 할 수 있다.
여기서
(..)
은 파라미터가 무엇이든 상관없다는 의미이다.
Cascade는 부모 Entity의 작업을 자식 Entity에게 전파하는 기능이다.
예를 들어 Todo가 생성될 때 Manager도 같이 생성해야 한다고 하자.
this.managers.add(new Manager(user, this));
Todo 안에 Manager 객체는 만들어졌지만, 이것만으로 DB에 자동 저장되는 것은 아니다.
Cascade를 설정하면
@OneToMany(
mappedBy = "todo",
cascade = CascadeType.PERSIST
)
private List<Manager> managers = new ArrayList<>();
Todo를 저장할 때 Manager도 같이 저장할 수 있다.
Todo 저장
↓
Cascade PERSIST
↓
Manager도 저장
PERSIST → 저장 전파
REMOVE → 삭제 전파
ALL → 모든 Cascade 적용
따라서
Cascade = 부모 Entity에서 발생한 작업을 연관된 자식 Entity에게 전파하는 기능
이라고 정리했다.
Redis는 매우 빠른 Key-Value 기반 데이터 저장소이다.
Spring Boot에서는 특히 캐시(Cache) 용도로 많이 사용한다.
예를 들어 게시글 조회를 할 때
요청
↓
Redis에 게시글이 있나?
↓
YES → Redis에서 바로 반환
NO
↓
DB 조회
↓
Redis에 저장
↓
응답
코드로 보면
PostDto cached = postCacheService.getPostCache(postId);
if (cached != null) {
return cached;
}
여기서
PostDto cached
는 Redis에서 가져온 PostDto 객체를 참조하는 변수이다.
Redis를 사용하는 이유는 DB에 매번 접근하는 것보다 빠르게 데이터를 조회할 수 있기 때문이다.
Kafka는 메시지/이벤트를 전달하고 처리하기 위한 이벤트 스트리밍 플랫폼이다.
예를 들어 주문이 발생했을 때
주문 서비스
↓
"주문 생성됨" 이벤트
↓
Kafka
↓
┌─────────────┬─────────────┐
↓ ↓
결제 서비스 알림 서비스
이런 식으로 여러 서비스에 이벤트를 전달할 수 있다.
Spring에서는
Spring for Apache Kafka
를 통해 Kafka를 사용할 수 있다.
Redis와 비교하면 기본적인 느낌은
Redis
→ 데이터를 빠르게 저장/조회
Kafka
→ 이벤트/메시지를 전달
이라고 이해하면 편하다.
Spring Security는 애플리케이션의 인증(Authentication)과 인가(Authorization) 를 담당한다.
인증
→ "너 누구야?"
인가
→ "너 이 기능 사용할 수 있어?"
예를 들어
로그인
↓
JWT 발급
↓
요청
↓
Spring Security
↓
사용자 인증
↓
권한 확인
↓
Controller
JWT, OAuth2 등의 인증 방식과 함께 사용되는 경우가 많다.
사용자가 보내는 데이터가 올바른지 검사할 때 사용한다.
예를 들어
@NotBlank
private String title;
@Size(max = 100)
private String contents;
그리고 Controller에서
public void create(@Valid @RequestBody Request request) {
}
처럼 사용할 수 있다.
즉,
Validation = 들어오는 데이터가 조건에 맞는지 검사
이다.
Spring에서는 예외 처리를 공통화하기 위해 @ControllerAdvice 등을 사용할 수 있다.
Controller
↓
예외 발생
↓
@ControllerAdvice
↓
공통 예외 처리
↓
일관된 응답 반환
Controller마다 똑같은 예외 처리 코드를 작성하지 않아도 된다.
Spring Boot에서는 주로
@SpringBootTest등을 사용한다.
예를 들어 동시성 테스트에서
ExecutorService executor =
Executors.newFixedThreadPool(2);
를 사용해서 여러 작업을 동시에 실행할 수 있다.
그리고
executor.shutdown();
은 낙관적 락을 구현하는 코드가 아니다.
shutdown()은 단순히
"새로운 작업은 더 이상 받지 말고, 이미 제출된 작업은 마무리해라."
라는 의미이다.
낙관적 락은 보통 Entity의
@Version
등을 이용해서 구현한다.
Docker는 Spring 기술 자체는 아니지만 Spring Boot 프로젝트에서 매우 자주 사용한다.
예를 들어 개발 환경에서
Spring Boot
MySQL
Redis
Kafka
등 여러 프로그램을 실행해야 할 때 Docker를 이용하면 환경을 쉽게 구성할 수 있다.
특히 Docker Compose를 사용하면 여러 컨테이너를 한 번에 실행할 수 있다.
Docker Compose
├── Spring Boot
├── MySQL
├── Redis
└── Kafka
Spring Boot를 공부하다 보면 앞으로 다음 기술들도 접하게 될 수 있다.
| 기술 | 주요 역할 |
|---|---|
| Spring MVC | HTTP 요청/응답 처리 |
| Spring Data JPA | JPA 사용 편의성 제공 |
| Hibernate | JPA 구현체 / ORM |
| JDBC | Java와 DB 연결 |
| Redis | 캐시 / Key-Value 저장 |
| Kafka | 이벤트 / 메시지 스트리밍 |
| Spring Security | 인증 / 인가 |
| Validation | 입력값 검증 |
| JUnit / Mockito | 테스트 |
| Actuator | 애플리케이션 모니터링 |
| Docker | 실행 환경 컨테이너화 |
| Elasticsearch | 검색 / 분석 |
| Spring Batch | 대량 데이터 배치 처리 |
| Spring Cloud | 분산 시스템 / MSA 지원 |
Spring Boot 생태계를 한 번에 보면 복잡해 보이지만 각각의 역할을 나눠보면 이해하기 쉽다.
Client
↓
Servlet / Spring MVC
↓
Controller
↓
Service
↓
┌──────────────┼──────────────┐
↓ ↓ ↓
Redis JPA Kafka
Cache ↓ Event
↓
Hibernate
↓
JDBC
↓
Database
그리고 Spring 자체에서는
Spring
├── DI / IoC
├── AOP
├── Proxy
├── Transaction
└── Security
를 담당한다.
Servlet은 웹 요청의 기반, Spring MVC는 HTTP 요청 처리, JPA/Hibernate는 객체와 DB 연결, JDBC는 실제 DB 통신, Redis는 빠른 캐시, Kafka는 이벤트 전달, AOP는 공통 기능 분리,
@Transactional은 DB 작업을 하나의 Transaction으로 묶는 기능이다.
그리고 지금 단계에서는 모든 기술을 깊게 파기보다 "이 기술이 전체 구조에서 무슨 역할을 하는지"를 먼저 잡는 게 중요하다.