

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

# 适用于 Go 的 Amazon QLDB 驱动程序
<a name="getting-started.golang"></a>

**重要**  
终止支持通知：现有客户将能够使用 Amazon QLDB，直到 2025 年 7 月 31 日终止支持。有关更多详细信息，请参阅[将亚马逊 QLDB 账本迁移到亚马逊 Aurora PostgreSQL](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/)。

要处理账本中的数据，您可以使用提供的驱动程序从 Go 应用程序连接到 Amazon QLDB。 AWS 以下主题介绍了如何开始使用适用于 Go 的 QLDB 驱动程序。

**Topics**
+ [驱动程序资源](#getting-started.golang.resources)
+ [先决条件](#getting-started.golang.prereqs)
+ [安装](#getting-started.golang.install)
+ [快速入门教程](driver-quickstart-golang.md)
+ [说明书参考](driver-cookbook-golang.md)

## 驱动程序资源
<a name="getting-started.golang.resources"></a>

有关 Go 驱动程序支持功能的更多信息，请参阅以下资源：
+ API 参考：[3.x](https://pkg.go.dev/github.com/awslabs/amazon-qldb-driver-go/v3/qldbdriver)、[2.x](https://pkg.go.dev/github.com/awslabs/amazon-qldb-driver-go/v2/qldbdriver)、[1.x](https://pkg.go.dev/github.com/awslabs/amazon-qldb-driver-go/qldbdriver)
+ [驱动程序源代码 (GitHub)](https://github.com/awslabs/amazon-qldb-driver-go)
+ [Amazon Ion 说明书](http://amzn.github.io/ion-docs/guides/cookbook.html)

## 先决条件
<a name="getting-started.golang.prereqs"></a>

开始使用适用于 Go 的 QLDB 驱动程序之前，您必须执行以下操作:

1. 按照中的 AWS 设置说明进行操作[访问 Amazon QLDB](accessing.md)。这包括以下这些：

   1. 报名参加 AWS.

   1. 创建具有适当 QLDB 权限的用户。

   1. 授权以编程方式访问开发。

1. （可选）安装您选择的集成式开发环境（IDE）。有关常用于 Go IDEs 的列表，请参阅[编辑器插件和 IDEs](https://golang.org/doc/editors.html) Go 网站。

1. 从 [Go 下载](https://golang.org/dl/)网站下载并安装以下 Go 版本之一：
   + **1.15 或更高版本 **- 适用于 Go v3 的 QLDB 驱动程序
   + **1.14** - 适用于 Go v1 或 v2 的 QLDB 驱动程序

1. 配置您的开发环境用于 [适用于 Go 的 AWS SDK](https://aws.amazon.com/sdk-for-go)：

   1. 设置您的 AWS 凭证。我们建议创建共享的凭证文件。

      有关说明，请参阅*适用于 Go 的 AWS SDK 开发者指南*中的[指定凭证](https://aws.github.io/aws-sdk-go-v2/docs/configuring-sdk/#specifying-credentials)。

   1. 设置您的默认 AWS 区域。要了解如何操作，请参阅[指定 AWS 区域](https://aws.github.io/aws-sdk-go-v2/docs/configuring-sdk/#specifying-the-aws-region)。

      有关可用区域的完整列表，请参阅 *AWS 一般参考* 中的 [Amazon QLDB 端点和限额](https://docs.aws.amazon.com/general/latest/gr/qldb.html)。

接下来，您可以设置基本的示例应用程序并运行简短的代码示例，也可以将驱动程序安装到现有的 Go 项目中。
+ 要在现有项目中安装 QLDB 驱动程序和 适用于 Go 的 AWS SDK ，请继续。[安装](#getting-started.golang.install)
+ 要设置项目并运行演示分类账上基本数据事务的简短代码示例，请参阅 [快速入门教程](driver-quickstart-golang.md)。

## 安装
<a name="getting-started.golang.install"></a>

[Go 的 QLDB 驱动程序在存储库 awslabs/ 中 GitHub 是开源的。amazon-qldb-driver-go](http://github.com/awslabs/amazon-qldb-driver-go)QLDB 支持以下驱动程序版本及其 Go 依赖项。


****  

| 驱动程序版本 | Go 版本 | 状态 | 最新发布日期 | 
| --- | --- | --- | --- | 
| [1.x](https://pkg.go.dev/github.com/awslabs/amazon-qldb-driver-go/qldbdriver) | 1.14 或更高版本 | 量产版 | 2021 年 6 月 16 日 | 
| [2.x](https://pkg.go.dev/github.com/awslabs/amazon-qldb-driver-go/v2/qldbdriver) | 1.14 或更高版本 | 量产版 | 2021 年 7 月 21 日 | 
| [3.x](https://pkg.go.dev/github.com/awslabs/amazon-qldb-driver-go/v3/qldbdriver) | 1.15 或更高版本 | 量产版 | 2022 年 11 月 10 日 | 

**安装驱动程序**

1. 确保您的项目使用 [Go 模块](https://blog.golang.org/using-go-modules)来安装项目依赖项。

1. 在您的项目目录中输入以下 `go get` 命令。

------
#### [ 3.x ]

   ```
   $ go get -u github.com/awslabs/amazon-qldb-driver-go/v3/qldbdriver
   ```

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

   ```
   $ go get -u github.com/awslabs/amazon-qldb-driver-go/v2/qldbdriver
   ```

------

安装驱动程序还会安装其依赖项，包括 [适用于 Go 的 AWS SDK](https://github.com/aws/aws-sdk-go) 或 [适用于 Go 的 AWS SDK v2 ](https://github.com/aws/aws-sdk-go-v2) 和 [Amazon Ion](https://github.com/amzn/ion-go)软件包。

有关如何在分类账上运行基本数据事务的简短代码示例，请参阅 [说明书参考](driver-cookbook-golang.md)。

# 适用于 Go 的 Amazon QLDB 驱动程序 — 快速入门教程
<a name="driver-quickstart-golang"></a>

**重要**  
终止支持通知：现有客户将能够使用 Amazon QLDB，直到 2025 年 7 月 31 日终止支持。有关更多详细信息，请参阅[将亚马逊 QLDB 账本迁移到亚马逊 Aurora PostgreSQL](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/)。

在本教程中，您将学习如何使用适用于 Go 的最新版 Amazon QLDB 驱动程序来设置简单应用程序。本指南包括安装驱动程序的步骤以及*创建、读取、更新和删除*（CRUD）的基本操作的简短代码示例。

**Topics**
+ [先决条件](#driver-quickstart-golang.prereqs)
+ [步骤 1：安装驱动程序](#driver-quickstart-golang.install)
+ [第 2 步：导入软件包](#driver-quickstart-golang.import)
+ [第 3 步：初始化驱动程序](#driver-quickstart-golang.initialize)
+ [第 4 步：创建表和索引](#driver-quickstart-golang.create-table-index)
+ [第 5 步：插入文档](#driver-quickstart-golang.insert)
+ [第 6 步：查询文档](#driver-quickstart-golang.query)
+ [步骤 7：更新文档](#driver-quickstart-golang.update)
+ [步骤 8：查询更新的文档](#driver-quickstart-golang.query-2)
+ [第 9 步：删除表格](#driver-quickstart-golang.drop-table)
+ [运行完整的应用程序](#driver-quickstart-golang.complete)

## 先决条件
<a name="driver-quickstart-golang.prereqs"></a>

在开始之前，请务必执行以下操作：

1. 请为 Go 驱动程序完成（[先决条件](getting-started.golang.md#getting-started.golang.prereqs)如果尚未执行此操作）。这包括注册 AWS、授予开发所需的编程访问权限以及安装 Go。

1. 创建一个名为 `quick-start` 分类账。

   要了解如何创建分类账，请参阅*控制台入门*中的 [Amazon QLDB 分类账的基本操作](ledger-management.basics.md) 或 [第 1 步：创建新分类账](getting-started-step-1.md)。

## 步骤 1：安装驱动程序
<a name="driver-quickstart-golang.install"></a>

确保您的项目使用 [Go 模块](https://blog.golang.org/using-go-modules)来安装项目依赖项。

在您的项目目录中运行以下 `go get` 命令。

```
$ go get -u github.com/awslabs/amazon-qldb-driver-go/v3/qldbdriver
```

安装驱动程序还会安装其依赖项，包括 [适用于 Go 的 AWS SDK v2](https://github.com/aws/aws-sdk-go-v2) 和 [Amazon Ion](https://github.com/amzn/ion-go) 软件包。

## 第 2 步：导入软件包
<a name="driver-quickstart-golang.import"></a>

导入以下 AWS 软件包。

```
import (
    "context"
    "fmt"

    "github.com/amzn/ion-go/ion"
    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go-v2/config"
    "github.com/aws/aws-sdk-go-v2/service/qldbSession"
    "github.com/awslabs/amazon-qldb-driver-go/v3/qldbdriver"
)
```

## 第 3 步：初始化驱动程序
<a name="driver-quickstart-golang.initialize"></a>

初始化连接到名为 `quick-start` 的分类账的驱动程序实例。

```
cfg, err := config.LoadDefaultConfig(context.TODO())
if err != nil {
  panic(err)
}

qldbSession := qldbsession.NewFromConfig(cfg, func(options *qldbsession.Options) {
    options.Region = "us-east-1"
})
driver, err := qldbdriver.New(
    "quick-start",
    qldbSession,
    func(options *qldbdriver.DriverOptions) {
        options.LoggerVerbosity = qldbdriver.LogInfo
    })
if err != nil {
    panic(err)
}

defer driver.Shutdown(context.Background())
```

**注意**  
在此代码示例中，*us-east-1*替换为您创建账本 AWS 区域 的位置。

## 第 4 步：创建表和索引
<a name="driver-quickstart-golang.create-table-index"></a>

以下代码示例显示如何运行 `CREATE TABLE` 和 `CREATE INDEX` 语句。

```
_, err = driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) {
    _, err := txn.Execute("CREATE TABLE People")
    if err != nil {
        return nil, err
    }

    // When working with QLDB, it's recommended to create an index on fields we're filtering on.
    // This reduces the chance of OCC conflict exceptions with large datasets.
    _, err = txn.Execute("CREATE INDEX ON People (firstName)")
    if err != nil {
        return nil, err
    }

    _, err = txn.Execute("CREATE INDEX ON People (age)")
    if err != nil {
        return nil, err
    }

    return nil, nil
})
if err != nil {
    panic(err)
}
```

它会创建一个名为 `People` 的表，并为该表上的 `firstName` 和 `age` 字段创建索引。[索引](ql-reference.create-index.md)是优化查询性能和帮助限制[乐观并发控制（OCC）冲突异常](concurrency.md)所必需的。

## 第 5 步：插入文档
<a name="driver-quickstart-golang.insert"></a>

以下代码示例显示如何运行 `INSERT` 语句。QLDB 支持 [PartiQL](ql-reference.md) 查询语言（兼容 SQL）和 [Amazon Ion](ion.md) 数据格式（JSON 的超集）。

### 使用文字 PartiQL
<a name="driver-quickstart-golang.insert.partiql"></a>

以下代码使用字符串文字 PartiQL 语句将文档插入 `People` 表中。

```
_, err = driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) {
    return txn.Execute("INSERT INTO People {'firstName': 'Jane', 'lastName': 'Doe', 'age': 77}")
})
if err != nil {
    panic(err)
}
```

### 使用 Ion 数据类型
<a name="driver-quickstart-golang.insert.ion"></a>

与 Go 内置的[ JSON 包](https://golang.org/pkg/encoding/json/)类似，您可以在 Ion 中封送和解封送 Go 数据类型。

1. 假设您具有以下名为 `Person` 的 Go 结构。

   ```
   type Person struct {
       FirstName string `ion:"firstName"`
       LastName  string `ion:"lastName"`
       Age       int    `ion:"age"`
   }
   ```

1. 创建 `Person` 的实例。

   ```
   person := Person{"John", "Doe", 54}
   ```

   驱动程序会为您编组 `person` 的一个 ION 编码的文本表示形式。
**重要**  
要使 marshal 和 unmarshal 正常工作，必须导出 Go 数据结构的字段名称（首字母大写）。

1. 将 `person` 实例传递给事务`Execute`的方法。

   ```
   _, err = driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) {
       return txn.Execute("INSERT INTO People ?", person)
   })
   if err != nil {
       panic(err)
   }
   ```

   此示例使用问号（`?`）作为变量占位符，将文档信息传递给语句。使用占位符时，必须传递 ION 编码的文本值。
**提示**  
要使用单个 [INSERT](ql-reference.insert.md) 语句插入多个文档，可以向该语句传递一个[列表](driver-working-with-ion.md#driver-ion-list)类型的参数，如下所示。  

   ```
   // people is a list
   txn.Execute("INSERT INTO People ?", people)
   ```
传递 Ion 列表时，不要将变量占位符（`?`）括在双尖括号（`<<...>>`）内。在手动 PartiQL 语句中，双尖括号表示名为*bag*的无序集合。

## 第 6 步：查询文档
<a name="driver-quickstart-golang.query"></a>

以下代码示例显示如何运行 `SELECT` 语句。

```
p, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) {
    result, err := txn.Execute("SELECT firstName, lastName, age FROM People WHERE age = 54")
    if err != nil {
        return nil, err
    }

    // Assume the result is not empty
    hasNext := result.Next(txn)
    if !hasNext && result.Err() != nil {
        return nil, result.Err()
    }

    ionBinary := result.GetCurrentData()

    temp := new(Person)
    err = ion.Unmarshal(ionBinary, temp)
    if err != nil {
        return nil, err
    }

    return *temp, nil
})
if err != nil {
    panic(err)
}

var returnedPerson Person
returnedPerson = p.(Person)

if returnedPerson != person {
    fmt.Print("Queried result does not match inserted struct")
}
```

此示例从 `People` 表中查询您的文档，假设结果集不为空，然后从结果中返回您的文档。

## 步骤 7：更新文档
<a name="driver-quickstart-golang.update"></a>

以下代码示例显示如何运行 `UPDATE` 语句。

```
person.Age += 10

_, err = driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) {
    return txn.Execute("UPDATE People SET age = ? WHERE firstName = ?", person.Age, person.FirstName)
})
if err != nil {
    panic(err)
}
```

## 步骤 8：查询更新的文档
<a name="driver-quickstart-golang.query-2"></a>

以下代码示例通过 `firstName` 查询 `People` 表，并返回结果集中的所有文档。

```
p, err = driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) {
    result, err := txn.Execute("SELECT firstName, lastName, age FROM People WHERE firstName = ?", person.FirstName)
    if err != nil {
        return nil, err
    }

    var people []Person
    for result.Next(txn) {
        ionBinary := result.GetCurrentData()

        temp := new(Person)
        err = ion.Unmarshal(ionBinary, temp)
        if err != nil {
            return nil, err
        }

        people = append(people, *temp)
    }
    if result.Err() != nil {
        return nil, result.Err()
    }

    return people, nil
})
if err != nil {
    panic(err)
}

var people []Person
people = p.([]Person)

updatedPerson := Person{"John", "Doe", 64}
if people[0] != updatedPerson {
    fmt.Print("Queried result does not match updated struct")
}
```

## 第 9 步：删除表格
<a name="driver-quickstart-golang.drop-table"></a>

以下代码示例显示如何运行 `DROP TABLE` 语句。

```
_, err = driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) {
    return txn.Execute("DROP TABLE People")
})
if err != nil {
    panic(err)
}
```

## 运行完整的应用程序
<a name="driver-quickstart-golang.complete"></a>

以下代码示例是 应用程序的完整版本。您还可以从头到尾复制并运行此代码示例，而不必单独执行前面的步骤。此应用程序演示了对名为 `quick-start` 的分类账的一些基本的 CRUD 操作。

**注意**  
在运行此代码之前，请确保 `quick-start` 分类账中还没有名为 `People` 的活动表。

```
package main

import (
    "context"
    "fmt"

    "github.com/amzn/ion-go/ion"
    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/aws/session"
    "github.com/aws/aws-sdk-go/service/qldbsession"
    "github.com/awslabs/amazon-qldb-driver-go/v2/qldbdriver"
)

func main() {
    awsSession := session.Must(session.NewSession(aws.NewConfig().WithRegion("us-east-1")))
    qldbSession := qldbsession.New(awsSession)

    driver, err := qldbdriver.New(
        "quick-start",
        qldbSession,
        func(options *qldbdriver.DriverOptions) {
            options.LoggerVerbosity = qldbdriver.LogInfo
        })
    if err != nil {
        panic(err)
    }
    defer driver.Shutdown(context.Background())

    _, err = driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) {
        _, err := txn.Execute("CREATE TABLE People")
        if err != nil {
            return nil, err
        }

        // When working with QLDB, it's recommended to create an index on fields we're filtering on.
        // This reduces the chance of OCC conflict exceptions with large datasets.
        _, err = txn.Execute("CREATE INDEX ON People (firstName)")
        if err != nil {
            return nil, err
        }

        _, err = txn.Execute("CREATE INDEX ON People (age)")
        if err != nil {
            return nil, err
        }

        return nil, nil
    })
    if err != nil {
        panic(err)
    }

    _, err = driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) {
        return txn.Execute("INSERT INTO People {'firstName': 'Jane', 'lastName': 'Doe', 'age': 77}")
    })
    if err != nil {
        panic(err)
    }

    type Person struct {
        FirstName string `ion:"firstName"`
        LastName  string `ion:"lastName"`
        Age       int    `ion:"age"`
    }

    person := Person{"John", "Doe", 54}

    _, err = driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) {
        return txn.Execute("INSERT INTO People ?", person)
    })
    if err != nil {
        panic(err)
    }

    p, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) {
        result, err := txn.Execute("SELECT firstName, lastName, age FROM People WHERE age = 54")
        if err != nil {
            return nil, err
        }

        // Assume the result is not empty
        hasNext := result.Next(txn)
        if !hasNext && result.Err() != nil {
            return nil, result.Err()
        }

        ionBinary := result.GetCurrentData()

        temp := new(Person)
        err = ion.Unmarshal(ionBinary, temp)
        if err != nil {
            return nil, err
        }

        return *temp, nil
    })
    if err != nil {
        panic(err)
    }

    var returnedPerson Person
    returnedPerson = p.(Person)

    if returnedPerson != person {
        fmt.Print("Queried result does not match inserted struct")
    }

    person.Age += 10

    _, err = driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) {
        return txn.Execute("UPDATE People SET age = ? WHERE firstName = ?", person.Age, person.FirstName)
    })
    if err != nil {
        panic(err)
    }

    p, err = driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) {
        result, err := txn.Execute("SELECT firstName, lastName, age FROM People WHERE firstName = ?", person.FirstName)
        if err != nil {
            return nil, err
        }

        var people []Person
        for result.Next(txn) {
            ionBinary := result.GetCurrentData()

            temp := new(Person)
            err = ion.Unmarshal(ionBinary, temp)
            if err != nil {
                return nil, err
            }

            people = append(people, *temp)
        }
        if result.Err() != nil {
            return nil, result.Err()
        }

        return people, nil
    })
    if err != nil {
        panic(err)
    }

    var people []Person
    people = p.([]Person)

    updatedPerson := Person{"John", "Doe", 64}
    if people[0] != updatedPerson {
        fmt.Print("Queried result does not match updated struct")
    }

    _, err = driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) {
        return txn.Execute("DROP TABLE People")
    })
    if err != nil {
        panic(err)
    }
}
```

# 适用于 Go 的 Amazon QLDB 驱动程序 — 说明书参考
<a name="driver-cookbook-golang"></a>

**重要**  
终止支持通知：现有客户将能够使用 Amazon QLDB，直到 2025 年 7 月 31 日终止支持。有关更多详细信息，请参阅[将亚马逊 QLDB 账本迁移到亚马逊 Aurora PostgreSQL](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/)。

本参考指南显示了 Go 的 Amazon QLDB 驱动程序的常见用例。它提供了 Go 代码示例，演示了如何使用该驱动程序运行基本的*创建、读取、更新和删除*（CRUD）操作。它还包括用于处理 Amazon Ion 数据的代码示例。此外，本指南还重点介绍了使事务具有幂等性和实现唯一性约束的最佳实践。

**注意**  
在适用的情况下，某些用例对于 Go 的 QLDB 驱动程序的每个支持主要版本都配备不同代码示例。

**Contents**
+ [导入驱动程序](#cookbook-golang.importing)
+ [实例化驱动程序](#cookbook-golang.instantiating)
+ [CRUD 操作](#cookbook-golang.crud)
  + [创建表](#cookbook-golang.crud.creating-tables)
  + [创建索引](#cookbook-golang.crud.creating-indexes)
  + [阅读文档](#cookbook-golang.crud.reading)
    + [使用查询参数](#cookbook-golang.reading-using-params)
  + [插入文档](#cookbook-golang.crud.inserting)
    + [在一条语句内插入多个文档](#cookbook-golang.crud.inserting.multiple)
  + [更新文档](#cookbook-golang.crud.updating)
  + [删除文档](#cookbook-golang.crud.deleting)
  + [在一个事务中运行多条语句](#cookbook-golang.crud.multi-statement)
  + [重试逻辑](#cookbook-golang.crud.retry-logic)
  + [实现唯一限制](#cookbook-golang.crud.uniqueness-constraints)
+ [使用 Amazon Ion](#cookbook-golang.ion)
  + [导入 Ion 模块](#cookbook-golang.ion.import)
  + [创建 Ion 类型](#cookbook-golang.ion.creating-types)
  + [获取二进制 Ion](#cookbook-golang.ion.getting-binary)
  + [获取 Ion 文本](#cookbook-golang.ion.getting-text)

## 导入驱动程序
<a name="cookbook-golang.importing"></a>

以下代码示例导入驱动程序和其他必需的 AWS 软件包。

------
#### [ 3.x ]

```
import (

    "github.com/amzn/ion-go/ion"
    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go-v2/config"
    "github.com/aws/aws-sdk-go-v2/service/qldbSession"
    "github.com/awslabs/amazon-qldb-driver-go/v3/qldbdriver"
)
```

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

```
import (

    "github.com/amzn/ion-go/ion"
    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/aws/session"
    "github.com/aws/aws-sdk-go/service/qldbsession"
    "github.com/awslabs/amazon-qldb-driver-go/v2/qldbdriver"
)
```

------

**注意**  
此示例还导入了 Amazon Ion 软件包（`amzn/ion-go/ion`）。在本参考中运行某些数据操作时，您需要此软件包来处理 Ion 数据。要了解更多信息，请参阅 [使用 Amazon Ion](#cookbook-golang.ion)。

## 实例化驱动程序
<a name="cookbook-golang.instantiating"></a>

以下代码示例在 AWS 区域中创建连接到指定分类账名称的驱动程序实例。

------
#### [ 3.x ]

```
cfg, err := config.LoadDefaultConfig(context.TODO())
if err != nil {
    panic(err)
}

qldbSession := qldbsession.NewFromConfig(cfg, func(options *qldbsession.Options) {
    options.Region = "us-east-1"
})
driver, err := qldbdriver.New(
  "vehicle-registration",
  qldbSession,
  func(options *qldbdriver.DriverOptions) {
    options.LoggerVerbosity = qldbdriver.LogInfo
})
if err != nil {
  panic(err)
}

defer driver.Shutdown(context.Background())
```

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

```
awsSession := session.Must(session.NewSession(aws.NewConfig().WithRegion("us-east-1")))
qldbSession := qldbsession.New(awsSession)

driver, err := qldbdriver.New(
  "vehicle-registration",
  qldbSession,
  func(options *qldbdriver.DriverOptions) {
    options.LoggerVerbosity = qldbdriver.LogInfo
  })
if err != nil {
  panic(err)
}
```

------

## CRUD 操作
<a name="cookbook-golang.crud"></a>

QLDB 作为事务的一部分运行*创建、读取、更新和删除*（CRUD）操作。

**警告**  
作为最佳实践，使写事务严格地幂等。

**使事务幂等**

我们建议将写事务设置为幂等，以避免重试时出现任何意想不到的副作用。如果事务可以运行多次并每次都产生相同的结果，则事务是*幂等的*。

例如，假设有一个事务，要将文档插入名为 `Person` 的表中。事务应该首先检查文档是否已经存在于表中。如果没有这种检查，表最终可能会有重复的文档。

假设 QLDB 在服务器端成功提交了事务，但客户端在等待响应时超时。如果事务不是幂等的，则在重试的情况下可以多次插入相同的文档。

**使用索引避免全表扫描**

我们还建议您在索引字段或文档 ID 上使用*相等*运算符来运行带有`WHERE`谓词子句的语句；例如，`WHERE indexedField = 123`或 `WHERE indexedField IN (456, 789)`。如果没有这种索引查找，QLDB 需要进行表扫描，这可能会导致事务超时或*乐观并发控制*（OCC）冲突。

有关 OCC 的更多信息，请参阅[Amazon QLDB 并发模型](concurrency.md)。

**隐式创建的事务**

[QLDBDriver.Execut](https://pkg.go.dev/github.com/awslabs/amazon-qldb-driver-go/v3/qldbdriver#QLDBDriver.Execute) e 函数接受一个 lambda 函数，该函数接收[交易](https://pkg.go.dev/github.com/awslabs/amazon-qldb-driver-go/v3/qldbdriver#Transaction)实例，您可以使用该实例来运行语句。的实例`Transaction`封装了隐式创建的事务。

您可以使用`Transaction.Execute`函数在 lambda 函数中运行语句。当 lambda 函数返回时，驱动程序会隐式提交事务。

以下各节介绍如何运行基本的 CRUD 操作、指定自定义重试逻辑以及如何实现唯一性约束。

**Contents**
+ [创建表](#cookbook-golang.crud.creating-tables)
+ [创建索引](#cookbook-golang.crud.creating-indexes)
+ [阅读文档](#cookbook-golang.crud.reading)
  + [使用查询参数](#cookbook-golang.reading-using-params)
+ [插入文档](#cookbook-golang.crud.inserting)
  + [在一条语句内插入多个文档](#cookbook-golang.crud.inserting.multiple)
+ [更新文档](#cookbook-golang.crud.updating)
+ [删除文档](#cookbook-golang.crud.deleting)
+ [在一个事务中运行多条语句](#cookbook-golang.crud.multi-statement)
+ [重试逻辑](#cookbook-golang.crud.retry-logic)
+ [实现唯一限制](#cookbook-golang.crud.uniqueness-constraints)

### 创建表
<a name="cookbook-golang.crud.creating-tables"></a>

```
result, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) {
  return txn.Execute("CREATE TABLE Person")
})
```

### 创建索引
<a name="cookbook-golang.crud.creating-indexes"></a>

```
result, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) {
  return txn.Execute("CREATE INDEX ON Person(GovId)")
})
```

### 阅读文档
<a name="cookbook-golang.crud.reading"></a>

```
var decodedResult map[string]interface{}

// Assumes that Person table has documents as follows:
// { "GovId": "TOYENC486FH", "FirstName": "Brent" }
_, err = driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) {
  result, err := txn.Execute("SELECT * FROM Person WHERE GovId = 'TOYENC486FH'")
  if err != nil {
    return nil, err
  }
  for result.Next(txn) {
    ionBinary := result.GetCurrentData()
    err = ion.Unmarshal(ionBinary, &decodedResult)
    if err != nil {
      return nil, err
    }
    fmt.Println(decodedResult) // prints map[GovId: TOYENC486FH FirstName:Brent]
  }
  if result.Err() != nil {
    return nil, result.Err()
  }
  return nil, nil
})
if err != nil {
  panic(err)
}
```

#### 使用查询参数
<a name="cookbook-golang.reading-using-params"></a>

以下代码示例使用原生类型查询参数。

```
result, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) {
  return txn.Execute("SELECT * FROM Person WHERE GovId = ?", "TOYENC486FH")
})
if err != nil {
  panic(err)
}
```

以下代码示例使用了多个查询参数。

```
result, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) {
  return txn.Execute("SELECT * FROM Person WHERE GovId = ? AND FirstName = ?", "TOYENC486FH", "Brent")
})
if err != nil {
  panic(err)
}
```

以下代码示例使用查询参数列表。

```
govIDs := []string{}{"TOYENC486FH", "ROEE1", "YH844"}

result, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) {
  return txn.Execute("SELECT * FROM Person WHERE GovId IN (?,?,?)", govIDs...)
})
if err != nil {
  panic(err)
}
```

**注意**  
当您在没有索引查找的情况下运行查询时，它会调用全表扫描。在此示例中，我们建议在`GovId`字段上设置 [索引](ql-reference.create-index.md)以优化性能。如果不开启`GovId`索引，查询可能会有更长的延迟，还可能导致 OCC 冲突异常或者事务超时。

### 插入文档
<a name="cookbook-golang.crud.inserting"></a>

以下代码示例插入本地数据类型。

```
_, err = driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) {
  // Check if a document with a GovId of TOYENC486FH exists
  // This is critical to make this transaction idempotent
  result, err := txn.Execute("SELECT * FROM Person WHERE GovId = ?", "TOYENC486FH")
  if err != nil {
    return nil, err
  }
  // Check if there are any results
  if result.Next(txn) {
    // Document already exists, no need to insert
  } else {
    person := map[string]interface{}{
      "GovId": "TOYENC486FH",
      "FirstName": "Brent",
    }
    _, err = txn.Execute("INSERT INTO Person ?", person)
    if err != nil {
      return nil, err
    }
  }
  return nil, nil
})
```

此事务将文档插入 `Person` 表中。在插入之前，它首先检查文档是否已存在于表格内。**此检查使事务本质上是幂等。**即使您多次运行此事务，也不会造成任何异常副作用。

**注意**  
在此示例中，我们建议在 `GovId` 字段上设置索引以优化性能。如果不开启`GovId`索引，语句可能会有更长的延迟，还可能导致 OCC 冲突异常或者事务超时。

#### 在一条语句内插入多个文档
<a name="cookbook-golang.crud.inserting.multiple"></a>

要使用单个 [INSERT](ql-reference.insert.md) 语句插入多个文档，可以向该语句传递一个[列表](driver-working-with-ion.md#driver-ion-list)类型的参数，如下所示。

```
// people is a list
txn.Execute("INSERT INTO People ?", people)
```

传递 Ion 列表时，不要将变量占位符（`?`）括在双尖括号（`<<...>>`）内。在手动 PartiQL 语句中，双尖括号表示名为*bag*的无序集合。

### 更新文档
<a name="cookbook-golang.crud.updating"></a>

以下代码示例使用原生数据类型。

```
result, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) {
  return txn.Execute("UPDATE Person SET FirstName = ? WHERE GovId = ?", "John", "TOYENC486FH")
})
```

**注意**  
在此示例中，我们建议在 `GovId` 字段上设置索引以优化性能。如果不开启`GovId`索引，语句可能会有更长的延迟，还可能导致 OCC 冲突异常或者事务超时。

### 删除文档
<a name="cookbook-golang.crud.deleting"></a>

以下代码示例使用原生数据类型。

```
result, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) {
  return txn.Execute("DELETE FROM Person WHERE GovId = ?", "TOYENC486FH")
})
```

**注意**  
在此示例中，我们建议在 `GovId` 字段上设置索引以优化性能。如果不开启`GovId`索引，语句可能会有更长的延迟，还可能导致 OCC 冲突异常或者事务超时。

### 在一个事务中运行多条语句
<a name="cookbook-golang.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.
func InsureCar(driver *qldbdriver.QLDBDriver, vin string) (bool, error) {
    insured, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) {

        result, err := txn.Execute(
            "SELECT insured FROM Vehicles WHERE vin = ? AND insured = FALSE", vin)
        if err != nil {
            return false, err
        }

        hasNext := result.Next(txn)
        if !hasNext && result.Err() != nil {
            return false, result.Err()
        }

        if hasNext {
            _, err = txn.Execute(
                "UPDATE Vehicles SET insured = TRUE WHERE vin = ?", vin)
            if err != nil {
                return false, err
            }
            return true, nil
        }
        return false, nil
    })
    if err != nil {
        panic(err)
    }

    return insured.(bool), err
}
```

### 重试逻辑
<a name="cookbook-golang.crud.retry-logic"></a>

驱动程序 `Execute` 函数具有内置的重试机制，如果发生可重试的异常（例如超时或 OCC 冲突），该机制可以重试事务。最大重试次数和退避策略为可配置。

默认的重试限制为`4`，默认的退避策略以毫秒[ExponentialBackoffStrategy](https://pkg.go.dev/github.com/awslabs/amazon-qldb-driver-go/v3/qldbdriver#ExponentialBackoffStrategy)为基准。`10`您可以使用的实例为每个驱动程序实例以及每个事务设置重试策略。[RetryPolicy](https://pkg.go.dev/github.com/awslabs/amazon-qldb-driver-go/v3/qldbdriver#RetryPolicy)

以下代码示例使用自定义重试限制和驱动程序实例的自定义退避策略指定重试逻辑。

```
import (
  "github.com/aws/aws-sdk-go/aws"
  "github.com/aws/aws-sdk-go/aws/session"
  "github.com/aws/aws-sdk-go/service/qldbsession"
  "github.com/awslabs/amazon-qldb-driver-go/v2/qldbdriver"
)

func main() {
  awsSession := session.Must(session.NewSession(aws.NewConfig().WithRegion("us-east-1")))
  qldbSession := qldbsession.New(awsSession)

  // Configuring retry limit to 2
  retryPolicy := qldbdriver.RetryPolicy{MaxRetryLimit: 2}

  driver, err := qldbdriver.New("test-ledger", qldbSession, func(options *qldbdriver.DriverOptions) {
    options.RetryPolicy = retryPolicy
  })
  if err != nil {
    panic(err)
  }

  // Configuring an exponential backoff strategy with base of 20 milliseconds
  retryPolicy = qldbdriver.RetryPolicy{
    MaxRetryLimit: 2,
    Backoff: qldbdriver.ExponentialBackoffStrategy{SleepBase: 20, SleepCap: 4000,
    }}

  driver, err = qldbdriver.New("test-ledger", qldbSession, func(options *qldbdriver.DriverOptions) {
    options.RetryPolicy = retryPolicy
  })
  if err != nil {
    panic(err)
  }
}
```

以下代码示例使用自定义重试限制和自定义回退策略指定重试逻辑，用于特定匿名函数。该 `SetRetryPolicy` 函数将覆盖为驱动程序实例设置的重试策略。

```
import (
  "context"
  "github.com/aws/aws-sdk-go/aws"
  "github.com/aws/aws-sdk-go/aws/session"
  "github.com/aws/aws-sdk-go/service/qldbsession"
  "github.com/awslabs/amazon-qldb-driver-go/v2/qldbdriver"
)

func main() {
  awsSession := session.Must(session.NewSession(aws.NewConfig().WithRegion("us-east-1")))
  qldbSession := qldbsession.New(awsSession)

  // Configuring retry limit to 2
  retryPolicy1 := qldbdriver.RetryPolicy{MaxRetryLimit: 2}

  driver, err := qldbdriver.New("test-ledger", qldbSession, func(options *qldbdriver.DriverOptions) {
    options.RetryPolicy = retryPolicy1
  })
  if err != nil {
    panic(err)
  }

  // Configuring an exponential backoff strategy with base of 20 milliseconds
  retryPolicy2 := qldbdriver.RetryPolicy{
    MaxRetryLimit: 2,
    Backoff: qldbdriver.ExponentialBackoffStrategy{SleepBase: 20, SleepCap: 4000,
    }}

  // Overrides the retry policy set by the driver instance
  driver.SetRetryPolicy(retryPolicy2)

  driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) {
    return txn.Execute("CREATE TABLE Person")
  })
}
```

### 实现唯一限制
<a name="cookbook-golang.crud.uniqueness-constraints"></a>

QLDB 不支持唯一索引，但您可在应用程序中实现此行为。

假设您要对 `Person` 表中的 `GovId` 字段实现唯一性约束。据此，可以编写执行以下操作的事务：

1. 断言该表中没有指定 `GovId` 的现有文档。

1. 如果断言通过，请插入文档。

如果一个竞争事务同时通过断言，则只有一个事务将成功提交。另一笔事务将失败，并显示 OCC 冲突异常。

以下代码示例显示了如何实现此唯一约束。

```
govID := "TOYENC486FH"

document := map[string]interface{}{
  "GovId":     "TOYENC486FH",
  "FirstName": "Brent",
}

result, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) {
  // Check if doc with GovId = govID exists
  result, err := txn.Execute("SELECT * FROM Person WHERE GovId = ?", govID)
  if err != nil {
    return nil, err
  }
  // Check if there are any results
  if result.Next(txn) {
    // Document already exists, no need to insert
    return nil, nil
  }
  return txn.Execute("INSERT INTO Person ?", document)
})
if err != nil {
  panic(err)
}
```

**注意**  
在此示例中，我们建议在 `GovId` 字段上设置索引以优化性能。如果不开启`GovId`索引，语句可能会有更长的延迟，还可能导致 OCC 冲突异常或者事务超时。

## 使用 Amazon Ion
<a name="cookbook-golang.ion"></a>

以下各节说明了如何使用 Amazon Ion 模块处理 Ion 数据。

**Contents**
+ [导入 Ion 模块](#cookbook-golang.ion.import)
+ [创建 Ion 类型](#cookbook-golang.ion.creating-types)
+ [获取二进制 Ion](#cookbook-golang.ion.getting-binary)
+ [获取 Ion 文本](#cookbook-golang.ion.getting-text)

### 导入 Ion 模块
<a name="cookbook-golang.ion.import"></a>

```
import "github.com/amzn/ion-go/ion"
```

### 创建 Ion 类型
<a name="cookbook-golang.ion.creating-types"></a>

Go 版 Ion 库当前不支持文档对象模型（DOM），因此您无法创建 Ion 数据类型。但是使用 QLDB 时，您可以在 Go 原生类型和 Ion 二进制文件之间进行编组和解组。

### 获取二进制 Ion
<a name="cookbook-golang.ion.getting-binary"></a>

```
aDict := map[string]interface{}{
  "GovId": "TOYENC486FH",
  "FirstName": "Brent",
}

ionBytes, err := ion.MarshalBinary(aDict)
if err != nil {
  panic(err)
}

fmt.Println(ionBytes) // prints [224 1 0 234 238 151 129 131 222 147 135 190 144 133 71 111 118 73 100 137 70 105 114 115 116 78 97 109 101 222 148 138 139 84 79 89 69 78 67 52 56 54 70 72 139 133 66 114 101 110 116]
```

### 获取 Ion 文本
<a name="cookbook-golang.ion.getting-text"></a>

```
aDict := map[string]interface{}{
  "GovId": "TOYENC486FH",
  "FirstName": "Brent",
}

ionBytes, err := ion.MarshalText(aDict)
if err != nil {
  panic(err)
}

fmt.Println(string(ionBytes)) // prints {FirstName:"Brent",GovId:"TOYENC486FH"}
```

有关 Ion 的更多信息，请参阅上的 [Amazon Ion 文档](http://amzn.github.io/ion-docs/) GitHub。有关在 QLDB 中使用 Ion 的更多代码示例，请参阅[使用 Amazon QLDB 中的 Amazon Ion 数据类型](driver-working-with-ion.md)。