

기계 번역으로 제공되는 번역입니다. 제공된 번역과 원본 영어의 내용이 상충하는 경우에는 영어 버전이 우선합니다.

# Java용 Amazon QLDB 드라이버 - Cookbook 참조
<a name="driver-cookbook-java"></a>

**중요**  
지원 종료 알림: 기존 고객은 07/31/2025에 지원이 종료될 때까지 Amazon QLDB를 사용할 수 있습니다. 자세한 내용은 [Amazon QLDB 원장을 Amazon Aurora PostgreSQL로 마이그레이션](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/)을 참조하세요.

이 참조 가이드는 Java용 Amazon QLDB 드라이버의 일반적인 사용 사례를 보여줍니다. 이 Java 코드 예제는 드라이버를 사용하여 기본 CRUD(*생성, 읽기, 업데이트, 삭제*) 작업을 수행하는 방법을 보여줍니다. 또한 Amazon Ion 데이터를 처리하기 위한 코드 예제도 포함되어 있습니다. 또한 이 가이드에서는 트랜잭션에 멱등성을 부여하고 고유성 제약하는 모범 사례를 중점적으로 설명합니다.

**참고**  
해당하는 경우, 일부 사용 사례에서는 Java용 QLDB 드라이버의 지원되는 각 주요 버전마다 다른 코드 예제가 있습니다.

