

# DynamoDB 및 버전 번호를 이용한 낙관적 잠금
<a name="DynamoDBMapper.OptimisticLocking"></a>

*낙관적 잠금*은 업데이트 또는 삭제하려는 클라이언트 측 항목이 Amazon DynamoDB의 항목과 동일하도록 하려는 전략입니다. 이 전략을 사용하면 다른 사용자의 쓰기 작업이 자신의 데이터베이스 쓰기 작업을 덮어쓰지 못하도록 보호하며, 그 반대도 마찬가지입니다.

낙관적 잠금 전략에서는 각 항목마다 버전 번호 역할을 하는 속성이 있습니다. 항목을 테이블에서 가져오면 애플리케이션이 해당 항목의 버전 번호를 기록합니다. 이 항목을 업데이트할 수는 있지만 서버 쪽 버전 번호가 바뀌지 않는 경우에 한합니다. 버전 불일치가 있는 경우 사용자가 항목을 수정하기 전에 다른 사람이 해당 항목을 수정했음을 의미합니다. 더 이상 유효하지 않은 항목 버전이 있으므로 업데이트 시도가 실패합니다. 이러한 경우 항목을 검색한 후 해당 항목 업데이트를 시도하여 재시도합니다. 낙관적 잠금을 통해 다른 사람이 수행한 변경 사항을 잘못 덮어쓰지 않게 됩니다. 또한 다른 사람이 사용자의 변경 사항을 잘못 덮어쓰지 않도록 합니다.

자체적인 낙관적 잠금 전략을 구현할 수 있지만 AWS SDK for Java에서는 `@DynamoDBVersionAttribute` 주석을 제공합니다. 테이블 매핑 클래스에서 버전 번호를 저장할 속성을 하나 지정하여 이 주석을 사용해 표시하면 됩니다. 그러면 객체를 저장할 때 DynamoDB 테이블의 해당 항목이 버전 번호를 저장하는 속성을 갖게 됩니다. 처음 객체를 저장할 때 `DynamoDBMapper`가 버전 번호를 할당하고, 이후 항목을 업데이트할 때마다 버전 번호가 일정하게 자동으로 오릅니다. 업데이트 또는 삭제 요청은 클라이언트 측 객체 버전이 DynamoDB 테이블의 해당 항목 버전 번호와 일치해야만 가능합니다.

 `ConditionalCheckFailedException`다음의 경우 이 발생합니다.
+  `@DynamoDBVersionAttribute`를 이용한 낙관적 잠금을 사용하며, 서버의 버전 값이 클라이언트 측의 값과 다를 경우 
+  `DynamoDBMapper`과 함께 `DynamoDBSaveExpression`를 사용하여 데이터를 저장하는 동안 고유의 조건부 제약 조건을 지정하며 이러한 제약 조건이 실패한 경우 

**참고**  
DynamoDB 전역 테이블은 동시 업데이트 간에 "last writer wins" 조정을 사용합니다. 전역 테이블을 사용할 경우 last writer 정책을 우선 적용합니다. 따라서 잠금 전략이 작동하지 않습니다.
`DynamoDBMapper` 트랜잭션 쓰기 작업은 동일한 객체에 대해 `@DynamoDBVersionAttribute` 주석 및 조건 표현식을 지원하지 않습니다. 트랜잭션 쓰기 내 객체에 `@DynamoDBVersionAttribute` 주석이 추가되고 조건식도 있는 경우에는 SdkClientException이 발생합니다.

예를 들어, 다음 Java 코드에서는 몇 가지 속성이 있는 `CatalogItem` 클래스를 정의합니다. `Version` 속성이 `@DynamoDBVersionAttribute` 주석으로 표시되어 있습니다.

**Example**  

```
@DynamoDBTable(tableName="ProductCatalog")
public class CatalogItem {

    private Integer id;
    private String title;
    private String ISBN;
    private Set<String> bookAuthors;
    private String someProp;
    private Long version;

    @DynamoDBHashKey(attributeName="Id")
    public Integer getId() { return id; }
    public void setId(Integer Id) { this.id = Id; }

    @DynamoDBAttribute(attributeName="Title")
    public String getTitle() { return title; }
    public void setTitle(String title) { this.title = title; }

    @DynamoDBAttribute(attributeName="ISBN")
    public String getISBN() { return ISBN; }
    public void setISBN(String ISBN) { this.ISBN = ISBN;}

    @DynamoDBAttribute(attributeName = "Authors")
    public Set<String> getBookAuthors() { return bookAuthors; }
    public void setBookAuthors(Set<String> bookAuthors) { this.bookAuthors = bookAuthors; }

    @DynamoDBIgnore
    public String getSomeProp() { return someProp;}
    public void setSomeProp(String someProp) {this.someProp = someProp;}

    @DynamoDBVersionAttribute
    public Long getVersion() { return version; }
    public void setVersion(Long version) { this.version = version;}
}
```

