SequenceをGeneratedKeyでエミュレート(MySQL+JDBC版)

  • 投稿日:
  • カテゴリ:

OracleやPostgreSQLにはSequenceがあるのですが、MySQLにはありません。
AUTO_INCREMENTは便利なのですが、外部キーの参照先となるような場合に困ってしまいます。
具体的には次のような感じです。

テーブル設計や正規化云々の話は面倒なので言及しません。

名簿テーブル
ID NAME YOMI
1 陽菜 ひな
2 あおい
3 さくら さくら
4 結衣 ゆい
5 結菜 ゆうな
係り活動テーブル 
ID NEN GAKKI KAKARI
1 1 1 いきもの
1 1 2 図書
1 1 3 保健
1 2 1 風紀

名簿テーブルのIDがAUTO_INCREMENTの場合に係り活動テーブルに入れるIDの取得に困ります。

  1. 名簿テーブルにデータをINSERTする。(IDは自動生成で1が入ったとする)
  2. 自動生成された1を取得するために以下の方法で取得する
    1. SELECTを発行する
    2. LAST_INSERT_ID()をコールする
  3. 係り活動テーブルにデータをINSERTする。

SELECTの場合、MAX関数使った場合はマルチスレッド、マルチプロセスで破綻します。
LAST_INSERT_ID()の場合、発行する順番に気をつければ同一セッション内で値が保持されている・・・?
詳しくはわかりません(´・ω・`)

またAUTO_INCREMENTをやめてシーケンステーブルを作成した場合、SELECTは上記と同じ理由で破綻(ロックかけるなら別ですけど。)、LAST_INSERT_IDは上手くいくが、どちらにせよ2往復している事になります。

もしDBがSEQUENCEをサポートしている場合(名簿テーブルのIDはAUTO_INCREMENTではありません)、

  1. シーケンスを進めて値を取得する(仮に1とする)
  2. 名簿テーブルにデータをINSERTする
  3. 係り活動テーブルにデータをINSERTする

こんな感じで済みます。

とりあえず、2往復は効率が悪そう。でMySQLはシーケンスをサポートしていない。となれば作るしかなさそうです。

INSERTの戻り値として自動生成された値を取得する場合、PERLではDBIのmysql_insertidを使えば出来、JDBCの場合はStatement#getGeneratedKey()を使う事で取得が可能になるようです。ちなみにOracleの場合はgetGeneratedKey()でカラムを取得してもRowId型になった気がします。(誰かのBLOGに載っていた予感)

というわけで、早速コードを書いてみました。 

 

まずテーブルを作成します。

CREATE TABLE SEQUENCE (
    ID VARCHAR(64) NOT NULL,
    SEQ BIGINT NOT NULL DEFAULT 0,
    CONSTRAINT PK_SEQUENCE PRIMARY KEY (ID)
);

INSERT INTO SEQUENCE VALUES('test_seq', 0);

次にJavaのコードです。 
DataSourceも使ってないしPoolingもしていません。サンプルなので(´・ω・)r

package test;

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


public class SequenceTestClient {

    public static void main(String[] args) throws ClassNotFoundException, SQLException {

        final String driver = "com.mysql.jdbc.Driver";
        final String url = "jdbc:mysql://tamsvr02:3307/moogle?useUnicode=true&characterEncoding=utf8";
        final String user = "totoro";
        final String password = "nekobasu";

        Class.forName(driver);

        Connection conn = null;
        PreparedStatement pst = null;
        ResultSet rs = null;

        try {
            conn = DriverManager.getConnection(url, user, password);
            pst = conn.prepareStatement(
                    "UPDATE SEQUENCE SET SEQ = LAST_INSERT_ID(SEQ+1) WHERE ID = ?",
                    Statement.RETURN_GENERATED_KEYS);
            pst.setString(1, "test_seq");
            int updCount = pst.executeUpdate();

            System.out.println("updCount=" + updCount);

            rs = pst.getGeneratedKeys();
            if (rs.next()) {
                long seq = rs.getLong(1); // 最初のカラムを取得する
                System.out.println("seq=" + seq);
            }

        } finally {
            close(rs);
            close(pst);
            close(conn);
        }

    }


    static void close(ResultSet rs) {
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) { }
        }
    }

    static void close(Statement st) {
        if (st != null) {
            try {
                st.close();
            } catch (SQLException e) { }
        }
    }

    static void close(Connection conn) {
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) { }
        }
    }

}

実行結果は次のようになります。
実行するたびにseqが増加していきます。

updCount=1
seq=1
新しいサイトもよろしくお願いします!