Java tutorial
/* * Sonar, open source software quality management tool. * Copyright (C) 2008-2012 SonarSource * mailto:contact AT sonarsource DOT com * * Sonar is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * Sonar is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sonar; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 */ package org.sonar.jpa.session; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Maps; import org.apache.commons.lang.StringUtils; import org.sonar.api.database.DatabaseSession; import javax.persistence.EntityManager; import javax.persistence.NonUniqueResultException; import javax.persistence.PersistenceException; import javax.persistence.Query; import java.util.*; public class JpaDatabaseSession extends DatabaseSession { private final DatabaseConnector connector; private EntityManager entityManager = null; private int index = 0; private boolean inTransaction = false; public JpaDatabaseSession(DatabaseConnector connector) { this.connector = connector; } /** * Note that usage of this method is discouraged, because it allows to construct and execute queries without additional exception handling, * which done in methods of this class. */ @Override public EntityManager getEntityManager() { if (entityManager == null) { entityManager = connector.createEntityManager(); } return entityManager; } @Override public void start() { getEntityManager(); index = 0; } @Override public void stop() { commitAndClose(); } @Override public void commitAndClose() { commit(); if (entityManager != null && entityManager.isOpen()) { entityManager.close(); entityManager = null; } } @Override public void commit() { if (inTransaction) { if (getEntityManager().isOpen()) { if (getEntityManager().getTransaction().getRollbackOnly()) { getEntityManager().getTransaction().rollback(); } else { getEntityManager().getTransaction().commit(); } getEntityManager().clear(); index = 0; } inTransaction = false; } } @Override public void rollback() { if (inTransaction) { getEntityManager().getTransaction().rollback(); inTransaction = false; } } @Override public <T> T save(T model) { startTransaction(); internalSave(model, true); return model; } @Override public Object saveWithoutFlush(Object model) { startTransaction(); internalSave(model, false); return model; } @Override public boolean contains(Object model) { startTransaction(); return getEntityManager().contains(model); } @Override public void save(Object... models) { startTransaction(); for (Object model : models) { save(model); } } private void internalSave(Object model, boolean flushIfNeeded) { try { getEntityManager().persist(model); } catch (PersistenceException e) { /* * See http://jira.codehaus.org/browse/SONAR-2234 * In some cases Hibernate can throw exceptions without meaningful information about context, so we improve them here. */ throw new PersistenceException("Unable to persist : " + model, e); } if (flushIfNeeded && (++index % BATCH_SIZE == 0)) { commit(); } } @Override public Object merge(Object model) { startTransaction(); return getEntityManager().merge(model); } @Override public void remove(Object model) { startTransaction(); getEntityManager().remove(model); if (++index % BATCH_SIZE == 0) { commit(); } } @Override public void removeWithoutFlush(Object model) { startTransaction(); getEntityManager().remove(model); } @Override public <T> T reattach(Class<T> entityClass, Object primaryKey) { startTransaction(); return getEntityManager().getReference(entityClass, primaryKey); } private void startTransaction() { if (!inTransaction) { getEntityManager().getTransaction().begin(); inTransaction = true; } } /** * Note that not recommended to directly execute {@link Query#getSingleResult()}, because it will bypass exception handling, * which done in {@link #getSingleResult(Query, Object)}. */ @Override public Query createQuery(String hql) { startTransaction(); return getEntityManager().createQuery(hql); } @Override public Query createNativeQuery(String sql) { startTransaction(); return getEntityManager().createNativeQuery(sql); } /** * @return the result or <code>defaultValue</code>, if not found * @throws NonUniqueResultException if more than one result */ @Override public <T> T getSingleResult(Query query, T defaultValue) { /* * See http://jira.codehaus.org/browse/SONAR-2225 * By default Hibernate throws NonUniqueResultException without meaningful information about context, * so we improve it here by adding all results in error message. * Note that in some rare situations we can receive too many results, which may lead to OOME, * but actually it will mean that database is corrupted as we don't expect more than one result * and in fact org.hibernate.ejb.QueryImpl#getSingleResult() anyway does loading of several results under the hood. */ List<T> result = query.getResultList(); if (result.size() == 1) { return result.get(0); } else if (result.isEmpty()) { return defaultValue; } else { Set<T> uniqueResult = new HashSet<T>(result); if (uniqueResult.size() > 1) { throw new NonUniqueResultException("Expected single result, but got : " + result.toString()); } else { return uniqueResult.iterator().next(); } } } @Override public <T> T getEntity(Class<T> entityClass, Object id) { startTransaction(); return getEntityManager().find(entityClass, id); } /** * @return the result or <code>null</code>, if not found * @throws NonUniqueResultException if more than one result */ @Override public <T> T getSingleResult(Class<T> entityClass, Object... criterias) { try { return getSingleResult(getQueryForCriterias(entityClass, true, criterias), (T) null); } catch (NonUniqueResultException ex) { NonUniqueResultException e = new NonUniqueResultException("Expected single result for entitiy " + entityClass.getSimpleName() + " with criterias : " + StringUtils.join(criterias, ",")); throw (NonUniqueResultException) e.initCause(ex); } } @Override public <T> List<T> getResults(Class<T> entityClass, Object... criterias) { return getQueryForCriterias(entityClass, true, criterias).getResultList(); } @Override public <T> List<T> getResults(Class<T> entityClass) { return getQueryForCriterias(entityClass, false, (Object[]) null).getResultList(); } private Query getQueryForCriterias(Class<?> entityClass, boolean raiseError, Object... criterias) { if (criterias == null && raiseError) { throw new IllegalStateException("criterias parameter must be provided"); } startTransaction(); StringBuilder hql = new StringBuilder("SELECT o FROM ").append(entityClass.getSimpleName()).append(" o"); if (criterias != null) { hql.append(" WHERE "); Map<String, Object> mappedCriterias = Maps.newHashMap(); for (int i = 0; i < criterias.length; i += 2) { mappedCriterias.put((String) criterias[i], criterias[i + 1]); } buildCriteriasHQL(hql, mappedCriterias); Query query = getEntityManager().createQuery(hql.toString()); for (Map.Entry<String, Object> entry : mappedCriterias.entrySet()) { if (entry.getValue() != null) { query.setParameter(entry.getKey(), entry.getValue()); } } return query; } return getEntityManager().createQuery(hql.toString()); } @VisibleForTesting void buildCriteriasHQL(StringBuilder hql, Map<String, Object> mappedCriterias) { for (Iterator<Map.Entry<String, Object>> i = mappedCriterias.entrySet().iterator(); i.hasNext();) { Map.Entry<String, Object> entry = i.next(); hql.append("o.").append(entry.getKey()); if (entry.getValue() == null) { hql.append(" IS NULL"); } else { hql.append("=:").append(entry.getKey()); } if (i.hasNext()) { hql.append(" AND "); } } } }