JavaでexecuteQueryがNULLを返すことは無いらしいが、JDBC(Java Database Connectivity)の仕様が分かり辛い...

f:id:ts0818:20210725130821j:plain

blog.qualys.com

The Qualys Research Team has discovered a size_t-to-int type conversion vulnerability in the Linux Kernel’s filesystem layer affecting most Linux operating systems. Any unprivileged user can gain root privileges on a vulnerable host by exploiting this vulnerability in a default configuration.

https://blog.qualys.com/vulnerabilities-threat-research/2021/07/20/sequoia-a-local-privilege-escalation-vulnerability-in-linuxs-filesystem-layer-cve-2021-33909

Linuxで「CVE-2021-33909」っていう脆弱性が公開されて、 

japan.zdnet.com

 Microsoftは米国時間7月20日Windows 10に存在する特権昇格の脆弱性(CVE-2021-36934)に関する情報を公開した。これを修正するセキュリティ更新プログラムはまだ無く、同社は回避策を紹介している。

Windows 10に特権昇格の未修正の脆弱性、マイクロソフトが回避策 - ZDNet Japan

Windowsでも「CVE-2021-36934」っていう脆弱性が公開されましたと。

というか、

  • 2021-7-13
    • CVE-2021-33909
  • 2021-7-20
    • CVE-2021-36934

1週間の間に、3025個の脆弱性が見つかってるっていうのが衝撃的なんですけど... 

これ、見つかってないだけで、潜在的脆弱性の数は膨大なのではないかという気がしてならないのだが…

パンドラの箱」のように希望が残るんなら良いけども...

まぁ、何て言うか、頑張ってWindows Updateしてきたのに意味無いやん...

脱線しましたが、今回は、Javaでデータベースを操作する時に利用される「JDBCJava Database Connectivity)」のAPIについて調べてみました。

レッツトライ~。

 

JDBCJava Database Connectivity)とは?

Oracleさんが公開してくれているドキュメントによると(Java SE 1.3とか情報がだいぶ旧そうなんだけど...)、Java SEで用意されてる標準APIの1つである「JDBC API」ってものらしい。

JDBC API は一連の抽象 Java インタフェースとして表現されます。アプリケーションプログラマはこれを使用して、特定のデータベースへの接続を開き、SQL 文を実行し、結果を処理することができます。 

f:id:ts0818:20210703113028p:plain

https://docs.oracle.com/javase/jp/1.3/guide/jdbc/spec/jdbc-spec.frame3.html

もっとも重要なインタフェースは次の 4 つです。

  • java.sql.DriverManager
    ドライバのロードを取り扱い、新規のデータベース接続の作成をサポートする
  • java.sql.Connection
    特定のデータベースへの接続を提供する
  • java.sql.Statement
    指定された接続上で SQL 文を実行するためのコンテナとして働く
  • java.sql.ResultSet
    指定された文の結果行へのアクセスを制御する

https://docs.oracle.com/javase/jp/1.3/guide/jdbc/spec/jdbc-spec.frame3.html

java.sql.Statement インタフェースには、2 つの重要なサブタイプがあります。コンパイル済みの SQL 文を実行するための java.sql.PreparedStatement と、データベースのストアドプロシージャの呼び出しを実行するための java.sql.CallableStatement です。

https://docs.oracle.com/javase/jp/1.3/guide/jdbc/spec/jdbc-spec.frame3.html

JavaからSQL文を実行した結果が「ResultSet」に格納されてるってことだと思うんだけど、SQL文を実行した結果に対していろいろ操作とかするメソッドとかも用意されてるってことなんすかね?

で、Java SE 1.4 とかの説明だと、

ResultSet は、SQL クエリーの実行結果を含む Java オブジェクトです。 つまり、ResultSet にはクエリーの条件を満たす行が含まれます。 ResultSet オブジェクトに格納されたデータは、現在行のさまざまな列へのアクセスを可能とする、get メソッドセットを使って取得されます。 ResultSet.next メソッドは、ResultSet の次の行に移動するために使用され、次の行が現在行になるようにします。

https://docs.oracle.com/javase/jp/1.4/guide/jdbc/getstart/resultset.html

⇧「ResultSet」は、「RDB(Relational DataBase)」のデータをJavaで扱える形にしてるんだと。

