RM-BLOG

IT系技術職のおっさんがIT技術とかライブとか日常とか雑多に語るブログです。* 本ブログに書かれている内容は個人の意見・感想であり、特定の組織に属するものではありません。/All opinions are my own.*

【Java】【Oracle】JDBCによるINSERT文実行と一意制約違反のキャッチ方法

JavaOracleの一意制約違反をキャッチする方法のメモ
(まあこれもググれば載ってそうだけど)


 

 
以下のようなテーブルがあるとする

create table TEST_TAB10(
    test_no number(10) primary key
   ,test_memo varchar2(100)
)
;


このテーブルに、以下のレコードが格納されているものとする

★test_notest_memo

1 重複テスト



★はKEYなので、このテーブルにtest_no=1のレコードは格納できない。

の、ときに、以下のようなJavaプログラムで
test_no=1のレコードを懲りずにINSERTしようと試みてみる。

import java.sql.*;
import java.util.*;
import java.text.*;
import java.io.*;
import oracle.jdbc.pool.*;
import oracle.jdbc.OraclePreparedStatement;

public class ORA0001SQLExceptionTest {

	private static final String DB_URL = "jdbc:oracle:thin:@192.168.100.101:1521:TESTDB";
	private static final String DB_USER = "TESTUSER";
	private static final String DB_PASS = "TESTPASS";
	
	public static void main(String[] args) throws Throwable {
		System.out.println("★開始");
		Connection con = null;
		try {
			con = getConnection();
			String sql = "INSERT INTO TEST_TAB10 values(1 , '重複テスト')";
			PreparedStatement ps = con.prepareStatement(sql);
			int result = ps.executeUpdate(sql);
			con.commit();

		} catch(SQLException se){
			int errorCode = se.getErrorCode();
			System.out.println("SQLException#getErrorCode = " + errorCode);
			if (errorCode == 1) {
				System.out.println("!!一意制約違反発生");
				se.printStackTrace();
			}

			
			con.rollback();
		} catch(Throwable e) {
			if (con != null) {
				con.rollback();
			}
			throw e;
		} finally {
			if (con != null) {
				con.close();
			}
		}
		
		System.out.println("★終了");
		
		
	}
	
	private static Connection getConnection() throws Throwable {
		Class.forName("oracle.jdbc.driver.OracleDriver");
		return DriverManager.getConnection(DB_URL,DB_USER,DB_PASS);
	}
}


このとき、赤太字部分のロジックで一意制約違反が起きたかどうかを判断できる。
ポイントは、SQLException#getErrorCodeでエラーコードを得るところ。
一意制約違反が「ORA-0001」だからか、Oracle相手に発生したSQLExceptionで↑のロジックでエラーコードを取り出すと、
数値の「1」が受け取れる。(ORA-xxxxxのxxxxxの部分が数値型になって返却される)

これを受けて、「登録するKEY値を1ずつカウントアップさせながら登録できるまで無限ループ」みたいな処理も実行できる。

	private static void insertLoop(Connection con) throws Throwable {
		int key = 1;
		int result = 0;
		int loopCount = 0;
		while(true){
			loopCount++;
			try {
				String sql = "INSERT INTO TEST_TAB10 values(" + String.valueOf(key) + " , '重複テスト無限ループ版')";
				PreparedStatement ps = con.prepareStatement(sql);
				result = ps.executeUpdate(sql);
				con.commit();
				break;
			} catch(SQLException se) {
				con.rollback();
				int errorCode = se.getErrorCode();
				if (errorCode == 1) {
					// 一意制約違反ならkey値をカウントアップしてリトライ
					key++;
					continue;
				} else {
					// それ以外ならよくわからんからもうやめる(無限ループしちゃうし)
					se.printStackTrace();
					break;
				}
			}
		}
		System.out.println("登録成功;リトライ回数=" + String.valueOf(loopCount - 1) + "、登録KEY値=" + key);
	}


WEBアプリケーション等のマルチスレッド環境下や、並列バッチ処理等で、1つのテーブルに同時に書込みが発生するような場合にたまに使う。
(まあFOR UPDATEするとかsyncronizedするほうが確実だが)