Einführung in JDBC

1. Überblick

In diesem Artikel werfen wir einen Blick auf JDBC (Java Database Connectivity), eine API zum Verbinden und Ausführen von Abfragen in einer Datenbank.

JDBC kann mit jeder Datenbank arbeiten, sofern die richtigen Treiber bereitgestellt werden.

2. JDBC-Treiber

Ein JDBC-Treiber ist eine JDBC-API-Implementierung, die zum Herstellen einer Verbindung zu einem bestimmten Datenbanktyp verwendet wird. Es gibt verschiedene Arten von JDBC-Treibern:

  • Typ 1 - enthält eine Zuordnung zu einer anderen Datenzugriffs-API; ein Beispiel für

Dies ist der JDBC-ODBC-Treiber ** Typ 2 - ist eine Implementierung, die clientseitige Bibliotheken der verwendet

Zieldatenbank; wird auch als native-API-Treiber bezeichnet ** Typ 3 - verwendet Middleware, um JDBC-Aufrufe in datenbankspezifische zu konvertieren

Anrufe; auch als Netzwerkprotokolltreiber bekannt ** Typ 4 - Stellen Sie eine direkte Verbindung zu einer Datenbank her, indem Sie JDBC-Aufrufe in konvertieren

datenbankspezifische Aufrufe; bekannt als Datenbankprotokolltreiber oder Thin-Treiber,

Der am häufigsten verwendete Typ ist Typ 4, da er plattformunabhängig ist. Die direkte Verbindung zu einem Datenbankserver bietet im Vergleich zu anderen Typen eine bessere Leistung. Der Nachteil dieses Treibertyps ist, dass er datenbankspezifisch ist - vorausgesetzt, jede Datenbank verfügt über ein eigenes Protokoll.

3. Verbindung zu einer Datenbank herstellen

Um eine Verbindung zu einer Datenbank herzustellen, müssen Sie lediglich den Treiber initialisieren und eine Datenbankverbindung öffnen.

3.1. Registrierung des Treibers

In unserem Beispiel verwenden wir einen Typ 4-Datenbankprotokolltreiber.

Da wir eine MySQL-Datenbank verwenden, benötigen wir https://search.maven.org/classic/#search%7Cga%7C1%7Ca%3A%22mysql-connector-java%22%20AND%20g%3A%22mysql % 22[ mysql-connector-java ]Abhängigkeit:

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

Als Nächstes registrieren wir den Treiber mit der Methode Class.forName () , die die Treiberklasse dynamisch lädt:

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

3.2. Verbindung herstellen

Um eine Verbindung zu öffnen, können wir die getConnection () - Methode der DriverManager -Klasse verwenden. Diese Methode erfordert einen Verbindungs-URL-Parameter String :

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

Die Syntax der Verbindungs-URL hängt von der Art der verwendeten Datenbank ab.

Schauen wir uns einige Beispiele an:

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

Um eine Verbindung zur angegebenen myDb -Datenbank herzustellen, müssen Sie die Datenbank und einen Benutzer erstellen und einen erforderlichen Zugriff hinzufügen:

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

4. SQL-Anweisungen ausführen

Die SQL-Anweisungen zum Senden an die Datenbank können Instanzen des Typs Statement , PreparedStatement oder CallableStatement verwenden. Diese werden mit dem Connection -Objekt abgerufen.

4.1. Aussage

Die Statement -Schnittstelle enthält die wesentlichen Funktionen zur Ausführung von SQL-Befehlen.

Zuerst erstellen wir ein Statement -Objekt:

Statement stmt = con.createStatement();

Die Ausführung von SQL-Anweisungen kann mit drei Methoden ausgeführt werden:

  • executeQuery () für SELECT-Anweisungen

  • executeUpdate () zum Aktualisieren der Daten oder der Datenbankstruktur

  • execute () kann in beiden Fällen verwendet werden, in denen das Ergebnis vorliegt

unbekannte

Verwenden Sie die Methode execute () , um eine students -Tabelle zu unserer Datenbank hinzuzufügen:

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);

Wenn die Methode execute () zum Aktualisieren der Daten verwendet wird, gibt die Methode stmt.getUpdateCount () die Anzahl der betroffenen Zeilen zurück. **

Wenn das Ergebnis 0 ist, waren entweder keine Zeilen betroffen oder es war ein Befehl zum Aktualisieren der Datenbankstruktur.

