019 例外と例外処理(トランザクションとロールバック) 031 解答例

以下は、Javaでの簡単な銀行取引プログラムの解答例と解説です。この例では、送金元のアカウントに十分な残高がない場合にトランザクションをロールバックし、エラーメッセージを表示します。データベースのトランザクションは try-with-resources 文を使用して確実にクローズされるようにします。

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

// カスタム例外クラス
class InsufficientFundsException extends Exception {
    public InsufficientFundsException(String message) {
        super(message);
    }
}

public class BankTransactionExample {
    private static final String DB_URL = "jdbc:mysql://localhost:3306/bank";
    private static final String DB_USER = "your_username";
    private static final String DB_PASSWORD = "your_password";

    // 銀行取引メソッド
    public static void transferFunds(int fromAccount, int toAccount, double amount) throws SQLException, InsufficientFundsException {
        // データベースへの接続
        try (Connection connection = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD)) {
            // トランザクションの開始
            connection.setAutoCommit(false);

            // 送金元の残高取得
            double balance = getAccountBalance(connection, fromAccount);

            // 残高が十分か確認
            if (balance < amount) {
                // 残高不足の場合、トランザクションをロールバックし例外をスロー
                connection.rollback();
                throw new InsufficientFundsException("送金元アカウントの残高が不足しています。");
            }

            // 送金元アカウントから引き落とし
            updateAccountBalance(connection, fromAccount, balance - amount);

            // 送金先アカウントに入金
            updateAccountBalance(connection, toAccount, getAccountBalance(connection, toAccount) + amount);

            // トランザクションをコミット
            connection.commit();
        }
    }

    // 指定されたアカウントの残高を取得するメソッド
    private static double getAccountBalance(Connection connection, int accountNumber) throws SQLException {
        String query = "SELECT balance FROM accounts WHERE account_number = ?";
        try (PreparedStatement preparedStatement = connection.prepareStatement(query)) {
            preparedStatement.setInt(1, accountNumber);
            var resultSet = preparedStatement.executeQuery();
            if (resultSet.next()) {
                return resultSet.getDouble("balance");
            }
            throw new SQLException("アカウントが見つかりません。");
        }
    }

    // 指定されたアカウントの残高を更新するメソッド
    private static void updateAccountBalance(Connection connection, int accountNumber, double newBalance) throws SQLException {
        String query = "UPDATE accounts SET balance = ? WHERE account_number = ?";
        try (PreparedStatement preparedStatement = connection.prepareStatement(query)) {
            preparedStatement.setDouble(1, newBalance);
            preparedStatement.setInt(2, accountNumber);
            int affectedRows = preparedStatement.executeUpdate();
            if (affectedRows == 0) {
                throw new SQLException("アカウントが見つかりません。");
            }
        }
    }

    public static void main(String[] args) {
        try {
            // ユーザーがアカウント間で資金を移動する例
            transferFunds(123456, 789012, 1000.0);
            System.out.println("取引が成功しました。");
        } catch (SQLException | InsufficientFundsException e) {
            System.err.println("取引エラー: " + e.getMessage());
        }
    }
}

この例では、InsufficientFundsException が送金元アカウントの残高不足時にスローされます。トランザクションの管理が重要であり、残高が十分でない場合はトランザクションをロールバックし、変更を元に戻します。

トランザクションとロールバック

1. トランザクションの概要

トランザクションは、データベースの一連の操作を意味します。これらの操作は、成功するか失敗するかのいずれかで、トランザクション全体が原子性を持ちます。原子性とは、トランザクションが全体として成功するか、全体として失敗するかのいずれかであることを指します。原子性が保たれない場合、途中でエラーが発生してもトランザクションの一部がデータベースに変更を加えた可能性があります。

2. トランザクションの管理

トランザクションの管理はデータベース接続を使用して行います。トランザクションは通常、以下の手順で実行されます。

  • トランザクションの開始: トランザクション内の操作が始まることをデータベースに通知します。これにより、トランザクションが始まり、それに関連する操作が原子的になります。
  • トランザクションのコミット: トランザクションが成功した場合、変更をデータベースに確定させます。これにより、トランザクション内で行われた変更がデータベースに永続的に反映されます。
  • トランザクションのロールバック: トランザクションが失敗した場合、変更を取り消し、トランザクション内で行われたすべての変更を元に戻します。

3. トランザクションとロールバックの重要性

トランザクションとロールバックはデータベースの一貫性を保つために非常に重要です。トランザクション内でエラーが発生した場合、ロールバックを行うことで、変更を取り消し、データベースを前の状態に戻すことができます。これにより、データの整合性が保たれ、データベースが信頼性を維持します。

4. サンプルプログラムの解説

サンプルプログラムでは、transferFunds メソッド内でトランザクションが開始され、成功するか失敗するかが判断されています。残高不足の場合、InsufficientFundsException がスローされ、トランザクションがロールバックされます。ロールバックは connection.rollback() で行います。

try (Connection connection = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD)) {
    // トランザクションの開始
    connection.setAutoCommit(false);

    // 送金元の残高取得
    double balance = getAccountBalance(connection, fromAccount);

    // 残高が十分か確認
    if (balance < amount) {
        // 残高不足の場合、トランザクションをロールバックし例外をスロー
        connection.rollback();
        throw new InsufficientFundsException("送金元アカウントの残高が不足しています。");
    }

    // ...(以下略)
    
    // トランザクションをコミット
    connection.commit();
}

このように、トランザクションとロールバックを使用することで、データベースの一貫性を確保し、エラーが発生した場合に適切に対処できます。

トランザクションとロールバックはデータベース処理において欠かせない概念であり、データの整合性や信頼性を確保するために重要な役割を果たします。トランザクションは一連のデータベース操作を原子的にまとめ、全体が成功するか失敗するかを保証します。ロールバックはトランザクション内でエラーが発生した場合に変更を取り消し、データベースを前の状態に戻します。

データベース取引においては、エラーが発生する可能性があるため、トランザクションとロールバックの理解と適切な利用が不可欠です。これにより、データベースが一貫性を保ち、システムが信頼性を確保できます。

適切なトランザクション管理はデータベース処理の安定性と信頼性に直結し、開発者がシステム全体の一貫性を維持できるようサポートします。

「019 例外と例外処理」問題集リスト