何て言うか、ドキュメントの記載が、ミスリードを引き起こしやすい書きっぷりになってるのが、まずもって宜しくない気がするんよね。

後述するんだけど、そもそもとして、ドキュメントの内容が、SQLクエリーの実行結果としてデータベースのテーブルからデータを取得できなかった場合についてを考慮した書き方になってないんよね...

ちなみに、

注: この章の内容は、Addison Wesley 社より Java シリーズの 1 巻として出版された『JDBCTM API Tutorial and Reference, Second Edition Universal Data Access for the JavaTM 2 Platform』(ISBN 0-201-43328-1) に基づいています。

https://docs.oracle.com/javase/jp/1.4/guide/jdbc/getstart/resultset.html

⇧ ってな感じで、「Addison Wesley 社」から出版された書籍に基づいた情報ですって言ってるんだけど、Javaを管理してたのってOracleさんだと思うんだけど、何故に他人事って感じなのかね...

 

正常に実行されたexecuteQueryはNULLを返すことはない 

「ResultSet」に実際に値を格納するには、「executeQuery」でSQL文を実行するってことになると思うんだけど、

stackoverflow.com

You could have looked onto the API of Statement#executeQuery() method. It says:

Returns:

  • a ResultSet object that contains the data produced by the given query; never null

https://stackoverflow.com/questions/18301326/can-a-resultset-be-null-in-java

⇧ 上記サイト様によりますと、「ResultSet」がNULLになることは無いんですと。

確かに、Java SE 7のAPIのドキュメントに「戻り値はNULLになることは決して無い、決してな」って書いてありますと。

まぁ、ただ、SQL文が間違ってたりした場合は、「SQLException」を起こすし、JDBCドライバーが接続でタイムアウトしたら「SQLTimeoutExcetion」を起こすと言ってますと。

なので、例外が起きない限りにおいては、という条件を前提とした上での「戻り値がNULLになることは絶対に無い、絶対にな」ってことらしい。

ちなみに、SQL文は大まかに分けて、

データベース言語SQLの文法の種別は、以下の3つに大別される。

SQL - Wikipedia

⇧ ってなっていますと。

で、よくアプリケーション開発とかだと、データを扱う機能をCRUDと呼んだりすることもあるらしいんだけど、

CRUD(クラッド)とは、ほとんど全てのコンピュータソフトウェアが持つ永続性の4つの基本機能のイニシャルを並べた用語。その4つとは、Create(生成)、Read(読み取り)、Update(更新)、Delete(削除)である。ユーザインタフェースが備えるべき機能(情報の参照/検索/更新)を指す用語としても使われる。

CRUD - Wikipedia

CRUDは上記のようなものですと。

で、SQL文のほうに話を戻すと、「データ操作言語(DML: data manipulation language)」の4つが、CRUDを実現してるってことになるっぽいんだけど、

  • 参照系
    • SELECT
  • 更新系
    • INSERT
    • UPDATE
    • DELETE

みたいな感じで、「参照系」と「更新系」って分けて考えることが多いですかね。

ちなみに、CRUDSQLの対応は、

CRUD SQL
 Create  INSERT
 Read  SELECT
 Update  UPDATE
 Delete  DELETE

⇧ てな感じになりますかね。

「ResultSet」に格納されるのは、「参照系」である「SELECT文」の「SQL文」が「executeQuery」メソッドで実行される時ってことになりますかね。 

 

正常に実行されたexecuteQueryの結果を受け止めるResultSetの罠

で、ここで、罠が待ち構えているわけですな。

例えば、「executeQuery」で実行するSQL文がSELECT文とかで、「RDB(Relational DataBase)」の何かしらのテーブルからデータを取得するものだった場合、且つ、例外が発生しない場合、

  1. SQLクエリーの実行結果で格納されたデータが存在する場合
  2. SQLクエリーの実行結果で格納されたデータが存在しない場合

のどっちかの結果になると思うんだけど、当然、「executeQuery」の結果を格納する「ResultSet」は「NULLになることは無い」となるんだけど、「2.SQLクエリーの実行結果で格納されたデータが存在しない場合」のケースは、「ResultSet」に格納されるデータの行数が0って状態になっていると思われるんすよ。

