Inleiding tot JDBC

Java Top

Ik heb zojuist het nieuwe aangekondigd Leer de lente natuurlijk, gericht op de basisprincipes van Spring 5 en Spring Boot 2:

>> BEKIJK DE CURSUS

1. Overzicht

In dit artikel gaan we kijken naar JDBC (Java Database Connectivity), een API voor het verbinden en uitvoeren van query's op een database.

JDBC kan met elke database werken, zolang de juiste stuurprogramma's worden geleverd.

2. JDBC-stuurprogramma's

Een JDBC-stuurprogramma is een JDBC API-implementatie die wordt gebruikt om verbinding te maken met een bepaald type database. Er zijn verschillende soorten JDBC-stuurprogramma's:

  • Type 1 - bevat een mapping naar een andere API voor gegevenstoegang; een voorbeeld hiervan is het JDBC-ODBC-stuurprogramma
  • Type 2 - is een implementatie die client-side bibliotheken van de doeldatabase gebruikt; ook wel een native API-stuurprogramma genoemd
  • Type 3 - gebruikt middleware om JDBC-oproepen om te zetten in databasespecifieke oproepen; ook wel bekend als een netwerkprotocolstuurprogramma
  • Type 4 - maak rechtstreeks verbinding met een database door JDBC-oproepen om te zetten in database-specifieke oproepen; bekend als databaseprotocolstuurprogramma's of dunne stuurprogramma's,

Het meest gebruikte type is type 4, omdat het het voordeel heeft dat het zo is platformonafhankelijk. Rechtstreeks verbinding maken met een databaseserver levert betere prestaties op in vergelijking met andere typen. Het nadeel van dit type stuurprogramma is dat het database-specifiek is, aangezien elke database zijn eigen specifieke protocol heeft.

3. Verbinding maken met een database

Om verbinding te maken met een database, hoeven we alleen maar het stuurprogramma te initialiseren en een databaseverbinding te openen.

3.1. De bestuurder registreren

Voor ons voorbeeld gebruiken we een type 4 databaseprotocolstuurprogramma.

Omdat we een MySQL-database gebruiken, hebben we de mysql-connector-java afhankelijkheid:

 mysql mysql-connector-java 6.0.6 

Laten we vervolgens de driver registreren met de Class.forName () methode, die de stuurprogrammaklasse dynamisch laadt:

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

In oudere versies van JDBC moesten we, voordat we verbinding konden maken, eerst het JDBC-stuurprogramma initialiseren door de Class.forName methode. Vanaf JDBC 4.0, alle stuurprogramma's die in het klassenpad worden gevonden, worden automatisch geladen. Daarom hebben we dit niet nodig Class.forName deel in moderne omgevingen.

3.2. De verbinding tot stand brengen

Om een ​​verbinding te openen, kunnen we de getConnection () methode van DriverManager klasse. Deze methode vereist een verbindings-URL Draad parameter:

probeer (Connection con = DriverManager .getConnection ("jdbc: mysql: // localhost: 3306 / myDb", "user1", "pass")) {// use con here}

Sinds de Verbinding is een AutoCloseable resource, we zouden het moeten gebruiken in een probeer-met-middelen blok.

De syntaxis van de verbindings-URL is afhankelijk van het type database dat wordt gebruikt. Laten we een paar voorbeelden bekijken:

jdbc: mysql: // localhost: 3306 / myDb? user = gebruiker1 & wachtwoord = pass
jdbc: postgresql: // localhost / myDb
jdbc: hsqldb: mem: myDb

Om verbinding te maken met het opgegeven myDb database, zullen we de database en een gebruiker moeten aanmaken en de nodige toegang moeten verlenen:

DATABASE AANMAKEN myDb; CREËER GEBRUIKER 'gebruiker1' GEÏDENTIFICEERD DOOR 'pass'; ALLES VERLENEN op myDb. * TO 'user1';

4. SQL-instructies uitvoeren

Om SQL-instructies naar de database te sturen, kunnen we instanties van het type gebruiken Uitspraak, PreparedStatement, of Oproepbare verklaring, die we kunnen verkrijgen met behulp van de Verbinding voorwerp.

4.1. Uitspraak

De Uitspraak interface bevat de essentiële functies voor het uitvoeren van SQL-opdrachten.

Laten we eerst een Uitspraak voorwerp:

probeer (Statement stmt = con.createStatement ()) {// gebruik hier stmt}

Nogmaals, we zouden ermee moeten werken Uitspraaks binnen een probeer-met-middelen blok voor automatisch middelenbeheer.

Hoe dan ook, het uitvoeren van SQL-instructies kan worden gedaan door middel van drie methoden:

  • executeQuery () voor SELECT instructies
  • uitvoerenUpdate () voor het bijwerken van de gegevens of de databasestructuur
  • uitvoeren () kan voor beide bovenstaande gevallen worden gebruikt als het resultaat onbekend is

Laten we de uitvoeren () methode om een studenten tabel naar onze database:

String tableSql = "MAAK TABEL INDIEN NIET BESTAAT werknemers" + "(emp_id int PRIMAIRE SLEUTEL AUTO_INCREMENT, naam varchar (30)," + "positie varchar (30), salaris verdubbeld)"; stmt.execute (tableSql);

Bij gebruik van de uitvoeren () methode om de gegevens bij te werken, en vervolgens de stmt.getUpdateCount () methode geeft het aantal betrokken rijen terug.

Als het resultaat 0 is, zijn er geen rijen aangetast of is het een opdracht voor het bijwerken van de databasestructuur.

Als de waarde -1 is, dan was de opdracht een SELECT-query; we kunnen dan het resultaat verkrijgen met stmt.getResultSet ().

Laten we vervolgens een record aan onze tabel toevoegen met de uitvoerenUpdate () methode:

String insertSql = "INVOEGEN IN werknemers (naam, functie, salaris)" + "WAARDEN ('john', 'ontwikkelaar', 2000)"; stmt.executeUpdate (insertSql);

De methode retourneert het aantal betrokken rijen voor een opdracht die rijen bijwerkt of 0 voor een opdracht die de databasestructuur bijwerkt.

We kunnen de records uit de tabel halen met behulp van de executeQuery () methode die een object van het type retourneert ResultSet:

String selectSql = "SELECT * VAN werknemers"; probeer (ResultSet resultSet = stmt.executeQuery (selectSql)) {// gebruik hier resultSet}

We moeten ervoor zorgen dat het ResultSet gevallen na gebruik. Anders kunnen we de onderliggende cursor veel langer open houden dan verwacht. Om dat te doen, wordt aanbevolen om een probeer-met-middelen block, zoals in ons voorbeeld hierboven.

4.2. PreparedStatement

PreparedStatement objecten bevatten vooraf gecompileerde SQL-reeksen. Ze kunnen een of meer parameters hebben die worden aangegeven met een vraagteken.

Laten we een PreparedStatement die records in het medewerkers tabel op basis van gegeven parameters:

String updatePositionSql = "UPDATE werknemers SET position =? WHERE emp_id =?"; probeer (PreparedStatement pstmt = con.prepareStatement (updatePositionSql)) {// gebruik hier pstmt}

Om parameters toe te voegen aan het PreparedStatement, we kunnen eenvoudige setters gebruiken - setX () - waarbij X het type parameter is en de methode-argumenten de volgorde en waarde van de parameter zijn:

pstmt.setString (1, "hoofdontwikkelaar"); pstmt.setInt (2, 1);

De instructie wordt uitgevoerd met een van dezelfde drie methoden die eerder zijn beschreven: executeQuery (), executeUpdate (), execute () zonder de SQL Draad parameter:

int rijenAffected = pstmt.executeUpdate ();

4.3. Oproepbare verklaring

De Oproepbare verklaring interface maakt het oproepen van opgeslagen procedures mogelijk.

Om een Oproepbare verklaring object, kunnen we de voorbereidenCall () methode van Verbinding:

String voorbereideSql = "{call insertEmployee (?,?,?,?)}"; probeer (CallableStatement cstmt = con.prepareCall (voorbereideSql)) {// gebruik cstmt hier}

Het instellen van invoerparameterwaarden voor de opgeslagen procedure wordt gedaan zoals in het PreparedStatement interface, met behulp van setX () methoden:

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

Als de opgeslagen procedure uitvoerparameters heeft, moeten we deze toevoegen met de registerOutParameter () methode:

cstmt.registerOutParameter (1, Types.INTEGER);

Laten we vervolgens de instructie uitvoeren en de geretourneerde waarde ophalen met een overeenkomstig getX () methode:

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

Om bijvoorbeeld te werken, moeten we de opgeslagen procedure in onze MySql-database maken:

scheidingsteken // CREATE PROCEDURE insertEmployee (OUT emp_id int, IN emp_name varchar (30), IN position varchar (30), IN salaris dubbel) BEGIN INSERT IN werknemers (naam, functie, salaris) WAARDEN (emp_name, positie, salaris); SET emp_id = LAST_INSERT_ID (); END // scheidingsteken;

De insertEmployee bovenstaande procedure zal een nieuw record in de medewerkers tabel met behulp van de opgegeven parameters en retourneer de id van het nieuwe record in de emp_id out parameter.

Om een ​​opgeslagen procedure vanuit Java te kunnen uitvoeren, moet de verbindingsgebruiker toegang hebben tot de metagegevens van de opgeslagen procedure. Dit kan worden bereikt door de gebruiker rechten te verlenen op alle opgeslagen procedures in alle databases:

VERLEENT ALLES OP mysql.proc AAN 'user1';

Als alternatief kunnen we de verbinding met de woning openen noAccessToProcedureBodies ingesteld op waar:

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

Hierdoor wordt de JDBC API geïnformeerd dat de gebruiker niet de rechten heeft om de metagegevens van de procedure te lezen, zodat alle parameters als INOUT worden aangemaakt Draad parameters.

5. Queryresultaten parseren

Na het uitvoeren van een query wordt het resultaat weergegeven door een ResultSet object, dat een structuur heeft die lijkt op een tabel, met lijnen en kolommen.

5.1. ResultSet Koppel

De ResultSet gebruikt de De volgende() methode om naar de volgende regel te gaan.

Laten we eerst een Werknemer klasse om onze opgehaalde records op te slaan:

openbare klasse Medewerker {privé int id; private String naam; private String-positie; particulier dubbel salaris; // standard constructor, getters, setters}

Laten we vervolgens het ResultSet en maak een Werknemer object voor elk record:

String selectSql = "SELECT * VAN werknemers"; probeer (ResultSet resultSet = stmt.executeQuery (selectSql)) {Lijst werknemers = nieuwe ArrayList (); while (resultSet.next ()) {Werknemer emp = nieuwe werknemer (); emp.setId (resultSet.getInt ("emp_id")); emp.setName (resultSet.getString ("naam")); emp.setPosition (resultSet.getString ("position")); emp.setSalary (resultSet.getDouble ("salaris")); medewerkers.add (emp); }}

Het ophalen van de waarde voor elke tabelcel kan worden gedaan met behulp van typemethoden getX () waarbij X staat voor het type celgegevens.

De getX () methoden kunnen worden gebruikt met een int parameter die de volgorde van de cel aangeeft, of een Draad parameter die de naam van de kolom vertegenwoordigt. De laatste optie verdient de voorkeur als we de volgorde van de kolommen in de query wijzigen.

5.2. Bij te werken ResultSet

Impliciet een ResultSet object kan alleen vooruit worden doorlopen en kan niet worden gewijzigd.

Als we de ResultSet om gegevens bij te werken en deze in beide richtingen te doorlopen, moeten we de Uitspraak object met aanvullende parameters:

stmt = con.createStatement (ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);

Om door dit type ResultSetkunnen we een van de volgende methoden gebruiken:

  • first (), last (), beforeFirst (), beforeLast () - om naar de eerste of laatste regel van een ResultSet of naar de regel ervoor
  • volgende vorige() - om vooruit en achteruit te navigeren in het ResultSet
  • getRow () - om het huidige rijnummer te verkrijgen
  • moveToInsertRow (), moveToCurrentRow () - om naar een nieuwe lege rij te gaan om in te voegen en terug naar de huidige in een nieuwe rij
  • absoluut (int rij) - om naar de opgegeven rij te gaan
  • relatief (int nrRows) - om de cursor het opgegeven aantal rijen te verplaatsen

Updaten van het ResultSet kan worden gedaan met behulp van methoden met het formaat updateX () waarbij X het type celgegevens is. Deze methoden werken alleen het ResultSet object en niet de databasetabellen.

Om het ResultSet wijzigingen in de database, moeten we verder een van de methoden gebruiken:

  • updateRow () - om de wijzigingen in de huidige rij in de database vast te houden
  • insertRow (), deleteRow () - om een ​​nieuwe rij toe te voegen of de huidige uit de database te verwijderen
  • refreshRow () - om het ResultSet met eventuele wijzigingen in de database
  • annulerenRowUpdates () - om wijzigingen aan de huidige rij te annuleren

Laten we eens kijken naar een voorbeeld van het gebruik van enkele van deze methoden door gegevens in het werknemer tafel:

try (Statement updatableStmt = con.createStatement (ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE)) {try (ResultSet updatableResultSet = updatableStmt.executeQuery (selectSql)) {updatable (selectSql)) {updatable (selectSql)) {updatable (selectSql)); updatableResultSet.updateString ("naam", "mark"); updatableResultSet.updateString ("positie", "analist"); updatableResultSet.updateDouble ("salaris", 2000); updatableResultSet.insertRow (); }}

6. Metagegevens parseren

De JDBC API maakt het mogelijk om informatie over de database op te zoeken, metadata genaamd.

6.1. DatabaseMetadata

De DatabaseMetadata interface kan worden gebruikt om algemene informatie over de database te verkrijgen, zoals de tabellen, opgeslagen procedures of SQL-dialect.

Laten we snel kijken hoe we informatie over de databasetabellen kunnen ophalen:

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

6.2. ResultSetMetadata

Deze interface kan worden gebruikt om informatie over een bepaald persoon te vinden ResultSet, zoals het nummer en de naam van de kolommen:

ResultSetMetaData rsmd = rs.getMetaData (); int nrColumns = rsmd.getColumnCount (); IntStream.range (1, nrColumns) .forEach (i -> {probeer {LOG.info (rsmd.getColumnName (i));} catch (SQLException e) {e.printStackTrace ();}});

7. Transacties afhandelen

Standaard wordt elke SQL-instructie vastgelegd direct nadat deze is voltooid. Het is echter ook mogelijk transacties programmatisch beheren.

Dit kan nodig zijn in gevallen waarin we gegevensconsistentie willen behouden, bijvoorbeeld wanneer we alleen een transactie willen plegen als een vorige succesvol is voltooid.

Eerst moeten we de autoCommit eigendom van Verbinding naar false, gebruik dan de plegen () en terugrollen() methoden om de transactie te controleren.

Laten we een tweede update-instructie toevoegen voor de salaris kolom achter de werknemer positie kolomupdate en verpak ze allebei in een transactie. Op deze manier wordt het salaris alleen bijgewerkt als de functie succesvol is bijgewerkt:

String updatePositionSql = "UPDATE werknemers SET position =? WHERE emp_id =?"; PreparedStatement pstmt = con.prepareStatement (updatePositionSql); pstmt.setString (1, "hoofdontwikkelaar"); pstmt.setInt (2, 1); String updateSalarySql = "UPDATE werknemers SET salaris =? WHERE emp_id =?"; PreparedStatement pstmt2 = con.prepareStatement (updateSalarySql); pstmt.setDouble (1, 3000); pstmt.setInt (2, 1); boolean autoCommit = con.getAutoCommit (); probeer {con.setAutoCommit (false); pstmt.executeUpdate (); pstmt2.executeUpdate (); con.commit (); } catch (SQLException exc) {con.rollback (); } eindelijk {con.setAutoCommit (autoCommit); }

Kortheidshalve laten we de probeer-met-middelen blokken hier.

8. Het sluiten van de bronnen

Als we het niet langer gebruiken, we moeten de verbinding verbreken om databasebronnen vrij te geven.

We kunnen dit doen met behulp van de dichtbij() API:

con.close ();

Als we de bron echter gebruiken in een probeer-met-middelen block, we hoeven de dichtbij() methode expliciet, zoals de probeer-met-middelen block doet dat automatisch voor ons.

Hetzelfde geldt voor de Uitspraaks, PreparedStatements, Oproepbare verklarings, en ResultSets.

9. Conclusie

In deze tutorial hebben we de basisprincipes van het werken met de JDBC API bekeken.

Zoals altijd is de volledige broncode van de voorbeelden te vinden op GitHub.

Java onderkant

Ik heb zojuist het nieuwe aangekondigd Leer de lente natuurlijk, gericht op de basisprincipes van Spring 5 en Spring Boot 2:

>> BEKIJK DE CURSUS