エキサイト株式会社 21卒 バックエンドエンジニアの山縣です。 MyBatisとFreeMarkerを使用した環境において、INSERT文を実行したときにAuto IncrementされたIDが取得できない問題とその解決策についてまとめました。
MyBatisとFreeMarkerを使用した環境
現在のチームでは、簡単なSELECT文やINSERT文であれば、MyBatis Generatorで生成したマッパーを使用してSQLを実行しています。 しかし、結合をする必要があるクエリーやその他複雑なクエリーは、FreeMarkerを使用するようにしています。 MyBatisとFreeMarkerに加えて、下記環境にて開発をしています。
- フレームワーク:SpringBoot / Java
- DB:SQL Server
詳細は下記記事にまとめています。 tech.excite.co.jp
問題点:MyBatis Generatorで自動生成生成したマッパーでINSERTを実行するとIDが取得できない
下記の3つをカラムに持つ書籍テーブルがあり、book_idはAuto IncrementされるIDであるとします。
- 書籍ID(book_id)
- タイトル(title)
- 著者(author)
このとき、自動生成したマッパーを使用してレコードを追加するときは下記のように記述します。
@Repository @RequiredArgsConstructor public class BookRepositoryImpl { private final BookMapper mapper; public Long create(String title, String author) { final Book book = new Book(); book.setTitle(title); book.setAuthor(author); return mapper.insertSelective(book); } }
自動生成したマッパーのコードを見ても、intが返り値となっているため、なんだかIDが返ってきそうな感じがします。
@Generated(value="org.mybatis.generator.api.MyBatisGenerator") default int insertSelective(Book record) {...}
しかし、insertSelective
の返り値はINSERT文で影響を与えた件数が返るため、IDが返ってくるわけではありません。
そのため、なんらかの方法でIDを取得する必要があります。
解決策:OUTPUT句を使用する
SQL Serverでは、OUTPUT句を使用することで、INSERT / DELETE / UPDATE で追加 / 削除 / 更新された行のデータを取得することができます。 ここでは、INSERT文でレコードを追加したときのAuto IncrementされたIDを取得するために使用します。
まず、下記のようにftlファイルを記述します。
INSERT INTO book ( title, author ) OUTPUT inserted.book_id VALUES ( <@p name="title"/>, <@p name="author"/> );
次に、Javaでは下記のようなカスタムマッパーを記述します。 このように記述することで、ftlファイルに記述したSQLを実行することができ、book_idを返り値として受け取ることができます。
@Mapper public interface BookCustomMapper { @Lang(FreeMarkerLanguageDriver.class) @Select("insert_book_return_book_id.ftl") Long insert( @Param("title") String title, @Param("url") String author ); }
ここで、INSERT文を実行しているのに、@Select("insert_book_return_book_id.ftl")
とSELECT文のアノテーションを使用していることに注意する必要があります。
@Insert
を使用してしまうと、OUTPUT句で指定したbook_id
を取得することができません。
補足
現在のプロジェクトでは上記の解決策でAuto IncrementされたIDを取得しています。 上記解決策の他にIDを取得することができる方法についてまとめます
- 生成したマッパーに
@SelectKey
や@Option("useGeneratedKeys = true")
を付与する - generatorConfig.xmlに
<table> <generatedKey ... /> </table>
を記述する - INSERT文を実行した後に、
SELECT SCOPE_IDENTITY()
を実行する
1については、MyBatis Generatorで生成したマッパーにアノテーションを付与しなくてはならないため、 マッパーを再度自動生成したときにアノテーションが消えてしまうといった問題があります。
2については、INSERT文を実行したときにIDが欲しいと思ったタイミングで、generatorConfig.xmlにXMLを記述し、その後自動生成を行うため手間がかかります。
3については、自分の環境で再現できなかったため断念しました。
まず、下記のようなカスタムマッパーを作成しました。
次に、生成したマッパーを使用してINSERT文を実行した後に、get_last_insert_id()
を実行したところ、IDを取得することができなかったです。(nullになってしまいました)
本件についてわかる方がいたら教えていただけると嬉しいです!
@Mapper public interface LastInsertIdCustomMapper { @Select("SELECT SCOPE_IDENTITY()") Long get_last_insert_id(); }
おわりに
MyBatisとFreeMarkerを使用した環境において、INSERT文を実行したときにAutoIncrementされたIDが取得できない問題とその解決策についてまとめました。 「IDの取得くらい簡単にできるだろう」と思っていましたが、想定していたよりも時間を使ってしまいました。 本記事を見ていただいた方のお役に立てれば幸いです。