---
title: "JDBCを使用したTiDBでの作業"
version: "v2"
status: "WIP"
description: "このラボでは、Javaを使用してTiDB Cloud Serverlessクラスターに接続し、データにアクセス・修正する方法を学びます。プリペアドステートメントとバッチ挿入を使用してアプリケーションのパフォーマンスを向上させる方法、ホットスポットを避けるためにAUTO_INCREMENTの代わりにAUTO_RANDOMを使用する方法、オンラインDDLを使用してオンラインスキーマ変更を実行する方法、トランザクション並行制御のための悲観的ロックモードと楽観的モードを使用・比較する方法を学びます。"
---

# JDBCを使用したTiDBでの作業

## 概要

TiDBは、ハイブリッドトランザクション・分析処理（HTAP）をサポートする分散型、クラウドネイティブ、水平スケーラブルなデータベースです。アプリケーション開発者にとって、TiDBは高い可用性を備えた、信頼性が高く、制限なくスケールできるデータベースです。

Connector/Jは、MySQL互換データベース用の公式JDBCドライバー実装です。このラボでは、Connector/Jを使用してTiDBで作業する便利さを体験できます。

+ 具体的には、このラボでは以下を学びます：
  + Javaコードを使用してTiDB Cloud Serverlessクラスターに接続し、データにアクセス・修正する方法
  + プリペアドステートメントとバッチ挿入を使用してアプリケーションのパフォーマンスを向上させる方法
  + ホットスポットを避けるために`AUTO_INCREMENT`の代わりに`AUTO_RANDOM`を使用する方法
  + オンラインDDLを使用して進行中のトランザクションをブロックまたは中断することなくテーブルに列を追加する方法
  + 並行制御のための悲観的モードと楽観的モードを使用・比較する方法

### 所要時間

+ このラボは約90分で完了する予定です。

> [!NOTE]
> このラボでは、WebベースのVS Codeで作業します。WebベースIDEの使用はTiDBでは必須ではありませんが、クラウドデータベースコラボレーションでは一般的なアプローチです。クラウドデータベースの近くに開発者クライアントをデプロイすることで、アプリケーションサーバーの実際の動作とネットワーク条件をより適切にシミュレートできます。

> [!TIP]
> ラボ環境がデプロイされたら、下部バーの**次のモジュール**をクリックして**Module 1**を開始してください。

## Module 1: 環境の準備

### Step 1. IDEでターミナルを開く

+ [ここ](${LAB:SECURITY_ACCESS_PROXY_URL}?next=${LAB:"/vscode/?folder=/root/tidb-lab-java"|urlencode})をクリックして、新しいブラウザウィンドウでこのラボ用のVS Code環境を開きます。

+ この操作により、ブラウザに`xx-xxx-xxx-xx.sslip.io wants to see text and images copied to the clipboard`というプロンプトが表示される場合があります。`Allow`をクリックしてください。

