bloggers bloggers

Marco Napolitano
Messaggi: 79
Stelle: 0
Data: 17/02/22
Jader Jed Francia
Messaggi: 63
Stelle: 0
Data: 18/02/21
Paolo Gambetti
Messaggi: 2
Stelle: 0
Data: 11/11/19
Katia Pazzi
Messaggi: 1
Stelle: 0
Data: 27/06/19
Ezio Lombardi
Messaggi: 11
Stelle: 0
Data: 10/04/18
Chiara Mambretti
Messaggi: 25
Stelle: 0
Data: 27/02/17
Serena Traversi
Messaggi: 3
Stelle: 0
Data: 21/07/16
Francesco Falanga
Messaggi: 8
Stelle: 0
Data: 14/06/16
Antonio Musarra
Messaggi: 2
Stelle: 0
Data: 18/11/13
Simone Celli Marchi
Messaggi: 6
Stelle: 0
Data: 09/07/13
Indietro

Collegare database esistenti a portlet sviluppate in Liferay

Problema: vi chiedono di scrivere dei portlet per Liferay che leggono dei dati da un database esistente.

Soluzione "sporca": vi fate il vostro accesso ai dati e integrate il database esterno alla vostra portlet.

Soluzione"pulita": istruite il service builder adeguatamente e gli fate wrappare le tabelle dell'altro database, così avrete la stessa modalità d'accesso ai dati che avete quando andate sul database di Liferay e chi usa la vostra API non dovrà impazzire per comprenderla!:)

Vediamo un po' nel dettaglio questa soluzione!

Per prima cosa, istruiamo il Service Builder ad utilizzare il database esterno.

Prima un concetto chiave: su Liferay è possibile specificare per singola entity il datasource da utilizzare. Quindi il grosso del lavoro viene fatto dal framework del Service Builder -e dalle classi che saranno generate-; ora vediamo nel dettaglio un po' di codice.

Creiamo una entity che mappi una tabella esistente nel database target -esterno al database di Liferay-. Di seguito evidenzio i pezzi "importanti", quelli che permettono la magia.. :)

<entity name="TimeSheet" table="public.time_sheet"

local-service="true" remote-service="false"

data-source="intranetDataSource"

session-factory="intranetSessionFactory"

tx-manager="intranetTxManager">

...

</entity>

Come vedete, nel nostro file service.xml, specifichiamo quale dev'essere il data-source da utilizzare, quale il session-factory e quale transaction manager.

È ovvio che chi conosce bene Spring ha già chiaro di che cosa stiamo parlando.

Per gli altri, invece, il concetto è molto semplice: Liferay farà in modo che, per l'entità in questione, siano utilizzate connessioni, persistenza e transazioni su un db diverso e non quelle "dirette" che creerebbe verso il suo database.

Una volta creato il nostro file service.xml dobbiamo buildarlo, in modo da farci generare gli oggetti che ci serviranno dopo.

Per farlo, dal build file del vostro portlet, eseguite il task build-service.

Dopo che il Service Builder avrà generato le classi e gli oggetti a partire dal nostro file, dovremo istruire il framework Spring a creare ed utilizzare gli oggetti che, per ora, abbiamo solo referenziato nel service.xml.

Per farlo, nella META-INF del nostro source folder (quindi docroot/WEB-INF/src/META-INF/), creiamo un file chiamato ext-spring.xml

Dentro a questo file, mettiamoci questo codice:

<?xml version="1.0"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"

"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>

<!-- Qui specifichiamo il JNDI name da utilizzare

per la nostra connessione. La risorsa JNDI va messa all'interno

del file <liferay_dir>/<tomcat_dir>/conf/context.xml -->
<bean id="intranetDataSourceTarget"

class="com.liferay.portal.spring.jndi.JndiObjectFactoryBean"

lazy-init="true">
<property name="jndiName">

<value>jdbc/intranetDB</value>
</property>
</bean>

<!-- Qui creiamo il nostro data source collegato alla connessione

JNDI che abbiamo referenziato poco sopra. -->
<bean id="intranetDataSource"

class="org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy">
<property name="targetDataSource">

<ref bean="intranetDataSourceTarget" />
</property>

</bean>

<bean id="intranetHibernateSessionFactory"

class="com.liferay.portal.spring.hibernate.PortletHibernateConfiguration">
<property name="dataSource">

<ref bean="intranetDataSource" />
</property>
</bean>

<!-- Qui specifichiamo la nostra SessionFactory -->
<bean id="intranetSessionFactory"

class="com.liferay.portal.dao.orm.hibernate.SessionFactoryImpl">

<property name="sessionFactoryImplementor">
<ref bean="intranetHibernateSessionFactory" />
</property>
</bean>

<!-- E qui creiamo il TransactionManager -->
<bean id="intranetTxManager"

