001/*
002 * Copyright 2006-2013 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.springframework.batch.item.database;
017
018import java.lang.reflect.Method;
019import java.util.Collection;
020import java.util.List;
021import java.util.Map;
022
023import org.hibernate.Query;
024import org.hibernate.ScrollMode;
025import org.hibernate.ScrollableResults;
026import org.hibernate.Session;
027import org.hibernate.SessionFactory;
028import org.hibernate.StatelessSession;
029
030import org.springframework.batch.item.database.orm.HibernateQueryProvider;
031import org.springframework.beans.factory.InitializingBean;
032import org.springframework.util.Assert;
033import org.springframework.util.ReflectionUtils;
034import org.springframework.util.StringUtils;
035
036/**
037 * Internal shared state helper for hibernate readers managing sessions and
038 * queries.
039 *
040 * @author Dave Syer
041 *
042 */
043@SuppressWarnings("rawtype")
044public class HibernateItemReaderHelper<T> implements InitializingBean {
045
046        private SessionFactory sessionFactory;
047
048        private String queryString = "";
049
050        private String queryName = "";
051
052        private HibernateQueryProvider<? extends T> queryProvider;
053
054        private boolean useStatelessSession = true;
055
056        private StatelessSession statelessSession;
057
058        private Session statefulSession;
059
060        /**
061         * @param queryName name of a hibernate named query
062         */
063        public void setQueryName(String queryName) {
064                this.queryName = queryName;
065        }
066
067        /**
068         * @param queryString HQL query string
069         */
070        public void setQueryString(String queryString) {
071                this.queryString = queryString;
072        }
073
074        /**
075         * @param queryProvider Hibernate query provider
076         */
077        public void setQueryProvider(HibernateQueryProvider<? extends T> queryProvider) {
078                this.queryProvider = queryProvider;
079        }
080
081        /**
082         * Can be set only in uninitialized state.
083         *
084         * @param useStatelessSession <code>true</code> to use
085         * {@link StatelessSession} <code>false</code> to use standard hibernate
086         * {@link Session}
087         */
088        public void setUseStatelessSession(boolean useStatelessSession) {
089                Assert.state(statefulSession == null && statelessSession == null,
090                                "The useStatelessSession flag can only be set before a session is initialized.");
091                this.useStatelessSession = useStatelessSession;
092        }
093
094        /**
095         * @param sessionFactory hibernate session factory
096         */
097        public void setSessionFactory(SessionFactory sessionFactory) {
098                this.sessionFactory = sessionFactory;
099        }
100
101        @Override
102        public void afterPropertiesSet() throws Exception {
103
104                Assert.state(sessionFactory != null, "A SessionFactory must be provided");
105
106                if (queryProvider == null) {
107                        Assert.notNull(sessionFactory, "session factory must be set");
108                        Assert.state(StringUtils.hasText(queryString) ^ StringUtils.hasText(queryName),
109                                        "queryString or queryName must be set");
110                }
111        }
112
113        /**
114         * Get a cursor over all of the results, with the forward-only flag set.
115         *
116         * @param fetchSize the fetch size to use retrieving the results
117         * @param parameterValues the parameter values to use (or null if none).
118         *
119         * @return a forward-only {@link ScrollableResults}
120         */
121        public ScrollableResults getForwardOnlyCursor(int fetchSize, Map<String, Object> parameterValues) {
122                Query query = createQuery();
123                if (parameterValues != null) {
124                        query.setProperties(parameterValues);
125                }
126                return query.setFetchSize(fetchSize).scroll(ScrollMode.FORWARD_ONLY);
127        }
128
129        /**
130         * Open appropriate type of hibernate session and create the query.
131         *
132         * @return a Hibernate Query
133         */
134        public Query createQuery() {
135
136                if (useStatelessSession) {
137                        if (statelessSession == null) {
138                                statelessSession = sessionFactory.openStatelessSession();
139                        }
140                        if (queryProvider != null) {
141                                queryProvider.setStatelessSession(statelessSession);
142                        }
143                        else {
144                                if (StringUtils.hasText(queryName)) {
145                                        return statelessSession.getNamedQuery(queryName);
146                                }
147                                else {
148                                        return statelessSession.createQuery(queryString);
149                                }
150                        }
151                }
152                else {
153                        if (statefulSession == null) {
154                                statefulSession = sessionFactory.openSession();
155                        }
156                        if (queryProvider != null) {
157                                queryProvider.setSession(statefulSession);
158                        }
159                        else {
160                                if (StringUtils.hasText(queryName)) {
161                                        return statefulSession.getNamedQuery(queryName);
162                                }
163                                else {
164                                        return statefulSession.createQuery(queryString);
165                                }
166                        }
167                }
168
169                // If queryProvider is set use it to create a query
170                return queryProvider.createQuery();
171
172        }
173
174        /**
175         * Scroll through the results up to the item specified.
176         *
177         * @param cursor the results to scroll over
178         * @param itemIndex index to scroll to
179         * @param flushInterval the number of items to scroll past before flushing
180         */
181        public void jumpToItem(ScrollableResults cursor, int itemIndex, int flushInterval) {
182                for (int i = 0; i < itemIndex; i++) {
183                        cursor.next();
184                        if (i % flushInterval == 0 && !useStatelessSession) {
185                                statefulSession.clear(); // Clears in-memory cache
186                        }
187                }
188        }
189
190        /**
191         * Close the open session (stateful or otherwise).
192         */
193        public void close() {
194                if (statelessSession != null) {
195                        statelessSession.close();
196                        statelessSession = null;
197                }
198                if (statefulSession != null) {
199
200                        Method close = ReflectionUtils.findMethod(Session.class, "close");
201                        ReflectionUtils.invokeMethod(close, statefulSession);
202                        statefulSession = null;
203                }
204        }
205
206        /**
207         * Read a page of data, clearing the existing session (if necessary) first,
208         * and creating a new session before executing the query.
209         *
210         * @param page the page to read (starting at 0)
211         * @param pageSize the size of the page or maximum number of items to read
212         * @param fetchSize the fetch size to use
213         * @param parameterValues the parameter values to use (if any, otherwise
214         * null)
215         * @return a collection of items
216         */
217        public Collection<? extends T> readPage(int page, int pageSize, int fetchSize, Map<String, Object> parameterValues) {
218
219                clear();
220
221                Query query = createQuery();
222                if (parameterValues != null) {
223                        query.setProperties(parameterValues);
224                }
225                @SuppressWarnings("unchecked")
226                List<T> result = query.setFetchSize(fetchSize).setFirstResult(page * pageSize).setMaxResults(pageSize).list();
227                return result;
228
229        }
230
231        /**
232         * Clear the session if stateful.
233         */
234        public void clear() {
235                if (statefulSession != null) {
236                        statefulSession.clear();
237                }
238        }
239
240}