001 /* 002 * Sonar, open source software quality management tool. 003 * Copyright (C) 2008-2011 SonarSource 004 * mailto:contact AT sonarsource DOT com 005 * 006 * Sonar is free software; you can redistribute it and/or 007 * modify it under the terms of the GNU Lesser General Public 008 * License as published by the Free Software Foundation; either 009 * version 3 of the License, or (at your option) any later version. 010 * 011 * Sonar is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 * Lesser General Public License for more details. 015 * 016 * You should have received a copy of the GNU Lesser General Public 017 * License along with Sonar; if not, write to the Free Software 018 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 019 */ 020 package org.sonar.jpa.session; 021 022 import org.apache.commons.lang.StringUtils; 023 import org.sonar.api.database.DatabaseSession; 024 025 import java.util.*; 026 027 import javax.persistence.EntityManager; 028 import javax.persistence.NonUniqueResultException; 029 import javax.persistence.PersistenceException; 030 import javax.persistence.Query; 031 032 public class JpaDatabaseSession extends DatabaseSession { 033 034 private final DatabaseConnector connector; 035 private EntityManager entityManager = null; 036 private int index = 0; 037 private boolean inTransaction = false; 038 039 public JpaDatabaseSession(DatabaseConnector connector) { 040 this.connector = connector; 041 } 042 043 /** 044 * Note that usage of this method is discouraged, because it allows to construct and execute queries without additional exception handling, 045 * which done in methods of this class. 046 */ 047 public EntityManager getEntityManager() { 048 return entityManager; 049 } 050 051 public void start() { 052 entityManager = connector.createEntityManager(); 053 index = 0; 054 } 055 056 public void stop() { 057 commit(); 058 if (entityManager != null && entityManager.isOpen()) { 059 entityManager.close(); 060 entityManager = null; 061 } 062 } 063 064 public void commit() { 065 if (entityManager != null && inTransaction) { 066 if (entityManager.isOpen()) { 067 if (entityManager.getTransaction().getRollbackOnly()) { 068 entityManager.getTransaction().rollback(); 069 } else { 070 entityManager.getTransaction().commit(); 071 } 072 entityManager.clear(); 073 index = 0; 074 } 075 inTransaction = false; 076 } 077 } 078 079 public void rollback() { 080 if (entityManager != null && inTransaction) { 081 entityManager.getTransaction().rollback(); 082 inTransaction = false; 083 } 084 } 085 086 public <T> T save(T model) { 087 startTransaction(); 088 internalSave(model, true); 089 return model; 090 } 091 092 public Object saveWithoutFlush(Object model) { 093 startTransaction(); 094 internalSave(model, false); 095 return model; 096 } 097 098 public boolean contains(Object model) { 099 startTransaction(); 100 return entityManager.contains(model); 101 } 102 103 public void save(Object... models) { 104 startTransaction(); 105 for (Object model : models) { 106 save(model); 107 } 108 } 109 110 private void internalSave(Object model, boolean flushIfNeeded) { 111 try { 112 entityManager.persist(model); 113 } catch (PersistenceException e) { 114 /* 115 * See http://jira.codehaus.org/browse/SONAR-2234 116 * In some cases Hibernate can throw exceptions without meaningful information about context, so we improve them here. 117 */ 118 throw new PersistenceException("Unable to persist : " + model, e); 119 } 120 if (flushIfNeeded && (++index % BATCH_SIZE == 0)) { 121 commit(); 122 } 123 } 124 125 public Object merge(Object model) { 126 startTransaction(); 127 return entityManager.merge(model); 128 } 129 130 public void remove(Object model) { 131 startTransaction(); 132 entityManager.remove(model); 133 if (++index % BATCH_SIZE == 0) { 134 commit(); 135 } 136 } 137 138 public void removeWithoutFlush(Object model) { 139 startTransaction(); 140 entityManager.remove(model); 141 } 142 143 public <T> T reattach(Class<T> entityClass, Object primaryKey) { 144 startTransaction(); 145 return entityManager.getReference(entityClass, primaryKey); 146 } 147 148 private void startTransaction() { 149 if (!inTransaction) { 150 entityManager.getTransaction().begin(); 151 inTransaction = true; 152 } 153 } 154 155 /** 156 * Note that not recommended to directly execute {@link Query#getSingleResult()}, because it will bypass exception handling, 157 * which done in {@link #getSingleResult(Query, Object)}. 158 */ 159 public Query createQuery(String hql) { 160 startTransaction(); 161 return entityManager.createQuery(hql); 162 } 163 164 @Override 165 public Query createNativeQuery(String sql) { 166 startTransaction(); 167 return entityManager.createNativeQuery(sql); 168 } 169 170 /** 171 * @return the result or <code>defaultValue</code>, if not found 172 * @throws NonUniqueResultException if more than one result 173 */ 174 public <T> T getSingleResult(Query query, T defaultValue) { 175 /* 176 * See http://jira.codehaus.org/browse/SONAR-2225 177 * By default Hibernate throws NonUniqueResultException without meaningful information about context, 178 * so we improve it here by adding all results in error message. 179 * Note that in some rare situations we can receive too many results, which may lead to OOME, 180 * but actually it will mean that database is corrupted as we don't expect more than one result 181 * and in fact org.hibernate.ejb.QueryImpl#getSingleResult() anyway does loading of several results under the hood. 182 */ 183 List<T> result = query.getResultList(); 184 185 if (result.size() == 1) { 186 return result.get(0); 187 188 } else if (result.isEmpty()) { 189 return defaultValue; 190 191 } else { 192 Set<T> uniqueResult = new HashSet<T>(result); 193 if (uniqueResult.size() > 1) { 194 throw new NonUniqueResultException("Expected single result, but got : " + result.toString()); 195 } else { 196 return uniqueResult.iterator().next(); 197 } 198 } 199 } 200 201 public <T> T getEntity(Class<T> entityClass, Object id) { 202 startTransaction(); 203 return getEntityManager().find(entityClass, id); 204 } 205 206 /** 207 * @return the result or <code>null</code>, if not found 208 * @throws NonUniqueResultException if more than one result 209 */ 210 public <T> T getSingleResult(Class<T> entityClass, Object... criterias) { 211 try { 212 return getSingleResult(getQueryForCriterias(entityClass, true, criterias), (T) null); 213 214 } catch (NonUniqueResultException ex) { 215 NonUniqueResultException e = new NonUniqueResultException("Expected single result for entitiy " + entityClass.getSimpleName() 216 + " with criterias : " + StringUtils.join(criterias, ",")); 217 throw (NonUniqueResultException) e.initCause(ex); 218 } 219 } 220 221 public <T> List<T> getResults(Class<T> entityClass, Object... criterias) { 222 return getQueryForCriterias(entityClass, true, criterias).getResultList(); 223 } 224 225 public <T> List<T> getResults(Class<T> entityClass) { 226 return getQueryForCriterias(entityClass, false, null).getResultList(); 227 } 228 229 private Query getQueryForCriterias(Class<?> entityClass, boolean raiseError, Object... criterias) { 230 if (criterias == null && raiseError) { 231 throw new IllegalStateException("criterias parameter must be provided"); 232 } 233 startTransaction(); 234 StringBuilder hql = new StringBuilder("SELECT o FROM ").append(entityClass.getSimpleName()).append(" o"); 235 if (criterias != null) { 236 hql.append(" WHERE "); 237 Map<String, Object> mappedCriterias = new HashMap<String, Object>(); 238 for (int i = 0; i < criterias.length; i += 2) { 239 mappedCriterias.put((String) criterias[i], criterias[i + 1]); 240 } 241 buildCriteriasHQL(hql, mappedCriterias); 242 Query query = getEntityManager().createQuery(hql.toString()); 243 244 for (Map.Entry<String, Object> entry : mappedCriterias.entrySet()) { 245 query.setParameter(entry.getKey(), entry.getValue()); 246 } 247 return query; 248 } 249 return getEntityManager().createQuery(hql.toString()); 250 } 251 252 private void buildCriteriasHQL(StringBuilder hql, Map<String, Object> mappedCriterias) { 253 for (Iterator<String> i = mappedCriterias.keySet().iterator(); i.hasNext();) { 254 String criteria = i.next(); 255 hql.append("o.").append(criteria).append("=:").append(criteria); 256 if (i.hasNext()) { 257 hql.append(" AND "); 258 } 259 } 260 } 261 262 }