스프링 데이터를 사용할 때 읽기-수정-쓰기 방지 패턴을 피하는 방법?

Pt. Terk

이 주제에 대한 Craig Ringer의 게시물 에서 :

내가 많이 보는 SQL 코딩 안티 패턴 : 순진한 읽기-수정-쓰기주기. 여기에서는이 일반적인 개발 실수가 무엇인지,이를 식별하는 방법 및 수정 방법에 대한 옵션을 설명합니다.

코드가 사용자의 잔액을 조회하고 음수가되지 않을 경우 100을 빼고 저장한다고 가정 해보십시오.

다음과 같은 세 단계로 작성되는 것이 일반적입니다.

 SELECT balance FROM accounts WHERE user_id = 1;
 -- in the application, subtract 100 from balance if it's above
 -- 100; and, where ? is the new balance: 
 UPDATE accounts SET balance = ? WHERE user_id =1;

그리고 모든 것이 개발자에게 잘 작동하는 것처럼 보일 것입니다. 그러나이 코드는 매우 잘못되었으며 동일한 사용자가 동시에 두 개의 다른 세션에서 업데이트되는 즉시 오작동합니다.

거래가 이것을 방지하지 않습니까?

나는 종종 Stack Overflow의 사람들이“트랜잭션이 이것을 막지 않습니까?”라는 조율을 요구합니다. 안타깝게도 트랜잭션은 쉬운 동시성을 위해 추가 할 수있는 마법의 비밀 소스가 아닙니다. 동시성 문제를 완전히 무시할 수있는 유일한 방법은 트랜잭션을 시작하기 전에 사용할 수있는 모든 테이블을 LOCK TABLE하는 것입니다 (그리고 교착 상태를 방지하려면 항상 동일한 순서로 잠 가야합니다).

읽기-수정-쓰기주기 방지

가장 좋은 해결책은 종종 SQL에서 작업을 수행하고 읽기-수정-쓰기주기를 완전히 피하는 것입니다.

그냥 써: UPDATE accounts SET balance = balance-100 WHERE user_id = 1; (sets balance=200)

SpringData를 사용하여 엔티티를 수정할 때 항상 읽기-수정-쓰기 패턴 내부에 있습니다. 다음은 예제 엔터티입니다.

@Entity
public class Customer {

    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private Long id;
    private String firstName;
    private String lastName;

    protected Customer() {}

    public Customer(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    @Override
    public String toString() {
        return String.format(
                "Customer[id=%d, firstName='%s', lastName='%s']",
                id, firstName, lastName);
    }

    /** GETTERS AND SETTERS */

} 

저장소:

public interface CustomerRepository extends CrudRepository<Customer, Long> {

    Customer findByLastName(String lastName);
}

그리고 애플리케이션 로직 :

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class);
    }

    @Bean
    public CommandLineRunner demo(CustomerRepository repository) {
        return (args) -> {

            // save a couple of customers
            repository.save(new Customer("Jack", "Bauer"));
            repository.save(new Customer("Chloe", "O'Brian"));

            Customer customer = repository.findByLastName("Bauer");
            customer.setFirstName("kek");
            repository.save(customer);
        };
    }

}

그러나 여기서는 읽기-수정-쓰기 방지 패턴이 실행되는 것을 볼 수 있습니다. 우리의 목표가이 안티 패턴을 피하는 것이라면 코드를 작성하는 다른 방법은 무엇일까요? 지금까지 제가 생각 해낸 해결책은 수정 쿼리를 저장소에 추가하고이를 사용하여 수정하는 것입니다. 그래서 CustomerRepository우리는 다음 방법을 추가합니다.

@Query(nativeQuery = true, value = "update customer set first_name = :firstName where id= :id")
@Modifying
void updateFirstName(@Param("id") long id, @Param("firstName") String firstName);

그리고 우리의 애플리케이션 로직은 다음과 같습니다.

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class);
    }

    @Bean
    public CommandLineRunner demo(CustomerRepository repository) {
        return (args) -> {

            // save a couple of customers
            repository.save(new Customer("Jack", "Bauer"));
            repository.save(new Customer("Chloe", "O'Brian"));

            Customer customer = repository.findByLastName("Bauer");
            repository.updateFirstName(customer.getId(), "kek");
        };
    }

}

