JDBCの紹介

1概要

この記事では、データベースにクエリを接続して実行するためのAPIであるJDBC(Java Database Connectivity)について説明します。

適切なドライバが提供されていれば、JDBCはどのデータベースでも機能します。

2 JDBCドライバ

JDBCドライバは、特定の種類のデータベースへの接続に使用されるJDBC API実装です。 JDBCドライバにはいくつかの種類があります。

  • タイプ1 - 他のデータアクセスAPIへのマッピングを含みます。の例

これはJDBC-ODBCドライバです ** Type 2 - のクライアントサイドライブラリを使用する実装です。

ターゲットデータベース。ネイティブAPIドライバとも呼ばれます ** タイプ3 - ミドルウェアを使用してJDBC呼び出しをデータベース固有のものに変換する

呼び出します。ネットワークプロトコルドライバとも呼ばれます ** タイプ4 - JDBC呼び出しをに変換してデータベースに直接接続する

データベース固有の呼び出しデータベースプロトコルドライバまたはシンドライバと呼ばれます。

最も一般的に使用されるタイプはタイプ4です。これは プラットフォームに依存しない という利点があります。データベースサーバーに直接接続すると、他のタイプに比べてパフォーマンスが向上します。このタイプのドライバの欠点は、データベースごとに異なることです。各データベースには独自のプロトコルがあります。

3データベースへの接続

データベースに接続するには、単にドライバを初期化してデータベース接続を開くだけです。

3.1. ドライバを登録する

この例では、タイプ4データベースプロトコルドライバを使用します。

MySQLデータベースを使用しているので、https://search.maven.org/classic/#search%7Cga%7C1%7Ca%3A%22mysql-connector-java%22%20AND%20g%3A%22mysqlが必要です%22[ mysql-connector-java ]依存関係:

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>6.0.6</version>
</dependency>

次に、ドライバクラスを動的にロードする Class.forName() メソッドを使ってドライバを登録しましょう。

Class.forName("com.mysql.cj.jdbc.Driver");

3.2. 接続を作成する

接続を開くには、 DriverManager クラスの getConnection() メソッドを使用できます。このメソッドには接続URLの String パラメータが必要です。

Connection con = DriverManager
  .getConnection("jdbc:mysql://localhost:3306/myDb", "user1", "pass");

接続URLの構文は、使用されるデータベースの種類によって異なります。

いくつか例を見てみましょう。

jdbc:mysql://localhost:3306/myDb?user=user1&password=pass
jdbc:postgresql://localhost/myDb
jdbc:hsqldb:mem:myDb

指定された myDb データベースに接続するには、データベースとユーザーを作成し、必要なアクセス権を追加する必要があります。

CREATE DATABASE myDb;
CREATE USER 'user1' IDENTIFIED BY 'pass';
GRANT ALL on myDb.**  TO 'user1';

4 SQL文の実行

データベースにSQL命令を送信するには、 Statement PreparedStatement 、または CallableStatement のインスタンスを使用します。これらは Connection オブジェクトを使用して取得されます。

4.1. ステートメント

Statement インターフェースには、SQLコマンドを実行するための重要な機能が含まれています。

まず、 Statement オブジェクトを作成しましょう。

Statement stmt = con.createStatement();

SQL命令の実行は、3つの方法を使用して実行できます。

  • SELECT命令の場合は executeQuery()

  • executeUpdate() データまたはデータベース構造を更新します

  • execute() は、結果が

道の

execute() メソッドを使用して students テーブルをデータベースに追加しましょう。

String tableSql = "CREATE TABLE IF NOT EXISTS employees"
  + "(emp__id int PRIMARY KEY AUTO__INCREMENT, name varchar(30),"
  + "position varchar(30), salary double)";
stmt.execute(tableSql);
  • execute() メソッドを使用してデータを更新した場合、 stmt.getUpdateCount() メソッドは影響を受けた行数を返します。

結果が0の場合は、影響を受けた行がないか、データベース構造更新コマンドでした。

値が-1の場合、commandはSELECTクエリでした。結果は stmt.getResultSet() を使って取得できます。

次に、 executeUpdate() メソッドを使用してテーブルにレコードを追加しましょう。

String insertSql = "INSERT INTO employees(name, position, salary)"
  + " VALUES('john', 'developer', 2000)";
stmt.executeUpdate(insertSql);

このメソッドは、行を更新するコマンドの場合は影響を受ける行数を返し、データベース構造を更新するコマンドの場合は0を返します。

ResultSet 型のオブジェクトを返す executeQuery() メソッドを使用して、テーブルからレコードを取得できます。

String selectSql = "SELECT **  FROM employees";
ResultSet resultSet = stmt.executeQuery(selectSql);

4.2. PreparedStatement

PreparedStatement オブジェクトには、プリコンパイル済みSQLシーケンスが含まれています。疑問符で示された1つ以上のパラメータを持つことができます。