class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="dataSource">

<ref bean="intranetDataSource" />

</property>
<property name="sessionFactory">
<ref bean="intranetHibernateSessionFactory" />

</property>
</bean>
</beans>

Una volta che abbiamo creato questo file il gioco è fatto!

Ci basterà deployare il nostro portlet e usare la nostra APILiferay per accedere anche alle tabelle del secondo database!

Spero d'esservi stato d'aiuto!

Alla prossima!:)

Precedente
Commenti
Aggiungi Commento
Paolo Piccoli
Probabilmente in liferay 6.06 c'e un problema di transazionalità .
Eseguendo il roll back di una operazione che ha coinvolto tabelle dei due db, il roll back viene effettuato solo sul db di liferay. Questo è quello che succede a noi . (Non ho implementato io la cosa, non so se è stato utilizzato l'approccio descritto nell'articolo). Pare comunque sia un bug (http://issues.liferay.com/browse/LPS-13454) risolto nella 6.1 . Una alternativa è usare un transaction manager jta in alternativa ad hibernate
https://www.liferay.com/community/wiki/-/wiki/Main/Configuring+JOTM+for+Tomcat+with+Liferay+5.2.x

Mi interesserebbe sapere se con la soluzione proposta dall'articolo viene rispettata la atomicità delle transazioni che coinvolgono tabelle di diversi db. Inoltre, quale versione di liferay state utilizzando?

saluti
Inviato il 09/07/15 14.02.
Jader Jed Francia
Ciao Paolo!
La versione che utilizziamo noi è, nel caso dell'articolo sopra riportato, la 6.0.6 e si, in effetti c'è il problema di cui parli tu.
Attenzione però, che il problema che riporti non c'entra niente con il tema del post: io parlo di come settare le connessioni verso una base dati esistente e di come wrapparle con il service builder; il ticket che hai linkato sul sito di Liferay si riferisce ad un bug del service builder che non gestisce correttamente la transazione _sullo stesso database_ e non su database differenti!

Per la domanda specifica che poni:

"Mi interesserebbe sapere se con la soluzione proposta dall'articolo viene rispettata la atomicità delle transazioni che coinvolgono tabelle di diversi db."

la risposta è semplice: dipende da come la imposti tu! emoticon
Come dicevo poco sopra, stai utilizzando due connessioni differenti verso due basi dati diverse; è quindi onere tuo istruire il sistema affinchè venga rispettata la transazione e sia quindi gestita correttamente la rollback applicativa.
Nel dettaglio: il framework non mette in un'unica transazione le eventuali insert su tabelle di database differenti; devi essere tu a wrappare il tutto e a gestire coerentemente il rollback sulle differenti connessioni.

Spero d'essermi spiegato; nel caso tu avessi bisogno di maggiori dettagli non esitare a chiedere! ;)

Ciao, J.
Inviato il 09/07/15 14.02 in risposta a Paolo Piccoli.
Paolo Piccoli
avevo raccolto una serie di issues sulle transazioni, avrò postato il link sbagliato.
Per rendere quindi le transazioni atomiche stai dicendo di scrivere un metodo (magari nella localserviceimpl di una delle entità coinvolte) che provi ad eseguire degli inserimenti / update tra le entità ed in caso di eccezione forzi il rollback esplicito, il tutto tramite righe di codice scritte esplicitamente?

Ciao

Paolo
Inviato il 09/07/15 14.02 in risposta a Jader Jed Francia.
Jader Jed Francia
Ciao Paolo!
Si: sto dicendo esattamente questo!

Onestamente io ho sempre fatto così perché mi sembrava "normale" fare così; ora che mi hai posto la domanda, però, mi fai venire qualche dubbio! emoticon
Mi informo meglio e poi ti faccio sapere; se tu invece hai esperienze diverse
accetto molto volentieri suggerimenti! ;)
Tu hai trovato il modo di farlo diversamente?

Ciao, J.
Inviato il 09/07/15 14.02 in risposta a Paolo Piccoli.
Antonio Musarra
Ciao,
articolo che trovo interessante.

Tutto ciò vale quando sviluppi delle Portlet che nascono e vivono in Liferay utilizzando l'SDK di Liferay. Quando però devi costruire delle Portlet standard (JSR-168 e JSR-286) da deployare in qualunque Portlet Container allora per l'accesso ai utilizzi come dici tu la soluzione "sporca" (nel mio caso: Spring Portlet MVC & MyBatis).

