【Java】java.sql.ResultSetから値を読み出すときメモ

JDBCは業務アプリや軽いデータ取得ツールを作るには、低レイヤすぎて面倒くさい。 特に値を読み出すところが面倒すぎてO/Rマッパに逃げた挙句、今日まともに使い方を理解したJavaおじさん5年目がメモ。

JDBCとは

Javaの標準APIに組み込まれている、データベースに接続してSQL投げてクエリするための各種セット。

サクっと(?)試せる記事があったので使い勝手はこちら参照。 https://qiita.com/wb773/items/27dc0e77a4c8b035d6fc

登場した頃の鳴り物的なものはこのあたりかも。 https://www.atmarkit.co.jp/ait/articles/0106/26/news001.html

データ取得

ResultSetの型がガバガバ

いい加減にしろっていうぐらいJava型に対応したget~メソッドが多い

            // SQLではint型で取得
            ResultSet resultSet = statement.executeQuery("SELECT 1");

            // 下記全部それっぽい値が取れてしまう
            System.out.println(resultSet.getByte(1));
            System.out.println(resultSet.getShort(1));
            System.out.println(resultSet.getInt(1));
            System.out.println(resultSet.getFloat(1));
            System.out.println(resultSet.getBigDecimal(1));
            System.out.println(resultSet.getObject(1));

困ったことにString型も、数字っぽかったらエラーなしで数値型に変換する始末。お前はPHPか。

ちょっと古いドキュメントだが、一番型的に正解で精緻な値が取れる組み合わせは、ResultSetMetaData.getType()からとれるjava.sql.Types 列挙体と下記ドキュメントを突き合せれば良い。 https://docs.oracle.com/cd/E16338_01/java.112/b56281/datacc.htm

NULL値の取り方

オブジェクト型であれば、get~メソッドでnullが帰ってくるが、プリミティブの数値型は 0 で帰ってくる。

            ResultSet resultSet = statement.executeQuery("SELECT NULL");

            // 0でとれる
            System.out.println(resultSet.getByte(1));
            System.out.println(resultSet.getShort(1));
            System.out.println(resultSet.getInt(1));
            System.out.println(resultSet.getFloat(1));

            // nullでとれる
            System.out.println(resultSet.getString(1));
            System.out.println(resultSet.getBigDecimal(1));
            System.out.println(resultSet.getObject(1));

ただし、プリミティブでも 直前のget~の取得結果がSQL上でnullだったかをResultSet.wasNullメソッドで検知できる。

            ResultSet resultSet = statement.executeQuery("SELECT NULL");

            System.out.println(resultSet.getByte(1));
            System.out.println(resultSet.wasNull());   // true

したがって、1行のデータを丸々取りたかったら、だいたいこんな感じになる。

public static ArrayList<String> readOneRecord(ResultSet rs, ResultSetMetaData metaData, int colcount, List<ColumnProp> props, SimpleDateFormat sfdTimestamp) throws SQLException {
        ArrayList<String> list = new ArrayList<String>(colcount);
        
        for (int i=0; i<colcount; i++) {
            // @see https://docs.oracle.com/cd/E16338_01/java.112/b56281/datacc.htm
            switch (metaData.getColumnType(i+1)) {

            //getstring
            case java.sql.Types.CHAR: 
            case java.sql.Types.LONGNVARCHAR: 
            case java.sql.Types.LONGVARCHAR: 
            case java.sql.Types.VARCHAR: 
            case java.sql.Types.NCHAR: 
            case java.sql.Types.NVARCHAR: 
                String vString = rs.getString(i+1);
                list.add(vString != null ? vString : props.get(i).nullas);
                break;
            //getBigDecimal
            case java.sql.Types.DECIMAL: 
            case java.sql.Types.NUMERIC:
                BigDecimal vBigDecimal = rs.getBigDecimal(i+1);
                list.add(vBigDecimal != null ? vBigDecimal.toString() : props.get(i).nullas);
                break;
            //gettimestamp
            case java.sql.Types.TIMESTAMP:
                java.sql.Timestamp vTimestamp = rs.getTimestamp(i+1);
                list.add(vTimestamp != null ? sfdTimestamp.format(vTimestamp) : props.get(i).nullas);
                break;
            //getdate
            case java.sql.Types.DATE:
                java.sql.Date vDate = rs.getDate(i+1);
                list.add(vDate != null ? sfdTimestamp.format(vDate) : props.get(i).nullas);
                break;

                //getint
            case java.sql.Types.TINYINT: 
                byte vByte = rs.getByte(i+1);
                list.add(rs.wasNull() ? Byte.toString(vByte) : props.get(i).nullas);
                break;
            case java.sql.Types.SMALLINT: 
                short vShort = rs.getShort(i+1);
                list.add(rs.wasNull() ? Short.toString(vShort) : props.get(i).nullas);
                break;
            case java.sql.Types.INTEGER: 
                int vInt = rs.getInt(i+1);
                list.add(rs.wasNull() ? Integer.toString(vInt) : props.get(i).nullas);
                break;
            case java.sql.Types.BIGINT: 
                long vLong = rs.getLong(i+1);
                list.add(rs.wasNull() ? Long.toString(vLong) : props.get(i).nullas);
                break;

            //getbool
            case java.sql.Types.BOOLEAN:
                boolean vBoolean = rs.getBoolean(i+1);
                list.add(rs.wasNull() ? Boolean.toString(vBoolean) : props.get(i).nullas);
                break;
            //getfloat
            case java.sql.Types.FLOAT: 
            case java.sql.Types.DOUBLE: 
            case java.sql.Types.REAL:
                double vDouble = rs.getDouble(i+1);
                list.add(rs.wasNull() ? Double.toString(vDouble) : props.get(i).nullas);
                break;
            //getnull
            case java.sql.Types.NULL: 
                list.add(props.get(i).nullas);
                break;
            //rowid
            case java.sql.Types.ROWID: 
                RowId vRowId = rs.getRowId(i+1);
                list.add(vRowId != null ? vRowId.toString() : props.get(i).nullas);
                break;
            //gettime
            case java.sql.Types.TIME: 
                Time vTime = rs.getTime(i+1);
                list.add(vTime != null ? sfdTimestamp.format(vTime) : props.get(i).nullas);
                break;
         default:
                list.add(props.get(i).nullas);
                break;
            // not supported
//         case java.sql.Types.ARRAY: 
//         case java.sql.Types.BIT: 
//         case java.sql.Types.BINARY: 
//         case java.sql.Types.BLOB: 
//         case java.sql.Types.CLOB: 
//         case java.sql.Types.DATALINK: 
//         case java.sql.Types.DISTINCT: 
//         case java.sql.Types.JAVA_OBJECT: 
//         case java.sql.Types.NCLOB: 
//         case java.sql.Types.OTHER: 
//         case java.sql.Types.REF: 
//         case java.sql.Types.LONGVARBINARY: 
//         case java.sql.Types.SQLXML: 
//         case java.sql.Types.STRUCT: 
//         case java.sql.Types.VARBINARY: 
            }
        }
        
        return list;
    }