残念ながら、ドキュメントには明示されてないんで、「2.SQLクエリーの実行結果で格納されたデータが存在しない場合」のケースは、「ResultSet」に格納されるデータの行数が0 っていう前提が成立するっていう仮定を行った時に、その仮定が矛盾しないという体で話を進めることにします。

そして、「ResultSet」には、

5.1.2.カーソル

ResultSet オブジェクトは、現在行のデータを指し示すカーソルを維持します。

カーソルは next メソッドが呼び出されるたびに 1 行ずつ下に移動します。ResultSet の最初の作成時に、カーソルが最初の行の前に配置されます。

このため、next メソッドの最初の呼び出しにより、カーソルは最初の行に移動して、そこが現在行になります。

https://docs.oracle.com/javase/jp/1.4/guide/jdbc/getstart/resultset.html

⇧ 上記の説明にあるように、データを1行1行取得するために、「カーソル」を移動させる仕組みが用意されてるんですが、0行(「2.SQLクエリーの実行結果で格納されたデータが存在しない場合」)であっても、nextメソッドはエラーになることは無いんですな。

nextメソッドってのは、 

カーソルを現在の位置から順方向に1行移動します。ResultSetのカーソルは、初期状態では最初の行の前に位置付けられています。nextメソッドの最初の呼出しによって、最初の行が現在の行になります。2番目の呼出しによって2行目が現在の行になり、以下同様に続きます。

https://docs.oracle.com/javase/jp/8/docs/api/java/sql/ResultSet.html#next--

nextメソッドの呼出しでfalseが返されると、カーソルは最終行の後ろに位置します。現在の行が必要なResultSetメソッドのあらゆる呼出しによって、SQLExceptionがスローされます。結果セットのタイプがTYPE_FORWARD_ONLYである場合、以降のnextの呼出しでJDBCドライバ実装がfalseを返すかSQLExceptionをスローするかは、そのベンダーによって指定されます。

https://docs.oracle.com/javase/jp/8/docs/api/java/sql/ResultSet.html#next--

戻り値:

新しい現在の行が有効である場合はtrue、行がそれ以上存在しない場合はfalse

https://docs.oracle.com/javase/jp/8/docs/api/java/sql/ResultSet.html#next--

⇧ ってあるので、

  • 行が存在する場合
    boolean値のtrueを返す
  • 行が存在しなかった場合
    boolean値のfalseを返す

ってことになるんではないかと。

「executeQuery」が「NULLを返すことは絶対に無い」ってあったと思うんだけど、「SELECT文」の「SQL文」によってデータベースのテーブルからデータが1件も取得できなかった場合は、「ResultSet」の nextメソッドは、問答無用でfalseを返すってことになるんじゃないかと。

このあたり、SELECT文の取得結果が0件のケースがどういう扱いになるのかドキュメントで明示してくれても良い気がするんだが...流石はOracleさん、安定の不親切さ。

長々と脱線したんだけど、「executeQuery」を実行したSELECT文で取得できるオブジェクトの件数が、1行分だろうと、複数行分だろうと、

String sql = "何かしらのSELECT文";
ResultSet rs = stmt.executeQuery(sql);
while (rs.next()) {
  // 「executeQuery」を実行したSELECT文で取得できた行数が0件で無い場合の処理
  
}

⇧ ってな感じで、while文で処理してしまえば良いんじゃなかろうか?

まぁ、何が言いたいかというと、ResultSetのnextメソッドがfalseの場合に、ResultSet のgetXXXみたいなメソッドを使うとエラーが起きるってことですかね。

あと、コード例には記述してなかったんだけど、closeをちゃんとしてあげた方が良いということですかね。

qiita.com

 

というわけで、もうちょっとドキュメントをしっかり記述して欲しいかな、という気がするんだけどね... 

まぁ、何が言いたいかと言うと、Javaのバージョンアップで機能を増やしてくれるのはありがたいんだけど、その前にちゃんとドキュメントを整備して欲しい...

JDBCJava Database Connectivity)」周りのAPIについては、フレームワークが隠蔽してくれて、意識しなくても良いのかもしらんけどさ...

毎度、モヤモヤ感しか残らんな...

今回はこのへんで。