JDBC met Groovy

1. Inleiding

In dit artikel gaan we bekijken hoe relationele databases kunnen worden opgevraagd met JDBC, met behulp van idiomatische Groovy.

Hoewel JDBC relatief laag is, vormt het de basis van de meeste ORM's en andere bibliotheken voor gegevenstoegang op hoog niveau op de JVM. En we kunnen JDBC natuurlijk direct in Groovy gebruiken; het heeft echter een nogal omslachtige API.

Gelukkig voor ons bouwt de Groovy-standaardbibliotheek voort op JDBC om een ​​interface te bieden die schoon, eenvoudig en toch krachtig is. Dus we zullen de Groovy SQL-module verkennen.

We gaan JDBC in gewoon Groovy bekijken, zonder rekening te houden met een raamwerk zoals Spring, waarvoor we andere handleidingen hebben.

2. JDBC en Groovy Setup

We moeten de groovy-sql-module tussen onze afhankelijkheden:

 org.codehaus.groovy groovy 2.4.13 org.codehaus.groovy groovy-sql 2.4.13 

Het is niet nodig om het expliciet te vermelden als we groovy-all gebruiken:

 org.codehaus.groovy groovy-all 2.4.13 

We kunnen de laatste versie van groovy, groovy-sql en groovy-all op Maven Central.

3. Verbinding maken met de database

Het eerste dat we moeten doen om met de database te kunnen werken, is er verbinding mee maken.

Laten we de groovy.sql.Sql class, die we zullen gebruiken voor alle bewerkingen in de database met de Groovy SQL-module.

Een exemplaar van SQL vertegenwoordigt een database waarop we willen opereren.

Echter, een exemplaar van SQLis geen enkele databaseverbinding. We zullen later over verbindingen praten, laten we ons daar nu geen zorgen over maken; laten we aannemen dat alles magisch werkt.

3.1. Verbindingsparameters specificeren

In dit artikel gaan we een HSQL-database gebruiken, een lichtgewicht relationele database die meestal in tests wordt gebruikt.

Een databaseverbinding heeft een URL, een stuurprogramma en toegangsreferenties nodig:

Map dbConnParams = [url: 'jdbc: hsqldb: mem: testDB', gebruiker: 'sa', wachtwoord: '', stuurprogramma: 'org.hsqldb.jdbc.JDBCDriver']

Hier hebben we ervoor gekozen om die te specificeren met een Kaart, hoewel het niet de enige mogelijke keuze is.

We kunnen dan een verbinding krijgen van de SQL klasse:

def sql = Sql.newInstance (dbConnParams)

In de volgende secties zullen we zien hoe u het kunt gebruiken.

Als we klaar zijn, moeten we altijd alle bijbehorende bronnen vrijgeven:

sql.close ()

3.2. Gebruik maken van een Databron

Het is gebruikelijk, vooral bij programma's die binnen een applicatieserver worden uitgevoerd, om een ​​gegevensbron te gebruiken om verbinding te maken met de database.

Ook als we verbindingen willen poolen of JNDI willen gebruiken, is een gegevensbron de meest natuurlijke optie.

Groovy's SQL klasse accepteert gegevensbronnen prima:

def sql = Sql.newInstance (gegevensbron)

3.3. Automatisch bronnenbeheer

Vergeet niet te bellen dichtbij() als we klaar zijn met een SQL instantie is vervelend; machines onthouden tenslotte dingen veel beter dan wij.

Met SQL we kunnen onze code in een afsluiting stoppen en Groovy bellen dichtbij() automatisch wanneer de controle het verlaat, zelfs in het geval van uitzonderingen:

Sql.withInstance (dbConnParams) {Sql sql -> haveFunWith (sql)}

4. Het afgeven van verklaringen over de database

Nu kunnen we verder gaan met de interessante dingen.

De meest eenvoudige en niet-gespecialiseerde manier om een ​​verklaring tegen de database af te geven, is de uitvoeren methode:

sql.execute "create table PROJECT (id geheel getal niet null, naam varchar (50), url varchar (100))"

In theorie werkt het zowel voor DDL / DML-statements als voor queries; Het eenvoudige formulier hierboven biedt echter geen manier om zoekresultaten terug te krijgen. We laten vragen achter voor later.

De uitvoeren method heeft verschillende overbelaste versies, maar nogmaals, we zullen in latere secties kijken naar de meer geavanceerde gebruiksscenario's van deze en andere methoden.

4.1. Gegevens invoegen

Voor het invoegen van gegevens in kleine hoeveelheden en in eenvoudige scenario's, de uitvoeren de eerder besproken methode is prima.

Voor gevallen waarin we kolommen hebben gegenereerd (bijvoorbeeld met reeksen of automatisch verhogen) en we de gegenereerde waarden willen weten, bestaat er een speciale methode: executeInsert.

