개인 프로젝트

데이터를 수정할 때..

오마이냥 2024. 5. 28. 13:40

내가 작성한 Card를 삭제할 때 물리적인 삭제가 아닌 논리적인 삭제가 이루어지도록 

int타입의 del_status 컬럼을 만들어서 (논리적인)삭제는 1, 미삭제는 0으로 설정했다.

 

 

JpaRepository는 기본적인 CRUD 메서드를 제공한다. 그렇다면 JpaRepository가 제공하지 않는 기능들은 어떻게 사용할 수 있을까?

 

 

사용자 정의 쿼리

 

사용자 정의 쿼리란? 말 그대로 JPA가 자동으로 생성하는 쿼리를 사용하는 게 아닌 사용자가 정의한 대로 쿼리가 생성 혹은 Native Query 가 생성되는 것을 말한다.

 

 

쿼리 메서드

 

 

먼저 JpaRepository 인터페이스를 extend한 Repository를 만들고

findByEmailAndPassword(String email, String password)처럼 쿼리 메서드를 작성하면

작성한 기능이 자동으로 수행된다.

 

쿼리 메서드 기능은 Spring Data JPA가 정해놓은 네이밍 컨벤션을 지키면 JPA가 해당 메서드 이름을 분석해서 적절한 JPQL을 구성한다.

 

 

https://docs.spring.io/spring-data/jpa/reference/jpa/query-methods.html

 

JPA Query Methods :: Spring Data JPA

By default, Spring Data JPA uses position-based parameter binding, as described in all the preceding examples. This makes query methods a little error-prone when refactoring regarding the parameter position. To solve this issue, you can use @Param annotati

docs.spring.io

 

그렇다면 복잡한 방식으로 쿼리를 작성해야 하는 상황이 온다면 어떻게 해야할까?

 

 

@Qeury

 

@Query를 이용한다면 개발자가 원하는 쿼리를 직접 작성할 수 있다.

 

@Query는 실행할 메서드 위에 정적 쿼리를 작성하고

여기 들어가는 쿼리는 JPQL이라는 쿼리가 들어가야 한다.

 

 

JQPL

 

JPA는 SQL을 추상화한 JPQL이라는 객체 지향 쿼리 언어는 제공한다. 

 

JQPL은 테이블을 대상으로 하는 쿼리가 아닌

엔티티 객체를 대상으로 하는 쿼리다.

 

JPQL은 SQL을 추상화했기 때문에 특정 데이터베이스 SQL에 의존하지 않는 장점이 있지만

동적으로 쿼리 언어를 작성하는 데 효율적이지 못하다. 예를 들어 특정 조건이 참일 경우엔 A SQL 쿼리를, 거짓일 경우엔 B SQL 쿼리를 실행하는 것이 힘들다.

 

 

다시 @Query로 돌아와서

 

@Query는 JpaRepository를 상속하는 인터페이스에서 사용한다. 

이와 같은 형태로 주로 사용된다.

 

중요한 것은 우리가 테이블을 대상으로 쿼리를 날리는 게 아니라 엔티티를 대상으로 날린다는 것이다.

 

 

 

참고: https://wonit.tistory.com/470

 

[배워보자 Spring Data JPA] 쿼리 메서드와 @Query를 이용한 사용자 정의 쿼리

해당 글은 배워보자 Spring Data JPA 시리즈 입니다. 해당 시리즈의 내용이 이어지는 형태이므로 글의 내용 중에 생략되는 말들이 있을 수 있으니, 자세한 사항은 아래 링크를 참고해주세요! Spring Dat

wonit.tistory.com

 

 

 

더티 체킹(Dirty Checking)

 

더티 체킹은 JPA가 엔티티의 변경 상태를 자동으로 감지하여, 트랜잭션이 커밋될 때 변경된 데이터를 자동으로 데이터베이스에 반영하는 기능을 말한다. 이는 JPA의 영속성 컨텍스트(Persistence Context)가 엔티티의 상태를 추적하고 있기 때문에 가능하다.

 

예시

@Transactional
public void updateOutfit(int outfitId, int delStatus) {
    Outfit outfit = outfitRepository.findById(outfitId).orElseThrow(() -> new EntityNotFoundException("Outfit not found"));
    outfit.setDelStatus(delStatus);
}

 

위의 예시에서 `updateOutfit` 메서드는 특정 Outfit 엔티티르 조회하고, 해당 엔티티의 delStatus 컬럼을 변경한다.

 

더티 체킹 메커니즘을 통해 `setDelStatus` 메서드 호출 후 별도의 `save`호출 없이도 변경사항이 데이터베이스에 반영된다.

JPA에서 엔티티를 저장하는 `insert`와 업데이트하는 `update`는 `save`메서드를 통해 이루어진다. 엔티티의 ID가 null이거나 기본 값이면 새로운 엔티티를 데이터베이스에 저장하고 

엔티티의 ID가 이미 데이터베이스에 존재한다면 기존 엔티티를 업데이트한다.

 

 

@Transactional 어노테이션

 

@Transactional 어노테이션은 메서드 또는 클래스에 적용하여, 해당 범위 내에서 트랜잭션 처리르 자동으로 수행하도록 한다. 

메서드가 호출될 때 트랜잭션을 시작해

메서드가 정상적으로 완료되면 트랜잭션을 커밋하고, 예외가 발생하면 트랜잭션을 롤백한다.

 

 

트랜잭션

 

트랜잭션은 데이터베이스 연산의 논리적인 단위이다. 트랜잭션 내에서 수행된 모든 작업은 하나의 작업으로 간주되며, 모든 작업이 성공하면 커밋되고, 하나라도 실패하면 롤백되어 모든 변경사항이 롤백된다. 

 

 

ACID 원칙

 

1. 원자성(Atomicity) 트랜잭션 내의 모든 작업이 모두 성공하거나 모두 실패한다.

2. 일관성(COnsistency) 트랜잭션이 완료되면 데이터베이스는 일관된 상태를 유지한다.

3. 고립성(Isolation) 동시에 실행되는 트랜잭션은 서로 간섭하지 않는다.

4. 지속성(Durability) 트랜잭션이 커밋되면 그 결과는 영구적으로 반영된다.

 

 

다시 위 예시를 살펴보면..

 

.orElseThrow(() -> new EntityNotFoundException("Outfit not found") 

는 Java의 Optional 클래스와 람다 표현식을 사용한 예외 처리 방식이다. 이 표현식은 Optional 객체가 비어 있을 경우,

지정된 예외를 던지도록 하는 역할을 한다. 

 

여기서는 JPA를 사용하여 데이터베이스에서 엔티티를 조회할 때, 해당 엔티티가 존재하지 않는 경우에 예외를 던지도록 구현하였다.

 

  • Optional<Outfit> optionalOutfit = outfitRepository.findById(outfitId)
    • outfitRepository.findById(outfitId)는 주어진 outfitId를 기반으로 데이터베이스에서 Outfit 엔티티를 조회한다.
    • 조회된 엔티티가 존재하면 Optional 객체에 포함되고, 존재하지 않으면 비어 있는 Optional 객체가 반환된다.
  • Outfit outfit = optionalOutfit.orElseThrow(() -> new EntityNotFoundException("Outfit not found"));
    • orElseThrow 메서드는 Optional 클래스의 메서드 중 하나로, optionalOutfit 객체가 값을 포함하고 있으면 그 값을 반환하고, 비어 있는 경우 지정된 예외를 던진다.