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

Service Builder e relazioni many-to-many

Il Service Builder fornito con Liferay è uno strumento molto potente per generare la maggior parte dello strato di persistenza necessario alle nostre portlet.

Purtroppo però l'unica documentazione ufficiale disponibile è rappresentata dai commenti contenuti all'interno del DTD del file service.xml; utile ma non sempre esaustiva.

Cerchiamo quindi di capire come preparare opportunamente il file service.xml per mappare una relazione molti-a-molti (many-to-many relationship).

Poniamoci quindi nella situazione in cui dobbiamo mappare una relazione tra autori e libri:

  • un autore può scrivere più libri
  • un libro può avere più autori

A livello di struttura di database sappiamo bene come mappare questa relazione:

  • una tabella per gli autori
  • una tabella per i libri
  • una tabella di join che collega autori e libri

La prima entity da creare è quella dell'autore:

<entity name="Autore" local-service="true">

<column name="autoreId" type="long" primary="true" />

<column name="nominativo" type="String" />

<!-- Seguono altre colonne, finder, ... -->

</entity>

Poi definiamo la entity del libro:

<entity name="Libro" local-service="true">

<column name="libroId" type="long" primary="true" />

<column name="titolo" type="String" />

<!-- Seguono altre colonne, finder, ... -->

</entity>

Infine definiamo la entity di join:

<entity name="Autore_Libro" local-service="true">

<column name="autoreId" type="long" primary="true" />

<column name="libroId" type="long" primary="true" />

</entity>

Quanto visto sopra sarebbe già sufficiente per un normale funzionamento; tuttavia sarebbe molto comodo avere out-of-the-box funzionalità del tipo: dammi tutti i libri di un autore, dammi tutti gli autori del libro.

Fortunatamente il service builder viene in nostro aiuto.

All'interno della entity Autore, andiamo ad aggiungere la seguente colonna:

<column name="libri" type="Collection" entity="Libro" mapping-table="Autore_Libro" />

Allo stesso modo aggiungiamo la seguente colonna alla entity Libro:

<column name="autori" type="Collection" entity="Autore" mapping-table="Autore_Libro" />

E' importante ricordare che, in caso di relazione many-to-many, occorre specificare la colonna di join su entrambe le entity.

Finito. Ora è possibile lanciare lo script Ant build-service e verranno così creati i metodi di persistenza per la gestione del join, nelle rispettive classi di Autore e Libro:

public List<Libro> getLibros(long autoreId) throws SystemException;

public List<Autore> getAutores(long libroId) throws SystemException;

Enjoy!

Precedente
Commenti
Aggiungi Commento
Giuseppe Pantaleo
Ciao Marco!
Ho seguito la tua utile guida per creare una relazione molti-a-molti e non ho riscontrato alcun problema.
Una volta realizzato l'esempio funzionante, ho provato ad ampliarlo per quelli che sono i miei scopi, ovvero creare una relazione con una entity definita in un altro file service.xml. Ho aggiunto quindi una colonna alla entity Autore e creato una nuova entity, ma ho un errore quando lancio lo scirpt build-service.
Questo il contenuto del mio service.xml:
<entity name="Autore" local-service="true">
<column name="autoreId" type="long" primary="true" />
<column name="nominativo" type="String" />
<column name="libri" type="Collection" entity="Libro" mapping-table="Autore_Libro" />

<column name="titoliStudio" type="Collection" entity="org.xmlportletfactory.portal.anagtitolostudio.AnagTitoloStudio" mapping-table="Autore_TitoloDiStudio" />
</entity>
<entity name="Libro" local-service="true">
<column name="libroId" type="long" primary="true" />
<column name="titolo" type="String" />
<column name="autori" type="Collection" entity="Autore" mapping-table="Autore_Libro" />
</entity>
<entity name="Autore_Libro" local-service="true">
<column name="autoreId" type="long" primary="true" />
<column name="libroId" type="long" primary="true" />
</entity>
<entity name="Autore_TitoloDiStudio" local-service="true">
<column name="autoreId" type="long" primary="true" />
<column name="idTitoloStudio" type="long" primary="true" />
</entity>