Bye,
Antonio.
Inviato il 09/07/15 14.02.
Jader Jed Francia
Ciao Antonio!
Si, chiaramente se vuoi rispettare lo standard cerchi di essere "svincolato" dalle API e dai framework messi a disposizione dal container.
Se non si sfruttano le peculiarità del sistema, però, a mio avviso, la curva di sviluppo è sicuramente più alta.
Tu che cosa ne pensi? Hai sempre sviluppato soluzioni JSR-compliant oppure hai anche fatto robe con API proprietarie (LR, ad esempio, ma anche Oracle, WebSphere, etc.)? Qual'è la tua sensazione in questo senso?

Ciao, J.
Inviato il 09/07/15 14.02 in risposta a Antonio Musarra.
Antonio Musarra
Ciao Jed,
scusa il mio ritardo ma ho visto solo adesso la tua risposta.
La curva di sviluppo probabilmente è più alta perchè dovresti conoscere bene gli standard.

Mi sono capitate molte situazioni ma non solo riguardo le portlet, dove ho dovuto sviluppare soluzioni aderenti a standard industriali e non legate a prodotti specifici, soprattuto nel campo delle Telecomunicazioni/Elettronica.

Ultimamente ho sviluppato anche su Liferay e devo dire che si riescono a fare parecchie cose in poco tempo, visto che mi occupo solitamente d'integrazione, ho apprezzato molto i servizi e la parte che riguarda la gestione Utenti/Gruppi/Sicurezza/SSO (CA Siteminder).

Io penso che la cosa migliore per chi sviluppa sia quella di scrivere il minor codice possibile e concentrasi sull'obiettivo della funzionalità, questo può essere possibile grazie a framework e set di API che offrano il maggior numero di servizi.

Bye,
Antonio.
Inviato il 09/07/15 14.02 in risposta a Jader Jed Francia.
Giovanni Davide Vergine
Mi sono iscritto giusto per dirti che mi hai risparmiato parecchio lavoro, bell'articolo :-) Da neofita di spring, hibernate e liferay sono incappato in una situazione strana: chiedevo 'id' e cercava il campo 'id_' nel database remoto, non trovandolo. Ho risolto, documentandomi, aggiungendo db-name="nomecampo" nel column dell'entity nel service.xml. Lascio questo commento per altri neofiti come me emoticon ciao!
Inviato il 09/07/15 14.02 in risposta a Antonio Musarra.
Jader Jed Francia
Hey!
Grazie mille per i complimenti, fanno sempre molto piacere!
E soprattutto fa piacere sapere d'esserti stato in qualche modo d'aiuto! ;)

Se ti serve qualche cosa, comunque, siamo qui! emoticon
A presto, ciao, J.
Inviato il 09/07/15 14.02 in risposta a Giovanni Davide Vergine.
Luigi Leoni
Ciao,
trovo il blog molto utile poichè illustrano le principali difficoltà che solitamente insorgono durante lo sviluppo su piattaforma Liferay.

Colgo occasione per segnalarvi un problema che sto notando usando la versione 6.1.1.ga2 .

La configurazione del service.xml e relativo ext-spring.xml non funziona, indicando l'assenza di alcune classi fondamentali come
com.liferay.portal.spring.hibernate.PortletHibernateConfiguration
e com.liferay.portal.spring.jndi.JndiObjectFactoryBean.

Ho verificato come viene recuperata la sessionFactory nei file di spring ed ho adattato la dichiarazione nell'ext-spring.xml :

Esempio :

<bean id="testHibernateSessionFactory" class="com.liferay.portal.kernel.spring.util.SpringFactoryUtil"
factory-method="newBean">
<constructor-arg
value="com.liferay.portal.spring.hibernate.PortletHibernateConfiguration" />
<constructor-arg>
<map>
<entry key="dataSource" value-ref="testDataSource" />
</map>
</constructor-arg>
</bean>


La stessa dichiarazione, cambiando ovviamente il class da instanziare (com.liferay.portal.dao.orm.hibernate.PortletSessionFactoryImpl, etc), è stata usata per gli altri oggetti necessari.

Vi risulta che con la versione di liferay indicata sopra sia cambiato l'approccio per l' utilizzo del service builder su db esterni?
Sono arrivato ad un punto in cui appaiono problemi di ClassLoader tali che il datasource non risulta instanziabile per ClassCastException, nonostante risulti correttamente dichiarato.


Ciao e grazie,
Gigi
Inviato il 09/07/15 14.02.
francesco scamarcio
Salve,

articolo molto interessante , io sviluppo sulla versione 6.0.12EE e la transazionalita solo funciona su tabelle nello stesso schema. Come noi abbiano custom portlet que usano tabelle di un altro schema e usuamo tomcat ho dovuto configurar tomcat con jotm e cambiare il transaction manager da hibernate a jta nel fichero portal properties. Facendo cosi liferay riesce a gestire transazionalita in un metodo tra tabelle in scherma diferrenti
Inviato il 09/07/15 14.02 in risposta a Luigi Leoni.