이것은 읽기-수정-쓰기 안티 패턴을 피하기 위해 완벽하게 작동하지만 엔티티의 속성을 수정하려는 모든 경우에 대해 저장소에 업데이트 메소드를 작성하는 것은 매우 지루할 것입니다. SpringData에서 이것을 수행하는 더 좋은 방법이 없습니까?

Jens shudder

이것은 읽기-수정-쓰기 안티 패턴을 피하기 위해 완벽하게 작동하지만 엔티티의 속성을 수정하려는 모든 경우에 대해 저장소에 업데이트 메소드를 작성하는 것은 매우 지루할 것입니다. SpringData에서 이것을 수행하는 더 좋은 방법이 없습니까?

TL; DR : 아니요, 그렇게하는 방법입니다.

더 긴 버전

JPA는 정확히 이러한 접근 방식을 기반으로 구축되었습니다.

  1. 데이터를 메모리에로드

  2. 원하는 방식으로 조작

  3. 결과 데이터 구조를 데이터베이스에 다시 저장하십시오.

하지만 보호 기능도 내장되어 있습니다. 낙관적 잠금. JPA 및 따라서 SpringData JPA는 버전 열이 있고 낙관적 잠금이 활성화되어 있다고 가정하여 저장된 행이로드 된 이후 변경 될 때 예외를 throw하고 트랜잭션을 롤백합니다.

따라서 일관성의 관점에서 보면 괜찮습니다.

물론 설명한 것과 같은 업데이트 (계정 잔액 업데이트)의 경우 이는 다소 낭비이며 직접 업데이트가 더 효율적입니다. @Modify주석이 목적을 위해 정확하게이다.

반면에 주석에 사용한 예제는 멱등 성이기 때문에 가능한 성능 이점을 제외하고는 전혀 필요하지 않습니다. 그리고 많은 실제 응용 프로그램에서 성능 이점조차 사라집니다.

이는 새 값이 계정 예에서와 같이 원래 값에 의존하는 경우에만 실제로 관련이 있습니다. 대부분의 응용 프로그램에서 이는 어쨌든 추상화 할 수없는 몇 가지 특수한 경우에 불과하므로 SQL 문을 직접 작성하는 것은 피할 수 없습니다.

쿼리 자체가 복잡한 경우 쿼리 작성을 위해 Querydsl 또는 jOOQ를 살펴 보는 것이 좋습니다.

이 기사는 인터넷에서 수집됩니다. 재 인쇄 할 때 출처를 알려주십시오.

침해가 발생한 경우 연락 주시기 바랍니다[email protected] 삭제

에서 수정
0

몇 마디 만하겠습니다

0리뷰
로그인참여 후 검토

관련 기사

cat 명령을 사용할 때 덮어쓰기를 피하는 방법

렌더링 함수에서 HTML을 사용할 때 정렬하기 위해 DataTables에서 직교 데이터를 사용하는 방법은 무엇입니까?

렌더링 함수에서 HTML을 사용할 때 정렬하기 위해 DataTables에서 직교 데이터를 사용하는 방법은 무엇입니까?

Spring을 사용하여 읽기 전용 및 읽기-쓰기로 데이터베이스 라우팅을 수행하는 방법

패턴 랩을 사용할 때 최대 호출 스택 크기 초과 오류를 수정하는 방법은 무엇입니까?

인쇄 기능을 사용할 때 중복 데이터를 수정하는 방법은 무엇입니까?

사용자가 자신의 프로필 데이터를 읽을 때 읽기 작업에 대한 Firebase 보안 규칙을 설정하는 방법

Highcharts의 영역에서 패턴을 지정할 때 기본 색상을 사용하는 방법

"아름다운 수프"를 사용하여 웹 페이지를 크롤링 할 때 특정 패턴을 찾는 방법은 무엇입니까?

변압기 없음 피크 마스크를 만들 때 numpy에서 "TypeError : 데이터 유형이 이해되지 않음"을 수정하는 방법

패턴을 기반으로 RxJS Observable을 사용하여 데이터를 필터링하는 방법

여러 프로세스가 동시에 파일에 쓰기 및 읽기를 시도 할 때 경쟁 조건을 방지하는 방법

MVVM viewmodel이 비동기를 사용할 때 데이터 컨텍스트를 설정하는 방법

데코레이터 패턴을 사용하여 기본 클래스의 멤버를 직접 수정하는 방법은 무엇입니까?

