Queries/Objecten in Java DataBase Interface v3 (JDBI)
Jdbi (Java DataBase Interface v3) is een lightweight library geschreven bovenop JDBC. Het gebruikt dus de interne Java API om te communiceren tussen de database en de Java applicatie. Echter, het maakt het leven voor ons als ontwikkelaar op heel wat vlakken véél aangenamer: waar JDBC eerder database-driven en dialect-afhankelijk is, is Jdbi eerder user-driven en met behulp van plugins dialect-onafhankelijk.
JDBI3 is opgedeeld in modules, waarvan wij de volgende drie gaan gebruiken:
jdbi3-core
(altijd nodig) - voor JDBC zit dit in de JDK.jdb3-sqlobject
- voor de eenvoudige mapping naar Plain Old Java Objects (POJOs)- Nog steeds je mysql driver:
implementation 'mysql:mysql-connector-java:8.0.33'
Voor SQLite heb je ook nog volgende implementation nodig jdbi3-sqlite
.
implementation 'mysql:mysql-connector-java:8.0.33'
// JDBI
implementation 'org.jdbi:jdbi3-core:3.45.0'
implementation 'org.jdbi:jdbi3-sqlobject:3.45.0'
Met JDBI3 wordt op de volgende manier Java met de DB verbonden:
Er komt dus één blokje bij tussen Java en JDBC: we gebruiken niet langer de ingebouwde JDK interfaces maar rechtstreeks de jdbi-core
dependency die via JDBC de MySQL connectie maakt.
Om voorgaand JDBC demo (met enkel een simpele student) te implementeren in Jdbi3 hebben we eerst een extractie van een interface nodig voor de repository acties, zodat we snel kunnen switchen tussen onze verschillende implementaties:
public interface StudentRepository {
List<Student> getStudentsByName(String student);
void saveNewStudent(Student student);
void updateStudent(Student student);
}
Nu kan StudentRepositoryJdbcImpl
(hernoem bovenstaande) en onze nieuwe StudentRepositoryJdbi3Impl
de interface implements
-en. Afhankelijk van een instelling bijvoorbeeld kunnen we switchen van SQL leverancier, zolang de code overal de interface gebruikt.
JDBC vs Jdbi3
Geen idee waar te beginnen? Hier: http://jdbi.org/
1. Connection openen
In plaats van JDBC’s DriverManager.getConnection()
om de Connection
instance te bootstrappen, gebruiken wij gewoon Jdbi.create()
met ook drie parameters, namelijk dezelfde ConnectionString, user en password.
Jdbi jdbi = Jdbi.create(connectionString, username, password);
2. Query uitvoeren
In plaats van de vervelende checked SQLException
s en de createStatement()
code, heb je nu de keuze om ofwel de Fluent API te gebruiken:
return jdbi.withHandle(handle -> {
return handle.createQuery("SELECT * FROM student WHERE naam = :naam")
.bind("naam", student)
.mapToBean(Student.class)
.list();
});
Merk op dat Jdbi3 er voor kan zorgen dat de resultaten van je query automatisch worden vertaald naar een Student
instantie door middel van bean mapping: de mapToBean()
methode. Die gaat via reflectie alle kolomnamen 1-op-1 mappen op properties van je object dat je wenst te mappen (Je kolomnamen moeten dan wel overeenkomen met de attribuut namen van je Java Klasse). Er zijn ook nog andere mogelijkheden, zoals mappen op een HashMap
, ea:
Om ervoor te zorgen dat de beans
correct werken is het belangrijk dat je een goed werkende equals(Object o)
methode hebt voor je Java Klassen die je met .bindBean
of .mapToBean
wil gebruiken.
Een query manueel behandelen
Voorbeeld:
jdbi.withHandle(handle -> {
// Create a Query object with a named parameter for the student
Query query = handle.createQuery(
"SELECT v.* FROM student_volgt_vak svv JOIN vak v ON svv.vak = v.vaknr WHERE svv.student = :student")
.bind("student", studentId);
// Iterate over each row in the result set manually
for (Row row : query) {
// Manually extract values from the row
int vaknr = row.getInt("vaknr");
String vakNaam = row.getString("naam");
// Process other columns as needed...
// You can now handle the output manually (e.g., print, collect, or process data)
System.out.println("Vak: " + vaknr + " - " + vakNaam);
}
return null; // or any other appropriate return value
});
Enkele methoden met uitleg
Voorbeeld 1: een create
jdbi.withHandle(handle -> {
return handle.createUpdate("INSERT INTO student (studnr, naam, voornaam, goedBezig) VALUES (:studnr, :naam, :voornaam, :goedBezig)")
.bindBean(student)
.execute();
});
withHandle
jdbi.withHandle(handle -> { ... })
De withHandle
methode zorgt voor het openen van een databaseverbinding (handle). Je geeft een lambda (anonieme functie ->
) door waarin je met de handle jouw SQL-statements bouwt en uitvoert.. Na afloop wordt de handle/connectie automatisch gesloten. Dit zorgt voor goed resource management.
handle.createUpdate(...)
Hiermee maak je een SQL INSERT-statement aan. De SQL bevat benoemde parameters (bijv. :studnr
, :naam
, etc.) die later gekoppeld worden aan waarden.
.bindBean(student)
Hiermee koppel je alle eigenschappen van het student object aan de benoemde parameters in je SQL. De velden in student (zoals studnr
, naam
, enz.) worden automatisch gekoppeld aan de overeenkomstige parameters.
.execute()
Dit voert de INSERT-operatie daadwerkelijk uit. Het retourneert meestal het aantal rijen dat is ingevoegd in de database.
Voorbeeld 2: een meer manuele create
public void addStudentToDb(Student student) {
jdbi.withHandle(handle -> {
return handle.execute("INSERT INTO student (studnr, naam, voornaam, goedBezig) VALUES (?, ?, ?, ?)",
student.getStudnr(), student.getNaam(), student.getVoornaam(), student.isGoedBezig());
});
}
Vergelijkbaar met een prepared statement
Voorbeeld 3: een read
(Student) jdbi.withHandle(handle -> {
return handle.createQuery("SELECT * FROM student WHERE studnr = :nummer")
.bind("nummer", stud_nr)
.mapToBean(Student.class)
.first();
});
handle.createQuery(...)
Hiermee maak je een SELECT-query aan. De query haalt alle kolommen op van de tabel student waar studnr gelijk is aan een bepaalde waarde (hier aangeduid met :nummer
).
.bind("nummer", stud_nr)
Hiermee koppel je de waarde van de variabele stud_nr
aan de parameter :nummer
in de SQL-query.
.mapToBean(Student.class)
Na het uitvoeren van de query zet JDBI de resultaatrij(en) om in een object van de klasse Student
. Hierbij worden de kolomnamen in de database vergeleken met de velden in de Student
-klasse.
.first()
Deze functie haalt het eerste resultaat op van de query. Als je er zeker van bent dat de query precies één resultaat oplevert, kun je deze methode gebruiken om direct het Student
object te verkrijgen.
Nog meer methoden
createUpdate
: Hiermee maak je een SQL-statement aan dat de database wijzigt (bijvoorbeeld een INSERT, UPDATE of DELETE).createQuery
: Hiermee maak je een SQL SELECT-query aan. Dit gebruik je wanneer je gegevens uit de database wilt ophalen.execute
: Deze functie voert het eerder samengestelde SQL-statement (zoals een INSERT of UPDATE) daadwerkelijk uit op de database. Vaak retourneert het een integer met het aantal rijen dat is aangetast.bind
: Hiermee koppel je een enkele waarde aan een benoemde parameter in je SQL-statement.bindBean
: Hiermee worden alle eigenschappen van een Java bean (bijvoorbeeld eenStudent
object) automatisch gekoppeld aan de benoemde parameters in je SQL-statement.mapToBean
: Nadat je een SELECT-query hebt uitgevoerd, zet deze functie elke rij uit het resultaat om in een Java bean (bijvoorbeeld een Student object). JDBI vergelijkt de kolomnamen uit de query met de velden in de bean.
Oefening
Hier vind je een zipfolder met een oplossing voor onderstaande oefening: dashboard voor database met enkel studenten tabel