E questo l'errore che intercetto:
build-service:
Loading jar:file:/D:/liferay-portal-6.1.0-ce-ga1/tomcat-7.0.23/webapps/ROOT/WEB-INF/lib/portal-impl.jar!/system.properties
29-giu-2012 15.41.43 com.liferay.portal.kernel.log.Jdk14LogImpl info
INFO: Global lib directory /D:/liferay-portal-6.1.0-ce-ga1/tomcat-7.0.23/lib/ext/
29-giu-2012 15.41.43 com.liferay.portal.kernel.log.Jdk14LogImpl info
INFO: Portal lib directory /D:/liferay-portal-6.1.0-ce-ga1/tomcat-7.0.23/webapps/ROOT/WEB-INF/lib/
15:41:43,109 INFO [EasyConf:122] Refreshed the configuration of all components
15:41:43,577 INFO [ConfigurationLoader:56] Properties for jar:file:/D:/liferay-portal-6.1.0-ce-ga1/tomcat-7.0.23/webapps/ROOT/WEB-INF/lib/portal-impl.jar!/portal loaded from [jar:file:/D:/liferay-portal-6.1.0-ce-ga1/tomcat-7.0.23/webapps/ROOT/WEB-INF/lib/portal-impl.jar!/com/liferay/portal/tools/dependencies/portal-tools.properties, jar:file:/D:/liferay-portal-6.1.0-ce-ga1/tomcat-7.0.23/webapps/ROOT/WEB-INF/lib/portal-impl.jar!/portal.properties]
Loading jar:file:/D:/liferay-portal-6.1.0-ce-ga1/tomcat-7.0.23/webapps/ROOT/WEB-INF/lib/portal-impl.jar!/portal.properties
Loading jar:file:/D:/liferay-portal-6.1.0-ce-ga1/tomcat-7.0.23/webapps/ROOT/WEB-INF/lib/portal-impl.jar!/com/liferay/portal/tools/dependencies/portal-tools.properties
Building Autore
Method public com.liferay.portal.tools.servicebuilder.Entity com.liferay.portal.tools.servicebuilder.ServiceBuilder.getEntity(java.lang.String) throws java.io.IOException threw an exception when invoked on com.liferay.portal.tools.servicebuilder.ServiceBuilder@7720d7
The problematic instruction:
----------
==> assignment: tempEntity=serviceBuilder.getEntity(column.getEJBName())
----------
Java backtrace for programmers:
----------
freemarker.template.TemplateModelException: Method public com.liferay.portal.tools.servicebuilder.Entity com.liferay.portal.tools.servicebuilder.ServiceBuilder.getEntity(java.lang.String) throws java.io.IOException threw an exception when invoked on com.liferay.portal.tools.servicebuilder.ServiceBuilder@7720d7
at freemarker.ext.beans.SimpleMethodModel.exec(SimpleMethodModel.java:130)
at freemarker.core.MethodCall._getAsTemplateModel(MethodCall.java:93)
at freemarker.core.Expression.getAsTemplateModel(Expression.java:89)
at freemarker.core.Assignment.accept(Assignment.java:90)
at freemarker.core.Environment.visit(Environment.java:221)
at freemarker.core.MixedContent.accept(MixedContent.java:92)
at freemarker.core.Environment.visit(Environment.java:221)
at freemarker.core.ConditionalBlock.accept(ConditionalBlock.java:79)
at freemarker.core.Environment.visit(Environment.java:221)
at freemarker.core.IteratorBlock$Context.runLoop(IteratorBlock.java:179)
at freemarker.core.Environment.visit(Environment.java:428)
at freemarker.core.IteratorBlock.accept(IteratorBlock.java:102)
at freemarker.core.Environment.visit(Environment.java:221)
at freemarker.core.MixedContent.accept(MixedContent.java:92)
at freemarker.core.Environment.visit(Environment.java:221)
at freemarker.core.Environment.process(Environment.java:199)
at freemarker.template.Template.process(Template.java:237)
at com.liferay.portal.freemarker.FreeMarkerUtil.process(FreeMarkerUtil.java:49)
at com.liferay.portal.freemarker.FreeMarkerUtil.process(FreeMarkerUtil.java:39)
at com.liferay.portal.tools.servicebuilder.ServiceBuilder._processTemplate(ServiceBuilder.java:4788)
at com.liferay.portal.tools.servicebuilder.ServiceBuilder._createPersistenceImpl(ServiceBuilder.java:2430)
at com.liferay.portal.tools.servicebuilder.ServiceBuilder.<init>(ServiceBuilder.java:649)
at com.liferay.portal.tools.servicebuilder.ServiceBuilder.<init>(ServiceBuilder.java:430)
at com.liferay.portal.tools.servicebuilder.ServiceBuilder.main(ServiceBuilder.java:146)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.apache.tools.ant.taskdefs.ExecuteJava.run(ExecuteJava.java:217)
at org.apache.tools.ant.taskdefs.ExecuteJava.execute(ExecuteJava.java:152)
at org.apache.tools.ant.taskdefs.Java.run(Java.java:771)
at org.apache.tools.ant.taskdefs.Java.executeJava(Java.java:221)
at org.apache.tools.ant.taskdefs.Java.executeJava(Java.java:135)
at org.apache.tools.ant.taskdefs.Java.execute(Java.java:108)
at org.apache.tools.ant.UnknownElement.execute(UnknownElement.java:291)
at sun.reflect.GeneratedMethodAccessor4.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.apache.tools.ant.dispatch.DispatchUtils.execute(DispatchUtils.java:106)
at org.apache.tools.ant.Task.perform(Task.java:348)
at org.apache.tools.ant.Target.execute(Target.java:392)
at org.apache.tools.ant.Target.performTasks(Target.java:413)
at org.apache.tools.ant.Project.executeSortedTargets(Project.java:1399)
at org.apache.tools.ant.Project.executeTarget(Project.java:1368)
at org.apache.tools.ant.helper.DefaultExecutor.executeTargets(DefaultExecutor.java:41)
at org.eclipse.ant.internal.launching.remote.EclipseDefaultExecutor.executeTargets(EclipseDefaultExecutor.java:32)
at org.apache.tools.ant.Project.executeTargets(Project.java:1251)
at org.eclipse.ant.internal.launching.remote.InternalAntRunner.run(InternalAntRunner.java:424)
at org.eclipse.ant.internal.launching.remote.InternalAntRunner.main(InternalAntRunner.java:138)
Caused by: java.io.IOException: Unable to open resource in class loader org/xmlportletfactory/portal/anagtitolostudio/service.xml
at com.liferay.portal.kernel.util.StringUtil.read(StringUtil.java:705)
at com.liferay.portal.kernel.util.StringUtil.read(StringUtil.java:668)
at com.liferay.portal.tools.servicebuilder.ServiceBuilder.getEntity(ServiceBuilder.java:886)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at freemarker.ext.beans.BeansWrapper.invokeMethod(BeansWrapper.java:866)
at freemarker.ext.beans.SimpleMethodModel.exec(SimpleMethodModel.java:106)
... 48 more


