Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

JOOQ: generazione ORM via maven

Utilizziamo il framework JOOQ tramite maven per la creazione di classi ORM
Utilizziamo il framework JOOQ tramite maven per la creazione di classi ORM
Link copiato negli appunti

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:

Figura 1. classi generate con JOOQ
(clic per ingrandire)


classi generate con JOOQ

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 JOOQ
  • jooq-esempio-fluent-main che conterrà l'esempio vero e proprio di utilizzo delle api
Figura 2. il progetto Eclipse/maven per l'esempio JOOQ
(clic per ingrandire)


il progetto Eclipse/maven per l'esempio JOOQ

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.

Ti consigliamo anche