`@DynamoDBVersionAttribute` 주석을 `Long`이나 `Integer` 같이 null이 허용된 형식을 제공하는 기본 래퍼 클래스에서 제공하는 null 형식에 적용할 수 있습니다.

낙관적 잠금 전략은 이러한 `DynamoDBMapper` 메서드에 아래와 같은 영향을 끼칩니다.
+ `save` - 새 항목의 경우 `DynamoDBMapper`는 초기 버전 번호로 1을 할당합니다. 항목을 검색하고, 해당 속성 중 하나 이상을 업데이트하고, 변경 사항 저장을 시도하면 클라이언트 측 버전 번호와 서버 측 버전 번호가 일치하는 경우에만 저장 작업이 성공합니다. 그런 다음 `DynamoDBMapper`가 버전 번호를 자동으로 일정하게 올립니다.
+ `delete` - `delete` 메서드가 객체를 파라미터로 사용하고 `DynamoDBMapper`가 항목을 삭제하기 전에 버전을 검사합니다. 요청 시 `DynamoDBMapperConfig.SaveBehavior.CLOBBER`를 지정하면 버전 검사는 비활성화됩니다.

  `DynamoDBMapper`에서 낙관적 잠금을 내부 구현할 경우에는 DynamoDB가 제공하는 조건부 업데이트와 조건부 삭제 지원을 사용합니다.
+ `transactionWrite` —
  + `Put` - 새 항목의 경우 `DynamoDBMapper`는 초기 버전 번호로 1을 할당합니다. 이후 항목을 가져와 속성을 하나 이상 업데이트한 후 변경 사항을 저장하려고 해도 클라이언트 측과 서버 쪽의 버전 번호가 일치해야만 넣기 작업이 가능합니다. 그런 다음 `DynamoDBMapper`가 버전 번호를 자동으로 일정하게 올립니다.
  + `Update` - 새 항목의 경우 `DynamoDBMapper`는 초기 버전 번호로 1을 할당합니다. 이후 항목을 가져와 속성을 하나 이상 업데이트한 후 변경 사항을 저장하려고 해도 클라이언트 측과 서버 쪽의 버전 번호가 일치해야만 업데이트 작업이 가능합니다. 그런 다음 `DynamoDBMapper`가 버전 번호를 자동으로 일정하게 올립니다.
  + `Delete` - `DynamoDBMapper`는 항목을 삭제하기 전에 버전을 확인합니다. 클라이언트 측과 서버 측 버전 번호가 일치할 경우에만 삭제 작업이 가능합니다.
  + `ConditionCheck` - `@DynamoDBVersionAttribute` 주석은 `ConditionCheck` 작업에 지원되지 않습니다. `ConditionCheck` 항목에 `@DynamoDBVersionAttribute` 주석이 추가될 때 SdkClientException이 발생합니다.

## 낙관적 잠금 비활성화
<a name="DynamoDBMapper.OptimisticLocking.Disabling"></a>

낙관적 잠금은 `DynamoDBMapperConfig.SaveBehavior` 열거 값을 `UPDATE`에서 `CLOBBER`로 변경하면 비활성화할 수 있습니다. 그런 다음 버전 검사를 제외한 `DynamoDBMapperConfig` 인스턴스를 생성하여 모든 요청에 사용하면 됩니다. `DynamoDBMapperConfig.SaveBehavior`와 그 밖에 옵션으로 제공되는 `DynamoDBMapper` 파라미터에 대한 자세한 내용은 [DynamoDBMapper의 구성 설정(선택 사항)](DynamoDBMapper.OptionalConfig.md) 단원을 참조하세요.

또한 잠금 기능을 특정 작업에만 설정할 수도 있습니다. 예를 들어 다음은 `DynamoDBMapper`를 사용하여 카탈로그 항목을 저장하는 Java 코드 조각입니다. 이 조각을 보면 옵션인 `DynamoDBMapperConfig.SaveBehavior` 파라미터를 `DynamoDBMapperConfig` 메서드에 추가하여 `save`를 지정하고 있습니다.

**참고**  
transactionWrite 메서드는 DynamoDBMapperConfig.SaveBehavior 구성을 지원하지 않습니다. transactionWrite의 낙관적 잠금 비활성화는 지원되지 않습니다.

**Example**  

```
DynamoDBMapper mapper = new DynamoDBMapper(client);

// Load a catalog item.
CatalogItem item = mapper.load(CatalogItem.class, 101);
item.setTitle("This is a new title for the item");
...
// Save the item.
mapper.save(item,
    new DynamoDBMapperConfig(
        DynamoDBMapperConfig.SaveBehavior.CLOBBER));
```