Tempo fa vi avevo già parlato di query custom con Liferay DXP (http://blog.d-vel.com/home/-/blogs/sql-custom-con-liferay-dxp) spiegando alcune piccole differenze rispetto alla versione 6.2.
Oggi voglio affrontare un'altra problematica, ossia eseguire query SQL custom che restituiscono oggetti del core come ad esempio User
, Layout
, JournalArticle
, ...
Supponiamo quindi di avere realizzato un plugin custom che referenzi la tabella degli utenti di Liferay e vogliamo eseguire una query che restituisca gli utenti che soddisfano determinati criteri:
<sql id="com.test.service.persistence.TabellaFinder.findUsers">
<![CDATA[
SELECT u.*
FROM User_ u, Tabella t
WHERE t.campo1 = ?
AND t.campo2 = ?
AND u.userId = t.userId
ORDER BY u.lastName, u.firstName
]]>
</sql>
La query è molto semplice così come lo sarebbe la classe finder da implementare:
public List<User> findUsers(long param1, long param2, int start, int end, OrderByComparator<User> comparator) {
Session session = null;
try {
session = openSession();
String sql = CustomSQLUtil.get(this.getClass(), FIND_USERS);
sql = CustomSQLUtil.replaceOrderBy(sql, comparator);
SQLQuery q = session.createSQLQuery(sql);
q.addEntity("User_", UserImpl.class);
QueryPos qPos = QueryPos.getInstance(q);
qPos.add(param1);
qPos.add(param2);
return (List<User>) QueryUtil.list(q, getDialect(), start, end);
}
finally {
closeSession(session);
}
}
Ma qui iniziano subito i nostri problemi, perchè per mappare i campi di output della query sull'oggetto User
di Liferay, bisogna invocare il metodo addEntity
passando come parametro la classe *Impl
del model; peccato che la classe UserImpl
non sia accessibile nei plugin custom.
Questo problema in realtà si può risolvere in fretta giocando un pò con il classloader e sostituendo la riga di codice con questa:
q.addEntity("User_", PortalClassLoaderUtil.getClassLoader().loadClass("com.liferay.portal.model.impl.UserImpl"));
Però se andiamo ad eseguire il nostro metodo custom del finder, il portale solleverà questa eccezione:
org.hibernate.MappingException: Unknown entity: com.liferay.portal.model.impl.UserImpl
Ma perchè? Per un problema legato al contesto in cui opera lo strato dei servizi.
Vale a dire che il nostro metodo finder opera all'interno del contesto del plugin custom e quindi il metodo openSession()
apre sì una sessione, ma confinata all'interno del nostro plugin nel quale l'entità UserImpl
non esiste perchè fa parte del contesto di portale.
Come fare allora? Beh, basta cambiare la modalità con cui si ottiene la sessione; dobbiamo restituire oggetti del core, quindi ci serve una sessione del core. Ecco quindi che il metodo del finder si modifica in questo modo:
public List<User> findUsers(long param1, long param2, int start, int end, OrderByComparator<User> comparator) {
Session session = null;
SessionFactory sessionFactory = (SessionFactory) PortalBeanLocatorUtil.locate("liferaySessionFactory");
try {
session = sessionFactory.openSession();
String sql = CustomSQLUtil.get(this.getClass(), FIND_USERS);
sql = CustomSQLUtil.replaceOrderBy(sql, comparator);
SQLQuery q = session.createSQLQuery(sql);
q.addEntity("User_", PortalClassLoaderUtil.getClassLoader().loadClass("com.liferay.portal.model.impl.UserImpl"));
QueryPos qPos = QueryPos.getInstance(q);
qPos.add(param1);
qPos.add(param2);
return (List<User>) QueryUtil.list(q, getDialect(), start, end);
}
finally {
sessionFactory.closeSession(session);
}
}
In grassetto ho evidenziato le modifiche:
- prima di tutto bisogna recuperare un'istanza della factory di portale, perchè dobbiamo usare quella anzichè quella predefinita del plugin;
- poi dobbiamo aprire la sessione usando la factory appena recuperata;
- poi referenziamo la classe
*Impl
attraverso il classloader di portale; - infine chiudiamo la sessione utilizzando sempre la factory recuperata sopra.
Spero di avervi risparmiato ore di inutili fatiche!
Ah, può essere che il meccanismo funzioni anche la versione 6.2.