Wenn der Wert -1 ist, war der Befehl eine SELECT-Abfrage. Das Ergebnis kann dann mit stmt.getResultSet () abgerufen werden.

Als Nächstes fügen wir unserer Tabelle einen Datensatz mit der Methode executeUpdate () hinzu:

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

Die Methode gibt die Anzahl der betroffenen Zeilen für einen Befehl zur Aktualisierung von Zeilen oder 0 für einen Befehl zur Aktualisierung der Datenbankstruktur zurück.

Wir können die Datensätze mit der Methode executeQuery () aus der Tabelle abrufen, die ein Objekt vom Typ ResultSet zurückgibt:

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

4.2. PreparedStatement

PreparedStatement -Objekte enthalten vorkompilierte SQL-Sequenzen. Sie können einen oder mehrere Parameter aufweisen, die mit einem Fragezeichen gekennzeichnet sind.

Erstellen Sie ein PreparedStatement , das Datensätze in der Tabelle employees basierend auf den angegebenen Parametern aktualisiert:

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

Um Parameter zu PreparedStatement hinzuzufügen, können Sie die einfachen Setter setX () verwenden, wobei X der Typ des Parameters ist und die Methodenargumente die Reihenfolge und den Wert des Parameters sind:

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

Die Anweisung wird mit einer der drei zuvor beschriebenen Methoden ausgeführt: executeQuery (), executeUpdate (), execute () ohne den SQL-Parameter String :

int rowsAffected = pstmt.executeUpdate();

4.3. CallableStatement

Die CallableStatement -Schnittstelle ermöglicht das Aufrufen von gespeicherten Prozeduren.

Um ein CallableStatement -Objekt zu erstellen, können wir die prepareCall () -Methode von Connection verwenden:

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

Das Festlegen von Eingabeparameterwerten für die gespeicherte Prozedur erfolgt wie in der Schnittstelle PreparedStatement mit den Methoden setX () :

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

Wenn die gespeicherte Prozedur Ausgabeparameter hat, müssen wir sie mit der Methode registerOutParameter () hinzufügen:

cstmt.registerOutParameter(1, Types.INTEGER);

Dann führen wir die Anweisung aus und rufen den zurückgegebenen Wert mit einer entsprechenden getX () -Methode ab:

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

Um beispielsweise arbeiten zu können, müssen Sie die gespeicherte Prozedur in unserer MySql-Datenbank erstellen:

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 ;

Die oben stehende insertEmployee -Prozedur fügt mit den angegebenen Parametern einen neuen Datensatz in die Tabelle employees ein und gibt die ID des neuen Datensatzes im emp id__out-Parameter zurück.

Um eine gespeicherte Prozedur von Java aus ausführen zu können, muss der Verbindungsbenutzer Zugriff auf die Metadaten der gespeicherten Prozedur haben. Dies kann erreicht werden, indem dem Benutzer Rechte für alle gespeicherten Prozeduren in allen Datenbanken erteilt werden:

GRANT ALL ON mysql.proc TO 'user1';

Alternativ können wir die Verbindung mit der Eigenschaft noAccessToProcedureBodies auf true setzen:

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

Dadurch wird die JDBC-API informiert, dass der Benutzer keine Rechte zum Lesen der Prozedur-Metadaten besitzt, sodass er alle Parameter als INOUT String -Parameter erstellt.

5. Analyse-Abfrageergebnisse

Nach dem Ausführen einer Abfrage wird das Ergebnis durch ein ResultSet -Objekt dargestellt, das eine tabellenähnliche Struktur mit Zeilen und Spalten aufweist.

5.1. ResultSet Schnittstelle

Das ResultSet verwendet die Methode next () , um zur nächsten Zeile zu gelangen.

Erstellen Sie zunächst eine Employee -Klasse zum Speichern unserer abgerufenen Datensätze:

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

   //standard constructor, getters, setters
}

Lassen Sie uns als Nächstes das ResultSet durchlaufen und für jeden Datensatz ein Employee -Objekt erstellen:

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);
}

Das Abrufen des Werts für jede Tabellenzelle kann mithilfe von Methoden des Typs getX ( ) erfolgen, wobei X den Typ der Zellendaten darstellt.

Die getX () - Methoden können mit einem int -Parameter, der die Reihenfolge der Zelle darstellt, oder einem String -Parameter, der den Namen der Spalte darstellt. Die letztere Option ist vorzuziehen, wenn wir die Reihenfolge der Spalten in der Abfrage ändern.

