Sembra che io non sia l’unico a chiederselo e sembra anche che le risposte date non siano necessariamente soddisfacenti. Non sarebbe bello poter usare il try-with-resources con gli EntityManager?
Pare che il motivo per cui EntityManager non implementa AutoCloseable sia che se lo facesse, potrebbe solo fare la close() di sè stesso, ma non la rollback() dell’eventuale transazione. Questo renderebbe l’intero sforzo inutile, dovendo comunque gestire la transazione attraverso la finally come al solito. Oppure illuderebbe l’utente che l’EntityManager, essendo AutoCloseable, faccia cose che in realtà non fa (la rollback() appunto). Onestamente mi sfugge il motivo per cui l’EntityManager non possa tenere traccia della EntityTransaction, così come fa il mio codice qui sotto, ma è probabile che sia un problema di specifiche e di ricerca della purezza a tutti costi, per venire incontro a qualche possibile implementazione di JPA proveniente da Marte o altri pianeti più lontani…
Ad ogni modo il mio codice funziona, o almeno funziona per me e per il mio stile di programmazione. Può darsi che in realtà non sia sufficientemente generico e che in altri casi non funzioni… se capita a voi fatemi sapere, grazie.
Notate che il fatto che io abbia aggiunto AutoCloseable è una conseguenza di un bug che avevo lasciato in un mio software per cui un EntityManager non veniva chiuso. Per scoprire quale fosse, ho dovuto aggiungere tutto il monitoraggio attraverso JConsole ed interfaccia MBean. Ora quindi avete una classe che vi permette di ottenere un EntityManager (in realtà lo dovete usare per quel che è, ovvero EMWrapper) che nel frattempo è anche monitorabile attraverso JConsole. A voi il codice.
/*
Copyright © 2014 Lucio Crusca <lucio@sulweb.org>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package com.virtual_bit.salix.web.persistence;
import java.lang.management.ManagementFactory;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.management.InstanceAlreadyExistsException;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectName;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.FlushModeType;
import javax.persistence.LockModeType;
import javax.persistence.Persistence;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.metamodel.Metamodel;
public class PersistenceManager
{
private static final PersistenceManager singleton = new PersistenceManager();
private HashMap<String, EntityManagerFactory> emfs;
private static final String mainUnitName = "my-unit";
private static final String[] databases = {
"some-unit",
"other-unit"
};
public static interface EMapMBean extends Map<String, List<EMWrapper>>
{
}
public static class EMap extends HashMap<String, List<EMWrapper>> implements EMapMBean
{
}
public static interface EntityMapMXBean
{
EMap getMap();
String[] getOverview();
String[] getDetails();
}
private static EMap activeWrappers;
public static class EntityMap implements EntityMapMXBean
{
@Override
public EMap getMap()
{
return activeWrappers;
}
public String[] getOverview()
{
List<String> result = new ArrayList<>();
synchronized(activeWrappers)
{
Set<String> keys = activeWrappers.keySet();
for (String s: keys)
result.add(s + " -> " + activeWrappers.get(s).size());
}
String[] ares = new String[result.size()];
return result.toArray(ares);
}
@Override
public String[] getDetails()
{
List<String> lresult = new LinkedList<>();
synchronized(activeWrappers)
{
Set<String> keys = activeWrappers.keySet();
int ki = 0;
for (String s: keys)
{
List<EMWrapper> wrappers = activeWrappers.get(s);
String[][] traces = new String[wrappers.size()][];
int i = 0;
for (EMWrapper emw: wrappers)
traces[i++] = emw.getStackStrace();
lresult.add(flatten(traces));
}
}
String[] result = new String[lresult.size()];
return lresult.toArray(result);
}
private String flatten(String[][] traces)
{
StringBuilder sb = new StringBuilder();
for (String[] key: traces)
for (String values: key)
sb.append(values).append("\n");
return sb.toString();
}
}
private PersistenceManager()
{
}
private static void initMBean()
{
synchronized(PersistenceManager.class)
{
if (activeWrappers != null)
return;
try
{
activeWrappers = new EMap();
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
EntityMap map = new EntityMap();
ObjectName name = new ObjectName(map.getClass().getPackage().getName() + ":type=" + map.getClass().getSimpleName());
mbs.registerMBean(map, name);
}
catch (InstanceAlreadyExistsException | MBeanRegistrationException | MalformedObjectNameException | NotCompliantMBeanException ex)
{
Logger.getLogger(PersistenceManager.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
private static PersistenceManager getInstance()
{
return singleton;
}
private EMWrapper createEntityManager(String jndiName)
{
EntityManagerFactory emf = _getFactory(jndiName);
EMWrapper result = new EMWrapper(emf.createEntityManager(), jndiName);
return result;
}
private EntityManagerFactory _getFactory(String jndiName)
{
if (emfs == null)
emfs = new HashMap<>();
if (!emfs.containsKey(jndiName))
emfs.put(jndiName, Persistence.createEntityManagerFactory(jndiName));
return emfs.get(jndiName);
}
public void closeFactories()
{
if (emfs == null)
return;
for (EntityManagerFactory emf: emfs.values())
emf.close();
emfs.clear();
emfs = null;
}
public static EMWrapper getEM()
{
return getInstance().createEntityManager(nameEM());
}
private static String suffix()
{
Thread current = Thread.currentThread();
if (current.isDaemon())
return "-bg";
return "";
}
public static String nameEM()
{
return mainUnitName + suffix();
}
public static String nameSomeEM()
{
return databases[0] + suffix();
}
public static String nameOtherEM()
{
return databases[1] + suffix();
}
public static EMWrapper getSomeEM()
{
return getInstance().createEntityManager(nameSomeEM());
}
public static EMWrapper getOtherEM()
{
return getInstance().createEntityManager(nameOtherEM());
}
public static EMWrapper getEM(String unitname)
{
return getInstance().createEntityManager(unitname);
}
public static Iterable<String> nameDatabases()
{
ArrayList<String> dbs = new ArrayList<>();
for (String d: databases)
dbs.add(d + suffix());
return dbs;
}
public static interface EMWrapperMBean extends EntityManager
{
};
public static class EMWrapper implements EMWrapperMBean, AutoCloseable
{
private final EntityManager wrapped;
private final String jndiName;
private StackTraceElement[] ste;
private EMWrapper(EntityManager wrapped, String jndiName)
{
this.wrapped = wrapped;
this.jndiName = jndiName;
this.ste = Thread.currentThread().getStackTrace();
initMBean();
synchronized(activeWrappers)
{
List<EMWrapper> active = activeWrappers.get(jndiName);
if (active == null)
active = new LinkedList<>();
active.add(this);
activeWrappers.put(jndiName, active);
}
}
@Override
public void persist(Object entity)
{
wrapped.persist(entity);
}
@Override
public <T> T merge(T entity)
{
return wrapped.<T>merge(entity);
}
@Override
public void remove(Object entity)
{
wrapped.remove(entity);
}
@Override
public <T> T find(Class<T> entityClass, Object primaryKey)
{
return wrapped.<T>find(entityClass, primaryKey);
}
@Override
public <T> T find(Class<T> entityClass, Object primaryKey, Map<String, Object> properties)
{
return wrapped.<T>find(entityClass, primaryKey, properties);
}
@Override
public <T> T find(Class<T> entityClass, Object primaryKey, LockModeType lockMode)
{
return wrapped.<T>find(entityClass, primaryKey, lockMode);
}
@Override
public <T> T find(Class<T> entityClass, Object primaryKey, LockModeType lockMode, Map<String, Object> properties)
{
return wrapped.<T>find(entityClass, primaryKey, lockMode, properties);
}
@Override
public <T> T getReference(Class<T> entityClass, Object primaryKey)
{
return wrapped.<T>getReference(entityClass, primaryKey);
}
@Override
public void flush()
{
wrapped.flush();
}
@Override
public void setFlushMode(FlushModeType flushMode)
{
wrapped.setFlushMode(flushMode);
}
@Override
public FlushModeType getFlushMode()
{
return wrapped.getFlushMode();
}
@Override
public void lock(Object entity, LockModeType lockMode)
{
wrapped.lock(entity, lockMode);
}
@Override
public void lock(Object entity, LockModeType lockMode, Map<String, Object> properties)
{
wrapped.lock(entity, lockMode, properties);
}
@Override
public void refresh(Object entity)
{
wrapped.refresh(entity);
}
@Override
public void refresh(Object entity, Map<String, Object> properties)
{
wrapped.refresh(entity, properties);
}
@Override
public void refresh(Object entity, LockModeType lockMode)
{
wrapped.refresh(entity, lockMode);
}
@Override
public void refresh(Object entity, LockModeType lockMode, Map<String, Object> properties)
{
wrapped.refresh(entity, lockMode, properties);
}
@Override
public void clear()
{
wrapped.clear();
}
@Override
public void detach(Object entity)
{
wrapped.detach(entity);
}
@Override
public boolean contains(Object entity)
{
return wrapped.contains(entity);
}
@Override
public LockModeType getLockMode(Object entity)
{
return wrapped.getLockMode(entity);
}
@Override
public void setProperty(String propertyName, Object value)
{
wrapped.setProperty(propertyName, value);
}
@Override
public Map<String, Object> getProperties()
{
return wrapped.getProperties();
}
@Override
public Query createQuery(String qlString)
{
return wrapped.createQuery(qlString);
}
@Override
public <T> TypedQuery<T> createQuery(CriteriaQuery<T> criteriaQuery)
{
return wrapped.<T>createQuery(criteriaQuery);
}
@Override
public <T> TypedQuery<T> createQuery(String qlString, Class<T> resultClass)
{
return wrapped.<T>createNamedQuery(qlString, resultClass);
}
@Override
public Query createNamedQuery(String name)
{
return wrapped.createNamedQuery(name);
}
@Override
public <T> TypedQuery<T> createNamedQuery(String name, Class<T> resultClass)
{
return wrapped.<T>createNamedQuery(name, resultClass);
}
@Override
public Query createNativeQuery(String sqlString)
{
return wrapped.createNativeQuery(sqlString);
}
@Override
public Query createNativeQuery(String sqlString, Class resultClass)
{
return wrapped.createNativeQuery(sqlString, resultClass);
}
@Override
public Query createNativeQuery(String sqlString, String resultSetMapping)
{
return wrapped.createNativeQuery(sqlString, resultSetMapping);
}
@Override
public void joinTransaction()
{
wrapped.joinTransaction();
}
@Override
public <T> T unwrap(Class<T> cls)
{
return wrapped.<T>unwrap(cls);
}
@Override
public Object getDelegate()
{
return wrapped.getDelegate();
}
private EntityTransaction transaction;
@Override
public void close()
{
if (transaction != null)
if (transaction.isActive())
transaction.rollback();
wrapped.close();
this.ste = null;
synchronized(activeWrappers)
{
List<EMWrapper> active = activeWrappers.get(jndiName);
active.remove(this);
activeWrappers.put(jndiName, active);
}
}
@Override
public boolean isOpen()
{
return wrapped.isOpen();
}
@Override
public EntityTransaction getTransaction()
{
transaction = wrapped.getTransaction();
return transaction;
}
@Override
public EntityManagerFactory getEntityManagerFactory()
{
return wrapped.getEntityManagerFactory();
}
@Override
public CriteriaBuilder getCriteriaBuilder()
{
return wrapped.getCriteriaBuilder();
}
@Override
public Metamodel getMetamodel()
{
return wrapped.getMetamodel();
}
public static HashMap<String, List<EMWrapper>> getWrappers()
{
return activeWrappers;
}
public String[] getStackStrace()
{
if (ste == null)
return null;
String[] result = new String[ste.length];
int i = 0;
for (StackTraceElement s: ste)
result[i++] = s.getClassName() + "#" + s.getMethodName() + ":" + s.getLineNumber();
return result;
}
}
}