トランザクション【transaction】とは、複数の SQL文によるデータ更新を1つの処理としてまとめてデータベースに反映させる仕組みです。

もしも、トランザクションが無いと以下のように困ったことが起こりえます。

例えば、借金返済のために私の銀行口座から皆さんの銀行口座に100万円を振り込むとします。

つまり、私の銀行の残高を-100万円して、皆さんの銀行の残高を+100万円します。この2つの処理は1セットですね。

もしも、私の銀行の残高を-100万円することには成功して、皆さんの銀行の残高を+100万円することに失敗したとなると困ったことになります。

そうならないように、複数の SQL文によるデータ更新を1つの処理として、「全て成功」か「すべて失敗」にするための仕組みがトランザクションです。

この場合のSQLは以下のようになります。(私のidが1であなたのidが2であるとします)

データの不整合を防ぐのがトランザクションの役割です。


ここからの操作は、まずはMySQL Workbenchで行います。その後、Javaから実行します。

また、その際、下図8.8のようにAuto Commitモードをオフにして行います。

なぜなら、Auto Commitモードがオンですと、1行実行するたびに処理がコミット(確定)してしまうからです。
※ただし、この実験が終わったらオートコミットモードをオフに戻しておいてください。

図 オートコミットモード

まずはMySQLの復習になりますが、以下transaction.sqlのソースコードを各自解析してください。

START TRANSACTION;
    
INSERT INTO sales(sale_id, car_id, customer_id, saleDateTime) VALUES(51, 5, 1, '2021-12-27 12:00:00');
    
UPDATE customers SET points = 100 WHERE customer_id = 1;

ROLLBACK;
  • 3行目のINSERT文は何をしていますか?
あなたの答え:
  • 5行目のUPDATE文は何をしていますか?
あなたの答え:

車をご購入いただいたお客様に100ポイント付けているという処理でした。ここで例えば、自動車の購入処理だけが行われて、ポイントを付ける処理が失敗する、あるいはその逆があったとしたらどうでしょうか?

そうならないようにするのがトランザクションです。

この4行を1行ずつ実行する処理をやってみます。(あるいは講師が実演しますので以下の4つの質問に答えてください)

  • START TRANSACTIONは成功しましたか?
あなたの答え:
  • INSERT文を実行した後にsalesテーブルを確認したらデータは挿入されていましたか?
あなたの答え:
  • UPDATE文を実行した後にcustomersテーブルを確認したら誰のポイントがどうなっていましたか?
あなたの答え:
  • ROLLBACKを実行した後上記2つの操作結果はどうなっていましたか?
あなたの答え:
  • 4行の処理を一度に実行したらどうなりましたか?
あなたの答え:

例えば、重複した商品(車)を登録しないようなJavaプログラムは以下Insert2NoDuplication.javaのようになります。

package p08;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class Insert2NoDuplication {

    public static final String CONNECT_STRING = "jdbc:mysql://localhost:3306/sip_a?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B9&rewriteBatchedStatements=true";
    public static final String USERID = "newuser";
    public static final String PASSWORD = "0";
    public static final String SQL = "INSERT INTO cars(name, price, end_of_life_dates) VALUES(?, ?, ? )";

    public static Connection con = null;
    public static PreparedStatement ps = null;
    public static ResultSet rs = null;

    public static void main(String[] args) {

        try {
            con = DriverManager.getConnection(
                    CONNECT_STRING, USERID, PASSWORD);

            con.setAutoCommit(false);

            ps = con.prepareStatement(SQL);

            String carName = "ジープ";

            ps.setString(1, carName);
            ps.setInt(2, 2000000);
            ps.setString(3, null);

            ps.executeUpdate();

            ps = con.prepareStatement(
                    "SELECT * FROM cars where name = ?");
            ps.setString(1, carName);

            rs = ps.executeQuery();
            int cnt = 0;
            while (rs.next()) {
                cnt++;
            }

            if (cnt == 1) {
                con.commit();
                System.out.println("商品を追加しました");
            } else {
                con.rollback();
                System.out.println("商品はすでに登録されています");
            }

            con.setAutoCommit(true);

        } catch (SQLException ex) {
            System.err.println("データベースへの接続時に問題が発生しました。");
        } finally {
            try {
                if (ps != null) {
                    ps.close();
                }
                if (con != null) {
                    con.close();
                }
            } catch (SQLException e) {
                System.err
                        .println("データベースからの切断時に問題が発生しました。");
            }
        }
    }
}

最後までお読みいただきありがとうございます。