DB 관련 의존성을 추가하고 실행했는데 에러가 나는 건 대부분 “DB 연결 정보가 없는데도 Spring이 서버 기동 시점에 DB 접근을 시도했기 때문”이다.
즉, 의존성을 넣는 순간 Spring Boot가 DataSource를 만들려고 하고, 이때 url/계정정보가 없어서 DataSource 생성이 실패하면서 애플리케이션이 터진다.
실습코드 전체
의존성 추가 후 바로 실행하게되면 DB 에러 발생

- Spring Boot는 DB 관련 스타터(JDBC/JPA 등)가 들어오면, “DB 쓰는 프로젝트”라고 판단한다.
- 그래서 서버 실행 시점에 아래 흐름이 자동으로 진행된다.
DataSource 생성 시도
- DB 연결 시도 (url, username, password 필요)
- 성공하면 이후 “DB 관리 객체 → 쿼리 실행” 흐름으로 넘어감
- 근데 yml 설정이 없으면
DataSource 생성이 실패해서 기동 자체가 안 된다.
공통 작업 (어떤 방식이든 무조건 하는 것)
- 드라이버/의존성 설치 (mysql/mariadb 드라이버 등)
application.yml에 DB 접속 정보 설정
- url
- username
- password
- (필요 시) dialect, ddl-auto 같은 추가 설정
1) JDBC
Spring이 DataSource를 통해 DB 관리 객체를 만들어주고, 개발자가 직접 SQL을 작성해서 실행한다.
- 특징
- raw 쿼리 작성해야 함
- 데이터 조회 결과를 객체로 “조립”하는 걸 수동으로 직접 처리해야 함
- 정리
2) MyBatis
여전히 raw SQL은 직접 쓰는데, SQL을 코드 밖으로 분리해서 관리하는 느낌이다.
- 기본 구조
- Repository에는 구현체가 없고, 보통 인터페이스 형태로 둔다
- 실제 SQL은
resources/mapper 폴더 아래 XML 파일로 모아서 작성한다
- yml에서 mapper 위치를 알려줘야 함
mybatis:
mapper-locations: classpath:/mapper/**/*.xml
- 특징
- raw 쿼리 작성 (O)
- SQL을 명시적으로 통제 가능
- JDBC보다 구조화/매핑이 편해짐
3) JPA(순수 JPA) vs Spring Data JPA
여기서부터는 관점이 SQL 중심 → 객체 중심으로 바뀐다.
- JPA는?
- Java Persistence API
- “자바 ORM 표준 스펙(인터페이스 묶음)”이다
- 구현체는 Hibernate 같은 기술이 맡는다
- JPA가 하는 일(핵심)
- 엔티티 확인 + 테이블 확인 → 비교
- 엔티티(코드)를 기준으로 테이블을 통제(DDL)할 수도 있음
- DML(조회/저장/수정/삭제) 관련 동작을 인터페이스 명세(
EntityManager)를 통해 제공
예시 설정(상황에 맞게 변형)
spring:
jpa:
database: mysql
database-platform: org.hibernate.dialect.MariaDBDialect
generate-ddl: true
- 순수 JPA vs Spring Data JPA 차이
- 순수 JPA: 클래스를 설계하고
EntityManager 중심으로 구현하는 느낌
- Spring Data JPA: Repository 인터페이스를 설계하면 CRUD/쿼리 메서드 등을 프레임워크가 만들어주는 느낌
JPA 작업 시 기본 워크플로우 ⭐⭐⭐
- 의존성 추가
Spring Data JPA, DB 드라이버(MySQL/PostgreSQL 등) 의존성을 프로젝트에 추가한다.
- yml 환경설정
datasource(공통) 설정과 jpa 관련 옵션을 함께 설정한다.
예: DB 접속 정보(URL, username, password), DDL 옵션, SQL 로그, 방언(dialect) 등.
- 엔티티(Entity) 작성 (
@Entity)
엔티티 클래스에 @Entity를 붙여 JPA가 해당 객체를 관리하도록 위임한다.
이때 엔티티와 DB 테이블의 매핑(컬럼명/타입/제약조건 등)이 맞아야 영속성 컨텍스트를 통해 안정적으로 상태를 추적할 수 있고, EntityManager(JPA)가 변경 감지/동기화 같은 기능을 제대로 수행할 수 있다.
- Repository 생성
JpaRepository 등을 상속받는 Repository를 만들고, 필요한 쿼리 메서드(또는 JPQL/QueryDSL)를 정의한다.
- Service 계층에서 트랜잭션 경계 설정 (
@Transactional)
서비스 메서드에 @Transactional을 붙여 트랜잭션 시작/종료를 스프링이 관리하도록 한다.
트랜잭션 범위 안에서 조회/수정/저장된 엔티티는 영속성 컨텍스트에서 관리되고, 보통 커밋 직전 flush 시점에 변경 사항이 DB에 반영된다.
트랜잭션 도중 예외가 발생하면 롤백되며, flush로 DB에 반영되기 전이라면 DB에는 최종 변경이 남지 않는다(이미 반영된 경우라도 DB 트랜잭션 롤백으로 되돌아간다).
- 조회 전용 최적화 (
@Transactional(readOnly = true))
단순 조회 메서드에는 @Transactional(readOnly = true)를 적용해 불필요한 변경 감지 비용을 줄이고, 일부 JPA 구현체에서 조회 성능 최적화를 기대할 수 있다.
단, “무조건 성능이 떨어지니 readOnly를 붙인다”라기보다는 “조회-only 의도가 명확한 메서드에 명시적으로 선언한다”는 관점으로 쓰는 게 좋다.
환경별 ddl-auto 설정 흐름 상상하기
여기 진짜 중요함. “처음엔 편해서 create/update 쓰다가, 운영에서 그대로 쓰면 사고” 나는 포인트라서.
프로젝트 시작 단계(초기 개발)
- 스키마가 자주 바뀜
ddl-auto: create 같은 모드로 “기동 시점에 테이블을 새로 생성”할 수 있음
- 전제: 데이터 유실 감수 (초기라 괜찮은 단계)

