[SOPT 세미나] 3차 - Spring Boot와 JPA

시훈·2025년 5월 2일

AWS SAA

목록 보기
43/43

📌 워밍업 : JVM과 스레드 이야기

자바는 싱글 프로세스, 멀티 스레드 환경이다.

이 말은 JVM 한 개가 프로세스 하나로 실행되고, 그 안에 여러 스레드가 돌아간다는 의미다.

실제로 JVM이 실행되면 아래와 같은 스레드들이 기본적으로 동작한다.

  • main thread : 우리가 작성한 main 메서드가 실행되는 스레드다.
  • GC thread : 메모리 누수를 막기 위해 돌아다니며 가비지를 수거하는 녀석.
  • JIT Compiler thread : 반복적으로 실행되는 코드를 네이티브 코드로 바꿔 성능을 최적화한다.

이처럼 자바는 기본적으로 멀티 스레드 환경이며, 우리가 신경 쓰지 않아도 많은 스레드가 백그라운드에서 열일하고 있다.

그런데 웹 서버를 띄우고 HTTP 요청을 받기 시작하면 이야기가 복잡해진다.

여기서 등장하는 것이 바로 스레드 풀(Thread Pool) 이다.


🌐 웹 요청 한 건당 스레드 하나?

Spring Boot를 실행하면 내장 Tomcat 서버가 같이 켜지는데, 이 Tomcat은 자체적으로 스레드 풀을 만든다.

그리고 각 HTTP 요청이 들어올 때마다 워커 스레드(worker thread) 를 하나 꺼내서 해당 요청을 처리한다.

🔁 하나의 요청 → 하나의 워커 스레드

처리가 끝나면 다시 풀(pool)로 반납하는 방식이다.

🧠 코드 한 줄 실행에 그치지 않는다

워커 스레드가 클라이언트의 요청을 받아 Controller → Service → Repository 순으로 코드를 실행한다.

그 과정에서 DB에 접근해야 하면?

  • 스레드가 직접 DB에 접속하지 않는다.
  • HikariCP 같은 커넥션 풀(Connection Pool) 에서 미리 확보해둔 커넥션을 꺼내서 사용한다.
HTTP 요청 → 워커 스레드 → 커넥션 풀 → DB 통신 → 결과 반환

⚠️ 워커 스레드 수 vs 커넥션 수

스레드는 많은데 커넥션이 적으면?

  • 스레드들은 커넥션을 빌릴 때까지 기다린다.
  • 일정 시간이 지나면 TimeoutException 이 발생한다.

커넥션이 너무 많으면?

  • DB가 과부하 상태가 될 수 있다.
  • Too many connections 에러가 날 수 있다.

결국 적절한 커넥션 풀 설정이 중요하다.

(보통 CPU 코어 수 × 2 + 1 공식을 참고한다.)


🧩 IoC, DI, 그리고 Spring Bean

이제 스프링스럽게 개발하기 위한 핵심 키워드,

IoC (제어의 역전)DI (의존성 주입) 이야기를 빼놓을 수 없다.

🧱 IoC 컨테이너란?

Spring Boot가 실행되면 내부적으로 IoC 컨테이너(ApplicationContext)가 만들어진다.

이 컨테이너는 @Component, @Service, @Repository 등 어노테이션이 붙은 클래스를 Bean으로 등록한다.

이렇게 등록된 Bean들은 스프링이 관리한다.

우리가 직접 new 하지 않아도 스프링이 객체를 생성하고 의존성까지 주입해준다.

@RestController
public class PostController {
    private final PostService postService;

    public PostController(PostService postService) {
        this.postService = postService;
    }
}

위 코드는 스프링의 DI(Dependency Injection)를 활용한 대표적인 예다.

우리는 new PostService() 라고 한 적이 없는데도, 스프링이 알아서 주입해준다.

이게 바로 IoC + DI의 마법이다. ✨


🚨 예외 처리는 왜 해야 할까?

예외는 어차피 터진다.

중요한 건 터졌을 때 어떻게 대응하느냐 이다.

❌ 잡지 않으면?

예외가 처리되지 않고 main까지 올라오면 자바 프로세스 자체가 종료된다.

Spring Boot에선 다행히 서버가 죽지는 않지만,

클라이언트는 이유도 모른 채 500 Internal Server Error만 받아보게 된다.

✅ 잡아주자

스프링에서는 전역 예외 처리기를 만들어 깔끔하게 잡아주는 것이 좋다.