+ ポップアップウィンドウで`Yes, I trust the authors`をクリックします。

  ![90%](https://pingcap-edu.s3.us-west-2.amazonaws.com/quick-demo/vscode-trust.jpg)

+ `≡`、`Terminal`、`New Terminal`をクリックして、VS Codeで新しいターミナルを開きます。

  ![90%](https://public-pingcap-innovation.s3.us-west-2.amazonaws.com/demo-405/405-open-terminal-0307.jpg)

### Step 2. env.shファイルの更新

+ `tidb-lab-java/`フォルダの下にある`env.sh`ファイルを見つけ、TiDB Serverlessの認証情報を更新して保存します。

  ```properties
  #!/bin/sh

  ## You only need to change this part
  export TIDB_HOST=${LAB:SERVERLESS_CLUSTER_HOST}
  export TIDB_PORT=${LAB:SERVERLESS_CLUSTER_PORT}
  export TIDB_USER='${LAB:SERVERLESS_CLUSTER_USERNAME}'
  export TIDB_PASSWORD='${LAB:SERVERLESS_CLUSTER_PASSWORD}'
  export TIDB_DB_NAME=${LAB:SERVERLESS_CLUSTER_DATABASE_NAME}
  export USE_SSL='true'

  ## Do not change the following part
  jdbc_url="jdbc:mysql://${TIDB_HOST}:${TIDB_PORT}/${TIDB_DB_NAME}"
  if [ 'true' = "${USE_SSL}" ]; then
    jdbc_url="${jdbc_url}?sslMode=VERIFY_IDENTITY&enabledTLSProtocols=TLSv1.2,TLSv1.3"
  fi

  export TIDB_JDBC_URL=${jdbc_url}
  ```

+ ターミナルで`source`コマンドを使って`env.sh`ファイルを読み込みます。
  ```bash
  source env.sh
  ```

+ 環境設定を検証します
  
  ```bash
  echo $TIDB_USER
  ```

  ```output
  '${LAB:SERVERLESS_CLUSTER_USERNAME}'
  ```

  ```bash
  echo $TIDB_PASSWORD
  ```

  ```output
  '${LAB:SERVERLESS_CLUSTER_PASSWORD}'
  ```

## Module 2: Javaを使用したTiDB基本操作

以下のJavaコードを実行して、TiDB Cloud Serverlessクラスターに接続し、いくつかの基本操作を行います。

### Step 1. TiDB Cloud Serverlessクラスターに接続する

+ 左側のVS Codeの**EXPLORER**セクションで、`DemoJdbcConnectionServerless.java`ファイルをクリックします。
  
+ `DemoJdbcConnectionServerless.java`の以下のJavaコードスニペットが、ラボのTiDB Cloud Serverlessクラスターに接続します。

  ```java
  ... 
  String tidbHost = System.getenv().getOrDefault("TIDB_HOST", "localhost");
  int tidbPort = Integer.parseInt(System.getenv().getOrDefault("TIDB_PORT", "4000"));
  String tidbUser = System.getenv().getOrDefault("TIDB_USER", "root");
  String tidbPassword = System.getenv().getOrDefault("TIDB_PASSWORD", "");
  String tidbDatabase = System.getenv().getOrDefault("TIDB_DATABASE", "test");
  
  String connectionUrl = "jdbc:mysql://" + tidbHost + ":" + tidbPort + "/" + tidbDatabase + 
          "?sslMode=VERIFY_IDENTITY&enabledTLSProtocols=TLSv1.2,TLSv1.3";

  connection = DriverManager.getConnection(connectionUrl, tidbUser, tidbPassword);
  ...
  ```

+ コードを実行して結果を確認できます。

  ```bash
  rm -f DemoJdbcConnectionServerless.class
  javac -cp mysql-connector-j-9.1.0.jar DemoJdbcConnectionServerless.java
  sleep 1
  java -cp :mysql-connector-j-9.1.0.jar DemoJdbcConnectionServerless
  ```
  ```output
  Connection established.
  Connection closed.
  ```

### Step 2. 基本的なCRUD操作

+ 左側のVS Codeの**EXPLORER**セクションで、`DemoJdbcExecute.java`ファイルをクリックし、ソースコードを簡単に確認できます。
  
+ 以下のコードスニペットでは、テーブルを作成し、データを挿入・クエリする方法の1つを示しています。

  ```java
  ...
  // Create table
  connection.createStatement().executeUpdate("CREATE TABLE t1 (id int PRIMARY KEY, name char(4))");

  // Describe table
  printResultSetStringString("DESCRIBE test.t1", connection);

  // Explain SQL
  printResultSetStringString("EXPLAIN SELECT * FROM test.t1", connection);

  // Select
  printResultSetStringString("SELECT * FROM test.t1", connection);

  // Try DML
  printResultSetStringString("INSERT INTO test.t1 VALUES (100, 'WXYZ'); INSERT INTO test.t1 VALUES (200, 'ABCD')", connection);

  // Select again
  printResultSetStringString("SELECT * FROM test.t1", connection);
  ...
  ```
  + 簡潔にするため、コード内で`SET tidb_multi_statement_mode='ON'`を設定し、1行で2つのSQLステートメントを実行しています。セキュリティリスク（SQLインジェクションなど）を理解してください。

+ コードを実行して結果を確認できます。

  ```bash
  rm -f DemoJdbcExecute.class
  javac -cp mysql-connector-j-9.1.0.jar DemoJdbcExecute.java
  sleep 1
  java -cp :mysql-connector-j-9.1.0.jar DemoJdbcExecute
  ```

  ```output
  Connection established.

  /* Executing: SET tidb_multi_statement_mode='ON'; */

  /* Executing: show variables like 'autocommit'; */
          Row#,  Variable_name, Value
          1) autocommit, ON

  /* Executing: describe test.t1; */
          Row#,  Field, Type
          1) id, int(11)
          2) name, char(4)

  /* Executing: explain select * from test.t1; */
          Row#,  id, estRows
          1) TableReader_5, 10000.00
          2) └─TableFullScan_4, 10000.00

  /* Executing: select * from test.t1; */
          Row#,  id, name

  /* Executing: insert into test.t1 values (100, 'WXYZ'); insert into test.t1 values (200, 'ABCD'); */

  /* Executing: select * from test.t1; */
          Row#,  id, name
          1) 100, WXYZ
          2) 200, ABCD
  Connection closed.
  ```

## Module 3: プリペアドステートメントとバッチ挿入の使用

プリペアドステートメントを使用すると、繰り返し実行するためのステートメントを事前にコンパイルできます。プリペアドステートメントはSQLインジェクションを防ぐのに役立ちます。さらに、クエリが長く複雑で、ネットワークがボトルネックになっている場合、プリペアドステートメントはクエリ実行パフォーマンスを向上させる可能性があります。

バッチ挿入により、ユーザーは単一のSQLステートメントで複数行のデータを挿入できます。

このモジュールでは、プリペアドステートメントとバッチ挿入の例を見ていきます。

### Step 1. プリペアドステートメントの使用

+ 左側のVS Codeの**EXPLORER**セクションで、`DemoJdbcPreparedStatement.java`ファイルをクリックし、コードファイルを読んでください。

+ プリペアドステートメントの使用には2つの方法があります。以下の最初のコードスニペットはサーバーサイドのプリペアドステートメントを示しています。2番目のコードブロックはクライアントサイドのプリペアドステートメントを示しています。この例では、実行時間が類似していることがわかります。

  ```java
  // Reuse PS
  connection.setAutoCommit(true);
  PreparedStatement update1_ps = connection.prepareStatement("UPDATE test.t1 SET name = ? WHERE id = 1");
  System.out.println(">>> Reuse PS Begin repeating update.");
  long s1 = System.currentTimeMillis();
  for (int i = 0; i < 200; i++) {
      update1_ps.setString(1, Integer.toString(i));
      update1_ps.executeUpdate();
  }
  update1_ps.close();
  ```

  ```java
  /**
  * Client side caching prepared statement, cache hit for prepareStatement and close.
  * Try set cachePrepStmts=false|true to see the difference on elapsed time.
  */
  PreparedStatement update2_ps = null;
  System.out.println(">>> Non-Reuse PS Begin repeating update.");
  long s2 = System.currentTimeMillis();
  for (int i = 0; i < 200; i++) {
      update2_ps = connection.prepareStatement("UPDATE test.t1 SET name = ? WHERE id = 1");
      update2_ps.setString(1, Integer.toString(i));
      update2_ps.executeUpdate();
      update2_ps.close();
  }
  ``` 

+ コードを実行して結果を確認できます。

  ```bash
  rm -f DemoJdbcPreparedStatement.class
  javac -cp mysql-connector-j-9.1.0.jar DemoJdbcPreparedStatement.java
  sleep 1
  java -cp :mysql-connector-j-9.1.0.jar DemoJdbcPreparedStatement
  ```
  ```output
  Connection established.
  >>> Reuse PS Begin repeating update.
  >>> End repeating update, elapsed: 4364(ms).
  >>> Non-Reuse PS Begin repeating update.
  >>> End repeating update, elapsed: 4225(ms).

  Executing query: select * from test.t1
          Row#, id, name
          1) 1, 199
  Turn on autocommit.
  Connection closed.
  ```

### Step 2. バッチ挿入の使用

+ 左側のVS Codeの**EXPLORER**セクションで、`DemoJdbcBatchInsert.java`ファイルをクリックします。

+ このステップでは、テーブルに1000行を挿入します。`rewriteBatchedStatements=true`と`rewriteBatchedStatements=false`の間のパフォーマンスを比較します。

  ```bash
  rm -f DemoJdbcBatchInsert.class
  javac -cp mysql-connector-j-9.1.0.jar DemoJdbcBatchInsert.java
  sleep 1
  java -cp :mysql-connector-j-9.1.0.jar DemoJdbcBatchInsert
  ```
  ```output
  Connection established.
  >>> Begin insert 1000 rows.
  >>> End batch insert,rewriteBatchedStatements=true,elapsed: 172 (ms).
  Connection established.
  >>> Begin insert 1000 rows.
  >>> End batch insert,rewriteBatchedStatements=false,elapsed: 13252 (ms).

  /* Executing query: select count(*), max(name) from test.t1_batchtest; */
          Row#, count(*), max(name)
          1) 1000, 999
  Turn on autocommit.
  Connection closed.
  ```

## Module 4: ホットスポットを避けるためにAUTO_INCREMENTの代わりにAUTO_RANDOMを使用

+ TiDBのような分散データベースでは、データはリージョンに分割され、複数のストレージノード（TiKVノード）に分散されます。

+ 主キーとして`AUTO_INCREMENT`を使用すると、以下の問題が発生する可能性があります：
  + `AUTO_INCREMENT`を使用すると、IDは順次（1、2、3、4、...）になります
  + 新しい挿入はすべて最新のIDが保存されている同じリージョンに移動します
  + これにより、1つのストレージノードがほとんどの書き込みトラフィックを処理する「ホットスポット」が作成されます
  + 残りのストレージノードは十分に活用されません
  + このボトルネックは書き込みスループットを大幅に制限する可能性があります

+ `AUTO_RANDOM`が解決策です：
  + `AUTO_RANDOM`は非順次で分散されたIDを生成します
  + シャードビットは異なるリージョン間で書き込みを分散するのに役立ちます
  + INSERTワークロードはすべてのストレージノード間でバランスが取れています
  + これにより、高同時実行シナリオでの書き込みスループットとスケーラビリティが向上します

  ```bash
  rm -f DemoJdbcAutoRandom.class
  javac -cp mysql-connector-j-9.1.0.jar DemoJdbcAutoRandom.java       
  sleep 1
  java -cp :mysql-connector-j-9.1.0.jar DemoJdbcAutoRandom
  ```
  ```output
  ...
  AUTO_RANDOM IDs (distributed to avoid hotspots):
  288230376151711745
  1441151880758558730
  3746994889972252677
  4323455642275676162
  4899916394579099654
  4899916394579099655
  6052837899185946628
  7493989779944505347
  7782220156096217097
  8070450532247928840

  AUTO_INCREMENT IDs (sequential, can cause hotspots):
  1
  2
  3
  4
  5
  6
  7
  8
  9
  10
  ...
  ```

## Module 5: オンラインDDLを使用してオンラインスキーマ変更を実行

+ このモジュールでは、進行中の**INSERTジョブ**が中断されることなく継続実行されることを保証しながら、**TiDB**で**オンラインスキーマ変更（DDL操作）**を実行する方法を示します。このプロセスの重要な課題は、スキーマ変更が一時的にトランザクションをブロックする際に発生する**エラーコード8028**を処理することです。このデモでは、スキーマ変更にもかかわらず**挿入ジョブ**が回復して継続できるようにする、このシナリオを管理する堅牢なアプローチを示します。

+ このモジュールは2つの主要な操作で構成されています：

1. **挿入ジョブ（`--insert`）**
   + 指定されたテーブルに新しいレコードを継続的に挿入します。
   + 同時DDL操作により発生する可能性のあるスキーマ変更例外（エラーコード**8028**）を処理します。
   + サービス可用性を維持するために、失敗した場合は挿入操作を再試行します。

2. **スキーマ変更（`--alter`）**
   + 挿入が進行中にテーブル構造を変更するために`ALTER TABLE`コマンドを実行します。
   + TiDBが**オンラインスキーマ変更**を許可することを示しますが、スキーマ変更によりトランザクションが一時的に失敗する可能性があります。
   + 進行中のトランザクションが適応する中で、新しいスキーマが正常に適用されることを保証します。

> [!TIP]
> **重要な実装詳細：**
>
> + **TiDBメタデータロックの処理**：スクリプトは**メタデータロックを無効化**（`SET GLOBAL tidb_enable_metadata_lock = OFF`）して、クエリをブロックせずにDDL変更を許可します。
> + **自動エラー処理**：挿入ジョブは**エラーコード8028を検出**し、クラッシュを防ぐために**失敗したトランザクションを再試行**します。
> + **同時実行**：スクリプトは**挿入ジョブ**と**スキーマ変更**を別々のターミナルで実行できるようにし、データベース更新が進行中のトランザクションと並行して発生する実際のシナリオをシミュレートします。

+ 最初に挿入ジョブを実行します。

  ```bash
  rm -f DemoJdbcOnlineDDL.class
  javac -cp mysql-connector-j-9.1.0.jar DemoJdbcOnlineDDL.java  
  sleep 1   
  java -cp :mysql-connector-j-9.1.0.jar DemoJdbcOnlineDDL --insert
  ```
  ```output
  ...
  Inserting record 0
  Inserting record 1
  Inserting record 2
  Inserting record 3
  Inserting record 4
  Inserting record 5
  ...
  ```

+ VS Codeで`+`ボタンをクリックして新しいターミナルを作成します。

    [comment]: <![90%](https://public-pingcap-innovation.s3.us-west-2.amazonaws.com/demo-405/405-add-terminal-0319.jpg)>

+ 新しいターミナルでスキーマ変更を実行します。

  ```bash
  source env.sh
  java -cp :mysql-connector-j-9.1.0.jar DemoJdbcOnlineDDL --alter
  ```
  ```output
  Executing ALTER TABLE on online_ddl_test
  Connected to TiDB: xxxxxx.xxxxxx@gateway01.us-west-2.prod.aws.tidbcloud.com:4000 using database test
  Table online_ddl_test exists, proceeding with ALTER TABLE
  Executing: ALTER TABLE online_ddl_test ADD COLUMN (ed VARCHAR(10) DEFAULT 'N/A')
  ALTER TABLE completed successfully in 0.39 seconds

  Updated table structure:
    id: int(11)
    k: int(11)
    c: char(120)
    pad: char(60)
    ed: varchar(10)
  ```

+ VS Codeで最初のターミナルに戻ると、挿入ジョブが再試行ログとともに継続していることがわかります。

  ```output
  ...
  Inserting record 105
  Inserting record 106
  Schema mutation encountered, retrying...
  Inserting record 107
  Inserting record 108
  ...
  ```

+ 挿入DMLもDDL操作もブロックされていません。

+ ターミナルで`CTRL + c`を使用して実行中の挿入プログラムを終了できます。

## Module 6: 並行制御 - 悲観的モード vs 楽観的モード

同時アクセスをサポートするすべてのデータベースでは、複数のトランザクションが同時に同じデータを更新しようとする競合のリスクがあります。適切に設計されたデータモデルはそのような競合の可能性を最小限に抑えることができますが、それらは可能性として残り、効果的に管理されなければなりません。データベースは同時データアクセスを処理するメカニズムを提供し、2つの主要なアプローチは悲観的と楽観的な並行制御です。TiDBは両方のアプローチをサポートしています。

このモジュールでは、TiDBでの悲観的ロックと楽観的ロックの動作の両方を示します。

### Step 1. 悲観的モードのデモコードを読む

+ 左側のVS Codeの**EXPLORER**セクションで、`DemoJdbcPessimisticLock.java`ファイルをクリックします。

+ 以下のコードスニペットでは、2つのスレッドが開始されます。各スレッドはTiDBへの接続を作成します。

  ```output
  ...
  for (int i = 0; i < 2; i++) {
      connections.add(DriverManager.getConnection(connection_string));
  }
  ...
  ```

+ メインメソッドでは、2つのスレッドが開始され、同じレコードを更新しようとします。スレッド1とスレッド2の両方で自動コミットが無効になっています。

  ```java
  new Thread(new DemoJdbcPessimisticLock.RowUpdater(0, id, 1, 6000)).start();
  new Thread(new DemoJdbcPessimisticLock.RowUpdater(1, id, 1000, 2000)).start();
  ```

### Step 2. 悲観的モードでデモコードを実行

+ 悲観的モードでデモコードを実行し、出力を観察します。スレッド2はスレッド1がコミットするまでコミットできないことに注意してください。可能な出力の1つを示します。

  ```bash
  rm -f DemoJdbcTxPessimisticLock.class
  javac -cp mysql-connector-j-9.1.0.jar DemoJdbcPessimisticLock.java
  sleep 1
  java -cp :mysql-connector-j-9.1.0.jar DemoJdbcPessimisticLock
  ```
  ```output
  Connection established.
  Connection B session started
  Connection A session started
  Connection A session: BEGIN PESSIMISTIC
  Connection A session: UPDATE test_tx_pessimistic SET name = 'Connection A' WHERE id = 5188146770730811393
  Connection B session: BEGIN PESSIMISTIC
  Connection B session: UPDATE test_tx_pessimistic SET name = 'Connection B' WHERE id = 5188146770730811393
  Connection A session: Commit
  Connection A session: Checking result

  /* Executing query: select id, name from test_tx_pessimistic; */
          Row#, id, name
          1) 5188146770730811393, Connection A

  Connection B session: Commit
  Connection B session: Checking result

  /* Executing query: select id, name from test_tx_pessimistic; */
          Row#, id, name
          1) 5188146770730811393, Connection B
  ```

### Step 3. 楽観的モードのデモコードを読む

+ 左側のVS Codeの**EXPLORER**セクションで、`DemoJdbcOptimisticLock.java`ファイルをクリックします。

+ 悲観的モードのコードと同様に、2つのスレッドが開始され、同時に同じ行を更新しようとします。

  ```java
  new Thread(new DemoJdbcOptimisticLock.RowUpdater(0, id, retryCommit, 1, 6000, 1000)).start();
  new Thread(new DemoJdbcOptimisticLock.RowUpdater(1, id, retryCommit, 1000, 2000, 9000)).start();
  ```

+ 競合の場合、簡単な再試行が実装されています。
  
  ```java
  if (this.retryCommit) {
    try {
        Thread.sleep(this.waitBefore2ndCommit);
    } catch (InterruptedException ie) {
        ie.printStackTrace();
    }
    try {
        System.out.println(connectionTags[this.connectionNo] + " session: " + "Commit");
        Statement s = c.createStatement();
        s.executeUpdate("UPDATE test_tx_optimistic SET name = '" + connectionTags[this.connectionNo]
                + "' WHERE id = " + rowid);
        c.commit();
    } catch (SQLException e1) {
        e1.printStackTrace();
    }
  }
  ```

### Step 4. 楽観的モードでデモを実行

+ 以下のコマンドを使用して楽観的ロックのデモコードを実行できます。最初に`no-retry`でコードを実行し、次に`retry`で実行します。可能な出力を示します。
  
  ```bash
  rm -f DemoJdbcOptimisticLock.class
  javac -cp mysql-connector-j-9.1.0.jar DemoJdbcOptimisticLock.java
  sleep 1
  java -cp :mysql-connector-j-9.1.0.jar DemoJdbcOptimisticLock no-retry
  ``` 

  ```output
  Connection established.
  Connection A session started
  Connection B session started
  Connection A session: BEGIN OPTIMISTIC
  Connection A session: UPDATE test_tx_optimistic SET name = 'Connection A' WHERE id = 4899916394579099649
  Connection B session: BEGIN OPTIMISTIC
  Connection B session: UPDATE test_tx_optimistic SET name = 'Connection B' WHERE id = 4899916394579099649
  Connection B session: Commit
  Connection B session: Checking result

  /* Executing query: select id, name from test_tx_optimistic; */
          Row#, id, name
          1) 4899916394579099649, Connection B

  Connection A session: Commit
  Connection A ErrorCode: 9007
  Connection A SQLState: HY000
  Connection A Error: java.sql.SQLException: Write conflict, txnStartTS=458625318024118280, conflictStartTS=458625318286262286, conflictCommitTS=458625318889193486, key=????, reason=Optimistic [try again later]
  < Session in Connection A RAISED THE EXCEPTION !!! >
  Connection A session: Checking result

  /* Executing query: select id, name from test_tx_optimistic; */
          Row#, id, name
          1) 4899916394579099649, Connection B
  ```

  ```bash
  rm -f DemoJdbcOptimisticLock.class
  javac -cp mysql-connector-j-9.1.0.jar DemoJdbcOptimisticLock.java
  sleep 1
  java -cp :mysql-connector-j-9.1.0.jar DemoJdbcOptimisticLock retry
  ```

  ```output
  Connection established.
  Connection A session started
  Connection B session started
  Connection A session: BEGIN OPTIMISTIC
  Connection A session: UPDATE test_tx_optimistic SET name = 'Connection A' WHERE id = 2017612633061982209
  Connection B session: BEGIN OPTIMISTIC
  Connection B session: UPDATE test_tx_optimistic SET name = 'Connection B' WHERE id = 2017612633061982209
  Connection B session: Commit
  Connection B session: Checking result

  /* Executing query: select id, name from test_tx_optimistic; */
          Row#, id, name
          1) 2017612633061982209, Connection B

  Connection A session: Commit
  Connection A ErrorCode: 9007
  Connection A SQLState: HY000
  Connection A Error: java.sql.SQLException: Write conflict, txnStartTS=458625338366492677, conflictStartTS=458625338628898820, conflictCommitTS=458625339231567877, key=????, reason=Optimistic [try again later]
  < Session in Connection A RAISED THE EXCEPTION !!! >
  Connection A session: Commit
  Connection A session: Checking result

  /* Executing query: select id, name from test_tx_optimistic; */
          Row#, id, name
          1) 2017612633061982209, Connection A
  ```

### Step 5. TiDB Cloudコンソールに移動して独自の旅を始める

+ ハンズオンラボはここで終了します。今すぐ[ここ](https://tidbcloud.com/clusters)でTiDB Cloudの旅を始めることができます。このラボで独自のフリーティアクラスターを使用していた場合、同じページでクラスター名${LAB:SERVERLESS_CLUSTER_NAME}でリストされているのが見つかります。

## 結論

このラボでは、Javaコードを使用してTiDBクラスターに接続する方法を学びました。また、JDBC ライブラリを使用してTiDBと対話する方法も学びました。接続、挿入、1つのクエリ、複数のクエリが含まれます。

次に、プリペアドステートメントとバッチ挿入を通じて、アプリケーションのパフォーマンスを向上させる方法を学びました。

その後、ホットスポットを避けるために`AUTO_RANDOM`を使用する方法と、ビジネスを中断することなくオンラインスキーマ変更を実行する方法を学びました。

最後に、TiDBでの並行制御のための悲観的ロックと楽観的ロックを示しました。

```hbsptform
{
  "ui": {
    "titleLevel": 3
  },
  "portalId": "4466002",
  "formId": "89fa8368-eba1-4b84-b91e-eb9eb6613ddf",
  "region": "na1",
  "sfdcCampaignId": "701RC00000VqCODYA3"
}
```