DAOの中でIDを取得してDBに挿入し、そのIDを使って次の処理をするJava・MySQLサンプル
こんにちは。ゆうせいです。
今回は、「DAOの中でIDを付番し、データベースに挿入し、そのIDを使って次の処理をする」とはどういうことかを、JavaとMySQL前提で解説します。
先に大事な結論です。
MySQLでは、IDをJava側で無理に作るより、AUTO_INCREMENTでデータベースに採番させるのが安全です。
DAOでは、INSERT後に発行されたIDを受け取り、そのIDを使って次のINSERTや処理を行います。
たとえば、次のような流れです。
| 順番 | 処理 | 例 |
|---|---|---|
| 1 | carsテーブルに車を登録する | プリウスを登録する |
| 2 | MySQLがcar_idを自動採番する | car_id = 1 が発行される |
| 3 | DAOが発行されたcar_idを取得する | Java側で1を受け取る |
| 4 | 取得したcar_idを使って次の処理をする | 登録ログテーブルにcar_id = 1で登録する |
たとえるなら、学校で新入生を登録するときに、まず学校側が出席番号を発行します。
そのあと、その出席番号を使って、出席簿、成績表、部活動名簿を作るようなものです。
今回作るサンプルの内容
今回は、次の2つのテーブルを使います。
| テーブル名 | 役割 |
|---|---|
| cars | 車の基本情報を保存する |
| car_register_logs | 車を登録した履歴を保存する |
処理の流れは次のとおりです。
| 処理 | 内容 |
|---|---|
| 1 | carsテーブルに車情報をINSERTする |
| 2 | MySQLが自動採番した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_id | name | price |
|---|---|---|
| 1 | プリウス | 2500000 |
すると、car_register_logsには次のようなデータが入ります。
| log_id | car_id | message |
|---|---|---|
| 1 | 1 | 車情報を登録しました。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への登録もなかったことにできます。
処理の流れを新人向けに整理
| 順番 | コード上の処理 | 意味 |
|---|---|---|
| 1 | getConnection() | DBに接続する |
| 2 | setAutoCommit(false) | トランザクションを開始する |
| 3 | carsにINSERT | 車情報を登録する |
| 4 | getGeneratedKeys() | 発行されたcar_idを取得する |
| 5 | car.setCarId(generatedCarId) | DTOにIDをセットする |
| 6 | car_register_logsにINSERT | 取得したcar_idでログを登録する |
| 7 | commit() | 2つのINSERTを確定する |
| 8 | 失敗時はrollback() | 途中までの処理を取り消す |
なぜDAOでこの処理を書くのか
DAOは、Data Access Objectの略です。
DAOは、DBアクセスを担当するクラスです。
今回のように、INSERTして、発行されたIDを取得して、別テーブルにINSERTする処理はDB操作そのものです。
そのため、DAOに書くのは自然です。
ただし、実務では注意点があります。
「車を登録したらログも登録する」という業務ルールまでDAOにたくさん書きすぎると、DAOが大きくなりすぎます。
本格的な設計では、Serviceクラスで業務の流れを管理し、DAOは個別のDB操作に分けることも多いです。
| クラス | 役割 |
|---|---|
| DAO | SQLを実行してDBにアクセスする |
| Service | 業務の流れを組み立てる |
| DTO | データを運ぶ |
今回のサンプルは、DAOの中で一連の流れを理解するために、1つのDAOメソッドにまとめています。
学習用としては、この形で十分です。
Java側でIDを手動採番しないほうがよい理由
新人エンジニアが迷いやすいポイントとして、「DAOの中でIDを付番するなら、Javaで次のIDを作ればよいのでは?」という考えがあります。
たとえば、次のような考え方です。
SELECT MAX(car_id) + 1 FROM carsこの方法は危険です。
理由は、同時実行に弱いからです。
| タイミング | ユーザーA | ユーザーB |
|---|---|---|
| 1 | MAX(car_id) + 1で10を取得 | MAX(car_id) + 1で10を取得 |
| 2 | car_id = 10でINSERT | car_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の両方にデータが入ることを確認してみましょう!
セイ・コンサルティング・グループでは新人エンジニア研修のアシスタント講師を募集しています。
投稿者プロフィール