5.2. Aktualisierbares ResultSet

Implizit kann ein ResultSet -Objekt nur vorwärts durchlaufen und nicht geändert werden.

Wenn Sie das ResultSet verwenden möchten, um Daten zu aktualisieren und sie in beide Richtungen zu durchlaufen, müssen Sie das Statement -Objekt mit zusätzlichen Parametern erstellen:

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

Um durch diesen Typ von ResultSet zu navigieren, können wir eine der folgenden Methoden verwenden:

  • first (), last (), beforeFirst (), beforeLast () - um zum ersten zu gelangen

oder letzte Zeile eines ResultSet oder der Zeile vor diesen ** next (), previous () - zum Navigieren vorwärts und rückwärts im

ResultSet ** getRow () – , um die aktuelle Zeilennummer zu erhalten

  • moveToInsertRow (), moveToCurrentRow () - zum Verschieben in eine neue leere Zeile

einfügen und zurück zur aktuellen Zeile, wenn in einer neuen Zeile ** absolute (int row) – , um zur angegebenen Zeile zu gelangen

  • relative (int nrRows) - um den Cursor um die angegebene Anzahl von Zeilen zu verschieben

Die Aktualisierung von ResultSet kann mithilfe von Methoden mit dem Format updateX () erfolgen, wobei X der Typ der Zellendaten ist. Diese Methoden aktualisieren nur das ResultSet -Objekt und nicht die Datenbanktabellen.

Um die Änderungen von ResultSet an der Datenbank zu erhalten, müssen wir weiterhin eine der Methoden verwenden:

  • updateRow () - um die Änderungen an der aktuellen Zeile zu übernehmen

Datenbank ** insertRow (), deleteRow () - um eine neue Zeile hinzuzufügen oder die aktuelle zu löschen

eines aus der Datenbank ** refreshRow () - um das ResultSet mit allen Änderungen im zu aktualisieren

Datenbank ** cancelRowUpdates () - zum Abbrechen der Änderungen an der aktuellen Zeile

Schauen wir uns ein Beispiel für die Verwendung einiger dieser Methoden an, indem Sie die Daten in der Tabelle employee’S aktualisieren:

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. Analysieren von Metadaten

Die JDBC-API ermöglicht das Nachschlagen von Informationen zur Datenbank, die als Metadaten bezeichnet werden.

6.1. DatabaseMetadata

Mit der Schnittstelle DatabaseMetadata können allgemeine Informationen zur Datenbank abgerufen werden, z. B. Tabellen, gespeicherte Prozeduren oder SQL-Dialekt.

Schauen wir uns kurz an, wie wir Informationen zu den Datenbanktabellen abrufen können:

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

6.2. ResultSetMetadata

Diese Schnittstelle kann verwendet werden, um Informationen zu einem bestimmten ResultSet zu finden, beispielsweise die Anzahl und den Namen der Spalten:

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. Abwicklung von Transaktionen

Standardmäßig wird jede SQL-Anweisung unmittelbar nach ihrem Abschluss festgeschrieben.

Es ist jedoch auch möglich, Transaktionen programmgesteuert zu steuern ** .

Dies kann in Fällen notwendig sein, in denen die Datenkonsistenz gewahrt werden soll, wenn zum Beispiel nur eine Transaktion festgeschrieben werden soll, wenn eine vorherige Transaktion erfolgreich abgeschlossen wurde.

Zuerst müssen Sie die autoCommit -Eigenschaft von Connection auf false setzen und dann die Methoden commit () und rollback () zur Steuerung der Transaktion verwenden.

Fügen wir nach der Aktualisierung der Spalte position mitarbeiter eine zweite Aktualisierungsanweisung für die Spalte salary hinzu und wickeln Sie beide in eine Transaktion ein

Auf diese Weise wird das Gehalt nur dann aktualisiert, wenn die Position erfolgreich aktualisiert wurde:

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. Verbindung schließen

Wenn wir nicht mehr verwenden, muss die Verbindung geschlossen werden, um Datenbankressourcen freizugeben.

Dies kann mithilfe der API close () durchgeführt werden:

con.close();

9. Fazit

In diesem Tutorial haben wir uns mit den Grundlagen der Arbeit mit der JDBC-API beschäftigt.

Den vollständigen Quellcode der Beispiele finden Sie wie immer unter over auf GitHub .