与えられたパラメータに基づいて employees テーブルのレコードを更新する PreparedStatement を作成しましょう:

String updatePositionSql = "UPDATE employees SET position=? WHERE emp__id=?";
PreparedStatement pstmt = con.prepareStatement(updatePositionSql);

PreparedStatement にパラメータを追加するには、単純なセッター - setX() - を使用できます。ここで、Xはパラメータの型、メソッドの引数はパラメータの順序と値です。

pstmt.setString(1, "lead developer");
pstmt.setInt(2, 1);

ステートメントは、前述の同じ3つのメソッドのうちの1つで実行されます。SQLの String パラメーターなしで、 executeQuery()、executeUpdate()、execute() です。

int rowsAffected = pstmt.executeUpdate();

4.3. CallableStatement

CallableStatement インタフェースを使用すると、ストアドプロシージャを呼び出すことができます。

CallableStatement オブジェクトを作成するには、 Connection prepareCall() メソッドを使用します。

String preparedSql = "{call insertEmployee(?,?,?,?)}";
CallableStatement cstmt = con.prepareCall(preparedSql);

ストアドプロシージャの入力パラメータ値の設定は、 PreparedStatement インタフェースと同様に、 setX() メソッドを使用して行います。

cstmt.setString(2, "ana");
cstmt.setString(3, "tester");
cstmt.setDouble(4, 2000);

ストアドプロシージャに出力パラメータがある場合は、 registerOutParameter() メソッドを使用してそれらを追加する必要があります。

cstmt.registerOutParameter(1, Types.INTEGER);

それでは、ステートメントを実行し、対応する getX() メソッドを使用して戻り値を取得しましょう。

cstmt.execute();
int new__id = cstmt.getInt(1);

たとえば動作させるには、MySqlデータベースにストアドプロシージャを作成する必要があります。

delimiter//CREATE PROCEDURE insertEmployee(OUT emp__id int,
  IN emp__name varchar(30), IN position varchar(30), IN salary double)
BEGIN
INSERT INTO employees(name, position,salary) VALUES (emp__name,position,salary);
SET emp__id = LAST__INSERT__ID();
END//delimiter ;

上記の insertEmployee プロシージャーは、指定されたパラメーターを使用して employees テーブルに新しいレコードを挿入し、 emp id__ outパラメーターに新しいレコードのIDを返します。

Javaからストアドプロシージャを実行できるようにするには、接続ユーザーはストアドプロシージャのメタデータにアクセスできる必要があります。これは、すべてのデータベース内のすべてのストアドプロシージャに対する権限をユーザーに付与することによって実現できます。

GRANT ALL ON mysql.proc TO 'user1';

あるいは、 noAccessToProcedureBodies true に設定して接続を開くこともできます。

con = DriverManager.getConnection(
  "jdbc:mysql://localhost:3306/myDb?noAccessToProcedureBodies=true",
  "user1", "pass");

これにより、JDBC APIに、プロシージャのメタデータを読み取る権限がないことがJDBC APIに通知され、すべてのパラメータがINOUT String パラメータとして作成されます。

5クエリ結果の解析

クエリを実行した後、結果は ResultSet オブジェクトで表され、行と列を含むテーブルと同様の構造を持ちます。

5.1. ResultSet インタフェース

ResultSet next() メソッドを使用して次の行に移動します。

まず、取得したレコードを格納するための Employee クラスを作成しましょう。

public class Employee {
    private int id;
    private String name;
    private String position;
    private double salary;

   //standard constructor, getters, setters
}

次に、 ResultSet を調べて、各レコードに Employee オブジェクトを作成しましょう。

String selectSql = "SELECT **  FROM employees";
ResultSet resultSet = stmt.executeQuery(selectSql);

List<Employee> employees = new ArrayList<>();

while (resultSet.next()) {
    Employee emp = new Employee();
    emp.setId(resultSet.getInt("emp__id"));
    emp.setName(resultSet.getString("name"));
    emp.setPosition(resultSet.getString("position"));
    emp.setSalary(resultSet.getDouble("salary"));
    employees.add(emp);
}

各テーブルセルの値を取得するには、 getX( )型のメソッドを使用します。ここで、Xはセルデータの型を表します。

getX() メソッドは、セルの順序を表す int パラメータ、または列の名前を表す String パラメータと共に使用できます。後者のオプションは、クエリ内の列の順序を変更する場合に適しています。

5.2. 更新可能 ResultSet

暗黙のうちに、 ResultSet オブジェクトは前にしか移動できず、変更できません。

ResultSet を使用してデータを更新し、それを両方向にトラバースするには、 Statement オブジェクトを追加のパラメータで作成する必要があります。

stmt = con.createStatement(
  ResultSet.TYPE__SCROLL__INSENSITIVE,
  ResultSet.CONCUR__UPDATABLE
);

