Spring으로 서비스를 구성하다보면,
Spring과 DB Server는 매우 밀접하기 때문에, Spring에서 DB를 능숙하게 다루는 방법을 고민할 것이다.
Spring에서는 DB를 다루는 표준 API가 존재한다. 이는 JPA라고 한다.
JPA는 뭐고 어떤 기능을 제공하는거지? 라는 궁금증이 생길 것이다.
먼저 정의를 알아보도록하자.
JPA (Java Persistence API)
정의:
JPA는 Java 애플리케이션에서 관계형 데이터베이스를 매핑하고 관리하기 위한 표준 API입니다.
객체와 테이블 간의 매핑을 제공하며, SQL 대신 객체지향적인 방법으로 데이터를 관리할 수 있게 해줍니다.
대표적인 구현체로는 Hibernate, EclipseLink 등이 있습니다.
주요 특징:
- 엔티티(Entity): 데이터베이스 테이블과 매핑되는 클래스.
- JPQL: SQL과 유사한 객체지향 쿼리 언어.
- CRUD 간소화: 복잡한 SQL 대신 메서드 호출로 데이터 조작.
정리하자면 이렇다. Java 애플리케이션(Spring)과 DB사이에 객체와 테이블 간의 매핑을 제공한다.
즉 객체(Entity)와 속성(Table)이 같을 경우 상호 매핑이 가능하다.
보통 우리는 DB의 DML(CRUD)을 통해 필요한 정보를 가져올때, SQL이라는 언어를 사용하여 DB에 명령을 내린다.
JPA는 이떄, SQL 대신 객체지향적인 방법으로 데이터를 관리할 수 있게 해준다.
즉, 기본적인 JPA의 내장기능 사용하여 우리는 SQL을 몰라도 DB에서 기본적인 동작을 수행하게 할 수 있다.
또한, JPQL 이라는 Query language를 따라 따로 Query를 구성할 수도 있고, Entity를 중심으로 구성하게 할 수도 있다.
이때, 우리는 Hibernate(관계형 데이터베이스 용)라는 구현체 클래스의 기능을 사용한다.
그래서 보통 데이터를 변환하는 Service 패키지에서는 JPA와 직접적인 Repository의 기능을 활용하여
DB를 통해 데이터를 가져오거나 DB에 데이터를 Insert하는 작업을 한다.
@Transactional
public List<BoardDTO> findAll() {
List<BoardEntity> boardEntityList = boardRepository.findAll();
List<BoardDTO> boardDTOList = new ArrayList<>();
for (BoardEntity boardEntity: boardEntityList) {
boardDTOList.add(BoardDTO.toBoardDTO(boardEntity));
}
return boardDTOList;
}
이러한 애플리케이션을 통해 DB를 관리할때는, DB의 일관성과 무결성을 지켜야한다.
그렇기 때문에, 우리는 트랜잭션이라는 것을 알아보도록하자.
정의는 다음과 같다.
트랜잭션(Transaction)
정의:
트랜잭션은 데이터베이스 작업의 논리적 단위로, 하나의 작업이 모두 성공하거나 모두 실패하도록 보장합니다.
ACID 특성:
- Atomicity(원자성): 작업이 전부 완료되거나 전부 취소됩니다.
- Consistency(일관성): 작업 전후 데이터의 무결성이 유지됩니다.
- Isolation(격리성): 동시 작업 간 간섭이 방지됩니다.
- Durability(내구성): 작업이 성공하면 영구적으로 반영됩니다.
JPA와의 관계:
JPA는 트랜잭션 내에서 동작하며, 작업 단위를 묶어 데이터 무결성을 보장합니다.
Spring에서는 @Transactional을 활용해 트랜잭션을 쉽게 관리합니다.
정의를 살펴보자면,
트랜잭션은 데이터의 일관성과 무결성을 보장하기 위해 여러 작업을 하나의 작업 단위로 묶는 것이다.
@Transactional
public void processGeoData(Long id) {
GeoEntity geoEntity = geoRepository.findById(id).orElseThrow(...); // 1. SELECT 실행
geoEntity.setLatitude(123.456); // 2. 엔티티 필드 수정 (메모리 상)
geoRepository.save(geoEntity); // 3. UPDATE 실행
}
예를들어 이러한 서비스 메서드가 있다고 치자
이때 실제 실행되는 순서는 다음과 같다.
실행 순서와 트랜잭션 처리 방식
- 트랜잭션 시작
- 메서드 호출 시 Spring이 트랜잭션을 열어줍니다.
- 트랜잭션 내부에서 모든 작업이 처리되며, 메서드가 정상적으로 끝날 경우 한 번에 커밋(commit) 됩니다.
- findById 실행 (SELECT)
- 데이터베이스에서 해당 id를 가진 GeoEntity를 가져옵니다.
- Lazy Loading 설정에 따라 연관된 엔티티 데이터는 아직 로드되지 않을 수 있습니다.
- geoEntity.setLatitude(123.456) 실행
- 이 부분은 JPA 엔티티의 필드 수정입니다.
- 여기서 데이터베이스에 직접 변경 사항이 반영되는 것은 아닙니다. 엔티티가 메모리 상에서만 수정됩니다.
JPA는 Dirty Checking 메커니즘을 통해 트랜잭션 종료 시점에 변경된 엔티티를 감지하고, 필요한 경우 UPDATE 쿼리를 실행합니다.
- save 호출 (UPDATE)
- geoRepository.save()가 호출되면, JPA는 해당 엔티티의 상태를 데이터베이스에 반영하는 SQL 문을 실행합니다.
- UPDATE 쿼리가 즉시 실행되는 경우도 있지만, 트랜잭션이 종료될 때 일괄 처리될 수도 있습니다.
- 트랜잭션 종료 및 커밋
- 메서드 실행이 끝나면, 트랜잭션이 커밋됩니다.
- 이 시점에 Dirty Checking이 발생하여 변경된 엔티티가 자동으로 데이터베이스에 반영됩니다.
(예: geoEntity의 latitude 값이 수정된 것을 감지하고 UPDATE 쿼리 실행)
자세히 설명해보자면 이렇다.
Java application에서 Service에서 DB에 명령을 내리고 Repository에서 명령을 전달해 실제 쿼리를 수행한다.
이때, Service 메소드에 우린 @Transactional이라는 어노테이션을 붙여 이 작업들을 하나의 논리적 단위로 묶는다.
그렇다면, 이러한 Service 메서드가 모두 정상적으로 실행되면 DB는 커밋이라는 작업을 한다.
실제 DB에 변경사항을 저장하는 것이다.
만약 메서드 실행 중 예외가 발생하면 해당 트랜잭션 내의 모든 작업을 롤백한다.
이러한 방식으로 트랜잭션을 관리하여 DB의 영속성을 보장한다.
우린 여기 @Transactional에 집중해보자.
@Transactional은 보통 DB에 데이터를 삽입, 수정, 삭제할 때 주로 사용한다.
생각해보니 조회 기능은 DB에 직접적인 변화를 주지 않으니 트랜잭션이 필요하지 않을 수도 있다.
다시 한번 이 서비스 메소드를 봐 보자.
@Transactional
public void processGeoData(Long id) {
GeoEntity geoEntity = geoRepository.findById(id).orElseThrow(...); // 1. SELECT 실행
geoEntity.setLatitude(123.456); // 2. 엔티티 필드 수정 (메모리 상)
geoRepository.save(geoEntity); // 3. UPDATE 실행
}
@Transactional은 데이터 조작(INSERT, UPDATE, DELETE)이 포함된 작업에서 사용하는 것이 일반적이지만,
SELECT 쿼리에서 사용해야하는 상황이 있다.
Lazy Loading이란, 실제 데이터를 사용할 때까지 관련 데이터를 조회하지 않는 방식이다.
예를 들어, findAll()이란 SELECT용 JPA 메서드를 실행 시 기본 테이블의 정보만 SELECT 해온다,
근데 테이블에서 foreign key 관계를 맺은 정보도 가져오고 싶다면,
SELECT 쿼리를 새로 짤 필요 없이 Entity.getRelatedEntity()를 사용해서 가져올 수 있다.
그러나 getRelatedEntity()의 경우 사전에 SELECT되었던 DB 정보를 토대로 쿼리를 수행하는 것이므로,
트랜잭션으로 묶지 않는다면 이미 findAll()을 호출할때, 이미 트랜잭션이 완료되고 초기화 됐으므로,
getRelateEntity가 연관된 정보를 가져오고 싶어도 정보가 없게된다.
그렇기 때문에 단순히 데이터를 조회하는 SELECT 쿼리만 수행한다면, @Transactional 없이 사용하면된다.
어쨌든 트랜잭션이라는 것은 DB의 무결성과 영속성을 유지하는데 필요하다.
그러므로 Service 메소드를 구성할때 메소드에 @Transactional를 사용하도록하자.
'공부 > Java' 카테고리의 다른 글
# 객체 # 프록시 객체 (0) | 2024.11.28 |
---|---|
#JPA 2# JpaRepository와 JPQL (0) | 2024.11.28 |
#API# Spring의 API 생성 방식과 Model (0) | 2024.11.26 |
#의존성 2# Spring의 의존성 주입 기능 (0) | 2024.11.25 |
#의존성 1#의존성 주입이란? (0) | 2024.11.25 |