@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<ErrorResponse> handleIllegalArg(IllegalArgumentException e) {
        return ResponseEntity.badRequest()
            .body(new ErrorResponse("BAD_REQUEST", e.getMessage()));
    }
}

@RestControllerAdvice 를 사용하면 특정 예외에 대해 공통된 응답을 만들 수 있다.


🧠 예외는 세 가지로 나뉜다

유형설명예시
단순 컴파일 에러문법 에러오타, 존재하지 않는 변수
CheckedException예외 처리가 강제됨IOException, SQLException
UncheckedException런타임 중 발생NullPointerException, IllegalArgumentException

💡 현실적으로 우리가 가장 많이 다루는 건 UncheckedException 이다.


📂 DB는 왜 필요한가?

1차 세미나까지는 데이터를 메모리에 저장했다.

서버를 재시작하면 데이터가 다 날아갔다.

데이터를 영구적으로 저장하고, 여러 사용자 요청에 대응하려면 DB가 필요하다.

🤹‍♂️ 파일? -> 한계가 있다

  • 동시성 문제
  • 검색 속도
  • 수정/삭제의 비효율성

그래서 우리는 데이터베이스라는 시스템을 사용한다.

이 시스템은 데이터를 저장, 조회, 수정, 삭제하기 위한 전문적인 환경을 제공한다.


🧱 RDB의 핵심 : 관계

관계형 데이터베이스(RDB)는 말 그대로 "관계"가 중심이다.

  • 테이블(Table) : 데이터를 저장하는 공간
  • 레코드(Row) : 한 줄
  • 속성(Column) : 열
  • 스키마(Schema) : 설계도

🔐 PK (Primary Key)

모든 테이블에는 유일한 값(PK)이 필요하다.

id 같은 값은 PK로 사용된다. 중복되거나 NULL이면 안 된다.

🔑 FK (Foreign Key)

다른 테이블의 PK를 참조하는 값이다.

예를 들어 post 테이블이 user_id 컬럼을 가지고 있다면,

이는 user 테이블의 id를 참조하는 외래 키(FK)다.


🔄 관계의 형태

관계설명
1:1유저 1명 ↔ 프로필 1개
1:N유저 1명 ↔ 게시글 여러 개
N:M유저 여러 명 ↔ 강의 여러 개 (중간 테이블 필요)

🔍 SQL 쿼리 기초

-- 테이블 생성
CREATE TABLE user (
  id BIGINT PRIMARY KEY,
  name VARCHAR(50)
);

-- 데이터 삽입
INSERT INTO user VALUES (1, '민장규');

-- 데이터 조회
SELECT * FROM user;

-- 데이터 수정
UPDATE user SET name = '장규' WHERE id = 1;

-- 데이터 삭제
DELETE FROM user WHERE id = 1;

🛠 JDBC는 힘들다

자바로 직접 SQL을 날릴 수 있다.

하지만 JDBC를 사용하면 다음과 같은 문제가 있다.

  • SQL 문자열을 직접 작성해야 함
  • 커넥션, 자원 해제 등을 수동으로 해야 함
  • 객체 매핑을 수동으로 해야 함

그래서 나온 것이 바로…


🎩 JPA + Hibernate

JPA는 자바 ORM의 표준 인터페이스이고,

Hibernate는 이를 구현한 실질적인 라이브러리이다.

우리는 엔티티 클래스를 정의하고,

@Entity, @Id, @Column 등을 붙이기만 하면 된다.

@Entity
public class User {
  @Id @GeneratedValue
  private Long id;

  private String name;
}

🪄 Spring Data JPA의 자동화

JPA도 코드가 많다. 그래서 Spring이 제공하는 확장 라이브러리

Spring Data JPA 를 사용하면 Repository만 인터페이스로 정의해도 자동 구현된다.

public interface UserRepository extends JpaRepository<User, Long> {
  Optional<User> findByName(String name);
}

이렇게 정의만 해두면,

Spring이 알아서 findByName에 맞는 SQL을 생성해 실행한다.


마무리하며

"서버 개발자"가 되기 위한 기본기를 다지기 위한 시간이었다.

  • JVM 스레드 구조
  • 스프링의 IoC/DI
  • 예외 처리
  • 관계형 데이터베이스의 본질
  • SQL
  • JPA

단순히 CRUD를 만드는 걸 넘어서

"어떻게 잘 설계하고, 잘 동작하게 만들 것인가" 에 대한 고민을 시작할 수 있었다.

profile
Backend Developer / Cloud Engineer

0개의 댓글