Dopo aver presentato in linea generale la libreria JOOQ nell'articolo precedente, ci dedicheremo stavolta a mostrare un esempio molto semplice di generazione automatica delle classi di mappatura ORM con maven.
Le classi per l'Object Relational Mapping risultano molto utili per semplificare la scrittura di codice pseudo-SQL, utilizzando le api di un linguaggio di programmazione ad oggetti come java
, o eventualmente una specifica DSL implementata tramite il linguaggio scala
, nel caso della libreria JOOQ.
Generazione delle factory per Object Relational Mapping.
Ci interessa a questo punto fare uso del mapping ad oggetti di JOOQ. A tal proposito nella libreria è presente una opportuna classe (org.jooq.util.DefaultGenerator
).
La classe può essere utilizzata da linea di comando in maniera del tutto simile alla seguente:
>> java -classpath lib/jooq-2.0.0.jar: lib/jooq-meta-2.0.0.jar: lib/jooq-codegen-2.0.0.jar: lib/mysql-connector-java-5.1.18.jar: . org.jooq.util.GenerationTool /hotel.properties
dove abbiamo cercato di formattare il comando per renderlo più leggibile: ovviamente andrà scritto invece su una sola riga, preoccupandoci di utilizzare i path corretti per le librerie relative (nel nostro esempio si fa riferimento ad una invocazione su console linux, dalla quale sia raggiungibile una directory "lib") contenente le librerie.
L'elemento fondamentale di questa procedura assistita è il file di properties hotel.properties
, contenente le configurazioni minime perché possano essere prodotte le classi di mapping sul database dell'esempio introdotto nel precedente articolo:
jdbc.Driver=com.mysql.jdbc.Driver jdbc.URL=jdbc:mysql://localhost:3306/hotel jdbc.User=root jdbc.Password=root generator=org.jooq.util.DefaultGenerator generator.database=org.jooq.util.mysql.MySQLDatabase generator.database.input-schema=hotel generator.database.includes=.* generator.database.excludes= generator.generate.relations=true generator.target.package=it.html.articoli.esempi.jooq.generated generator.target.directory=src/main/java
(il file hotel.properties)
Le classi prodotte avranno la struttura:
All'interno di questa struttura è infine abbastanza facile riconoscere la presenza di una classe per ogni tabella, e di una tabella "Tables
" che contiene un riferimento statico a ciascuna delle tabelle prodotte automaticamente (una sorta di bean
): questo ci permetterà -come vedremo tra poco- di semplificare la scrittura assistita di SQL per mezzo delle api appena prodotte tramite la code generation che abbiamo lanciato.
Una classe di particolare interesse è infine la HotelFactory
: in generale verrà creata una factory principale per ogni database di default, anche se è possibile modificare questo comportamento agendo sulla configurazione.
Il progetto di esempio: eclipse, maven, JOOQ code generation
La struttura del progetto di esempio
Per il nostro esempio abbiamo scelto di utilizzare maven
per gestire le dipendenze ed il build. Definiamo quindi un progetto principale Introduzione_JOOQ_parte_2
come parent pom e due moduli:
jooq-classi-generate
conterrà le classi prodotte dalla code generation di JOOQjooq-esempio-fluent-main
che conterrà l'esempio vero e proprio di utilizzo delle api
Configurazione della code generation nel progetto maven.
La parte che ci interessa maggiormente e che ci ha spinto a questa separazione è però la possibilità di generare le classi direttamente all'interno del ciclo di build di maven.
A tal proposito abbiamo scelto di utilizzare per semplicità la fase install
del modulo jooq-classi-generate
, separando il più possibile la scrittura assistita di codice dalla usuale compilazione e build/test
L'integrazione del plugin di JOOQ per maven richiede una configurazione simile alla seguente (per i dettagli si veda l'esempio allegato), che è corrispondente (ed alternativa, se si usa maven invece della riga di comando!) all'utilizzo del file properties già mostrato:
<build>
<plugins>
<plugin>
<groupId>org.jooq</groupId>
<artifactId>jooq-codegen-maven</artifactId>
<version>2.0.0</version>
<executions>
<execution>
<phase>install</phase>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<dependencies>...</dependencies>
<configuration>
<tasks>
<echo>GENERATING JAVA SOURCES with JOOQ...</echo>
</tasks>
<jdbc>
<driver>com.mysql.jdbc.Driver</driver>
<url>jdbc:mysql://localhost:3306/hotel</url>
<user>root</user>
<password>root</password>
</jdbc>
<generator>
<name>org.jooq.util.DefaultGenerator</name>
<database>
<name>org.jooq.util.mysql.MySQLDatabase</name>
<includes>.*</includes>
<excludes></excludes>
<inputSchema>hotel</inputSchema>
</database>
<generate>
<relations>true</relations>
<deprecated>false</deprecated>
</generate>
<target>
<packageName>it.html.articoli.esempi.jooq.generated</packageName>
<directory>src/main/java</directory>
</target>
</generator>
</configuration>
</plugin>
</plugins>
</build>
Questo snippet farà parte nel nostro caso del file pom.xml del modulo di code generation
Partendo da questa idea iniziale è possibile personalizzare il proprio processo di build: in ogni caso per far girare l'esempio accontententiamoci di lanciare il comando >> mvn install
sul modulo jooq-classi-generate
per la generazione dei sorgenti prima del build complessivo vero e proprio sull'intero progetto.
Un esempio di query SQL tramite gli oggetti e le factory JOOQ
Vogliamo a questo punto eseguire una query che ci consenta di stabilire i dettagli di prenotazione delle camere nel database del nostro albergo in un particolare giorno.
la query SQL di esempio
La query SQL che ci interessa riprodurre con JOOQ avrà una forma del tipo:
SELECT camere.numero, camere.piano, clienti.nominativo FROM camere JOIN prenotazioni ON (prenotazioni.camera = camere.numero) LEFT OUTER JOIN clienti ON (prenotazioni.idCliente = clienti.id) WHERE ( (prenotazioni.periodoAl <= '2006-07-15') AND (prenotazioni.periodoAl >= '2006-07-15') )
creazione della factory
Come abbiamo già più volte anticipato JOOQ opera su connessioni già pronte ed aperte e non si preoccupa di chiuderle, quindi rimandiamo ai sorgenti in allegato per la parte di codice relativa. A partire dalla connessione attualmente aperta e se la precedente fase di generazione delle classi di "mappatura" è andata a buon fine, dovremmo essere in grado di scrivere qualcosa di simile:
HotelFactory jooq = new HotelFactory(connection);
con cui instanziamo la factory che ci aiuterà nella creazione delle query, esattamente come nell'esempio precedente, ma stavolta è facile osservare che si tratta di una factory specifica sul dominio del nostro database schema.
Giacchè i dati sono stati caricati nell'esempio precedente ci limitiamo ad azzerare le prenotazioni eventualmente presenti sul db per poter inserire dei dati ad hoc per il nostro esempio. Come si vedere svuotare una tabella è particolarmente semplice:
pulizia dei dati dalle tabelle
jooq.truncate(Tables.PRENOTAZIONI).execute();
jooq.truncate(CLIENTI).execute();
Da notare che mentre nel primo caso utilizziamo il nome completo della tabella Tables per accedere ad uno dei membri statici che al suo interno fanno riferimento alle classi generate da JOOQ per il mapping delle tabelle, nel secondo caso abbiamo utilizzato direttamente il riferimento "CLIENTI
" (riconoscibile perché convenzionalmente il nome è tutto in maiuscolo, come per le costanti)
É in genrale consigliabile per favorire la leggibilità importare staticamente questi riferimenti dalla classe Tables
, e anche qui rimandiamo ai sorgenti allegati.
import static it.html.articoli.esempi.jooq.generated.Tables.CLIENTI;
generazione e inserimento di nuovi dati nelle tabelle
A questo punto inseriremo qualche dato di prova nel database, utilizzando due modalità differenti
Come prima cosa possiamo inserire dati e salvarli all'interno del db relazionandoci alle tabelle come se fossero oggetti, tramite le corrispettive classi generate:
ClientiRecord nuovoCliente = jooq.newRecord(CLIENTI);
nuovoCliente.setIndirizzo("nessun indirizzo");
nuovoCliente.setNominativo("nuovo cliente");
nuovoCliente.setTelefono("nessun telefono");
nuovoCliente.store();
naturalmente se volessimo cancellare i dati del cliente appena creato ci basterebbe scrivere
nuovoCliente.delete();
e anche le operazioni di update saranno molto simili. Ovviamente ClientiRecord
è un'altra delle classi create per noi da JOOQ, che in questo caso rappresenta un singolo record della tabella "clienti"
Una modalità ancora più interessante consente di inserire una serie di dati in modalità batch, e facendo uso finalmente delle api. É abbastanza semplice riconoscere le query SQL INSERT corrispondenti...
jooq.batch(
jooq.insertInto(CLIENTI, CLIENTI.NOMINATIVO, CLIENTI.INDIRIZZO)
.values("Luigi Bianchi","via vai, 13","06-1111"),
jooq.insertInto(PRENOTAZIONI, PRENOTAZIONI.IDCLIENTE, PRENOTAZIONI.CAMERA)
.values(1, 303),
//...
).execute();
Anche in questo caso abbiamo un po' semplificato il codice mostrato per non appesantire la lettura e per sottolineare la semplicità d'uso delle api di JOOQ
É interessante notare come sia l'oggetto query
ad esporre un metodo execute
, in pieno stile ad oggetti.
Per stampare l'elenco delle prenotazioni possiamo allora scrivere:
// visualizziamo tutti i dati inseriti fin qui
Result<Record> result = jooq.select().from(CLIENTI).fetch();
System.out.println(result.format());
A questo punto non dovrebbe essere difficile leggere l'esempio seguente:
SelectConditionStep query = jooq.select(CAMERE.NUMERO, CAMERE.PIANO, CLIENTI.NOMINATIVO)
.from(CAMERE)
.join(PRENOTAZIONI).on(PRENOTAZIONI.CAMERA.equal(CAMERE.NUMERO))
.leftOuterJoin(CLIENTI).on(PRENOTAZIONI.IDCLIENTE.equal(CLIENTI.ID))
.where(PRENOTAZIONI.PERIODOAL.lessOrEqual("2006-07-15"))
.and(PRENOTAZIONI.PERIODOAL.greaterOrEqual("2006-07-15"));
query.execute();
result = query.fetch();
System.out.println("RISULTATO della QUERY (json):n"+result.formatJSON());
L'esempio mostra come costruiamo via via la query che avevamo immaginato tramite una composizione di chiamate a metodi sugli oggetti del nostro dominio, fino a farci stampare la tabella di dati corrispondenti. Anche qui abbiamo sfruttato una carattestica interessante che certo piacerà molto a chi si occupa di produrre semplici report: ogni risultato espone dei metodi di trasformazione in html, xml, csv e json (che è la soluzione mostrata), oltre che la semplice stampa testuale mostrata precedentemente.
conclusioni
A questo punto risulta chiaro che questa libreria offre moltissime funzionalità in maniera molto semplice, ed essendo tutt'ora in fase di evoluzione può costituire uno strumento via via interessante da integrare nei nostri progetti java. Ancora più interessante e semplice da usare è l'utilizzo via DSl con scala, che qui abbiamo dovuto omettere perché esulava dai nostri scopi
In ogni caso per gli opportuni approfondimenti è bene seguire il manuale ufficiale di JOOQ, che è ricco di contenuti semplici e molto chiari
L'esempio che abbiamo scelto di mostrare è particolarmente semplice e serve soltanto a dare una idea generale delle potenzialità di una libreria tutt'ora in fase di evoluzione, e che andrebbe considerata se si desidera utilizzare un approccio "ad oggetti" per le interrogazioni SQL, senza avere necessariamente bisogno delle complessità di ORM completi, quali hibernate o ibatis, ad esempio.
note
- Le factory JOOQ non chiudono le connessioni SQL: in pratica la libreria opera su connessioni già aperte ed attive, quindi la logica per apertura e chiusura va gestita esternamente.
- Per lo stesso motivo vanno gestite esternamente alla libreria le transazioni, poiché JOOQ non gestisce transazioni, nè effettua rollback.
- JOOQ non utilizza nessun tipo di cache, pertanto le query SQL prodotte saranno girate direttamente al DBMS.
- Il riferimento alle factory JOOQ è immutabile per tutto il loro ciclo di vita, il che è particolarmente interessante nel caso si vogliano utilizzare operazioni CRUD su degli oggetti rappresentativi dei record di tabella.