001
002 /*
003 * Copyright (C) 2011 Archie L. Cobbs. All rights reserved.
004 *
005 * $Id: AbstractDAO.java 307 2012-03-07 22:00:33Z archie.cobbs $
006 */
007
008 package org.dellroad.stuff.dao;
009
010 import java.util.List;
011
012 import javax.persistence.EntityManager;
013 import javax.persistence.EntityManagerFactory;
014 import javax.persistence.FlushModeType;
015 import javax.persistence.NoResultException;
016 import javax.persistence.TypedQuery;
017 import javax.persistence.criteria.CriteriaBuilder;
018 import javax.persistence.criteria.CriteriaQuery;
019
020 import org.slf4j.Logger;
021 import org.slf4j.LoggerFactory;
022 import org.springframework.orm.jpa.JpaCallback;
023 import org.springframework.orm.jpa.JpaTemplate;
024 import org.springframework.orm.jpa.support.JpaDaoSupport;
025 import org.springframework.transaction.support.TransactionSynchronizationManager;
026
027 /**
028 * Support superclass for JPA DAO implementations.
029 *
030 * @param <T> persistent instance type
031 */
032 public abstract class AbstractDAO<T> extends JpaDaoSupport implements DAO<T> {
033
034 protected final Logger log = LoggerFactory.getLogger(getClass());
035
036 /**
037 * Persistent instance type.
038 */
039 protected final Class<T> type;
040
041 /**
042 * Constructor.
043 *
044 * @param type persistent instance type
045 */
046 protected AbstractDAO(Class<T> type) {
047 if (type == null)
048 throw new IllegalArgumentException("null type");
049 this.type = type;
050 }
051
052 /**
053 * Constructor.
054 *
055 * @param type persistent instance type
056 * @param entityManagerFactory {@link EntityManagerFactory} from which to create the {@link JpaTemplate} used by this instance
057 */
058 protected AbstractDAO(Class<T> type, EntityManagerFactory entityManagerFactory) {
059 this(type);
060 this.setEntityManagerFactory(entityManagerFactory);
061 }
062
063 /**
064 * Constructor.
065 *
066 * @param type persistent instance type
067 * @param jpaTemplate {@link JpaTemplate} to be used by this instance
068 */
069 protected AbstractDAO(Class<T> type, JpaTemplate jpaTemplate) {
070 this(type);
071 this.setJpaTemplate(jpaTemplate);
072 }
073
074 // Access methods
075
076 @Override
077 public T getById(long id) {
078 return this.getJpaTemplate().find(this.type, id);
079 }
080
081 @Override
082 public List<T> getAll() {
083 return this.getBy(new DAOCriteriaListCallback() {
084 @Override
085 protected void configureQuery(CriteriaQuery<T> criteriaQuery, CriteriaBuilder criteriaBuilder) {
086 // no criteria - we want them all
087 }
088 });
089 }
090
091 @Override
092 public T getReference(long id) {
093 return this.getJpaTemplate().getReference(this.type, id);
094 }
095
096 /**
097 * Find instances using a query string and query parameters.
098 */
099 protected List<T> find(final String queryString, final Object... params) {
100 return this.getBy(new DAOQueryListCallback() {
101 @Override
102 protected TypedQuery<T> buildQuery(EntityManager entityManager) {
103 return AbstractDAO.this.buildQuery(entityManager, queryString, params);
104 }
105 });
106 }
107
108 /**
109 * Find a unique instance using a query string and query parameters.
110 *
111 * @return unique instance found, or null if none was found
112 */
113 protected T findUnique(final String queryString, final Object... params) {
114 return this.getBy(new DAOQueryUniqueCallback() {
115 @Override
116 protected TypedQuery<T> buildQuery(EntityManager entityManager) {
117 return AbstractDAO.this.buildQuery(entityManager, queryString, params);
118 }
119 });
120 }
121
122 /**
123 * Search using a {@link QueryCallback}.
124 */
125 protected <R> R getBy(QueryCallback<R> callback) {
126 return this.getJpaTemplate().execute(callback);
127 }
128
129 /**
130 * Perform a bulk update.
131 */
132 protected int bulkUpdate(UpdateCallback callback) {
133 return this.getJpaTemplate().execute(callback);
134 }
135
136 // Lifecycle methods
137
138 @Override
139 public void save(T obj) {
140 this.getJpaTemplate().persist(obj);
141 }
142
143 @Override
144 public void delete(T obj) {
145 this.getJpaTemplate().remove(obj);
146 }
147
148 @Override
149 public T merge(T obj) {
150 return this.getJpaTemplate().merge(obj);
151 }
152
153 @Override
154 public void refresh(T obj) {
155 this.getJpaTemplate().refresh(obj);
156 }
157
158 @Override
159 public void detach(final Object obj) {
160 this.getJpaTemplate().execute(new JpaCallback<Void>() {
161 @Override
162 public Void doInJpa(EntityManager entityManager) {
163 entityManager.detach(obj);
164 return null;
165 }
166 });
167 }
168
169 // Session methods
170
171 @Override
172 public void flush() {
173 this.getJpaTemplate().flush();
174 }
175
176 @Override
177 public void setFlushMode(final FlushModeType flushMode) {
178 this.getJpaTemplate().execute(new JpaCallback<Void>() {
179 @Override
180 public Void doInJpa(EntityManager entityManager) {
181 entityManager.setFlushMode(flushMode);
182 return null;
183 }
184 });
185 }
186
187 @Override
188 public void clear() {
189 this.getJpaTemplate().execute(new JpaCallback<Void>() {
190 @Override
191 public Void doInJpa(EntityManager entityManager) {
192 entityManager.clear();
193 return null;
194 }
195 });
196 }
197
198 @Override
199 public boolean isReadOnly() {
200 return TransactionSynchronizationManager.isCurrentTransactionReadOnly();
201 }
202
203 // Type and cast methods
204
205 /**
206 * Cast the given object to this instance's persistent instance type.
207 */
208 protected T cast(Object obj) {
209 return this.type.cast(obj);
210 }
211
212 /**
213 * Cast the given list to a list of this instance's persistent instance type.
214 * Does not actually inspect the contents of the list.
215 */
216 @SuppressWarnings("unchecked")
217 protected List<T> castList(List<?> list) {
218 return (List<T>)list;
219 }
220
221 // Helper methods
222
223 private TypedQuery<T> buildQuery(EntityManager entityManager, String queryString, Object[] params) {
224 TypedQuery<T> query = entityManager.createQuery(queryString, this.type);
225 if (params != null) {
226 for (int i = 0; i < params.length; i++)
227 query.setParameter(i + 1, params[i]);
228 }
229 return query;
230 }
231
232 // Helper classes
233
234 /**
235 * Convenience subclass of {@link QueryCallback} for use by DAO subclasses when returning lists of persistent instances.
236 */
237 protected abstract class DAOQueryListCallback extends TypedQueryCallback<T, List<T>> {
238
239 @Override
240 protected final List<T> executeQuery(TypedQuery<T> query) {
241 return query.getResultList();
242 }
243 }
244
245 /**
246 * Convenience subclass of {@link QueryCallback} for use by DAO subclasses when returning a single persistent instance.
247 *
248 * <p>
249 * Returns null if instance is not found.
250 */
251 protected abstract class DAOQueryUniqueCallback extends TypedQueryCallback<T, T> {
252
253 @Override
254 protected final T executeQuery(TypedQuery<T> query) {
255 try {
256 return query.getSingleResult();
257 } catch (NoResultException e) {
258 return null;
259 }
260 }
261 }
262
263 /**
264 * Convenience subclass of {@link CriteriaCallback} for use by DAO subclasses when returning lists of persistent instances.
265 */
266 protected abstract class DAOCriteriaListCallback extends CriteriaCallback<T, List<T>> {
267
268 protected DAOCriteriaListCallback() {
269 super(AbstractDAO.this.type);
270 }
271
272 @Override
273 protected final List<T> executeQuery(TypedQuery<T> query) {
274 return query.getResultList();
275 }
276 }
277
278 /**
279 * Convenience subclass of {@link CriteriaCallback} for use by DAO subclasses when returning a single persistent instance.
280 *
281 * <p>
282 * Returns null if instance is not found.
283 */
284 protected abstract class DAOCriteriaUniqueCallback extends CriteriaCallback<T, T> {
285
286 protected DAOCriteriaUniqueCallback() {
287 super(AbstractDAO.this.type);
288 }
289
290 @Override
291 protected final T executeQuery(TypedQuery<T> query) {
292 try {
293 return query.getSingleResult();
294 } catch (NoResultException e) {
295 return null;
296 }
297 }
298 }
299 }
300