DAOの中でIDを取得してDBに挿入し、そのIDを使って次の処理をするJava・MySQLサンプル

こんにちは。ゆうせいです。

今回は、「DAOの中でIDを付番し、データベースに挿入し、そのIDを使って次の処理をする」とはどういうことかを、JavaとMySQL前提で解説します。

先に大事な結論です。

MySQLでは、IDをJava側で無理に作るより、AUTO_INCREMENTでデータベースに採番させるのが安全です。

DAOでは、INSERT後に発行されたIDを受け取り、そのIDを使って次のINSERTや処理を行います。

たとえば、次のような流れです。

順番処理
1carsテーブルに車を登録するプリウスを登録する
2MySQLがcar_idを自動採番するcar_id = 1 が発行される
3DAOが発行されたcar_idを取得するJava側で1を受け取る
4取得したcar_idを使って次の処理をする登録ログテーブルにcar_id = 1で登録する

たとえるなら、学校で新入生を登録するときに、まず学校側が出席番号を発行します。

そのあと、その出席番号を使って、出席簿、成績表、部活動名簿を作るようなものです。

今回作るサンプルの内容

今回は、次の2つのテーブルを使います。

テーブル名役割
cars車の基本情報を保存する
car_register_logs車を登録した履歴を保存する

処理の流れは次のとおりです。

処理内容
1carsテーブルに車情報をINSERTする
2MySQLが自動採番したcar_idをJavaで取得する
3取得したcar_idを使ってcar_register_logsテーブルにログをINSERTする
4すべて成功したらcommitする
5途中で失敗したらrollbackする

ここで重要なのが、トランザクションです。

トランザクションとは、複数のDB処理を「全部成功するか、全部なかったことにするか」で管理する仕組みです。

たとえば、車情報だけ登録されて、登録ログの保存に失敗すると困りますよね。

そのような中途半端な状態を防ぐために、commitとrollbackを使います。

テーブル作成SQL

まず、MySQL側にテーブルを作成します。

CREATE TABLE cars (
    car_id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100) NOT NULL,
    price INT NOT NULL,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE car_register_logs (
    log_id INT PRIMARY KEY AUTO_INCREMENT,
    car_id INT NOT NULL,
    message VARCHAR(255) NOT NULL,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (car_id) REFERENCES cars(car_id)
);




carsテーブルのcar_idにはAUTO_INCREMENTを付けています。

AUTO_INCREMENTとは、MySQLが自動で連番を付けてくれる仕組みです。

1件目なら1、2件目なら2、3件目なら3のように、自動でIDが増えていきます。

新人エンジニアがやりがちな危険な方法として、次のような考え方があります。

SELECT MAX(car_id) + 1 FROM cars




この方法はおすすめしません。

なぜなら、同時に2人が登録したときに、同じIDを取ってしまう可能性があるからです。

文化祭で整理券を配るときに、2人の係が同時に「次は10番」と言ってしまうようなものです。

整理券の番号は、1か所で管理したほうが安全ですよね。

DBのIDも同じです。

MySQLのAUTO_INCREMENTに任せるのが基本です。

CarDtoクラス

次に、車情報を運ぶためのCarDtoを作ります。

DTOとは、Data Transfer Objectの略です。

データを運ぶための入れ物です。

public class CarDto {

    private int carId;
    private String name;
    private int price;

    public int getCarId() {
        return carId;
    }

    public void setCarId(int carId) {
        this.carId = carId;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "CarDto{" +
                "carId=" + carId +
                ", name='" + name + '\'' +
                ", price=" + price +
                '}';
    }
}




DTOは、宅配便の箱のようなものです。

nameやpriceというデータを箱に入れてDAOに渡します。

DAOで登録が終わったら、発行されたcarIdを箱に戻すこともできます。

SuperDaoクラス

DB接続を共通化するために、SuperDaoを用意します。





getConnectionメソッドは、JavaアプリとMySQLをつなぐための接続を返します。

Connectionは、JavaとDBの電話回線のようなものです。

DAOはこのConnectionを使ってSQLを実行します。

CarsDaoクラス

次に、今回の中心となるCarsDaoです。

このDAOでは、carsテーブルにINSERTし、発行されたcar_idを取得し、そのcar_idを使ってcar_register_logsにINSERTします。

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

public class SuperDao {

    private static final String DB_URI =
            "jdbc:mysql://localhost:3306/sip_a?"
            + "characterEncoding=utf8&"
            + "useSSL=false&"
            + "serverTimezone=Asia/Tokyo";

    private static final String DB_USER = "newuser";
    private static final String DB_PASS = "0";

    protected Connection getConnection() throws SQLException {
        return DriverManager.getConnection(DB_URI, DB_USER, DB_PASS);
    }
}




mainメソッドで動かすサンプル

実際にCarsDaoを呼び出してみます。

public class Main {