Wat betreft uitvoeren, zullen we nu kijken naar de meest eenvoudige methode-overbelasting die beschikbaar is, waardoor meer complexe varianten overblijven voor een latere sectie.

Stel dat we een tabel hebben met een primaire sleutel voor automatisch verhogen (identiteit in HSQLDB-taalgebruik):

sql.execute "create table PROJECT (ID IDENTITY, NAME VARCHAR (50), URL VARCHAR (100))"

Laten we een rij in de tabel invoegen en het resultaat opslaan in een variabele:

def ids = sql.execute Invoegen "" "INVOEGEN IN PROJECT (NAAM, URL) WAARDEN ('tutorials', 'github.com/eugenp/tutorials')" ""

executeInsert gedraagt ​​zich precies zoals uitvoeren, maar wat levert het op?

Het blijkt dat de geretourneerde waarde een matrix is: de rijen zijn de ingevoegde rijen (onthoud dat een enkele instructie ervoor kan zorgen dat meerdere rijen worden ingevoegd) en de kolommen zijn de gegenereerde waarden.

Het klinkt ingewikkeld, maar in ons geval, dat verreweg het meest voorkomt, is er een enkele rij en een enkele gegenereerde waarde:

assertEquals (0, ids [0] [0])

Een volgende invoeging zou een gegenereerde waarde van 1 retourneren:

ids = sql.executeInsert "" "INSERT IN PROJECT (NAME, URL) VALUES ('REST with Spring', 'github.com/eugenp/REST-With-Spring')" "" assertEquals (1, ids [0] [ 0])

4.2. Gegevens bijwerken en verwijderen

Evenzo bestaat er een speciale methode voor het wijzigen en verwijderen van gegevens: executeUpdate.

Nogmaals, dit verschilt van uitvoeren alleen in de retourwaarde, en we zullen alleen naar de eenvoudigste vorm kijken.

De geretourneerde waarde is in dit geval een geheel getal, het aantal betrokken rijen:

def count = sql.executeUpdate ("UPDATE PROJECT SET URL = '//' + URL") assertEquals (2, count)

5. Opvragen van de database

Het begint Groovy te worden als we de database doorzoeken.

Omgaan met de JDBC ResultSet les is niet bepaald leuk. Gelukkig voor ons biedt Groovy daarover een mooie abstractie.

5.1. Resultaten van zoekopdrachten herhalen

Terwijl loops zo oud zijn ... we zijn tegenwoordig allemaal in de buurt van sluitingen.

En Groovy is er om aan onze smaak te voldoen:

sql.eachRow ("SELECT * VAN PROJECT") {GroovyResultSet rs -> haveFunWith (rs)}

De elke rij method voert onze query uit op de database en roept een afsluiting op voor elke rij.

Zoals we kunnen zien, een rij wordt vertegenwoordigd door een instantie van GroovyResultSet, wat een uitbreiding is van gewoon oud ResultSet met een paar extra lekkers. Lees verder om er meer over te weten te komen.

5.2. Toegang tot resultatensets

Naast alle ResultSet methoden, GroovyResultSet biedt een aantal handige hulpprogramma's.

Het toont voornamelijk benoemde eigenschappen die overeenkomen met kolomnamen:

sql.eachRow ("SELECT * VAN PROJECT") {rs -> assertNotNull (rs.name) assertNotNull (rs.URL)}

Merk op dat namen van eigenschappen niet hoofdlettergevoelig zijn.

GroovyResultSet biedt ook toegang tot kolommen met behulp van een op nul gebaseerde index:

sql.eachRow ("SELECT * FROM PROJECT") {rs -> assertNotNull (rs [0]) assertNotNull (rs [1]) assertNotNull (rs [2])}

5.3. Paginering

We kunnen de resultaten gemakkelijk doorbladeren, d.w.z. alleen een subset laden vanaf een bepaalde offset tot een bepaald maximum aantal rijen. Dit is bijvoorbeeld een veelvoorkomend probleem in webapplicaties.

elke rij en gerelateerde methoden hebben overbelastingen die een offset accepteren en een maximum aantal geretourneerde rijen:

def offset = 1 def maxResults = 1 def rijen = sql.rows ('SELECT * FROM PROJECT ORDER BY NAME', offset, maxResults) assertEquals (1, rijen.size ()) assertEquals ('REST met veer', rijen [0 ].naam)

Hier de rijen methode retourneert een lijst met rijen in plaats van eroverheen te herhalen, zoals elke rij.

6. Geparametriseerde vragen en verklaringen

Vaker wel dan niet, zijn vragen en verklaringen niet volledig opgelost tijdens het compileren; ze hebben meestal een statisch deel en een dynamisch deel, in de vorm van parameters.

Als je aan stringconcatenatie denkt, stop dan nu en lees meer over SQL-injectie!

We hebben eerder vermeld dat de methoden die we in eerdere secties hebben gezien, veel overbelastingen hebben voor verschillende scenario's.