**Contents**
+ [드라이버 가져오기](#cookbook-java.importing)
+ [드라이버 인스턴스화](#cookbook-java.instantiating)
+ [CRUD 작업](#cookbook-java.crud)
  + [테이블 생성](#cookbook-java.crud.creating-tables)
  + [인덱스 생성](#cookbook-java.crud.creating-indexes)
  + [문서 읽기](#cookbook-java.crud.reading)
  + [문서 삽입하기](#cookbook-java.crud.inserting)
    + [하나의 명령문에 여러 문서 삽입](#cookbook-java.crud.inserting.multiple)
  + [문서 업데이트](#cookbook-java.crud.updating)
  + [문서 삭제](#cookbook-java.crud.deleting)
  + [하나의 트랜잭션에서 여러 명령문 실행](#cookbook-java.crud.multi-statement)
  + [재시도 로직](#cookbook-java.crud.retry-logic)
  + [고유성 제약 조건 구현](#cookbook-java.crud.uniqueness-constraints)
+ [Amazon Ion 작업](#cookbook-java.ion)
  + [Ion 패키지 가져오기](#cookbook-java.ion.importing)
  + [Ion 초기화](#cookbook-java.ion.initializing)
  + [Ion 객체 생성](#cookbook-java.ion.creating)
  + [Ion 객체 읽기](#cookbook-java.ion.reading)

## 드라이버 가져오기
<a name="cookbook-java.importing"></a>

다음 코드 예제는 드라이버, QLDB 세션 클라이언트, Amazon Ion 패키지 및 기타 관련 종속 항목을 가져옵니다.

------
#### [ 2.x ]

```
import com.amazon.ion.IonStruct;
import com.amazon.ion.IonSystem;
import com.amazon.ion.IonValue;
import com.amazon.ion.system.IonSystemBuilder;

import software.amazon.awssdk.services.qldbsession.QldbSessionClient;
import software.amazon.qldb.QldbDriver;
import software.amazon.qldb.Result;
```

------
#### [ 1.x ]

```
import com.amazon.ion.IonStruct;
import com.amazon.ion.IonSystem;
import com.amazon.ion.IonValue;
import com.amazon.ion.system.IonSystemBuilder;

import com.amazonaws.services.qldbsession.AmazonQLDBSessionClientBuilder;
import software.amazon.qldb.PooledQldbDriver;
import software.amazon.qldb.Result;
```

------

## 드라이버 인스턴스화
<a name="cookbook-java.instantiating"></a>

다음 코드 예제는 지정된 원장 이름에 연결되는 드라이버 인스턴스를 만들고 사용자 지정 재시도 제한이 있는 지정된 [재시도 로직](#cookbook-java.crud.retry-logic)을 사용합니다.

**참고**  
또한 이 예제는 Amazon Ion 시스템 객체(`IonSystem`)를 인스턴스화합니다. 이 참조에서 일부 데이터 작업을 실행할 때 Ion 데이터를 처리하려면 이 객체가 필요합니다. 자세한 내용은 [Amazon Ion 작업](#cookbook-java.ion) 섹션을 참조하세요.

------
#### [ 2.x ]

```
QldbDriver qldbDriver = QldbDriver.builder()
    .ledger("vehicle-registration")
    .transactionRetryPolicy(RetryPolicy
        .builder()
        .maxRetries(3)
        .build())
    .sessionClientBuilder(QldbSessionClient.builder())
    .build();

IonSystem SYSTEM = IonSystemBuilder.standard().build();
```

------
#### [ 1.x ]

```
PooledQldbDriver qldbDriver = PooledQldbDriver.builder()
    .withLedger("vehicle-registration")
    .withRetryLimit(3)
    .withSessionClientBuilder(AmazonQLDBSessionClientBuilder.standard())
    .build();

IonSystem SYSTEM = IonSystemBuilder.standard().build();
```

------

## CRUD 작업
<a name="cookbook-java.crud"></a>

QLDB는 트랜잭션의 일부로 CRUD(*생성, 읽기, 업데이트, 삭제*) 작업을 실행합니다.

**주의**  
가장 좋은 방법은 쓰기 트랜잭션이 완전한 멱등성을 부여하는 것입니다.

**트랜잭션에 멱등성 부여하기**

재시도 시 예상치 못한 부작용이 발생하지 않도록 쓰기 트랜잭션에 멱등성을 부여하는 것이 좋습니다. 여러 번 실행하여 매번 동일한 결과를 생성할 수 있는 트랜잭션은 *멱등성*을 가집니다.

이름이 `Person`인 테이블에 문서를 삽입하는 트랜잭션을 예로 들어 보겠습니다. 트랜잭션은 먼저 문서가 테이블에 이미 존재하는지 여부를 확인해야 합니다. 이렇게 확인하지 않으면 테이블에 문서가 중복될 수 있습니다.

QLDB가 서버 측에서 트랜잭션을 성공적으로 커밋했지만 응답을 기다리는 동안 클라이언트 제한 시간이 초과되었다고 가정해 보겠습니다. 트랜잭션이 멱등성을 가지지 않는 경우 재시도 시 동일한 문서가 두 번 이상 삽입될 수 있습니다.

**인덱스를 사용하여 전체 테이블 스캔 방지**

인덱싱된 필드 또는 문서 ID(예: `WHERE indexedField = 123` 또는 `WHERE indexedField IN (456, 789)`)에서 동등 *연산자*를 사용하여 `WHERE` 조건자 절이 포함된 문을 실행하는 것이 좋습니다. 이 인덱싱된 조회가 없으면 QLDB는 테이블 스캔을 수행해야 하며, 이로 인해 트랜잭션 제한 시간이 초과되거나 *OCC(낙관적 동시성 제어)* 충돌이 발생할 수 있습니다.

OCC에 대한 자세한 내용은 [Amazon QLDB 동시성 모델](concurrency.md) 단원을 참조하세요.

**암시적으로 생성된 트랜잭션**

[QldbDriver.execute](https://javadoc.io/doc/software.amazon.qldb/amazon-qldb-driver-java/latest/software/amazon/qldb/QldbDriver.html) 메서드는 [Executor](https://javadoc.io/doc/software.amazon.qldb/amazon-qldb-driver-java/latest/software/amazon/qldb/Executor.html) 인스턴스를 수신하는 Lambda 함수를 받아들이며, 이 함수를 사용하여 명령문을 실행할 수 있습니다. `Executor` 인스턴스는 암시적으로 생성된 트랜잭션을 래핑합니다.

`Executor.execute` 메서드를 사용하여 Lambda 함수 내에서 명령문을 실행할 수 있습니다. 드라이버는 Lambda 함수가 반환될 때 트랜잭션을 암시적으로 커밋합니다.

다음 섹션에서는 기본 CRUD 작업을 실행하고, 사용자 지정 재시도 로직을 지정하고, 고유성 제약 조건을 구현하는 방법을 보여줍니다.

**참고**  
해당하는 경우 이 섹션에서는 내장된 Ion 라이브러리와 Jackson Ion 매퍼 라이브러리를 모두 사용하여 Amazon Ion 데이터를 처리하는 코드 예제를 제공합니다. 자세한 내용은 [Amazon Ion 작업](#cookbook-java.ion) 섹션을 참조하세요.

**Contents**
+ [테이블 생성](#cookbook-java.crud.creating-tables)
+ [인덱스 생성](#cookbook-java.crud.creating-indexes)
+ [문서 읽기](#cookbook-java.crud.reading)
+ [문서 삽입하기](#cookbook-java.crud.inserting)
  + [하나의 명령문에 여러 문서 삽입](#cookbook-java.crud.inserting.multiple)
+ [문서 업데이트](#cookbook-java.crud.updating)
+ [문서 삭제](#cookbook-java.crud.deleting)
+ [하나의 트랜잭션에서 여러 명령문 실행](#cookbook-java.crud.multi-statement)
+ [재시도 로직](#cookbook-java.crud.retry-logic)
+ [고유성 제약 조건 구현](#cookbook-java.crud.uniqueness-constraints)

### 테이블 생성
<a name="cookbook-java.crud.creating-tables"></a>

```
qldbDriver.execute(txn -> {
    txn.execute("CREATE TABLE Person");
});
```

### 인덱스 생성
<a name="cookbook-java.crud.creating-indexes"></a>

```
qldbDriver.execute(txn -> {
    txn.execute("CREATE INDEX ON Person(GovId)");
});
```

### 문서 읽기
<a name="cookbook-java.crud.reading"></a>

```
// Assumes that Person table has documents as follows:
// { GovId: "TOYENC486FH", FirstName: "Brent" }

qldbDriver.execute(txn -> {
    Result result = txn.execute("SELECT * FROM Person WHERE GovId = 'TOYENC486FH'");
    IonStruct person = (IonStruct) result.iterator().next();
    System.out.println(person.get("GovId")); // prints TOYENC486FH
    System.out.println(person.get("FirstName")); // prints Brent
});
```

**쿼리 파라미터 사용**

다음 코드 예제는 Ion 유형 쿼리 파라미터를 사용합니다.

```
qldbDriver.execute(txn -> {
    Result result = txn.execute("SELECT * FROM Person WHERE GovId = ?",
            SYSTEM.newString("TOYENC486FH"));
    IonStruct person = (IonStruct) result.iterator().next();
    System.out.println(person.get("GovId")); // prints TOYENC486FH
    System.out.println(person.get("FirstName")); // prints Brent
});
```

다음 코드 예제는 여러 쿼리 파라미터를 사용합니다.

```
qldbDriver.execute(txn -> {
    Result result = txn.execute("SELECT * FROM Person WHERE GovId = ? AND FirstName = ?",
            SYSTEM.newString("TOYENC486FH"),
            SYSTEM.newString("Brent"));
    IonStruct person = (IonStruct) result.iterator().next();
    System.out.println(person.get("GovId")); // prints TOYENC486FH
    System.out.println(person.get("FirstName")); // prints Brent
});
```

다음 코드 예제는 쿼리 파라미터 목록을 사용합니다.

```
qldbDriver.execute(txn -> {
    final List<IonValue> parameters = new ArrayList<>();
    parameters.add(SYSTEM.newString("TOYENC486FH"));
    parameters.add(SYSTEM.newString("ROEE1"));
    parameters.add(SYSTEM.newString("YH844"));
    Result result = txn.execute("SELECT * FROM Person WHERE GovId IN (?,?,?)", parameters);
    IonStruct person = (IonStruct) result.iterator().next();
    System.out.println(person.get("GovId")); // prints TOYENC486FH
    System.out.println(person.get("FirstName")); // prints Brent
});
```

#### Jackson 매퍼 사용
<a name="cookbook-java.crud.reading.jackson"></a>

```
// Assumes that Person table has documents as follows:
// {GovId: "TOYENC486FH", FirstName: "Brent" }

qldbDriver.execute(txn -> {
    try {
        Result result = txn.execute("SELECT * FROM Person WHERE GovId = 'TOYENC486FH'");
        Person person = MAPPER.readValue(result.iterator().next(), Person.class);
        System.out.println(person.getFirstName()); // prints Brent
        System.out.println(person.getGovId()); // prints TOYENC486FH
    } catch (IOException e) {
        e.printStackTrace();
    }
});
```

**쿼리 파라미터 사용**

다음 코드 예제는 Ion 유형 쿼리 파라미터를 사용합니다.

```
qldbDriver.execute(txn -> {
    try {
        Result result = txn.execute("SELECT * FROM Person WHERE GovId = ?",
                MAPPER.writeValueAsIonValue("TOYENC486FH"));
        Person person = MAPPER.readValue(result.iterator().next(), Person.class);
        System.out.println(person.getFirstName()); // prints Brent
        System.out.println(person.getGovId()); // prints TOYENC486FH
    } catch (IOException e) {
        e.printStackTrace();
    }
});
```

다음 코드 예제는 여러 쿼리 파라미터를 사용합니다.

```
qldbDriver.execute(txn -> {
    try {
        Result result = txn.execute("SELECT * FROM Person WHERE GovId = ? AND FirstName = ?",
                MAPPER.writeValueAsIonValue("TOYENC486FH"),
                MAPPER.writeValueAsIonValue("Brent"));
        Person person = MAPPER.readValue(result.iterator().next(), Person.class);
        System.out.println(person.getFirstName()); // prints Brent
        System.out.println(person.getGovId()); // prints TOYENC486FH
    } catch (IOException e) {
        e.printStackTrace();
    }
});
```

다음 코드 예제는 쿼리 파라미터 목록을 사용합니다.

```
qldbDriver.execute(txn -> {
    try {
        final List<IonValue> parameters = new ArrayList<>();
        parameters.add(MAPPER.writeValueAsIonValue("TOYENC486FH"));
        parameters.add(MAPPER.writeValueAsIonValue("ROEE1"));
        parameters.add(MAPPER.writeValueAsIonValue("YH844"));
        Result result = txn.execute("SELECT * FROM Person WHERE GovId IN (?,?,?)", parameters);
        Person person = MAPPER.readValue(result.iterator().next(), Person.class);
        System.out.println(person.getFirstName()); // prints Brent
        System.out.println(person.getGovId()); // prints TOYENC486FH
    } catch (IOException e) {
        e.printStackTrace();
    }
});
```

**참고**  
인덱싱된 조회 없이 쿼리를 실행하면 전체 테이블 스캔이 호출됩니다. 이 예제에서는 성능을 최적화하기 위해 `GovId` 필드에 [인덱스](ql-reference.create-index.md)를 사용하는 것이 좋습니다. `GovId`에 인덱스를 사용하지 않으면 쿼리에 지연 시간이 길어지고 OCC 충돌 예외 또는 트랜잭션 시간 초과가 발생할 수도 있습니다.

### 문서 삽입하기
<a name="cookbook-java.crud.inserting"></a>

다음 코드 예제는 Ion 데이터 유형을 삽입합니다.

```
qldbDriver.execute(txn -> {
    // Check if a document with GovId:TOYENC486FH exists
    // This is critical to make this transaction idempotent
    Result result = txn.execute("SELECT * FROM Person WHERE GovId = ?",
            SYSTEM.newString("TOYENC486FH"));
    // Check if there is a result
    if (!result.iterator().hasNext()) {
        IonStruct person = SYSTEM.newEmptyStruct();
        person.put("GovId").newString("TOYENC486FH");
        person.put("FirstName").newString("Brent");
        // Insert the document
        txn.execute("INSERT INTO Person ?", person);
    }
});
```

#### Jackson 매퍼 사용
<a name="cookbook-java.crud.inserting.jackson"></a>

다음 코드 예제는 Ion 데이터 유형을 삽입합니다.

```
qldbDriver.execute(txn -> {
    try {
        // Check if a document with GovId:TOYENC486FH exists
        // This is critical to make this transaction idempotent
        Result result = txn.execute("SELECT * FROM Person WHERE GovId = ?",
                MAPPER.writeValueAsIonValue("TOYENC486FH"));
        // Check if there is a result
        if (!result.iterator().hasNext()) {
            // Insert the document
            txn.execute("INSERT INTO Person ?",
                    MAPPER.writeValueAsIonValue(new Person("Brent", "TOYENC486FH")));
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
});
```

이 트랜잭션은 문서를 `Person` 테이블에 삽입합니다. 삽입하기 전에 먼저 문서가 테이블에 이미 있는지 확인합니다. **이 검사를 통해 트랜잭션은 본질적으로 멱등성을 가지게 됩니다.** 이 트랜잭션을 여러 번 실행하더라도 의도하지 않은 부작용이 발생하지는 않습니다.

**참고**  
이 예제에서는 성능을 최적화하기 위해 `GovId` 필드에 인덱스를 사용하는 것이 좋습니다. `GovId`에 인덱스를 설정하지 않으면 명령문의 지연 시간이 길어지고 OCC 충돌 예외 또는 트랜잭션 시간 초과가 발생할 수도 있습니다.

#### 하나의 명령문에 여러 문서 삽입
<a name="cookbook-java.crud.inserting.multiple"></a>

단일 [INSERT](ql-reference.insert.md) 문을 사용하여 여러 문서를 삽입하려면 다음과 같이 [IonList](driver-working-with-ion.md#driver-ion-list) 타입의 파라미터(명시적으로 `IonValue`로 캐스팅됨)를 해당 문에 전달할 수 있습니다.

```
// people is an IonList explicitly cast as an IonValue
txn.execute("INSERT INTO People ?", (IonValue) people);
```

`IonList`를 전달할 때는 변수 자리 표시자(`?`)를 이중 꺾쇠 괄호( `<<...>>` )로 묶지 마세요. 수동 PartiQL 문에서 이중 꺾쇠 괄호는 *백*으로 알려진 정렬되지 않은 모음을 의미합니다.

##### 명시적 캐스팅이 필요한 이유는 무엇인가요?
<a name="cookbook-java.crud.inserting.multiple.cast"></a>

[TransactionExecutor.execute](https://javadoc.io/doc/software.amazon.qldb/amazon-qldb-driver-java/latest/software/amazon/qldb/TransactionExecutor.html) 메서드가 오버로드되었습니다. 이는 다양한 수의 `IonValue` 인수(*varargs*) 또는 단일 `List<IonValue>` 인수를 받아들입니다. [ion-java](https://www.javadoc.io/doc/com.amazon.ion/ion-java/latest/index.html)에서는 `IonList`는 `List<IonValue>`로 구현됩니다.

Java는 오버로드된 메서드를 호출할 때 기본적으로 가장 구체적인 메서드를 구현합니다. 이 경우 `IonList` 파라미터를 전달하면 `List<IonValue>`를 받는 메서드가 기본값으로 사용됩니다. 호출 시 이 메서드 구현은 목록의 `IonValue` 요소를 고유한 값으로 전달합니다. 따라서 varargs 메서드를 대신 호출하려면 `IonList` 파라미터를 `IonValue`으로 명시적으로 캐스팅해야 합니다.

### 문서 업데이트
<a name="cookbook-java.crud.updating"></a>

```
qldbDriver.execute(txn -> {
    final List<IonValue> parameters = new ArrayList<>();
    parameters.add(SYSTEM.newString("John"));
    parameters.add(SYSTEM.newString("TOYENC486FH"));
    txn.execute("UPDATE Person SET FirstName = ? WHERE GovId = ?", parameters);
});
```

#### Jackson 매퍼 사용
<a name="cookbook-java.crud.updating.jackson"></a>

```
qldbDriver.execute(txn -> {
    try {
        final List<IonValue> parameters = new ArrayList<>();
        parameters.add(MAPPER.writeValueAsIonValue("John"));
        parameters.add(MAPPER.writeValueAsIonValue("TOYENC486FH"));
        txn.execute("UPDATE Person SET FirstName = ? WHERE GovId = ?", parameters);
    } catch (IOException e) {
        e.printStackTrace();
    }
});
```

**참고**  
이 예제에서는 성능을 최적화하기 위해 `GovId` 필드에 인덱스를 사용하는 것이 좋습니다. `GovId`에 인덱스를 설정하지 않으면 명령문의 지연 시간이 길어지고 OCC 충돌 예외 또는 트랜잭션 시간 초과가 발생할 수도 있습니다.

### 문서 삭제
<a name="cookbook-java.crud.deleting"></a>

```
qldbDriver.execute(txn -> {
    txn.execute("DELETE FROM Person WHERE GovId = ?",
            SYSTEM.newString("TOYENC486FH"));
});
```

#### Jackson 매퍼 사용
<a name="cookbook-java.crud.deleting.jackson"></a>

```
qldbDriver.execute(txn -> {
    try {
        txn.execute("DELETE FROM Person WHERE GovId = ?",
                MAPPER.writeValueAsIonValue("TOYENC486FH"));
    } catch (IOException e) {
        e.printStackTrace();
    }
});
```

**참고**  
이 예제에서는 성능을 최적화하기 위해 `GovId` 필드에 인덱스를 사용하는 것이 좋습니다. `GovId`에 인덱스를 설정하지 않으면 명령문의 지연 시간이 길어지고 OCC 충돌 예외 또는 트랜잭션 시간 초과가 발생할 수도 있습니다.

### 하나의 트랜잭션에서 여러 명령문 실행
<a name="cookbook-java.crud.multi-statement"></a>

```
// This code snippet is intentionally trivial. In reality you wouldn't do this because you'd
// set your UPDATE to filter on vin and insured, and check if you updated something or not.
public static boolean InsureCar(QldbDriver qldbDriver, final String vin) {
    final IonSystem ionSystem = IonSystemBuilder.standard().build();
    final IonString ionVin = ionSystem.newString(vin);

    return qldbDriver.execute(txn -> {
        Result result = txn.execute(
                "SELECT insured FROM Vehicles WHERE vin = ? AND insured = FALSE",
                ionVin);
        if (!result.isEmpty()) {
            txn.execute("UPDATE Vehicles SET insured = TRUE WHERE vin = ?", ionVin);
            return true;
        }
        return false;
    });
}
```

### 재시도 로직
<a name="cookbook-java.crud.retry-logic"></a>

드라이버의 `execute` 메서드에는 재시도 가능한 예외(예: 시간 초과 또는 OCC 충돌)가 발생할 경우 트랜잭션을 재시도하는 재시도 메커니즘이 내장되어 있습니다.

------
#### [ 2.x ]

최대 재시도 횟수와 백오프 전략을 구성할 수 있습니다.

기본 재시도 제한은 `4`이고 기본 백오프 전략은 [DefaultQldbTransactionBackoffStrategy](https://javadoc.io/doc/software.amazon.qldb/amazon-qldb-driver-java/latest/software/amazon/qldb/DefaultQldbTransactionBackoffStrategy.html)입니다. [RetryPolicy](https://javadoc.io/doc/software.amazon.qldb/amazon-qldb-driver-java/latest/software/amazon/qldb/RetryPolicy.html) 인스턴스를 사용하여 드라이버 인스턴스별 및 트랜잭션별 재시도 구성을 설정할 수 있습니다.

다음 코드 예제는 드라이버 인스턴스에 대한 사용자 지정 재시도 제한 및 사용자 지정 백오프 전략을 사용하여 재시도 로직을 지정합니다.

```
public void retry() {
    QldbDriver qldbDriver = QldbDriver.builder()
            .ledger("vehicle-registration")
            .transactionRetryPolicy(RetryPolicy.builder()
                    .maxRetries(2)
                    .backoffStrategy(new CustomBackOffStrategy()).build())
            .sessionClientBuilder(QldbSessionClient.builder())
            .build();
}

private class CustomBackOffStrategy implements BackoffStrategy {

    @Override
    public Duration calculateDelay(RetryPolicyContext retryPolicyContext) {
        return Duration.ofMillis(1000 * retryPolicyContext.retriesAttempted());
    }
}
```

다음 코드 예제는 특정 함수에 대한 사용자 지정 재시도 제한 및 사용자 지정 백오프 전략을 사용하여 재시도 로직을 지정합니다. `execute`에 대한 이 구성은 드라이버 인스턴스에 설정된 재시도 로직을 재정의합니다.

```
public void retry() {
    Result result = qldbDriver.execute(txn -> { txn.execute("SELECT * FROM Person WHERE GovId = ?",
            SYSTEM.newString("TOYENC486FH")); },
            RetryPolicy.builder()
                    .maxRetries(2)
                    .backoffStrategy(new CustomBackOffStrategy())
                    .build());
}

private class CustomBackOffStrategy implements BackoffStrategy {

    // Configuring a custom backoff which increases delay by 1s for each attempt.
    @Override
    public Duration calculateDelay(RetryPolicyContext retryPolicyContext) {
        return Duration.ofMillis(1000 * retryPolicyContext.retriesAttempted());
    }
}
```

------
#### [ 1.x ]

최대 재시도 횟수는 구성 가능합니다. `PooledQldbDriver`를 초기화할 때 `retryLimit` 속성을 설정하여 재시도 제한을 구성할 수 있습니다.

기본 재시도 한도는 `4`입니다.

------

### 고유성 제약 조건 구현
<a name="cookbook-java.crud.uniqueness-constraints"></a>

QLDB는 고유 인덱스를 지원하지 않지만 애플리케이션에서 이 동작을 구현할 수 있습니다.

`Person` 테이블의 `GovId` 필드에 고유성 제약 조건을 구현하려고 한다고 가정해 보겠습니다. 이렇게 하면 다음 작업을 수행하는 트랜잭션을 작성합니다.

1. 테이블에 지정된 `GovId`가 있는 기존 문서가 없는지 확인합니다.

1. 어설션이 통과하면 문서를 삽입합니다.

경쟁 트랜잭션이 어설션을 동시에 통과하면 트랜잭션 중 하나만 성공적으로 커밋됩니다. 다른 트랜잭션은 OCC 충돌 예외가 발생하여 실패합니다.

다음 코드 예제는 이 고유성 제약 조건 구현 방법을 보여줍니다.

```
qldbDriver.execute(txn -> {
    Result result = txn.execute("SELECT * FROM Person WHERE GovId = ?",
            SYSTEM.newString("TOYENC486FH"));
    // Check if there is a result
    if (!result.iterator().hasNext()) {
        IonStruct person = SYSTEM.newEmptyStruct();
        person.put("GovId").newString("TOYENC486FH");
        person.put("FirstName").newString("Brent");
        // Insert the document
        txn.execute("INSERT INTO Person ?", person);
    }
});
```

**참고**  
이 예제에서는 성능을 최적화하기 위해 `GovId` 필드에 인덱스를 사용하는 것이 좋습니다. `GovId`에 인덱스를 설정하지 않으면 명령문의 지연 시간이 길어지고 OCC 충돌 예외 또는 트랜잭션 시간 초과가 발생할 수도 있습니다.

## Amazon Ion 작업
<a name="cookbook-java.ion"></a>

QLDB에서 Amazon Ion 데이터를 처리하는 방법은 여러 가지가 있습니다. [Ion 라이브러리](https://github.com/amzn/ion-java)의 기본 제공 메서드를 사용하여 필요에 따라 유연하게 문서를 만들고 수정할 수 있습니다. 또는 FasterXML의 [Ion용 Jackson 데이터 형식 모듈](https://github.com/FasterXML/jackson-dataformats-binary/tree/master/ion)을 사용하여 Ion 문서를 *POJO(Plain Old Java Object)* 모델에 매핑할 수 있습니다.

다음 섹션에서는 두 기술을 모두 사용하여 Ion 데이터를 처리하는 코드 예제를 제공합니다.

**Contents**
+ [Ion 패키지 가져오기](#cookbook-java.ion.importing)
+ [Ion 초기화](#cookbook-java.ion.initializing)
+ [Ion 객체 생성](#cookbook-java.ion.creating)
+ [Ion 객체 읽기](#cookbook-java.ion.reading)

### Ion 패키지 가져오기
<a name="cookbook-java.ion.importing"></a>

아티팩트 [ion-java](https://search.maven.org/artifact/com.amazon.ion/ion-java/1.6.1/bundle)를 Java 프로젝트에 종속 항목으로 추가합니다.

------
#### [ Gradle ]

```
dependencies {
    compile group: 'com.amazon.ion', name: 'ion-java', version: '1.6.1'
}
```

------
#### [ Maven ]

```
<dependencies>
  <dependency>
    <groupId>com.amazon.ion</groupId>
    <artifactId>ion-java</artifactId>
    <version>1.6.1</version>
  </dependency>
</dependencies>
```

------

다음 Ion 패키지를 가져옵니다.

```
import com.amazon.ion.IonStruct;
import com.amazon.ion.IonSystem;
import com.amazon.ion.system.IonSystemBuilder;
```

#### Jackson 매퍼 사용
<a name="cookbook-java.ion.importing.jackson"></a>

Java 프로젝트에 아티팩트 [jackson-dataformat-ion](https://search.maven.org/artifact/com.fasterxml.jackson.dataformat/jackson-dataformat-ion/2.10.0/bundle)을 종속 항목으로 추가합니다. QLDB에는 버전 `2.10.0` 이상이 필요합니다.

------
#### [ Gradle ]

```
dependencies {
    compile group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-ion', version: '2.10.0'
}
```

------
#### [ Maven ]

```
<dependencies>
  <dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-ion</artifactId>
    <version>2.10.0</version>
  </dependency>
</dependencies>
```

------

다음 Ion 패키지를 가져옵니다.

```
import com.amazon.ion.IonReader;
import com.amazon.ion.IonStruct;
import com.amazon.ion.system.IonReaderBuilder;
import com.amazon.ion.system.IonSystemBuilder;

import com.fasterxml.jackson.dataformat.ion.IonObjectMapper;
import com.fasterxml.jackson.dataformat.ion.ionvalue.IonValueMapper;
```

### Ion 초기화
<a name="cookbook-java.ion.initializing"></a>

```
IonSystem SYSTEM = IonSystemBuilder.standard().build();
```

#### Jackson 매퍼 사용
<a name="cookbook-java.ion.initializing.jackson"></a>

```
IonObjectMapper MAPPER = new IonValueMapper(IonSystemBuilder.standard().build());
```

### Ion 객체 생성
<a name="cookbook-java.ion.creating"></a>

다음 코드 예제는 `IonStruct` 인터페이스와 내장 메서드를 사용하여 Ion 객체를 만듭니다.

```
IonStruct ionStruct = SYSTEM.newEmptyStruct();

ionStruct.put("GovId").newString("TOYENC486FH");
ionStruct.put("FirstName").newString("Brent");

System.out.println(ionStruct.toPrettyString()); // prints a nicely formatted copy of ionStruct
```

#### Jackson 매퍼 사용
<a name="cookbook-java.ion.creating.jackson"></a>

다음과 같이 `Person`라는 명칭의 JSON 매핑된 모델 클래스가 있다고 가정하겠습니다.

```
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;

public static class Person {
    private final String firstName;
    private final String govId;

    @JsonCreator
    public Person(@JsonProperty("FirstName") final String firstName,
                  @JsonProperty("GovId") final String govId) {
        this.firstName = firstName;
        this.govId = govId;
    }

    @JsonProperty("FirstName")
    public String getFirstName() {
        return firstName;
    }

    @JsonProperty("GovId")
    public String getGovId() {
        return govId;
    }
}
```

다음 코드 예제는 `Person`의 인스턴스에서 `IonStruct` 객체를 만듭니다.

```
IonStruct ionStruct = (IonStruct) MAPPER.writeValueAsIonValue(new Person("Brent", "TOYENC486FH"));
```

### Ion 객체 읽기
<a name="cookbook-java.ion.reading"></a>

다음 코드 예제는 `ionStruct` 인스턴스의 각 필드를 인쇄합니다.

```
// ionStruct is an instance of IonStruct
System.out.println(ionStruct.get("GovId")); // prints TOYENC486FH
System.out.println(ionStruct.get("FirstName")); // prints Brent
```

#### Jackson 매퍼 사용
<a name="cookbook-java.ion.reading.jackson"></a>

다음 코드 예제는 `IonStruct` 객체를 읽고 이를 `Person`의 인스턴스에 매핑합니다.

```
// ionStruct is an instance of IonStruct
IonReader reader = IonReaderBuilder.standard().build(ionStruct);
Person person = MAPPER.readValue(reader, Person.class);
System.out.println(person.getFirstName()); // prints Brent
System.out.println(person.getGovId()); // prints TOYENC486FH
```

Ion 작업에 대한 자세한 정보는 GitHub의 [Amazon Ion 설명서](http://amzn.github.io/ion-docs/)를 참조하세요. QLDB에서 Ion을 사용하는 방법에 대한 추가 코드 예제는 [Amazon QLDB에서 Amazon Ion 데이터 타입을 사용한 작업](driver-working-with-ion.md) 섹션을 참조하세요.