    public static void main(String[] args) {

        CarDto car = new CarDto();
        car.setName("プリウス");
        car.setPrice(2500000);

        CarsDao carsDao = new CarsDao();

        int newCarId = carsDao.insertCarAndLog(car);

        System.out.println("登録されたcar_id: " + newCarId);
        System.out.println("登録後のCarDto: " + car);
    }
}




このmainメソッドを実行すると、carsテーブルに車情報が登録されます。

そのあと、MySQLが発行したcar_idをJava側で取得し、car_register_logsテーブルにも登録ログが作られます。

出力イメージは次のようになります。

登録されたcar_id: 1
登録後のCarDto: CarDto{carId=1, name='プリウス', price=2500000}





コードの重要ポイント1:AUTO_INCREMENTでIDを採番する

今回のコードでは、Java側でcar_idを作っていません。

MySQLが自動でcar_idを作ります。

car_id INT PRIMARY KEY AUTO_INCREMENT




この設計により、IDの重複を防ぎやすくなります。

新人エンジニアは、まずこの考え方を覚えてください。

方法おすすめ度理由
Java側でMAX(id) + 1する低い同時登録でID重複の危険がある
MySQLのAUTO_INCREMENTを使う高いDBが安全に連番を管理してくれる

ID採番は、基本的にDBに任せましょう。

DAOの役割は、DBが採番したIDを受け取って、次の処理に使うことです。

コードの重要ポイント2:RETURN_GENERATED_KEYS

発行されたIDを取得するために、次の部分を使っています。

con.prepareStatement(insertCarSql, Statement.RETURN_GENERATED_KEYS)




Statement.RETURN_GENERATED_KEYSは、「INSERTしたあとに、自動生成されたキーを取得したいです」という指定です。

この指定がないと、getGeneratedKeysでIDを取れない場合があります。

たとえるなら、レストランで注文したあとに「注文番号もください」と言っているようなものです。

注文だけして番号を受け取らないと、あとで料理を受け取りにくいですよね。

INSERT後に発行されたIDを使うなら、RETURN_GENERATED_KEYSを指定します。

コードの重要ポイント3:getGeneratedKeysでIDを取得する

INSERT後に、次のコードで発行されたcar_idを取得しています。

try (ResultSet generatedKeys = carPs.getGeneratedKeys()) {
    if (generatedKeys.next()) {
        generatedCarId = generatedKeys.getInt(1);
        car.setCarId(generatedCarId);
    } else {
        throw new SQLException("採番されたcar_idを取得できませんでした。");
    }
}




getGeneratedKeysは、DBが自動採番した値をResultSetとして返します。

今回の場合、取得できる値はcar_idです。

generatedKeys.getInt(1)で、1列目の値を取得しています。

取得したIDは、次の2か所で使っています。

使い道コード
JavaのDTOにセットするcar.setCarId(generatedCarId);
登録ログに使うlogPs.setInt(1, generatedCarId);

DTOにcarIdをセットしておくと、呼び出し元でも登録後のIDを使えます。

コードの重要ポイント4:取得したIDを次のINSERTに使う

取得したgeneratedCarIdを、登録ログのINSERTで使っています。

try (PreparedStatement logPs = con.prepareStatement(insertLogSql)) {
    logPs.setInt(1, generatedCarId);
    logPs.setString(2, "車情報を登録しました。car_id=" + generatedCarId);
    logPs.executeUpdate();
}




この処理により、car_register_logsテーブルには、どの車に対するログなのかが保存されます。

たとえば、carsに次のデータが入ったとします。

car_idnameprice
1プリウス2500000

すると、car_register_logsには次のようなデータが入ります。

log_idcar_idmessage
11車情報を登録しました。car_id=1

car_register_logs.car_idを見ると、cars.car_id = 1の車に関するログだとわかります。

これは、注文テーブルと注文明細テーブルの関係にも似ています。

まず注文を作る。

注文IDが発行される。

その注文IDを使って注文明細を登録する。

実務では、この流れがかなりよく出てきます。

コードの重要ポイント5:トランザクションで守る

今回の処理では、次の2つのINSERTを行っています。

INSERT先内容
cars車情報
car_register_logs登録ログ

この2つは、セットで成功してほしい処理です。

そのため、次のように自動コミットを止めています。

con.setAutoCommit(false);




自動コミットとは、SQLを1回実行するたびにすぐ確定する設定です。

今回は複数のSQLを1つのまとまりとして扱いたいので、falseにしています。

すべて成功したら、commitします。

con.commit();




途中で失敗したら、rollbackします。

con.rollback();




commitは「確定」です。

rollbackは「取り消し」です。

ゲームでたとえるなら、commitはセーブです。

rollbackは、セーブ前の状態に戻すことです。

途中でログ登録に失敗した場合、carsだけ登録されると中途半端です。

rollbackすれば、carsへの登録もなかったことにできます。

処理の流れを新人向けに整理

