Non mi soffermerò a spiegare Spring né a spiegare iBatis ma solo ad usarli assieme per fare una insert ed una query ad un DB Oracle. La stampa sarà fatta su console.
Parte 1 - web.xml
Torniamo a parlare di questo file dove andremo ad aggiungere la componente per far partire il contesto Spring ovvero la "nuvola" applicativa del nostro progetto.
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version="2.4"> <display-name>Wello World</display-name> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <context-param> <param-name>contextConfigLocation</param-name> <param-value> WEB-INF/classes/primo_esempio.xml </param-value> </context-param> </web-app>
Il codice è molto semplice dato che non abbiamo volutamente messo la parte di Wicket che è più sostanzione. In generale quindi il nostro web.xml avrà solo la chiamata al listener di spring e la parametrizzazione dello stesso che avverte il server che le specifiche del contesto master sono da ricercare nel file primo_esempio.xml.
Parte 2 - primo_esempio.xml
Andiamo ora a vedere come comporre il nostro contesto di esempio.
<?xml version="1.0" encoding="UTF-8"?> <bean TUTTALAROBA> <import resource="dati_connessione.xml" /> <bean id="gestioneDatiSemplice" class="it.trewps.primoesempio.manager.GestioneDatiSemplice" lazy-init="true"> <property name="lunghezza" value="100" /> <property name="prendiDati" ref="prendiDati" /> </bean> <bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean"> <property name="configLocation" value="classpath:elenco_query.xml" /> <property name="dataSource" ref="datiConnessione" /> </bean> <bean id="prendiDati" class="it.trewps.primoesempio.dao.PrendiDati"> <property name="sqlMapClient" ref="sqlMapClient" /> </bean> </beans>
Dove ho scritto TUTTALAROBA va inserita tutta melassa che ovviamente potete copia-incollare:
xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:dwr="http://www.directwebremoting.org/schema/spring-dwr" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd http://www.directwebremoting.org/schema/spring-dwr http://www.directwebremoting.org/schema/spring-dwr-2.0.xsd"
La spiegazione del contesto è molto semplice ed intuitiva. Stiamo definendo 3 bean ovvero 3 diverse entità della nuvola. Ogni bean è mappata su una classe Java indicata nel parametro class e può avere già settata delle proprietà che altro non sono che variabili interne alla classe. Nella definizione del bean Spring permette molte altre cose ma per questo esempio ci limiteremo a pre-settare delle proprietà.
La cosa interessante è che alcune proprietà non hanno un value ma un ref. Il value permette di impostare un valore fisso al parametro della classe direttamente al momento della chiamata di Spring. La seconda crea un riferimento ad un altra entità bean definita o nello stesso xml o in altri xml di contesto importati in quello master (come potete vedere dall'import in alto).
Il secondo BEAN rappresenta il punto di aggancio tra Spring e iBatis in quanto il BEAN è mappato direttamente su una classe del framework a cui ovviamente occorre specificare le impostazioni del dataSource (un bean definito nel contesto importato) e la mappa delle query ovvero l'elenco di file XML (strutturati per logica) che contengono le vere e proprie query scritte in iBatis.
Il terzo BEAN rappresenta invece la classe che farà da ponte tra quella base e quella di interfacciamento con il DB (attraverso iBatis).
NB: Questo file va messo nella directory strettamente legata ad un path montato su WEB-INF/classes/ . Nel mio caso il banale src.
Parte 3 - elenco_query.xml
Qui vediamo nel dettaglio la mappa delle query che il BEAN legato a iBatis caricherà.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE sqlMapConfig PUBLIC "-//iBATIS.com//DTD SQL Map Config 2.0//EN" "http://www.ibatis.com/dtd/sql-map-config-2.dtd"> <sqlMapConfig> <settings useStatementNamespaces="true" defaultStatementTimeout="30"/> <sqlMap resource="maps/semplice_query.xml"/> </sqlMapConfig>
Questo file è molto semplice ed avverte il sistema che avremo un file di query chiamato semplice_query.xml. Possiamo ovviamente avere tanti file sqlMap e solitamente questi sono divisi per area semantica di utilizzo.
Parte 4 - semplice_query.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE sqlMap PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN" "http://ibatis.apache.org/dtd/sql-map-2.dtd"> <sqlMap namespace="it.trewps.primoesempio.dao"> <select id="dammiUtenti" resultClass="it.trewps.primoesempio.dao.UtenteEsempio" > select idutente,nomeutente,cognomeutente from tabella_esempio </select> <insert id="inserisciUtente" parameterClass="it.trewps.primoesempio.dao.UtenteEsempio"> insert into tabella_esempio (nomeutente,cognomeutente) values (#nomeutente#,#cognomeutente#) </insert> </sqlMap>
Questo è un esempio di file XML di query. Il namespace indica dove andremo ad avere le classi che gestiranno questa query. Nel nostro caso abbiamo creato un package ad HOC chiamato it.trewps.primoesempio.dao.
Abbiamo quindi due tag che identificano il tipo di query. La prima è una select da una tabella chiamata tabella_esempio nella quale andiamo a prendere due campi. La seconda è una insert. Entrambe le query devono avere un ID UNIVOCO associato che sarà poi quello richiamato dalla nostra classe DAO.
La seconda query è invece più interessante perché possiede due placeholder dove la classe parametro in ingresso andrà a mapparci il proprio valore. Il placeholder si può scrivere in due modi:
#nomeparametro#
oppure
$nomeparametro$
La differenza tra i due è sottile ma fondamentale se avete DB potenti. Nel primo caso stiamo dicendo al DB che questa è un'area da mappare. Non gli stiamo passando direttamente il dato dentro ma gli stiamo solo passando una query parametrizzata. Questo è utile perché il DB trasforma la query in una funzione che prende in ingresso i parametri. Se avete tante insert questo è utile perché permette al DB di fare la cache della query velocizzando le operazioni.
Nel secondo modo invece, il sistema crea la query già riempita quindi query consecutive dovranno sempre essere interpretate da zero anche se a cambiare sono solo i dati.
Concludiamo parlando di resultClass e parameterClass. Questi due parametri avvertono il sistema di quali classi mapperanno rispettivamente il risultato della query o gli eventuali dati in ingresso alla query.
Una query può avere entrambi i parametri (la select le ha quasi sempre) perché è tramite la classe ivi specificate che è possibile parametrizzare una select o dare i dati ad una insert/update.
La class data come parametro ha ovviamente l'obbligo di avere parametri interni con lo stesso nome del placeholder usato nella query ed ovviamente un metodo GET relativo.
La resultClass deve invece essere mappata sulle colonne del DB. Questa deve quindi avere dei parametri interni identici a quelli della tabella su cui facciamo la query.
E' lecito pensare che una classe capace di accogliere i dati di una tabella sia anche quella usata come parametro per fare una insert. La cosa è ovviamente lecita ma occorre ricordare che la result per funzionare deve avere tutti i metodi get e set dei parametri ma soprattutto un costruttore vuoto senza parametri.
Nel nostro esempio saremo in questa casistica.
Parte 6 - daticonnessione
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd"> <bean id="datiConnessione" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" /> <property name="url" value="jdbc:oracle:thin:@192.168.1.214/orcl" /> <property name="username" value="test" /> <property name="password" value="test" /> <property name="initialSize" value="1" /> <property name="maxActive" value="5" /> <property name="maxIdle" value="5" /> </bean> </beans>
Il bean di datiConnessione richiesto dal bean di iBatis messo nel contesto, sarà una semplice sequenza di dati definiti nelle proprietà di un altro oggetto standard di Java ovvero BasicDataSource.
I dati sono abbastanza semplici. Ricordatevi solo che il driverClassName cambia in base al vostro DB e che l'URL è l'indirizzo del server del database dove dopo lo slashes finale compare il nome del database (in questo caso orcl).
Parte 7 - UtenteEsempio.java
Come abbiamo visto sopra entrambe le query usavano questa classe come result o parameter class. Andiamo a vederla nel dettaglio:
package it.trewps.primoesempio.dao; public class UtenteEsempio { Integer idutente; String nomeutente; String cognomeutente; public UtenteEsempio(){ } public UtenteEsempio(String nome, String cognome){ nomeutente = nome; cognomeutente = cognome; } /*Tutti i metodi GET e SET */ }
E' una classe semplicissima. Nel codice sopra ho omesso tutti i GET e SET delle proprietà interne. E' importante però notare come esistano due costruttori. Uno utile ad istanziare l'oggetto all'interno dell'applicativo ed uno utile a iBatis (ovvero il costruttore vuoto).
Parte 8 - PrendiDati.java
Andiamo ora a vedere la classe che in effetti comunica con il database tramite iBatis e mette a disposizione alle classi dell'appllicativo, queste interazioni.
package it.trewps.primoesempio.dao; import java.util.List; import org.springframework.orm.ibatis.support.SqlMapClientDaoSupport; public class PrendiDati extends SqlMapClientDaoSupport{ public static final String NAMESPACE = "it.trewps.primoesempio.dao"; public static final String UTENTI = NAMESPACE + ".dammiUtenti"; public static final String IMMETTI = NAMESPACE + ".inserisciUtente"; public PrendiDati(){ } public List<UtenteEsempio> caricaUtenti (){ return getSqlMapClientTemplate().queryForList(UTENTI); } public void insertisciUtente(UtenteEsempio utente){ getSqlMapClientTemplate().insert(IMMETTI,utente); } }
Può sembrare strana ma in realtà è molto semplice. Partendo dal basso abbiamo due metodi mappati come logica sulla select e sulla insert. La prima ritorna una LIST di oggetti UtenteEsempio andandoli a chiedere al DB tramite l'istruzione sotto chiamata queryForList che banalmente dice a iBatis di darci una query mappata su una lista. La costante UTENTI utilizzata è solo una facilitazione per evitare di dover scrivere tutto il namespace ed il nome dell'ID della query.
NB: Il namespace è lo stesso espresso nel parametro del file XML di cui sopra.
Cosa analoga nel metodo di inserimento che ovviamente non ha una return ma una semplice insert che accetta due parametri ovvero la stringa che identifica la query da eseguire e l'oggetto che sarà, guarda caso, la parameterClass della query. Ovviamente dammiUtenti e inserisciUtente definiti nelle costanti non sono altro che gli ID delle query nell'xml.
Parte 9 - GestioneDatiSemplice.java
Ecco il file che abbiamo visto associato al bean nel contesto e che rappresenta l'arbitro del nostro applicativo. Nel contesto abbiamo visto che Spring gli darà gratuitamente la classe PrendiDati che verrà usata per dare il via alle operazioni sul DB.
package it.trewps.primoesempio.manager; import java.util.List; import it.trewps.primoesempio.dao.PrendiDati; import it.trewps.primoesempio.dao.UtenteEsempio; public class GestioneDatiSemplice { private Integer lunghezza; PrendiDati prendiDati; public GestioneDatiSemplice(){ } public void faiQuellaCosa(){ UtenteEsempio unocosi = new UtenteEsempio("Marina","Massironi"); prendiDati.insertisciUtente(unocosi); List <UtenteEsempio> listaDati = prendiDati.caricaUtenti(); System.out.println("Lungo: "+listaDati.size()); } public void setLunghezza(Integer lunghezza) { this.lunghezza = lunghezza; } public Integer getLunghezza() { return lunghezza; } public PrendiDati getPrendiDati() { return prendiDati; } public void setPrendiDati(PrendiDati prendiDati) { this.prendiDati = prendiDati; } }
Il metodo faiQuellaCosa, che chiaremo da un main, crea un oggetto di tipo UtenteEsempio e lo da in pasto alla query di inserimento. Crea poi una varibile lista che riceverà il risultato della select ovvero una lista di oggetti di tipo UtenteEsempio e lancia quindi la query stampando poi la lunghezza della lista ovvero 1 (dato che abbiamo appena inserito Marina Massironi).
Parte 10 - La classe Main
Potrebbe sembrare superfluo scriverla ma in realtà è fondamentale perché in essa possiamo vedere il codice con la quale una classe qualsiasi chiede al contesto un oggetto bean da usare.
package it.trewps.primoesempio.manager; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class PrimoEsempio { public static void main(String[] args){ ApplicationContext context = new ClassPathXmlApplicationContext("primo_esempio.xml"); GestioneDatiSemplice hmw = (GestioneDatiSemplice) context.getBean("gestioneDatiSemplice"); hmw.faiQuellaCosa(); System.out.println("Ciao Mondo"); } }
Il main lancia due importanti metodi. Il primo è quello che crea un oggetto di tipo ApplicationContext con la quale interagire con la nuvola Spring. Sia chiaro, qui NON stiamo creando la nuvola ma solo un oggetto con la quale comunicare con lei.
Il secondo metodo invece serve a chiedere alla nuvola di darci un oggetto bean con ID gestioneDatiSemplice che noi sappiamo essere mappato sulla classe GestioneDatiSemplice.
Come ultima istruzione avremo il lancio del metodo ed una print inutile Ciao Mondo.
Nessun commento:
Posta un commento