001 /* 002 * Sonar, open source software quality management tool. 003 * Copyright (C) 2009 SonarSource SA 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.api.database; 021 022 import org.apache.commons.lang.StringUtils; 023 import org.slf4j.LoggerFactory; 024 import org.sonar.api.BatchExtension; 025 026 import java.util.HashMap; 027 import java.util.Iterator; 028 import java.util.List; 029 import java.util.Map; 030 import javax.persistence.EntityManager; 031 import javax.persistence.NoResultException; 032 import javax.persistence.NonUniqueResultException; 033 import javax.persistence.Query; 034 035 /** 036 * @since 1.10 037 */ 038 public class DatabaseSession implements BatchExtension { 039 040 // IMPORTANT : this value must be the same than the property 041 // hibernate.jdbc.batch_size from /META-INF/persistence.xml : 042 public static final int BATCH_SIZE = 100; 043 044 private final DatabaseConnector connector; 045 private EntityManager entityManager = null; 046 private int index = 0; 047 private boolean inTransaction = false; 048 049 public DatabaseSession(DatabaseConnector connector) { 050 this.connector = connector; 051 } 052 053 public EntityManager getEntityManager() { 054 return entityManager; 055 } 056 057 public void start() { 058 entityManager = connector.createEntityManager(); 059 index = 0; 060 } 061 062 public void stop() { 063 commit(); 064 if (entityManager != null && entityManager.isOpen()) { 065 entityManager.clear(); 066 entityManager.close(); 067 entityManager = null; 068 } 069 } 070 071 public void commit() { 072 if (entityManager != null && inTransaction) { 073 if (entityManager.isOpen()) { 074 if (entityManager.getTransaction().getRollbackOnly()) { 075 entityManager.getTransaction().rollback(); 076 } else { 077 entityManager.getTransaction().commit(); 078 } 079 } 080 inTransaction = false; 081 index = 0; 082 } 083 } 084 085 public void rollback() { 086 if (entityManager != null && inTransaction) { 087 entityManager.getTransaction().rollback(); 088 inTransaction = false; 089 index = 0; 090 } 091 } 092 093 public <T> T save(T model) { 094 startTransaction(); 095 internalSave(model, true); 096 return model; 097 } 098 099 public Object saveWithoutFlush(Object model) { 100 startTransaction(); 101 internalSave(model, false); 102 return model; 103 } 104 105 public boolean contains(Object model) { 106 startTransaction(); 107 return entityManager.contains(model); 108 } 109 110 public void save(Object... models) { 111 startTransaction(); 112 for (Object model : models) { 113 save(model); 114 } 115 } 116 117 private void internalSave(Object model, boolean flushIfNeeded) { 118 entityManager.persist(model); 119 if (flushIfNeeded && (++index % BATCH_SIZE == 0)) { 120 flush(); 121 } 122 } 123 124 public Object merge(Object model) { 125 startTransaction(); 126 return entityManager.merge(model); 127 } 128 129 public void remove(Object model) { 130 startTransaction(); 131 entityManager.remove(model); 132 if (++index % BATCH_SIZE == 0) { 133 flush(); 134 } 135 } 136 137 public <T> T reattach(Class<T> entityClass, Object primaryKey) { 138 startTransaction(); 139 return entityManager.getReference(entityClass, primaryKey); 140 } 141 142 private void startTransaction() { 143 if (!inTransaction) { 144 entityManager.getTransaction().begin(); 145 inTransaction = true; 146 } 147 } 148 149 private void flush() { 150 entityManager.flush(); 151 entityManager.clear(); 152 } 153 154 public Query createQuery(String hql) { 155 startTransaction(); 156 return entityManager.createQuery(hql); 157 } 158 159 public <T> T getSingleResult(Query query, T defaultValue) { 160 try { 161 return (T) query.getSingleResult(); 162 } catch (NoResultException ex) { 163 return defaultValue; 164 } 165 } 166 167 public <T> T getEntity(Class<T> entityClass, Object id) { 168 startTransaction(); 169 return getEntityManager().find(entityClass, id); 170 } 171 172 public <T> T getSingleResult(Class<T> entityClass, Object... criterias) { 173 try { 174 return getSingleResult(getQueryForCriterias(entityClass, true, criterias), (T) null); 175 } catch (NonUniqueResultException ex) { 176 LoggerFactory.getLogger(DatabaseSession.class).warn("NonUniqueResultException on entity {} with criterias : {}", 177 entityClass.getSimpleName(), StringUtils.join(criterias, ",")); 178 throw ex; 179 } 180 } 181 182 public <T> List<T> getResults(Class<T> entityClass, Object... criterias) { 183 return getQueryForCriterias(entityClass, true, criterias).getResultList(); 184 } 185 186 public <T> List<T> getResults(Class<T> entityClass) { 187 return getQueryForCriterias(entityClass, false, null).getResultList(); 188 } 189 190 private Query getQueryForCriterias(Class<?> entityClass, boolean raiseError, Object... criterias) { 191 if (criterias == null && raiseError) { 192 throw new IllegalStateException("criterias parameter must be provided"); 193 } 194 startTransaction(); 195 StringBuilder hql = new StringBuilder("SELECT o FROM ").append(entityClass.getSimpleName()).append(" o"); 196 if (criterias != null) { 197 hql.append(" WHERE "); 198 Map<String, Object> mappedCriterias = new HashMap<String, Object>(); 199 for (int i = 0; i < criterias.length; i += 2) { 200 mappedCriterias.put((String) criterias[i], criterias[i + 1]); 201 } 202 buildCriteriasHQL(hql, mappedCriterias); 203 Query query = getEntityManager().createQuery(hql.toString()); 204 205 for (Map.Entry<String, Object> entry : mappedCriterias.entrySet()) { 206 query.setParameter(entry.getKey(), entry.getValue()); 207 } 208 return query; 209 } 210 return getEntityManager().createQuery(hql.toString()); 211 } 212 213 private void buildCriteriasHQL(StringBuilder hql, Map<String, Object> mappedCriterias) { 214 for (Iterator<String> i = mappedCriterias.keySet().iterator(); i.hasNext();) { 215 String criteria = i.next(); 216 hql.append("o.").append(criteria).append("=:").append(criteria); 217 if (i.hasNext()) { 218 hql.append(" AND "); 219 } 220 } 221 } 222 223 }