順番コード上の処理意味
1getConnection()DBに接続する
2setAutoCommit(false)トランザクションを開始する
3carsにINSERT車情報を登録する
4getGeneratedKeys()発行されたcar_idを取得する
5car.setCarId(generatedCarId)DTOにIDをセットする
6car_register_logsにINSERT取得したcar_idでログを登録する
7commit()2つのINSERTを確定する
8失敗時はrollback()途中までの処理を取り消す

なぜDAOでこの処理を書くのか

DAOは、Data Access Objectの略です。

DAOは、DBアクセスを担当するクラスです。

今回のように、INSERTして、発行されたIDを取得して、別テーブルにINSERTする処理はDB操作そのものです。

そのため、DAOに書くのは自然です。

ただし、実務では注意点があります。

「車を登録したらログも登録する」という業務ルールまでDAOにたくさん書きすぎると、DAOが大きくなりすぎます。

本格的な設計では、Serviceクラスで業務の流れを管理し、DAOは個別のDB操作に分けることも多いです。

クラス役割
DAOSQLを実行してDBにアクセスする
Service業務の流れを組み立てる
DTOデータを運ぶ

今回のサンプルは、DAOの中で一連の流れを理解するために、1つのDAOメソッドにまとめています。

学習用としては、この形で十分です。

Java側でIDを手動採番しないほうがよい理由

新人エンジニアが迷いやすいポイントとして、「DAOの中でIDを付番するなら、Javaで次のIDを作ればよいのでは?」という考えがあります。

たとえば、次のような考え方です。

SELECT MAX(car_id) + 1 FROM cars




この方法は危険です。

理由は、同時実行に弱いからです。

タイミングユーザーAユーザーB
1MAX(car_id) + 1で10を取得MAX(car_id) + 1で10を取得
2car_id = 10でINSERTcar_id = 10でINSERTしようとする
3成功主キー重複で失敗

2人が同時に同じ番号を取ってしまう可能性があります。

整理券を手作業で配っていると、係の人同士で番号がかぶるようなものです。

AUTO_INCREMENTなら、MySQLが安全に番号を管理してくれます。

そのため、通常は次の形にしましょう。

やりたいことおすすめの方法
IDを発行するMySQLのAUTO_INCREMENT
発行されたIDをJavaで使うgetGeneratedKeys()
複数INSERTをまとめるトランザクション

このサンプルを少し実務寄りにするなら

今回のサンプルは学習用にわかりやすくしています。

実務では、さらに次の点を考えるとよいです。

改善ポイント内容
入力チェックnameが空でないか、priceが0以上か確認する
例外処理RuntimeExceptionにするか、独自例外にするか検討する
ログ出力e.printStackTraceではなくロガーを使う
Service分離業務の流れをServiceに移す
テスト登録成功、ログ登録失敗、rollback確認をテストする

特に、Service分離は次の段階で学ぶとよいです。

DAOにすべて詰め込むと、DAOが大きくなりすぎます。

最初はDAOで流れを理解し、慣れてきたらServiceとDAOに役割を分けてください。

まとめ

DAOの中でIDを付番して次の処理に使う、という話は、MySQLでは次のように考えるのが基本です。

ポイント内容
ID採番JavaではなくMySQLのAUTO_INCREMENTに任せる
ID取得Statement.RETURN_GENERATED_KEYSとgetGeneratedKeysを使う
次の処理取得したIDを使って別テーブルにINSERTする
安全性複数のDB処理はトランザクションでまとめる
失敗時rollbackして中途半端な登録を防ぐ

一言でまとめるなら、IDはDBに採番させ、DAOでそのIDを受け取り、次のSQLに使うという流れです。

新人エンジニアは、まずAUTO_INCREMENT、PreparedStatement、RETURN_GENERATED_KEYS、getGeneratedKeys、commit、rollbackをセットで覚えてください。

今後の学習では、今回のDAO内完結パターンを理解したあと、Serviceで業務の流れを管理し、DAOはINSERTやSELECTなどのDB操作に専念させる設計へ進むとよいです。まずは今回のコードを動かして、carsとcar_register_logsの両方にデータが入ることを確認してみましょう!

セイ・コンサルティング・グループでは新人エンジニア研修のアシスタント講師を募集しています。

投稿者プロフィール

山崎講師
山崎講師代表取締役
セイ・コンサルティング・グループ株式会社代表取締役。
岐阜県出身。
2000年創業、2004年会社設立。
IT企業向け人材育成研修歴業界歴20年以上。
すべての無駄を省いた費用対効果の高い「筋肉質」な研修を提供します!
この記事に間違い等ありましたらぜひお知らせください。

学生時代は趣味と実益を兼ねてリゾートバイトにいそしむ。長野県白馬村に始まり、志賀高原でのスキーインストラクター、沖縄石垣島、北海道トマム。高じてオーストラリアのゴールドコーストでツアーガイドなど。現在は野菜作りにはまっている。