Laten we de overbelastingen introduceren die te maken hebben met parameters in SQL-query's en -instructies.

6.1. Tekenreeksen met tijdelijke aanduidingen

In stijl vergelijkbaar met gewone JDBC, kunnen we positionele parameters gebruiken:

sql.execute ('INSERT IN PROJECT (NAME, URL) VALUES (?,?)', 'tutorials', 'github.com/eugenp/tutorials')

of we kunnen benoemde parameters gebruiken met een kaart:

sql.execute ('INSERT IN PROJECT (NAME, URL) VALUES (: name,: url)', [name: 'REST with Spring', url: 'github.com/eugenp/REST-With-Spring'])

Dit werkt voor uitvoeren, executeUpdate, rijen en elke rij. executeInsert ondersteunt ook parameters, maar de handtekening is een beetje anders en lastiger.

6.2. Groovy snaren

We kunnen ook kiezen voor een Groovier-stijl met GStrings met tijdelijke aanduidingen.

Alle methoden die we hebben gezien, vervangen plaatshouders in GStrings niet op de gebruikelijke manier; in plaats daarvan voegen ze ze in als JDBC-parameters, waardoor de SQL-syntaxis correct wordt behouden, zonder dat er iets hoeft te worden geciteerd of ontsnapt en dus geen risico op injectie.

Dit is prima, veilig en Groovy:

def name = 'REST met Spring' def url = 'github.com/eugenp/REST-With-Spring' sql.execute "INSERT IN PROJECT (NAME, URL) VALUES ($ {name}, $ {url})"

7. Transacties en verbindingen

Tot nu toe hebben we een zeer belangrijke zorg overgeslagen: transacties.

In feite hebben we het helemaal niet gehad over hoe Groovy is SQL beheert ook verbindingen.

7.1. Kortstondige verbindingen

In de voorbeelden die tot nu toe zijn gepresenteerd, elke vraag of verklaring werd naar de database gestuurd via een nieuwe, speciale verbinding. SQL verbreekt de verbinding zodra de bewerking wordt beëindigd.

Als we een verbindingspool gebruiken, kan de impact op de prestaties natuurlijk klein zijn.

Nog steeds, als we meerdere DML-instructies en -query's willen uitgeven als een enkele, atomaire bewerking, we hebben een transactie nodig.

Om een ​​transactie überhaupt mogelijk te maken, hebben we een verbinding nodig die meerdere verklaringen en vragen omvat.

7.2. Transacties met een cacheverbinding

Groovy SQL staat ons niet toe om expliciet transacties aan te maken of toegang te krijgen.

In plaats daarvan gebruiken we de metTransactie methode met sluiting:

sql.withTransaction {sql.execute "" "INSERT IN PROJECT (NAME, URL) VALUES ('tutorials', 'github.com/eugenp/tutorials')" "" sql.execute "" "INSERT IN PROJECT (NAME, URL) ) WAARDEN ('REST met lente', 'github.com/eugenp/REST-With-Spring') "" "}

Binnen de afsluiting wordt een enkele databaseverbinding gebruikt voor alle queries en statements.

Bovendien wordt de transactie automatisch vastgelegd wanneer de sluiting wordt beëindigd, tenzij deze vanwege een uitzondering vroegtijdig wordt beëindigd.

We kunnen de huidige transactie echter ook handmatig vastleggen of terugdraaien met methoden in de SQL klasse:

sql.withTransaction {sql.execute "" "INSERT IN PROJECT (NAME, URL) VALUES ('tutorials', 'github.com/eugenp/tutorials')" "" sql.commit () sql.execute "" "INSERT INTO PROJECT (NAME, URL) VALUES ('REST with Spring', 'github.com/eugenp/REST-With-Spring') "" "sql.rollback ()}

7.3. Verbindingen in cache zonder een transactie

Ten slotte gebruiken we om een ​​databaseverbinding opnieuw te gebruiken zonder de transactiesemantiek die hierboven is beschreven cacheConnection:

sql.cacheConnection {sql.execute "" "INSERT IN PROJECT (NAME, URL) VALUES ('tutorials', 'github.com/eugenp/tutorials')" "" throw new Exception ('This does not roll back')}

8. Conclusies en verder lezen

In dit artikel hebben we gekeken naar de Groovy SQL-module en hoe deze JDBC verbetert en vereenvoudigt met sluitingen en Groovy-strings.

We kunnen dan gerust concluderen dat gewone oude JDBC er wat moderner uitziet met een scheutje Groovy!

We hebben het nog niet gehad over elke functie van Groovy SQL; We hebben bijvoorbeeld batchverwerking, opgeslagen procedures, metadata en andere zaken weggelaten.

Zie de Groovy-documentatie voor meer informatie.

De implementatie van al deze voorbeelden en codefragmenten is te vinden in het GitHub-project - dit is een Maven-project, dus het zou gemakkelijk moeten kunnen worden geïmporteerd en uitgevoerd zoals het is.