피벗 테이블을 생성 할 때 기록 된 매크로 (VBA)에서 동적 소스 데이터를 사용하는 방법

for 루프를 사용할 때 스팸을 피하는 방법

간단한 사용자 지정 데이터 형식에 대한 읽기 인스턴스를 수행하는 방법

libp2p를 사용하여 golang의 피어에 버퍼링 된 읽기-쓰기 스트림을 처리하는 방법은 무엇입니까?

"new"를 사용하여 생성 할 때 기본 값을 스프링 빈에 주입하는 방법

c 프로그램에서 파이프를 사용하여 데이터 쓰기 또는 읽기를 수정하는 방법이 잘못된 출력을 제공합니까?

생성자를 재정의 할 수 없기 때문에 Fragment를 인스턴스화 할 때 데이터를 전달하는 방법은 무엇입니까?

정적 팩토리 패턴을 사용하는 동안 분기를 피하는 방법은 무엇입니까?

분산 파일 시스템을 사용할 때 읽기 / 쓰기 속도를 향상시키는 방법은 무엇입니까?

Javascript에서 장기 실행 계산을 수행 할 때 브라우저 정지를 피하는 방법

두 명의 사용자 만 firebase 데이터베이스에 액세스하여 읽기 및 쓰기 작업을 수행하도록 허용하는 방법은 무엇입니까? 그렇게하려고했지만 사용자를 1 명만 추가 할 수 있습니다.

사용자가 호버링할 때 크기를 조정할 수 있는 konva 모양을 만드는 방법

python에서 xlwings 또는 openpyxl 패키지를 사용하여 파일에 읽기 및 쓰기를 수행하는 동안 Excel을 숨기는 방법

ggarrange를 사용할 때 범례 컷오프를 피하기 위해 플롯의 여백을 늘리는 방법

이 패턴을 정리하는 방법? 사용자가 UI를 변경할 때와 클래스가 UI를 변경할 때를 나타 내기 위해 private bool 플래그를 사용합니까?

TOP 리스트

  1. 1

    JNDI를 사용하여 Spring Boot에서 다중 데이터 소스 구성

  2. 2

    std :: regex의 일관성없는 동작

  3. 3

    JSoup javax.net.ssl.SSLHandshakeException : <url>과 일치하는 주체 대체 DNS 이름이 없습니다.

  4. 4

    PrematureCloseException : 연결이 너무 일찍 닫혔습니다.

  5. 5

    Xcode10 유효성 검사 : 이미지에 투명성이 없지만 여전히 수락되지 않습니까?

  6. 6

    정점 셰이더에서 카메라에서 개체까지의 XY 거리

  7. 7

    Ionic 2 로더가 적시에 표시되지 않음

  8. 8

    Seaborn에서 축 제목 숨기기

  9. 9

    C #에서 'System.DBNull'형식의 개체를 'System.String'형식으로 캐스팅 할 수 없습니다.

  10. 10

    복사 / 붙여 넣기 비활성화

  11. 11

    ArrayBufferLike의 typescript 정의의 깊은 의미

  12. 12

    Google Play Console에서 '예기치 않은 오류가 발생했습니다. 나중에 다시 시도해주세요. (7100000)'오류를 수정하는 방법은 무엇입니까?

  13. 13

    Kubernetes Horizontal Pod Autoscaler (HPA) 테스트

  14. 14

    jfreecharts에서 x 및 y 축 선을 조정하는 방법

  15. 15

    PRNG 기간보다 순열이 더 많은 목록을 무작위로 섞는 방법은 무엇입니까?

  16. 16

    C # HttpWebRequest 기본 연결이 닫혔습니다. 전송시 예기치 않은 오류가 발생했습니다.

  17. 17

    다음 컨트롤이 추가되었지만 사용할 수 없습니다.

  18. 18

    잘못된 구성 개체입니다. Webpack이 Angular의 API 스키마와 일치하지 않는 구성 개체를 사용하여 초기화되었습니다.

  19. 19

    Android Kotlin은 다른 활동에서 함수를 호출합니다.

  20. 20

    R의 마침표와 숫자 사이에 문자열 삽입

  21. 21

    Assets의 BitmapFactory.decodeStream이 Android 7에서 null을 반환합니다.

뜨겁다태그

보관