/* * Copyright 2006-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.batch.item.database; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.EntityTransaction; import javax.persistence.Query; import org.springframework.batch.item.ExecutionContext; import org.springframework.batch.item.database.orm.JpaQueryProvider; import org.springframework.dao.DataAccessResourceFailureException; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; /** * <p> * {@link org.springframework.batch.item.ItemReader} for reading database * records built on top of JPA. * </p> * * <p> * It executes the JPQL {@link #setQueryString(String)} to retrieve requested * data. The query is executed using paged requests of a size specified in * {@link #setPageSize(int)}. Additional pages are requested when needed as * {@link #read()} method is called, returning an object corresponding to * current position. * </p> * * <p> * The performance of the paging depends on the JPA implementation and its use * of database specific features to limit the number of returned rows. * </p> * * <p> * Setting a fairly large page size and using a commit interval that matches the * page size should provide better performance. * </p> * * <p> * In order to reduce the memory usage for large results the persistence context * is flushed and cleared after each page is read. This causes any entities read * to be detached. If you make changes to the entities and want the changes * persisted then you must explicitly merge the entities. * </p> * * <p> * The reader must be configured with an * {@link javax.persistence.EntityManagerFactory}. All entity access is * performed within a new transaction, independent of any existing Spring * managed transactions. * </p> * * <p> * The implementation is thread-safe in between calls to * {@link #open(ExecutionContext)}, but remember to use * <code>saveState=false</code> if used in a multi-threaded client (no restart * available). * </p> * * * @author Thomas Risberg * @author Dave Syer * @author Will Schipp * @since 2.0 */ public class JpaPagingItemReader<T> extends AbstractPagingItemReader<T> { private EntityManagerFactory entityManagerFactory; private EntityManager entityManager; private final Map<String, Object> jpaPropertyMap = new HashMap<>(); private String queryString; private JpaQueryProvider queryProvider; private Map<String, Object> parameterValues; private boolean transacted = true;//default value public JpaPagingItemReader() { setName(ClassUtils.getShortName(JpaPagingItemReader.class)); } /** * Create a query using an appropriate query provider (entityManager OR * queryProvider). */ private Query createQuery() { if (queryProvider == null) { return entityManager.createQuery(queryString); } else { return queryProvider.createQuery(); } } public void setEntityManagerFactory(EntityManagerFactory entityManagerFactory) { this.entityManagerFactory = entityManagerFactory; } /** * The parameter values to be used for the query execution. * * @param parameterValues the values keyed by the parameter named used in * the query string. */ public void setParameterValues(Map<String, Object> parameterValues) { this.parameterValues = parameterValues; } /** * By default (true) the EntityTransaction will be started and committed around the read. * Can be overridden (false) in cases where the JPA implementation doesn't support a * particular transaction. (e.g. Hibernate with a JTA transaction). NOTE: may cause * problems in guaranteeing the object consistency in the EntityManagerFactory. * * @param transacted indicator */ public void setTransacted(boolean transacted) { this.transacted = transacted; } @Override public void afterPropertiesSet() throws Exception { super.afterPropertiesSet(); if (queryProvider == null) { Assert.notNull(entityManagerFactory, "EntityManager is required when queryProvider is null"); Assert.hasLength(queryString, "Query string is required when queryProvider is null"); } } /** * @param queryString JPQL query string */ public void setQueryString(String queryString) { this.queryString = queryString; } /** * @param queryProvider JPA query provider */ public void setQueryProvider(JpaQueryProvider queryProvider) { this.queryProvider = queryProvider; } @Override protected void doOpen() throws Exception { super.doOpen(); entityManager = entityManagerFactory.createEntityManager(jpaPropertyMap); if (entityManager == null) { throw new DataAccessResourceFailureException("Unable to obtain an EntityManager"); } // set entityManager to queryProvider, so it participates // in JpaPagingItemReader's managed transaction if (queryProvider != null) { queryProvider.setEntityManager(entityManager); } } @Override @SuppressWarnings("unchecked") protected void doReadPage() { EntityTransaction tx = null; if (transacted) { tx = entityManager.getTransaction(); tx.begin(); entityManager.flush(); entityManager.clear(); }//end if Query query = createQuery().setFirstResult(getPage() * getPageSize()).setMaxResults(getPageSize()); if (parameterValues != null) { for (Map.Entry<String, Object> me : parameterValues.entrySet()) { query.setParameter(me.getKey(), me.getValue()); } } if (results == null) { results = new CopyOnWriteArrayList<>(); } else { results.clear(); } if (!transacted) { List<T> queryResult = query.getResultList(); for (T entity : queryResult) { entityManager.detach(entity); results.add(entity); }//end if } else { results.addAll(query.getResultList()); tx.commit(); }//end if } @Override protected void doJumpToPage(int itemIndex) { } @Override protected void doClose() throws Exception { entityManager.close(); super.doClose(); } }