Author: tchemit Date: 2012-09-05 17:58:04 +0200 (Wed, 05 Sep 2012) New Revision: 2655 Url: http://nuiton.org/repositories/revision/topia/2655 Log: fixes #2301: Add methods findAllLazyByQuery on TopiaDAO to iterate on result by a lazy mecanism Modified: branches/topia-2.6.x/topia-persistence/src/main/java/org/nuiton/topia/persistence/TopiaDAO.java branches/topia-2.6.x/topia-persistence/src/main/java/org/nuiton/topia/persistence/TopiaDAOImpl.java branches/topia-2.6.x/topia-persistence/src/test/java/org/nuiton/topia/persistence/TopiaDAOTest.java Modified: branches/topia-2.6.x/topia-persistence/src/main/java/org/nuiton/topia/persistence/TopiaDAO.java =================================================================== --- branches/topia-2.6.x/topia-persistence/src/main/java/org/nuiton/topia/persistence/TopiaDAO.java 2012-08-30 14:44:13 UTC (rev 2654) +++ branches/topia-2.6.x/topia-persistence/src/main/java/org/nuiton/topia/persistence/TopiaDAO.java 2012-09-05 15:58:04 UTC (rev 2655) @@ -60,7 +60,7 @@ * @author tchemit <chemit@codelutin.com> * @version $Id$ */ -public interface TopiaDAO<E extends TopiaEntity> extends TopiaDAODeprecated<E> { +public interface TopiaDAO<E extends TopiaEntity> extends TopiaDAODeprecated<E>, Iterable<E> { //------------------------------------------------------------------------// //-- Create - update - delete methods ------------------------------------// @@ -328,6 +328,74 @@ Object... params) throws TopiaException; /** + * Gets all entities in lazy mode when executing the given select query + * for the dao entity type. + * <p/> + * <strong>Important note:</strong> + * + * @param hql hql query + * @param params query params + * @return entites of the query result + * @throws TopiaException if any pb while getting datas + * @since 2.6.14 + */ + Iterable<E> findAllLazyByQuery(String hql, + Object... params) throws TopiaException; + + /** + * Gets all entities in lazy mode when executing the given select query + * for the given {@code type} which may not be a entity type (int, long, map,...). + * <p/> + * <strong>Important note:</strong> + * + * @param type type of data to return + * @param hql hql query + * @param params query params + * @return entites of the query result + * @throws TopiaException if any pb while getting datas + * @since 2.6.14 + */ + <R> Iterable<R> findAllLazyByQuery(Class<R> type, + String hql, + Object... params) throws TopiaException; + + /** + * Gets all entities in lazy mode when executing the given select query + * for the dao entity type. + * <p/> + * <strong>Important note:</strong> + * + * @param batchSize batch size + * @param hql hql query + * @param params query params + * @return entites of the query result + * @throws TopiaException if any pb while getting datas + * @since 2.6.14 + */ + Iterable<E> findAllLazyByQuery(int batchSize, + String hql, + Object... params) throws TopiaException; + + /** + * Gets all entities in lazy mode when executing the given select query + * for the given {@code type} which may not be a entity type (int, long, map,...). + * <p/> + * <strong>Important note:</strong> + * + * @param type type of data to return + * @param batchSize batch size + * @param hql hql query + * @param params query params + * @return entites of the query result + * @throws TopiaException if any pb while getting datas + * @since 2.6.14 + */ + <R> Iterable<R> findAllLazyByQuery(Class<R> type, + int batchSize, + String hql, + Object... params) throws TopiaException; + + /** * Gets a page of entities when executing the given select query for the dao * entity type (will only return the window of {@code startIndex - * endIndex} entities). @@ -340,6 +408,25 @@ * @throws TopiaException if any pb while getting datas * @since 2.6.12 */ + <R> List<R> findAllByQueryWithBound(Class<R> type, + String hql, + int startIndex, + int endIndex, + Object... params) throws TopiaException; + + /** + * Gets a page of entities when executing the given select query for the dao + * entity type (will only return the window of {@code startIndex - + * endIndex} entities). + * + * @param hql hql query to execute + * @param startIndex first index of entity to return + * @param endIndex last index of entity to return + * @param params query params + * @return entites of the paginated query result + * @throws TopiaException if any pb while getting datas + * @since 2.6.12 + */ List<E> findAllByQueryWithBound(String hql, int startIndex, int endIndex, @@ -349,6 +436,7 @@ * Gets a page of entities of the given select {@code hql} query using the * {@code pager} to obtain the window of entities to return. * + * @param type type of data to return * @param hql hql query to execute * @param pager pager to obtan the correct window of data * @param params params of the query @@ -357,6 +445,23 @@ * @see TopiaFilterPagerUtil.FilterPagerBean * @since 2.6.12 */ + <R> List<R> findAllByQueryAndPager(Class<R> type, + String hql, + TopiaFilterPagerUtil.FilterPagerBean pager, + Object... params) throws TopiaException; + + /** + * Gets a page of entities of the given select {@code hql} query using the + * {@code pager} to obtain the window of entities to return. + * + * @param hql hql query to execute + * @param pager pager to obtan the correct window of data + * @param params params of the query + * @return entities of the paginated query result + * @throws TopiaException if any pb while getting datas + * @see TopiaFilterPagerUtil.FilterPagerBean + * @since 2.6.12 + */ List<E> findAllByQueryAndPager(String hql, TopiaFilterPagerUtil.FilterPagerBean pager, Object... params) throws TopiaException; @@ -520,6 +625,24 @@ Class<E> getEntityClass(); /** + * Obtains the batch size used to load data. + * <p/> + * Default value if 1000. + * + * @return the batch size. + * @since 2.6.14 + */ + int getBatchSize(); + + /** + * Set a new default batch size. + * + * @param batchSize new batch size to use when iterating. + * @since 2.6.14 + */ + void setBatchSize(int batchSize); + + /** * When TopiaContextImpl create the TopiaDAOHibernate, it must call this * method just after. * Modified: branches/topia-2.6.x/topia-persistence/src/main/java/org/nuiton/topia/persistence/TopiaDAOImpl.java =================================================================== --- branches/topia-2.6.x/topia-persistence/src/main/java/org/nuiton/topia/persistence/TopiaDAOImpl.java 2012-08-30 14:44:13 UTC (rev 2654) +++ branches/topia-2.6.x/topia-persistence/src/main/java/org/nuiton/topia/persistence/TopiaDAOImpl.java 2012-09-05 15:58:04 UTC (rev 2655) @@ -38,6 +38,7 @@ package org.nuiton.topia.persistence; import com.google.common.base.Preconditions; +import com.google.common.collect.Iterators; import org.apache.commons.beanutils.PropertyUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; @@ -46,6 +47,7 @@ import org.hibernate.Session; import org.hibernate.metadata.ClassMetadata; import org.nuiton.topia.TopiaException; +import org.nuiton.topia.TopiaRuntimeException; import org.nuiton.topia.event.TopiaEntityListener; import org.nuiton.topia.event.TopiaEntityVetoable; import org.nuiton.topia.framework.TopiaContextImplementor; @@ -56,8 +58,10 @@ import java.lang.reflect.InvocationTargetException; import java.security.Permission; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.NoSuchElementException; /** * Cette classe permet d'avoir un ensemble de méthode implantée de façon @@ -79,10 +83,27 @@ /** to use log facility, just put in your code: log.info(\"...\"); */ private static Log log = LogFactory.getLog(TopiaDAOImpl.class); + /** + * Type of entity managed by this dao. + * + * @since ever + */ protected Class<E> entityClass; + /** + * Underlying context used by this dao to do actions on db. + * + * @since ever + */ protected TopiaContextImplementor context; + /** + * Default batch size used to iterate on data. + * + * @since 2.6.14 + */ + private int batchSize = 1000; + @Override public TopiaEntityEnum getTopiaEntityEnum() { throw new UnsupportedOperationException( @@ -95,6 +116,28 @@ "This method must be overided in generated DAO"); } + @Override + public int getBatchSize() { + return batchSize; + } + + @Override + public void setBatchSize(int batchSize) { + this.batchSize = batchSize; + } + + @Override + public Iterator<E> iterator() { + + Iterator<E> iterator = new FindAllIterator<E, E>( + this, + getEntityClass(), + batchSize, + "FROM " + getTopiaEntityEnum().getImplementationFQN()); + + return iterator; + } + /** * Retourne l'id de l'entity * @@ -592,12 +635,14 @@ + " n'a pas de cle primaire naturelle"); } + @Override public boolean existsByQuery(String hql, Object... params) throws TopiaException { long count = countByQuery(hql, params); return count > 0; } + @Override public long countByQuery(String hql, Object... params) throws TopiaException { @@ -607,11 +652,13 @@ return findByQuery(Long.class, hql, params); } + @Override public E findByQuery(String hql, Object... params) throws TopiaException { return findByQuery(getEntityClass(), hql, params); } + @Override public <R> R findByQuery(Class<R> type, String hql, Object... params) throws TopiaException { @@ -625,12 +672,14 @@ return (R) unique; } + @Override public List<E> findAllByQuery(String hql, Object... params) throws TopiaException { return findAllByQuery(getEntityClass(), hql, params); } + @Override public <R> List<R> findAllByQuery(Class<R> type, String hql, Object... params) throws TopiaException { @@ -642,20 +691,75 @@ return result; } - public List<E> findAllByQueryWithBound(String hql, - int startIndex, - int endIndex, - Object... params) throws TopiaException { + @Override + public Iterable<E> findAllLazyByQuery(String hql, + Object... params) throws TopiaException { + return findAllLazyByQuery(batchSize, hql, params); + } + @Override + public <R> Iterable<R> findAllLazyByQuery(Class<R> type, + String hql, + Object... params) throws TopiaException { + return findAllLazyByQuery(type, batchSize, hql, params); + } + + @Override + public Iterable<E> findAllLazyByQuery(int batchSize, + String hql, + Object... params) throws TopiaException { + return findAllLazyByQuery(getEntityClass(), batchSize, hql, params); + } + + @Override + public <R> Iterable<R> findAllLazyByQuery(Class<R> type, + int batchSize, + String hql, + Object... params) throws TopiaException { + + final Iterator<R> iterator = new FindAllIterator<E, R>(this, + type, + batchSize, + hql, + params); + return new Iterable<R>() { + @Override + public Iterator<R> iterator() { + return iterator; + } + }; + } + + @Override + public <R> List<R> findAllByQueryWithBound(Class<R> type, + String hql, + int startIndex, + int endIndex, + Object... params) throws TopiaException { + Preconditions.checkNotNull(type); Preconditions.checkNotNull(hql); - List<E> result = getContext().find(hql, startIndex, endIndex, params); + List<R> result = getContext().find(hql, startIndex, endIndex, params); return result; } - public List<E> findAllByQueryAndPager(String hql, - TopiaFilterPagerUtil.FilterPagerBean pager, - Object... params) throws TopiaException { + @Override + public List<E> findAllByQueryWithBound(String hql, + int startIndex, + int endIndex, + Object... params) throws TopiaException { + return findAllByQueryWithBound(getEntityClass(), + hql, + startIndex, + endIndex, + params); + } + + @Override + public <R> List<R> findAllByQueryAndPager(Class<R> type, + String hql, + TopiaFilterPagerUtil.FilterPagerBean pager, + Object... params) throws TopiaException { Preconditions.checkNotNull(pager); Preconditions.checkNotNull(hql); @@ -665,13 +769,25 @@ hql += " DESC"; } } - List<E> result = findAllByQueryWithBound(hql, + List<R> result = findAllByQueryWithBound(type, hql, pager.getRecordStartIndex(), pager.getRecordEndIndex() - 1, params); return result; } + @Override + public List<E> findAllByQueryAndPager(String hql, + TopiaFilterPagerUtil.FilterPagerBean pager, + Object... params) throws TopiaException { + + return findAllByQueryAndPager(getEntityClass(), + hql, + pager, + params); + } + + @Override public void computeAndAddRecordsToPager(String hql, TopiaFilterPagerUtil.FilterPagerBean pager, Object... params) throws TopiaException { @@ -709,4 +825,82 @@ return meta; } + public static class FindAllIterator<E extends TopiaEntity, R> implements Iterator<R> { + + protected Iterator<R> data; + + protected final TopiaDAO<E> dao; + + protected final Class<R> type; + + protected final String hql; + + protected final Object[] params; + + protected TopiaFilterPagerUtil.FilterPagerBean pager; + + public FindAllIterator(TopiaDAO<E> dao, + Class<R> type, + int batchSize, + String hql, + Object... params) { + this.dao = dao; + this.type = type; + this.hql = hql; + this.params = params; + try { + long count = dao.countByQuery("SELECT COUNT(*) " + hql, params); + pager = TopiaFilterPagerUtil.newFilterPagerBean(); + pager.setRecords((int) count); + pager.setPageSize(batchSize); + TopiaFilterPagerUtil.computeRecordIndexesAndPagesNumber(pager); + } catch (TopiaException e) { + throw new TopiaRuntimeException(e); + } + + // empty iterator (will be changed at first next call) + data = Iterators.emptyIterator(); + } + + @Override + public boolean hasNext() { + return data.hasNext() || // no more data + pager.getPageIndex() < pager.getPagesNumber(); + } + + @Override + public R next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + + if (!data.hasNext()) { + + // must load iterator + + // increments page index + pager.setPageIndex(pager.getPageIndex() + 1); + TopiaFilterPagerUtil.computeRecordIndexesAndPagesNumber(pager); + + // load new window of data + try { + data = dao.findAllByQueryAndPager(type, + hql, + pager, + params).iterator(); + } catch (TopiaException e) { + throw new TopiaRuntimeException(e); + } + } + + R next = data.next(); + return next; + } + + @Override + public void remove() { + throw new UnsupportedOperationException( + "This iterator does not support remove operation."); + } + } } //TopiaDAOImpl Modified: branches/topia-2.6.x/topia-persistence/src/test/java/org/nuiton/topia/persistence/TopiaDAOTest.java =================================================================== --- branches/topia-2.6.x/topia-persistence/src/test/java/org/nuiton/topia/persistence/TopiaDAOTest.java 2012-08-30 14:44:13 UTC (rev 2654) +++ branches/topia-2.6.x/topia-persistence/src/test/java/org/nuiton/topia/persistence/TopiaDAOTest.java 2012-09-05 15:58:04 UTC (rev 2655) @@ -25,12 +25,15 @@ package org.nuiton.topia.persistence; +import com.google.common.collect.Lists; import org.junit.Assert; +import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.matchers.JUnitMatchers; import org.nuiton.topia.TopiaContext; import org.nuiton.topia.TopiaDatabase; +import org.nuiton.topia.TopiaException; import org.nuiton.topia.TopiaTestDAOHelper; import org.nuiton.topia.test.entities.Person; import org.nuiton.topia.test.entities.PersonDAO; @@ -51,6 +54,18 @@ @Rule public final TopiaDatabase db = new TopiaDatabase(); + protected TopiaContext context; + + protected PersonDAO dao; + + @Before + public void setup() throws TopiaException { + + context = db.beginTransaction(); + + dao = TopiaTestDAOHelper.getPersonDAO(context); + } + /** * Test de creer une entité et de verifier qu'elle est * présente dans la persistence au sein de la transaction. @@ -60,30 +75,128 @@ @Test public void testCreateAndFindInTransaction() throws Exception { - TopiaContext context = db.beginTransaction(); - - PersonDAO personDAO = TopiaTestDAOHelper.getPersonDAO(context); - // appel 1 find all - Person person = personDAO.create(Person.PROPERTY_NAME, "toto"); - List<Person> allPerson = personDAO.findAll(); + Person person = createPerson("toto"); + List<Person> allPerson = dao.findAll(); Assert.assertEquals(1, allPerson.size()); context.commitTransaction(); // recherce la personne créée dans la même transaction - Person person2 = personDAO.create(Person.PROPERTY_NAME, "titi"); - allPerson = personDAO.findAll(); + Person person2 = createPerson("titi"); + allPerson = dao.findAll(); Assert.assertEquals(2, allPerson.size()); Assert.assertThat(allPerson, JUnitMatchers.hasItem(person2)); context.rollbackTransaction(); // meme test apres roolback - Person person3 = personDAO.create(Person.PROPERTY_NAME, "tata"); - allPerson = personDAO.findAll(); + Person person3 = createPerson("tata"); + allPerson = dao.findAll(); Assert.assertEquals(2, allPerson.size()); Assert.assertThat(allPerson, JUnitMatchers.hasItem(person3)); context.commitTransaction(); } + + @Test + public void findAllLazyByQuery() throws TopiaException { + + Assert.assertEquals(dao.count(), 0); + + createPersons(101); + + Iterable<Person> allByLazy = dao.findAllLazyByQuery( + 100, + "FROM " + dao.getTopiaEntityEnum().getImplementationFQN()); + + List<Person> actual = Lists.newArrayList(); + + for (Person person : allByLazy) { + actual.add(person); + } + Assert.assertEquals(dao.count(), actual.size()); + + allByLazy = dao.findAllLazyByQuery( + 54, + "FROM " + dao.getTopiaEntityEnum().getImplementationFQN()); + + actual = Lists.newArrayList(); + + for (Person person : allByLazy) { + actual.add(person); + } + Assert.assertEquals(dao.count(), actual.size()); + + allByLazy = dao.findAllLazyByQuery( + 49, + "FROM " + dao.getTopiaEntityEnum().getImplementationFQN()); + + actual = Lists.newArrayList(); + + for (Person person : allByLazy) { + actual.add(person); + } + Assert.assertEquals(dao.count(), actual.size()); + + allByLazy = dao.findAllLazyByQuery( + 101, + "FROM " + dao.getTopiaEntityEnum().getImplementationFQN()); + + actual = Lists.newArrayList(); + + for (Person person : allByLazy) { + actual.add(person); + } + Assert.assertEquals(dao.count(), actual.size()); + + allByLazy = dao.findAllLazyByQuery( + 102, + "FROM " + dao.getTopiaEntityEnum().getImplementationFQN()); + + actual = Lists.newArrayList(); + + for (Person person : allByLazy) { + actual.add(person); + } + Assert.assertEquals(dao.count(), actual.size()); + } + + @Test + public void iterateOnTopiaDAO() throws TopiaException { + + createPersons(1999); + + List<Person> excepted = dao.findAll(); + + List<Person> actual = Lists.newArrayList(); + + for (Person person : dao) { + Assert.assertThat(excepted, JUnitMatchers.hasItem(person)); + actual.add(person); + } + Assert.assertEquals(excepted.size(), actual.size()); + + dao.setBatchSize(54); + + actual = Lists.newArrayList(); + + for (Person person : dao) { + Assert.assertThat(excepted, JUnitMatchers.hasItem(person)); + actual.add(person); + } + Assert.assertEquals(excepted.size(), actual.size()); + + } + + protected void createPersons(int number) throws TopiaException { + for (int i = 0; i < number; i++) { + createPerson("toto" + i); + } + + context.commitTransaction(); + } + + protected Person createPerson(String name) throws TopiaException { + return dao.create(Person.PROPERTY_NAME, name); + } }