executeBatchの戻り値の内容が11gと12cで異なる

# 実行環境

本記事を書くにあたって、利用した主なソフトウェアのバージョンは次の通りです。なおOracle Databaseの構築にあたってはDockerおよび[Oracle公式のDocker Image](https://github.com/oracle/docker-images)を利用しています。

|software|version, edition|
|---|---|
| Oracle Database 11g | Oracle Database 11g Express Edition Release 11.2.0.2.0 - 64bit Production |
| Oracle Database 12c | Oracle Database 12c Standard Edition Release 12.2.0.1.0 - 64bit Production |
| ojdbc6.jar (11g JDBC Driver) | Oracle 11.2.0.2.0 JDBC 4.0 compiled with JDK6 on Sat_Aug_14_12:18:34_PDT_2010 |
| ojdbc8.jar (12c JDBC Driver) | Oracle 12.2.0.1.0 JDBC 4.2 compiled with javac 1.8.0_91 on Tue_Dec_13_06:08:31_PST_2016 |
| javac | javac 11.0.4 |
| java | openjdk version "11.0.4" 2019-07-16 |

# 事象概要

JavaアプリケーションのデータベースをOracle Database 11cからOracle Database 12gにバージョンアップするにあたって、`PreparedStatement::executeBatch`の戻り値(int型配列)の内容が微妙に異なり、時間を無限に溶かしたので、メモを残しておきます。

以下のソースコードは、バッチ更新を利用してテーブル`USERS`に3件のデータを挿入するというものです。データベースへの接続情報はすべて実行時引数として受け取ることとします。

```java:Main.java
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        String url = args[0];
        String user = args[1];
        String password = args[2];

        try (Connection c = DriverManager.getConnection(url, user, password);
             PreparedStatement ps = c.prepareStatement("INSERT INTO USERS (ID, NAME) VALUES(?, ?)")) {
            ps.setInt(1, 1);
            ps.setString(2, "Alice");
            ps.addBatch();

            ps.setInt(1, 2);
            ps.setString(2, "Bob");
            ps.addBatch();

            ps.setInt(1, 3);
            ps.setString(2, "Carol");
            ps.addBatch();

            int[] updateCounts = ps.executeBatch();
            System.out.println(Arrays.toString(updateCounts));
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}
```



このコードをコンパイルしたのち、まずは接続先を11gに向けて実行、次に接続先を12cに変えて実行した結果が次の通りです。

```
$ java -cp ../lib/ojdbc6.jar:. Main jdbc:oracle:thin:@192.168.99.100:1511:xe user1 password
[-2, -2, -2]
$ java -cp ../lib/ojdbc8.jar:. Main jdbc:oracle:thin:@192.168.99.100:1512/ORCLPDB1 user1 password
[1, 1, 1]
```

`PreparedStatement::executeBatch`の戻り値である`updateCounts`の内容が11gと12cで異なることが確認できました。

12cについては、内容は明白で、更新した件数がint型の配列に格納されています。一方、11gの`executeBatch`は`Statement.SUCCESS_NO_INFO`を格納した配列を戻り値とします。

# 結論: ドキュメントはちゃんと読もう

実はこの挙動の違いはOracle Databaseのドキュメントを読むと、ちゃんと書かれています。

まずは[11gのドキュメント](http://otndnld.oracle.co.jp/document/products/oracle11g/111/doc_dvd/java.111/E05720-02/oraperf.htm):

> 文バッチが正常に処理された場合、文のexecuteBatchコールから戻される整数配列、つまり、更新件数配列には、常にバッチ操作1つに対して1つの要素が含まれます。標準バッチ更新のOracle実装では、配列要素の値は次のようになります。
> プリコンパイルされたSQL文のバッチの場合、バッチに含まれている個々の文によって影響を受けたデータベースの行数はわかりません。そのため、配列要素の値はすべて-2になります。JDBC 2.0仕様によれば、値-2は、操作は正常終了したが影響を受けた行数は不明であることを示します。

一方[12cのドキュメント](https://docs.oracle.com/cd/E57425_01/121/JJDBC/oraperf.htm)には11gとは挙動が異なることが明記されています、

> Oracle Database 12cリリース1 (12.1)以降、executeBatchメソッドは改良され、バッチのレコード数と同じサイズのint配列を戻します。戻り配列の各項目は、バッチの対応するレコードの影響を受けたデータベース表の行数です。

ちなみに実際に試していませんが、[Oracle Database 18cのドキュメント](https://docs.oracle.com/cd/F19136_01/jjdbc/performance-extensions.html#GUID-20BF8463-A574-48CB-AD56-18CF8DEF603D)
には、上記の12cのそれとまったく同じ文言が掲載されています。つまり12cと18cでは`PreparedStatement::executeBatch`の挙動に差はないと思われます。