Ho anche provato a refenziare nel service.xml una delle tabelle di sistema di Liferay (come da esempio riportato nel file liferay-service-builder_6_1_0.dtd), ma ho lo stesso tipo di errore.
Hai idea di quale possa essere il problema?
Inviato il 09/07/15 14.02.
Marco Napolitano
Ciao,
diciamo che in linea di principio il Service Builder vuole che tutto sia racchiuso all'interno del medesimo file service.xml; inoltre ti ricordo che per le relazioni many-to-many devi dichiarare obbligatoriamente la column di join su entrambe le entità.
Tuttavia potresti provare un paio di strade.

La prima è quella di includere un service.xml nell'altro; occhio però che l'inclusione deve generare un file di service well-formed.
Quello che puoi fare è tenere il service.xml di XMLPortletFactory così com'è; dopodichè puoi crearti un file full_service.xml (o come ti piace di più) in cui vai ad inserire le tue entity e solo quelle! Vale a dire niente author o namespace perchè è già dichiarato dentro service.xml.
Dopodichè in fondo al file full_service.xml inserisci il tag: <service-builder-import file="service.xml" />.
Infine devi modificare il file di properties del Liferay SDK, ossia build.TUONOME.properties aggiungendo:
service.input.file = ${basedir}/docroot/WEB-INF/full_service.xml
Praticamente abbiamo riconfigurato il Service Builder per usare il nostro file full_service.xml che, guarda caso, include quello di XMLPortletFactory.
Ah, i 2 file sono nello stesso progetto di Eclipse...

Questo dovrebbe essere sufficiente, altrimenti potresti provare ad utilizzare l'elemento <reference> che serve per includere dipendenze di altre entity. Ammetto però di non averlo mai usato.

Enjoy!
Inviato il 09/07/15 14.02 in risposta a Giuseppe Pantaleo.