このタイプの ResultSet を操作するには、次のいずれかの方法を使用できます。

  • first()、last()、beforeFirst()、beforeLast() - 最初に移動します

ResultSet の最後の行、またはその前の行 ** next()、previous() - 前後にナビゲートする

結果セット ** getRow() - 現在の行番号を取得するための

  • moveToInsertRow()、moveToCurrentRow() - 新しい空の行に移動します

新しい行にある場合は現在の行に挿入して戻す ** absolute(int row) - 指定行に移動する場合は __

  • relative(int nrRows) - 指定された行数だけカーソルを移動する

ResultSet の更新は、 updateX() という形式のメソッドを使用して実行できます。ここで、Xはセルデータの型です。これらのメソッドは ResultSet オブジェクトのみを更新し、データベーステーブルは更新しません。

データベースへの ResultSet の変更を永続化するには、さらに次のいずれかの方法を使用する必要があります。

  • updateRow() - 現在の行への変更を保持する

データベース ** insertRow()、deleteRow() - 新しい行を追加するか現在の行を削除します。

データベースからのもの ** refreshRow() - 内のすべての変更で ResultSet を更新します。

データベース ** cancelRowUpdates() - 現在の行に加えられた変更をキャンセルします

__従業員のテーブルのデータを更新して、これらの方法のいくつかを使用する例を見てみましょう。

Statement updatableStmt = con.createStatement(
  ResultSet.TYPE__SCROLL__INSENSITIVE, ResultSet.CONCUR__UPDATABLE);
ResultSet updatableResultSet = updatableStmt.executeQuery(selectSql);

updatableResultSet.moveToInsertRow();
updatableResultSet.updateString("name", "mark");
updatableResultSet.updateString("position", "analyst");
updatableResultSet.updateDouble("salary", 2000);
updatableResultSet.insertRow();

6. メタデータの解析

JDBC APIでは、メタデータと呼ばれるデータベースに関する情報を検索できます。

6.1. DatabaseMetadata

DatabaseMetadata インタフェースは、テーブル、ストアドプロシージャ、SQL方言など、データベースに関する一般的な情報を取得するために使用できます。

データベーステーブルの情報を取得する方法を簡単に見てみましょう。

DatabaseMetaData dbmd = con.getMetaData();
ResultSet tablesResultSet = dbmd.getTables(null, null, "%", null);
while (tablesResultSet.next()) {
    LOG.info(tablesResultSet.getString("TABLE__NAME"));
}

6.2. ResultSetMetadata

このインタフェースは、列の数や名前など、特定の ResultSet に関する情報を見つけるために使用できます。

ResultSetMetaData rsmd = rs.getMetaData();
int nrColumns = rsmd.getColumnCount();

IntStream.range(1, nrColumns).forEach(i -> {
    try {
        LOG.info(rsmd.getColumnName(i));
    } catch (SQLException e) {
        e.printStackTrace();
    }
});

7. トランザクション処理

デフォルトでは、各SQL文は完了後すぐにコミットされます。

ただし、トランザクションをプログラムで制御することも可能です。

これは、前のトランザクションが正常に完了した場合にのみトランザクションをコミットしたい場合など、データの一貫性を維持したい場合に必要になることがあります。

まず、 Connection autoCommit プロパティを false に設定し、次に commit() メソッドと rollback() メソッドを使用してトランザクションを制御します。

従業員の position 列の更新後に salary 列の2番目のupdateステートメントを追加し、両方をトランザクションでラップしましょう。

これにより、ポジションが正常に更新された場合にのみ給与が更新されます。

String updatePositionSql = "UPDATE employees SET position=? WHERE emp__id=?";
PreparedStatement pstmt = con.prepareStatement(updatePositionSql);
pstmt.setString(1, "lead developer");
pstmt.setInt(2, 1);

String updateSalarySql = "UPDATE employees SET salary=? WHERE emp__id=?";
PreparedStatement pstmt2 = con.prepareStatement(updateSalarySql);
pstmt.setDouble(1, 3000);
pstmt.setInt(2, 1);

boolean autoCommit = con.getAutoCommit();
try {
    con.setAutoCommit(false);
    pstmt.executeUpdate();
    pstmt2.executeUpdate();
    con.commit();
} catch (SQLException exc) {
    con.rollback();
} finally {
    con.setAutoCommit(autoCommit);
}

8.接続を閉じる

使用しなくなったら、接続を閉じてデータベースリソースを解放する必要があります。

これは、 close() APIを使用して実行できます。

con.close();

9結論

このチュートリアルでは、JDBC APIを使用した作業の基本について説明しました。

いつものように、例の完全なソースコードはhttps://github.com/eugenp/tutorials/tree/master/persistence-modules/core-java-persistence[GitHubに載っています]。