Introduktion til JDBC

Java Top

Jeg har lige annonceret det nye Lær foråret kursus med fokus på det grundlæggende i Spring 5 og Spring Boot 2:

>> KONTROLLER KURSEN

1. Oversigt

I denne artikel skal vi se på JDBC (Java Database Connectivity), som er en API til at forbinde og udføre forespørgsler i en database.

JDBC kan arbejde med enhver database, så længe der findes korrekte drivere.

2. JDBC-drivere

En JDBC-driver er en JDBC API-implementering, der bruges til at oprette forbindelse til en bestemt type database. Der er flere typer JDBC-drivere:

  • Type 1 - indeholder en kortlægning til en anden dataadgang API; et eksempel på dette er JDBC-ODBC-driveren
  • Type 2 - er en implementering, der bruger klientsidebiblioteker i måldatabasen; også kaldet en native-API-driver
  • Type 3 - bruger middleware til at konvertere JDBC-opkald til databasespecifikke opkald; også kendt som en netværksprotokoldriver
  • Type 4 - forbind direkte til en database ved at konvertere JDBC-opkald til databasespecifikke opkald; kendt som database protokol drivere eller tynde drivere,

Den mest anvendte type er type 4, da den har den fordel, at den er platformuafhængig. Forbindelse direkte til en databaseserver giver bedre ydeevne sammenlignet med andre typer. Ulempen ved denne type driver er, at den er databasespecifik - givet hver database har sin egen specifikke protokol.

3. Oprette forbindelse til en database

For at oprette forbindelse til en database er vi simpelthen nødt til at initialisere driveren og åbne en databaseforbindelse.

3.1. Registrering af driveren

I vores eksempel bruger vi en type 4-database protokoldriver.

Da vi bruger en MySQL-database, har vi brug for mysql-connector-java afhængighed:

 mysql mysql-connector-java 6.0.6 

Lad os derefter registrere driveren ved hjælp af Class.forName () metode, der dynamisk indlæser førerklassen:

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

I ældre versioner af JDBC, inden vi fik en forbindelse, måtte vi først initialisere JDBC-driveren ved at ringe til Class.forName metode. Fra og med JDBC 4.0, alle drivere, der findes på klassestien, indlæses automatisk. Derfor har vi ikke brug for dette Class.forName del i moderne miljøer.

3.2. Oprettelse af forbindelsen

For at åbne en forbindelse kan vi bruge getConnection () metode til DriverManager klasse. Denne metode kræver en forbindelses-URL Snor parameter:

prøv (Connection con = DriverManager .getConnection ("jdbc: mysql: // localhost: 3306 / myDb", "user1", "pass")) {// brug con her}

Siden den Forbindelse er en Kan lukkes automatisk ressource, skal vi bruge den i en prøv med ressourcer blok.

Syntaksen for forbindelses-URL'en afhænger af den anvendte database. Lad os se på et par eksempler:

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

For at oprette forbindelse til det angivne myDb database, bliver vi nødt til at oprette databasen og en bruger og tilføje den nødvendige adgang:

Opret DATABASE myDb; OPRET BRUGER 'bruger1' IDENTIFICERET MED 'pass'; TILGIV ALLE på myDb. * TIL 'bruger1';

4. Udførelse af SQL-sætninger

Send SQL-instruktioner til databasen, vi kan bruge forekomster af typen Udmelding, Forberedt erklæring, eller Opkaldelig erklæring, som vi kan få ved hjælp af Forbindelse objekt.

4.1. Udmelding

Det Udmelding interface indeholder de væsentlige funktioner til udførelse af SQL-kommandoer.

Lad os først oprette en Udmelding objekt:

prøv (Statement stmt = con.createStatement ()) {// brug stmt her}

Igen skal vi arbejde med Udmeldings inde i en prøv med ressourcer blok til automatisk ressourcehåndtering.

Under alle omstændigheder kan udførelse af SQL-instruktioner gøres ved hjælp af tre metoder:

  • executeQuery () for SELECT instruktioner
  • executeUpdate () til opdatering af data eller databasestruktur
  • udføre () kan bruges til begge tilfælde ovenfor, når resultatet er ukendt

Lad os bruge udføre () metode til at tilføje en studerende tabel til vores database:

String tableSql = "Opret TABEL, HVIS IKKE FINDER medarbejdere" + "(emp_id int PRIMÆR NØGLE AUTO_INCREMENT, navn varchar (30)," + "position varchar (30), dobbelt løn)"; stmt.execute (tableSql);

Når du bruger udføre () metode til at opdatere dataene, derefter stmt.getUpdateCount () metode returnerer antallet af berørte rækker.

Hvis resultatet er 0, blev enten ingen rækker påvirket, eller det var en opdateringskommando til databasestruktur.

Hvis værdien er -1, var kommandoen en SELECT-forespørgsel; vi kan derefter opnå resultatet ved hjælp af stmt.getResultSet ().

Lad os derefter tilføje en post til vores tabel ved hjælp af executeUpdate () metode:

Streng insertSql = "INDSÆT I medarbejdere (navn, stilling, løn)" + "VÆRDIER ('john', 'udvikler', 2000)"; stmt.executeUpdate (insertSql);

Metoden returnerer antallet af berørte rækker for en kommando, der opdaterer rækker eller 0 for en kommando, der opdaterer databasestrukturen.

Vi kan hente poster fra tabellen ved hjælp af executeQuery () metode, som returnerer et objekt af typen ResultSet:

String selectSql = "VÆLG * FRA medarbejdere"; prøv (ResultSet resultSet = stmt.executeQuery (selectSql)) {// brug resultSet her}

Vi skal sørge for at lukke ResultSet tilfælde efter brug. Ellers holder vi muligvis den underliggende markør åben i en meget længere periode end forventet. For at gøre det anbefales det at bruge en prøv med ressourcer blok, som i vores eksempel ovenfor.

4.2. PreparedStatement

PreparedStatement objekter indeholder prækompilerede SQL-sekvenser. De kan have en eller flere parametre angivet med et spørgsmålstegn.

Lad os oprette en PreparedStatement der opdaterer poster i medarbejdere tabel baseret på givne parametre:

Streng updatePositionSql = "OPDATER medarbejdere SÆT position =? HVOR emp_id =?"; prøv (PreparedStatement pstmt = con.prepareStatement (updatePositionSql)) {// brug pstmt her}

For at tilføje parametre til PreparedStatement, vi kan bruge enkle settere - setX () - hvor X er typen af ​​parameteren, og metodeargumenterne er rækkefølgen og værdien af ​​parameteren:

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

Erklæringen udføres med en af ​​de samme tre metoder beskrevet før: executeQuery (), executeUpdate (), execute () uden SQL Snor parameter:

int rowsAffected = pstmt.executeUpdate ();

4.3. CallableStatement

Det CallableStatement interface muliggør opkald af lagrede procedurer.

At oprette en CallableStatement objekt, kan vi bruge prepareCall () metode til Forbindelse:

String preparedSql = "{kald indsæt medarbejder (?,?,?,?)}"; prøv (CallableStatement cstmt = con.prepareCall (preparedSql)) {// brug cstmt her}

Indstilling af inputparameterværdier for den gemte procedure udføres som i PreparedStatement interface ved hjælp af setX () metoder:

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

Hvis den gemte procedure har outputparametre, skal vi tilføje dem ved hjælp af registerOutParameter () metode:

cstmt.registerOutParameter (1, Types.INTEGER);

Lad os så udføre udsagnet og hente den returnerede værdi ved hjælp af en tilsvarende getX () metode:

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

For eksempel for at arbejde skal vi oprette den lagrede procedure i vores MySql-database:

afgrænser // Opret PROCEDURE insertMedarbejder (OUT emp_id int, IN emp_name varchar (30), IN position varchar (30), IN løn dobbelt) BEGIN INDSÆT I medarbejdere (navn, position, løn) VÆRDIER (emp_name, position, løn); SET emp_id = LAST_INSERT_ID (); END // afgrænser;

Det indsæt Medarbejder ovenstående procedure vil indsætte en ny post i medarbejdere tabel ved hjælp af de givne parametre og returnere id for den nye post i emp_id ud-parameter.

For at kunne køre en lagret procedure fra Java skal forbindelsesbrugeren have adgang til den gemte procedurs metadata. Dette kan opnås ved at give brugeren rettigheder til alle lagrede procedurer i alle databaser:

TILGIV ALLE PÅ mysql.proc TIL 'bruger1';

Alternativt kan vi åbne forbindelsen til ejendommen noAccessToProcedureBodies indstillet til rigtigt:

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

Dette vil informere JDBC API om, at brugeren ikke har rettighederne til at læse metadata for proceduren, så den opretter alle parametre som INOUT Snor parametre.

5. Analyse af forespørgselsresultater

Efter udførelse af en forespørgsel repræsenteres resultatet af a ResultSet objekt, som har en struktur svarende til en tabel med linjer og kolonner.

5.1. ResultSet Interface

Det ResultSet bruger Næste() metode til at gå til næste linje.

Lad os først oprette en Medarbejder klasse til at gemme vores hentede poster:

offentlig klassemedarbejder {privat int id; privat strengnavn; privat strengposition; privat dobbelt løn // standard konstruktør, getters, setters}

Lad os derefter krydse ResultSet og opret en Medarbejder objekt for hver post:

String selectSql = "VÆLG * FRA medarbejdere"; prøv (ResultSet resultSet = stmt.executeQuery (selectSql)) {Liste medarbejdere = ny ArrayList (); mens (resultSet.next ()) {Medarbejder emp = ny medarbejder (); emp.setId (resultSet.getInt ("emp_id")); emp.setName (resultSet.getString ("navn")); emp.setPosition (resultSet.getString ("position")); emp.setSalary (resultSet.getDouble ("løn")); medarbejdere. tilføj (emp); }}

Hentning af værdien for hver tabelcelle kan gøres ved hjælp af typemetoder getX () hvor X repræsenterer typen af ​​celledataene.

Det getX () metoder kan bruges med en int parameter, der repræsenterer rækkefølgen af ​​cellen, eller a Snor parameter, der repræsenterer kolonnens navn. Sidstnævnte mulighed foretrækkes, hvis vi ændrer rækkefølgen af ​​kolonnerne i forespørgslen.

5.2. Opdateres ResultSet

Implicit, a ResultSet objekt kan kun krydses fremad og kan ikke ændres.

Hvis vi vil bruge ResultSet For at opdatere data og krydse dem i begge retninger er vi nødt til at oprette Udmelding objekt med yderligere parametre:

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

For at navigere i denne type ResultSet, kan vi bruge en af ​​metoderne:

  • first (), last (), beforeFirst (), beforeLast () - for at flytte til den første eller sidste linje i en ResultSet eller til linjen før disse
  • næste (), forrige () - for at navigere frem og tilbage i ResultSet
  • getRow () - for at få det aktuelle række nummer
  • moveToInsertRow (), moveToCurrentRow () - for at flytte til en ny tom række for at indsætte og tilbage til den aktuelle hvis den er på en ny række
  • absolut (int række) - for at flytte til den angivne række
  • relativ (int nrRows) - for at flytte markøren det givne antal rækker

Opdatering af ResultSet kan gøres ved hjælp af metoder med formatet updateX () hvor X er typen af ​​celledata. Disse metoder opdaterer kun ResultSet objekt og ikke databasetabellerne.

For at fortsætte ResultSet ændringer til databasen, skal vi yderligere bruge en af ​​metoderne:

  • updateRow () - for at fortsætte ændringerne i den aktuelle række til databasen
  • insertRow (), deleteRow () - for at tilføje en ny række eller slette den aktuelle fra databasen
  • refreshRow () - for at opdatere ResultSet med eventuelle ændringer i databasen
  • cancelRowUpdates () - for at annullere ændringer foretaget i den aktuelle række

Lad os se på et eksempel på brug af nogle af disse metoder ved at opdatere data i medarbejderens bord:

prøv (Statement updatableStmt = con.createStatement (ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE)) {try (ResultSet updatableResultSet = updatableStmt.executeQuery (selectSql)) {updatableResultInsetRove.move updatableResultSet.updateString ("navn", "mark"); updatableResultSet.updateString ("position", "analytiker"); updatableResultSet.updateDouble ("løn", 2000); updatableResultSet.insertRow (); }}

6. Analyse af metadata

JDBC API giver mulighed for at finde oplysninger om databasen, kaldet metadata.

6.1. Database Metadata

Det Database Metadata interface kan bruges til at få generel information om databasen, såsom tabeller, lagrede procedurer eller SQL-dialekt.

Lad os se hurtigt på, hvordan vi kan hente oplysninger i databasetabellerne:

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

6.2. ResultSetMetadata

Denne grænseflade kan bruges til at finde information om et bestemt ResultSet, såsom antallet og navnet på dens kolonner:

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

7. Håndtering af transaktioner

Som standard er hver SQL-sætning begået lige efter den er afsluttet. Det er dog også muligt at kontrollere transaktioner programmatisk.

Dette kan være nødvendigt i tilfælde, hvor vi ønsker at bevare datakonsistens, for eksempel når vi kun vil begå en transaktion, hvis en tidligere har gennemført en succes.

Først skal vi indstille autoCommit ejendom af Forbindelse til falsk, brug derefter begå() og tilbageførsel () metoder til kontrol af transaktionen.

Lad os tilføje en anden opdateringserklæring til løn kolonne efter medarbejderen position kolonneopdatering og pakk dem begge ind i en transaktion. På denne måde opdateres lønnen kun, hvis stillingen blev opdateret:

Streng updatePositionSql = "OPDATER medarbejdere SÆT position =? HVOR emp_id =?"; PreparedStatement pstmt = con.prepareStatement (updatePositionSql); pstmt.setString (1, "blyudvikler"); pstmt.setInt (2, 1); String updateSalarySql = "OPDATER medarbejdere SÆT løn =? HVOR emp_id =?"; PreparedStatement pstmt2 = con.prepareStatement (updateSalarySql); pstmt.setDouble (1, 3000); pstmt.setInt (2, 1); boolsk autoCommit = con.getAutoCommit (); prøv {con.setAutoCommit (false); pstmt.executeUpdate (); pstmt2.executeUpdate (); con.commit (); } fange (SQLException exc) {con.rollback (); } endelig {con.setAutoCommit (autoCommit); }

Af hensyn til korthed udelader vi prøv med ressourcer blokke her.

8. Lukning af ressourcerne

Når vi ikke længere bruger det, vi er nødt til at lukke forbindelsen for at frigive databaseressourcer.

Vi kan gøre dette ved hjælp af tæt() API:

luk ();

Men hvis vi bruger ressourcen i en prøv med ressourcer blokere, vi behøver ikke at ringe til tæt() metode eksplicit, som den prøv med ressourcer block gør det automatisk for os.

Det samme gælder for Udmeldings, PreparedStatements, CallableStatements, og ResultSets.

9. Konklusion

I denne vejledning kiggede vi på det grundlæggende i at arbejde med JDBC API.

Som altid kan den fulde kildekode for eksemplerne findes på GitHub.

Java bund

Jeg har lige annonceret det nye Lær foråret kursus med fokus på det grundlæggende i Spring 5 og Spring Boot 2:

>> KONTROLLER KURSEN