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