개발 환경(개발 서버 ↔ 개발 DB)
- 테스트 데이터가 어느 정도 “지속적으로 쌓여 있어야” 시나리오 검증이 가능함
- 선택지
ddl-auto: update: 스키마를 따라가되 기존 데이터는 유지하려는 선택
- 다만 의도치 않은 스키마 변경 위험이 있어서 항상 정답은 아님
- 추가로 디버깅할 때
show_sql: true 켜면 SQL 로그가 보여서 확인이 빠름

운영 환경(운영 서버 ↔ 운영 DB)
- 데이터 무결성/안정성이 최우선
- 운영에서 자동 스키마 변경은 위험하다
- 그래서 보통
ddl-auto: validate: 엔티티 매핑이 DB 스키마와 일치하는지만 검증
- 스키마 변경은 별도 마이그레이션 절차(Flyway/Liquibase/배포 파이프라인/DBA 승인 등)로 진행
- 운영에서는 보통
show_sql도 끈다 (로그 노이즈/성능 고려)

서비스 계층에서 트랜잭션을 거는 이유
GUI 툴로 쿼리 날릴 때는 보통 오토커밋이라 “한 번 실행 = 한 번 반영”처럼 느껴진다.
근데 애플리케이션은 보통 “한 유스케이스”가 여러 쿼리로 쪼개지기 때문에 서비스 계층에서 트랜잭션을 걸어야 한다.
- commit / rollback 정리
- 트랜잭션 안의 작업이 전부 성공하면 commit
- 실행 중 에러가 발생하면 rollback으로 전체 취소
- 핵심
- 트랜잭션은 “레포지토리 메서드 1개”가 아니라
- “비즈니스 시나리오 1건(서비스 메서드 단위)”으로 묶는 개념이다
예를 들어 회원 탈퇴는 단순히 author row 하나만 삭제하는 게 아니라,
- 게시글 삭제
- 댓글 삭제
- 좋아요/로그/연관 데이터 정리
- 기타 상태 변경
같은 작업이 연쇄적으로 따라온다.
이걸 레포지토리 단위로 따로따로 실행하다가 중간에 하나라도 실패하면 “일부만 삭제된 이상한 상태(부분 성공)”가 된다.
그래서 서비스 메서드 전체를 트랜잭션으로 묶고,
- 메서드가 끝까지 정상 종료되면 commit
- 실행 중 예외가 나면 rollback
이 기준으로 “유스케이스 단위 원자성”을 보장하는 것이다.