1. 학습 주제
- Spring에서 MyBatis로 CRUD API를 만들면서 @Transactional이 왜 필요한지 보기
- JPA를 시작하면서 ORM, Entity, 영속성 컨텍스트, EntityManager 같은 기본 개념 잡기
이번 범위에서 가장 중요한 건 문법보다 “누가 언제 DB 작업을 관리하는가” 였다.
MyBatis에서는 SQL을 직접 쓰지만, Spring이 그 SQL 실행 단위를 트랜잭션으로 묶어줄 수 있고,
JPA에서는 한 단계 더 나아가 객체 자체를 관리하면서 DB와 맞춰 나간다.
즉 이번 공부는 이렇게 이어진다.
- MyBatis: SQL 중심
- Spring @Transactional: SQL 실행 묶음 관리
- JPA: 객체 중심 + 영속성 컨텍스트 관리
2. Spring + MyBatis에서 이번에 한 일
이번 커밋 기준으로 MyBatis 쪽에서는 다음 흐름이 들어갔다.
- 메뉴 목록 조회
- 메뉴 단건 조회
- 카테고리 목록 조회
- 신규 메뉴 등록
- 메뉴 수정
- 메뉴 삭제
구조는 전형적인 Spring 구조였다.
- Controller: HTTP 요청 받기
- Service: 비즈니스 흐름과 트랜잭션 경계
- Mapper: SQL 실행
- XML Mapper: 실제 SQL 작성
즉 클라이언트 요청이 들어오면:
- Controller가 요청을 받음
- Service를 호출함
- Service가 Mapper를 호출함
- Mapper가 XML의 SQL을 실행함
- 결과를 다시 Controller까지 돌려줌
이 구조 자체는 익숙할 수 있는데, 이번 포인트는
등록/수정 같은 “쓰기 작업”에서 @Transactional이 왜 붙는지를 이해하는 것이었다.
3. @Transactional은 왜 쓰는가
트랜잭션은 쉽게 말하면:
여러 DB 작업을 하나의 작업 단위로 묶는 것
이다.
핵심은 이것이다.
- 다 성공하면 commit
- 중간에 하나라도 실패하면 rollback
즉 “All or Nothing” 개념이다.
예를 들어 메뉴 등록이 단순히 한 줄 insert로 끝나면 별로 복잡해 보이지 않을 수 있다.
그런데 실제 서비스에서는 보통 이런 식으로 커질 수 있다.
- 메뉴 등록
- 등록 로그 저장
- 재고 초기값 저장
- 이력 테이블 저장
이때 중간에 하나 실패하면 앞에서 성공한 것도 같이 취소돼야 데이터가 맞다.
이걸 보장하는 장치가 트랜잭션이다.
즉 @Transactional은
“이 메서드 안의 DB 작업들은 한 덩어리로 처리해줘”
라고 Spring에 말하는 어노테이션이다.
4. @Transactional이 실제로 어떻게 동작하는가
여기가 제일 중요하다.
@Transactional은 그냥 마법처럼 붙는 게 아니다.
내부적으로는 Spring Proxy가 중간에서 동작한다.
흐름을 순서대로 보면:
- 클라이언트가 Controller를 호출
- Controller가 Service 메서드를 호출
- 그런데 실제로는 Service 진짜 객체를 바로 호출하는 게 아니라, Spring Proxy를 먼저 거침
- Proxy가 “이 메서드에 @Transactional이 붙어 있나?” 확인
- 붙어 있으면 트랜잭션 시작
- 실제 Service 메서드 실행
- 정상 종료면 commit
- 예외가 발생하면 rollback
- 결과 반환 또는 예외 전달
즉 네가 적은 흐름대로 보면:
menuService 호출
-> Spring Proxy가 중간에서 가로챔
-> 트랜잭션 시작
-> 실제 MenuService 메서드 실행
-> 정상 종료 시 commit
-> 예외 발생 시 rollback
-> 결과 반환
이 구조다.
즉 @Transactional은 단순 표시가 아니라,
Spring AOP/Proxy 기반으로 메서드 실행 전후에 트랜잭션 코드를 끼워 넣는 방식이다.
5. MyBatis에서 @Transactional이 붙으면 내부에서 무슨 일이 생기나
이걸 더 현실적으로 보면 다음과 같다.
Spring은 트랜잭션을 시작할 때:
- DB 커넥션을 가져오고
- 자동 커밋을 끄고
- 현재 스레드에 그 커넥션을 묶어둔다
그다음 Service 안에서 호출하는 Mapper들은
그 같은 트랜잭션 커넥션을 사용하게 된다.
즉:
- menuMapper.registMenu(newMenu)
- menuMapper.modifyMenu(menu)
같은 작업이 따로 놀지 않고,
같은 트랜잭션 안에서 실행된다.
그리고 마지막에:
- 예외 없이 끝나면 Spring이 commit
- 예외가 터지면 Spring이 rollback
을 대신 해준다.
예전 JDBC/MyBatis 수동 방식에서는 우리가 직접:
- sqlSession.commit()
- sqlSession.rollback()
을 했는데,
Spring에서는 @Transactional을 붙이면
그 commit/rollback 책임을 Spring이 가져간다고 보면 된다.
6. 이번 코드에서 왜 등록/수정에 @Transactional이 붙고 삭제는 안 붙어 있나
이번 코드에서는:
- 등록: @Transactional
- 수정: @Transactional
- 삭제: 없음
형태였다.
이걸 보고 헷갈릴 수 있는데, 핵심은 이렇다.
1. 개념적으로
등록/수정/삭제 같은 쓰기 작업은 전부 트랜잭션 대상이다.
2. 실습 코드 관점에서는
삭제가 단일 UPDATE 한 줄이라서 겉보기에는 바로 동작할 수 있다.
삭제 메서드는 실제로는 물리 삭제가 아니라:
- orderable_status = 'N'
으로 바꾸는 soft delete였다.
즉 SQL 한 줄만 실행하는 구조다.
그래서 실습에서는 동작해 보일 수 있지만,
개념적으로는 삭제도 쓰기 작업이므로 트랜잭션 관리 대상이라고 이해하는 게 맞다.
실무에서는 “쓰기 작업이면 기본적으로 트랜잭션 범위에 포함된다”는 감각을 가지는 게 좋다.
7. @Transactional이 없으면 무슨 문제가 생기나
이걸 감각적으로 이해하는 게 중요하다.
예를 들어 Service 메서드 안에서:
- 메뉴 등록
- 카테고리 통계 갱신
- 로그 저장
을 한다고 해보자.
그런데 트랜잭션이 없으면:
- 1번 성공
- 2번 성공
- 3번 실패
했을 때, 앞의 성공이 이미 DB에 반영될 수 있다.
그러면 데이터가 어중간하게 남는다.
즉 트랜잭션이 없으면
논리적으로는 하나의 작업인데, DB에는 반쯤만 반영된 상태가 생길 수 있다.
그래서 트랜잭션은 “에러를 없애는 기술”이 아니라,
데이터를 일관성 있게 유지하는 기술이라고 이해하는 게 맞다.
8. useGeneratedKeys는 왜 중요한가
등록 쿼리에서는 useGeneratedKeys="true"와 keyProperty="code"가 있었다.
이건:
- DB가 자동 생성한 PK 값을
- 다시 자바 객체의 code 필드에 넣어주는 기능
이다.
즉 메뉴를 INSERT하면 DB가 메뉴 번호를 만들어주고,
MyBatis가 그 값을 다시 DTO에 채워준다.
이게 중요한 이유는 등록 후에:
- 새로 생성된 ID를 응답으로 바로 줄 수 있고
- 이후 추가 처리에도 사용할 수 있기 때문이다.
즉 등록 처리에서는
“DB에 넣기만 하는 것”이 아니라
DB가 생성한 값까지 객체로 다시 가져오는 흐름도 중요하다.
9. soft delete는 왜 쓰는가
삭제 쿼리를 보면 실제 DELETE가 아니라:
- orderable_status = 'N'
으로 바꾸고 있었다.
이걸 soft delete라고 본다.
즉 데이터 행을 실제로 지우는 게 아니라,
“더 이상 사용하지 않는 상태”로 표시하는 방식이다.
이 방식이 쓰이는 이유는:
- 복구 가능성
- 삭제 이력 보존
- 참조 무결성 문제 감소
때문이다.
즉 실무에서는 “삭제 = 항상 물리 삭제”가 아니라,
상태값으로 숨기는 삭제 방식도 매우 흔하다.
10. 이제 JPA를 왜 배우는가
여기서 JPA가 시작된다.
MyBatis는 SQL을 직접 쓰는 방식이다.
즉 SQL에 대한 제어력이 좋고 복잡한 쿼리에 강하다.
하지만 단점도 있다.
- SQL 작성이 많다
- 객체와 테이블 사이 변환 코드를 계속 신경 써야 한다
- 연관관계가 복잡해질수록 귀찮다
그래서 나온 것이 JPA다.
11. JPA란 무엇인가
JPA
Java 진영의 ORM 표준(spec)
즉 JPA는 라이브러리 이름이라기보다
“자바에서 객체와 테이블을 어떻게 매핑할지에 대한 규약”에 가깝다.
그래서 JPA 자체가 직접 다 해주는 구현체는 아니다.
12. ORM이란 무엇인가
ORM
Object Relational Mapping
즉:
- Java 객체
- 관계형 DB 테이블
을 연결해주는 개념이다.
쉽게 말하면:
- 테이블 한 행(row)을 객체 하나처럼 다루고
- 객체를 저장하면 INSERT/UPDATE가 일어나고
- 객체를 조회하면 SELECT 결과가 객체가 된다
는 방식이다.
즉 ORM은
SQL 결과를 객체로 바꾸는 수준을 넘어서, 객체와 테이블을 대응시키는 방식 전체라고 보면 된다.
13. Hibernate는 무엇인가
Hibernate
JPA를 구현한 대표적인 구현체
즉 관계를 이렇게 이해하면 된다.
- JPA = 규격, 인터페이스, 표준
- Hibernate = 그 표준을 실제로 구현한 엔진
그래서 보통 Spring Boot에서 JPA를 쓴다고 하면,
실제로 내부 구현은 Hibernate인 경우가 많다.
즉 JPA와 Hibernate를 같은 말처럼 쓰는 경우가 많지만,
정확히는:
- JPA는 표준
- Hibernate는 구현체
이다.
14. MyBatis는 ORM인가
MyBatis는 ORM이 아니다
왜냐하면 MyBatis는:
- SQL을 직접 작성하고
- 결과를 객체에 매핑해주는 도구
에 더 가깝기 때문이다.
즉 “객체를 중심으로 자동 상태 관리”하는 것이 아니라,
여전히 SQL 중심이다.
정리하면:
- MyBatis = SQL Mapper
- JPA = ORM
이 차이를 꼭 기억하면 좋다.
15. JPA를 왜 쓰는가: 직접 SQL의 문제
이번 JPA 입문 테스트에서 직접 SQL 사용의 문제를 보여줬다.
핵심 문제는 다음과 같았다.
1. 중복 코드
- SQL 작성
- JDBC 코드
- ResultSet -> 객체 변환
을 계속 반복해야 한다.
2. SQL 의존성
조회 컬럼이 조금만 바뀌어도 Java 코드도 같이 바뀐다.
3. 패러다임 불일치
객체는 상속, 연관관계, 객체 그래프를 가지는데
테이블은 그런 구조와 직접 같지 않다.
4. 동일성 보장 문제
같은 데이터를 두 번 조회해도 서로 다른 객체가 된다.
이 문제들은 “JDBC가 나쁘다”가 아니라,
객체지향과 관계형 DB를 직접 이어 붙일 때 구조적으로 생기는 문제다.
JPA는 이 불편을 줄이려는 기술이다.
16. Entity란 무엇인가
이 부분이 JPA에서 가장 중요하다.
Entity
DB 테이블과 매핑되는, JPA가 관리하는 객체
즉 그냥 DTO랑 완전히 같지 않다.
DTO는 보통:
- 데이터 전달용 객체
인데,
Entity는:
- DB 테이블과 연결되어 있고
- JPA가 상태를 추적하고
- 영속성 컨텍스트가 관리하는 객체
이다.
즉 Entity는 단순 데이터 박스가 아니라
“DB에 저장될 대상이자 JPA 관리 대상 객체”라고 이해해야 한다.
17. 영속성 컨텍스트란 무엇인가
이 개념이 진짜 핵심이다.
영속성 컨텍스트
엔티티를 관리하는 메모리 공간
이걸 쉽게 말하면:
- JPA가 DB에서 읽어온 엔티티를 그냥 바로 버리지 않고
- 일단 메모리 안의 관리 공간에 올려둔다
는 뜻이다.
여기서 관리한다는 말은:
- 어떤 엔티티가 있는지 기억
- 변경되었는지 추적
- 같은 PK를 다시 조회하면 같은 객체를 돌려줄 수 있음
- 필요할 때 INSERT/UPDATE/DELETE SQL 실행
같은 것을 의미한다.
즉 영속성 컨텍스트는
엔티티를 “살아 있는 상태”로 관리하는 공간이다.
18. 영속성이란 무슨 뜻인가
여기서 “영속성”은 너무 어렵게 생각하지 말고,
- DB와 연결된 상태로 관리된다
정도로 먼저 이해해도 충분하다.
즉 엔티티가 영속성 컨텍스트 안에 들어가 있으면:
- JPA가 그 객체를 알고 있고
- 변경 사항도 추적하고
- 같은 엔티티를 다시 찾을 때 동일성도 보장해줄 수 있다
이게 JPA가 MyBatis와 가장 다른 지점 중 하나다.
19. EntityManagerFactory는 무엇인가
코드에서는 Persistence.createEntityManagerFactory("jpatest")를 사용했다.
EntityManagerFactory
EntityManager를 만들어내는 공장
이건 무거운 객체다.
즉:
- DB 연결 설정
- 매핑 정보
- JPA 전반 설정
같은 것을 읽어서 준비하는 역할을 한다.
그래서 보통 애플리케이션 전체에서
하나만 만들어 재사용한다.
20. EntityManager는 무엇인가
EntityManager
엔티티를 실제로 다루는 관리자
즉 개발자가 JPA에서 직접 많이 만나는 객체는 EntityManager다.
이걸 통해:
- 조회 (find)
- 저장 (persist)
- 삭제 (remove)
- 변경 감지 흐름
같은 작업을 한다.
쉽게 말하면:
- EntityManagerFactory = 공장
- EntityManager = 실무 담당자
라고 보면 된다.
21. 영속성 컨텍스트와 EntityManager의 관계
이 둘을 같이 보면 더 이해가 쉽다.
- EntityManager는 엔티티를 다루는 창구
- 영속성 컨텍스트는 그 엔티티를 실제로 보관/관리하는 공간
즉 EntityManager가 엔티티를 다루면,
내부적으로는 영속성 컨텍스트에서 상태를 관리한다고 보면 된다.
그래서 보통:
- EntityManager를 통해 조회한 엔티티는
- 영속성 컨텍스트에 올라가고
- 이후 관리 대상이 된다
는 흐름으로 이해하면 된다.
22. find()가 의미하는 것
코드에서:
entityManager.find(Menu.class, menuCode);
를 사용했다.
이 메서드는:
- Menu 엔티티 타입에서
- 기본키가 menuCode인 엔티티를 찾아라
는 의미다.
중요한 건 이게 단순 SELECT 한 번이 아니라는 점이다.
JPA는 먼저 영속성 컨텍스트를 보고:
- 이미 있으면 그 객체를 바로 반환
- 없으면 DB 조회 후 영속성 컨텍스트에 올리고 반환
할 수 있다.
즉 find()는 단순 DB 조회 API가 아니라,
영속성 컨텍스트까지 고려한 엔티티 조회 방식이다.
23. 동일성 보장 문제와 JPA
직접 SQL 방식의 테스트에서는 같은 행을 두 번 조회하면:
- 값은 같아도
- 서로 다른 객체였다
즉 menu1 == menu2는 false였다.
그런데 JPA는 같은 영속성 컨텍스트 안에서 같은 PK의 엔티티를 조회하면
같은 객체로 관리할 수 있다.
이게 바로 JPA가 말하는 동일성 보장의 핵심이다.
즉 JPA는 단순히 데이터를 읽어오는 게 아니라,
“같은 엔티티는 같은 객체여야 한다”는 객체지향 감각을 더 잘 지원한다.
24. native SQL이란 무엇인가
native SQL
직접 SQL을 작성해서 실행하는 방식
JPA를 쓰더라도 모든 걸 JPA 메서드만으로 처리하는 건 아니다.
복잡한 쿼리에서는 직접 SQL을 써야 할 때가 있다.
즉 native SQL은
JPA를 사용하더라도 필요하면 DB용 SQL을 직접 작성해 실행하는 방식이다.
25. QueryDSL, MyBatis 같은 추가 도구는 왜 쓰나
JPA는 객체 중심이라 편리하지만,
쿼리가 복잡해질수록 표현이 불편해질 수 있다.
그래서 다음 같은 도구를 병행하기도 한다.
QueryDSL
- 타입 안전한 방식으로 동적 쿼리 작성
- 문자열 JPQL보다 유지보수성이 좋음
MyBatis
- SQL을 직접 강하게 제어하고 싶을 때 유리
- 복잡한 조회 쿼리에 강함
즉 실무에서는:
- 기본 CRUD, 객체 상태 관리 -> JPA
- 복잡한 조회 SQL -> QueryDSL 또는 MyBatis
처럼 역할을 나눠 쓰기도 한다.
26. JPA를 공부할 때 꼭 잡아야 할 큰 그림
1. JPA는 객체를 DB 테이블에 맞춰 관리하려는 기술이다
즉 SQL보다 객체 중심이다.
2. Entity는 JPA가 관리하는 객체다
단순 DTO와 다르다.
3. 영속성 컨텍스트가 진짜 핵심이다
JPA가 엔티티 상태를 기억하고 관리하는 공간이다.
4. EntityManager는 영속성 컨텍스트를 다루는 창구다
조회, 저장, 삭제를 담당한다.
5. Hibernate는 JPA 구현체다
JPA 자체가 엔진은 아니다.
즉 JPA를 이해하려면
“테이블을 객체처럼 다루는 것”보다
**“객체를 JPA가 관리한다”**는 관점으로 보는 게 훨씬 중요하다.
27. MyBatis와 JPA를 비교하며 이해하면 더 쉬운 점
MyBatis
- SQL을 내가 쓴다
- Mapper가 SQL을 실행한다
- 결과를 DTO에 담는다
- 트랜잭션은 Spring이 메서드 단위로 묶어줄 수 있다
JPA
- 엔티티를 중심으로 작업한다
- EntityManager가 엔티티를 관리한다
- 영속성 컨텍스트가 상태를 추적한다
- SQL은 내부적으로 Hibernate가 만들어 실행할 수 있다
즉 비교하면:
- MyBatis = SQL 중심
- JPA = 객체 상태 관리 중심
이다.
28. 학습 흐름 정리
28.1 Spring + MyBatis
- Controller -> Service -> Mapper 구조
- 메뉴 등록, 수정, 삭제 API 구현
- soft delete 적용
- useGeneratedKeys로 PK 다시 받기
- @Transactional로 쓰기 작업 트랜잭션 관리
28.2 JPA 입문
- 직접 SQL 방식의 한계 확인
- ORM 개념 이해
- JPA는 표준, Hibernate는 구현체
- Entity 개념 시작
- EntityManagerFactory, EntityManager 구조 확인
- find()를 통한 엔티티 조회
- 영속성 컨텍스트 개념 시작
29. 핵심 정리
- @Transactional은 메서드 안의 DB 작업을 하나의 트랜잭션으로 묶어주는 Spring 기능이다.
- 내부적으로는 Spring Proxy가 메서드 호출을 가로채고, 실행 전 트랜잭션을 시작한 뒤 종료 시 commit/rollback을 처리한다.
- MyBatis에서 @Transactional을 쓰면 같은 서비스 메서드 안의 Mapper 호출들이 같은 트랜잭션 경계 안에서 동작한다.
- 트랜잭션의 핵심 목적은 에러 방지가 아니라 데이터 일관성 보장이다.
- MyBatis는 ORM이 아니라 SQL Mapper에 가깝다.
- ORM은 객체와 관계형 DB 테이블을 연결해주는 개념이다.
- JPA는 ORM 표준이고, Hibernate는 그 대표 구현체이다.
- Entity는 JPA가 관리하는 객체로, 단순 DTO와는 다르다.
- 영속성 컨텍스트는 엔티티를 메모리에서 관리하는 공간이다.
- EntityManagerFactory는 EntityManager를 만드는 공장이고, EntityManager는 엔티티를 다루는 관리자이다.
- find()는 단순 SELECT가 아니라 영속성 컨텍스트를 고려한 엔티티 조회 방식이다.
- JPA를 이해하려면 “SQL을 덜 쓰는 기술”보다 “엔티티를 관리하는 기술”이라고 보는 게 더 정확하다.
'TIL > [TIL]' 카테고리의 다른 글
| [TIL]JPQL의 Projection, Paging, Group Function 정리 (0) | 2026.06.15 |
|---|---|
| [TIL]JPA 연관관계의 주인, JPQL, 그리고 지연 로딩 (0) | 2026.06.12 |
| [TIL]MyBatis 동적 쿼리와 Spring MyBatis 기본 흐름 (0) | 2026.06.08 |
| [TIL]MyBatis Mapper 방식, #{}와 ${}, 동적 SQL (0) | 2026.06.05 |
| [TIL]MyBatis 영속성 프레임워크, SqlSession 생명주기, XML Mapper CRUD (0) | 2026.06.04 |