- 總筆數
- 依每頁顯示筆數計算總頁數
- 計算要顯示頁數第一筆資料的offset
如果我們仔細分析,會發現事實上取得資料總筆數和撈取某一頁所需的資料,這兩者的SQL statement在where condition應是一模一樣的,根據這一點我們可以design出paging專用的DAO。
以下會包含兩大部分,一是generic paging DAO的設計,二是test case。
Generic Paging DAO:
- GenericPagingDao<Entity extends BaseDto, Condition>
- AbstractGenericPagingDaoImpl<Entity extends BaseDto, Condition>
- HibernateGenericPagingDao<Entity extends BaseDto, Condition>
- AbstractHibernateGenericPagingDaoImpl<Entity extends BaseDto, Condition>
- BaseDto
GenericPagingDao<Entity extends BaseDto, Condition>
package com.gss.gmo.cao.data;
import java.util.List;
import com.gss.gmo.cao.base.BaseDto;
/**
* @author linus_chien
*
* @param <Entity>
* @param <Condition>
*/
public interface GenericPagingDao<Entity extends BaseDto, Condition> {
Class<Entity> getEntityClass();
List<Entity> findPagingResults(Condition condition, int firstResult, int maxResults);
<ResultClass> List<ResultClass> findPagingTransformResults(Condition condition, int firstResult, int maxResults, Class<ResultClass> resultClass);
long getRowCount(Condition condition);
}
import java.util.List;
import com.gss.gmo.cao.base.BaseDto;
/**
* @author linus_chien
*
* @param <Entity>
* @param <Condition>
*/
public interface GenericPagingDao<Entity extends BaseDto, Condition> {
Class<Entity> getEntityClass();
List<Entity> findPagingResults(Condition condition, int firstResult, int maxResults);
<ResultClass> List<ResultClass> findPagingTransformResults(Condition condition, int firstResult, int maxResults, Class<ResultClass> resultClass);
long getRowCount(Condition condition);
}
AbstractGenericPagingDaoImpl<Entity extends BaseDto, Condition>
package com.gss.gmo.cao.data.impl;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import com.gss.gmo.cao.base.BaseDto;
import com.gss.gmo.cao.data.GenericPagingDao;
/**
* @author linus_chien
*
* @param <Entity>
* @param <Condition>
*/
public abstract class AbstractGenericPagingDaoImpl<Entity extends BaseDto, Condition> implements GenericPagingDao<Entity, Condition> {
/**
* Class of entity.
*/
private final Class<Entity> entityClass;
/**
* Init entityClass by generic type.
*/
@SuppressWarnings("unchecked")
public AbstractGenericPagingDaoImpl() {
Type t = getClass().getGenericSuperclass();
ParameterizedType pt = (ParameterizedType) t;
entityClass = (Class<Entity>) pt.getActualTypeArguments()[0];
}
@Override
public final Class<Entity> getEntityClass() {
return entityClass;
}
}
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import com.gss.gmo.cao.base.BaseDto;
import com.gss.gmo.cao.data.GenericPagingDao;
/**
* @author linus_chien
*
* @param <Entity>
* @param <Condition>
*/
public abstract class AbstractGenericPagingDaoImpl<Entity extends BaseDto, Condition> implements GenericPagingDao<Entity, Condition> {
/**
* Class of entity.
*/
private final Class<Entity> entityClass;
/**
* Init entityClass by generic type.
*/
@SuppressWarnings("unchecked")
public AbstractGenericPagingDaoImpl() {
Type t = getClass().getGenericSuperclass();
ParameterizedType pt = (ParameterizedType) t;
entityClass = (Class<Entity>) pt.getActualTypeArguments()[0];
}
@Override
public final Class<Entity> getEntityClass() {
return entityClass;
}
}
HibernateGenericPagingDao<Entity extends BaseDto, Condition>
package com.gss.gmo.cao.hibernate;
import org.hibernate.SessionFactory;
import com.gss.gmo.cao.base.BaseDto;
import com.gss.gmo.cao.data.GenericPagingDao;
/**
* @author linus_chien
*
* @param <Entity>
* @param <Condition>
*/
public interface HibernateGenericPagingDao<Entity extends BaseDto, Condition> extends GenericPagingDao<Entity, Condition> {
/**
* @param sessionFactory
*/
void setSessionFactory(SessionFactory sessionFactory);
}
import org.hibernate.SessionFactory;
import com.gss.gmo.cao.base.BaseDto;
import com.gss.gmo.cao.data.GenericPagingDao;
/**
* @author linus_chien
*
* @param <Entity>
* @param <Condition>
*/
public interface HibernateGenericPagingDao<Entity extends BaseDto, Condition> extends GenericPagingDao<Entity, Condition> {
/**
* @param sessionFactory
*/
void setSessionFactory(SessionFactory sessionFactory);
}
AbstractHibernateGenericPagingDaoImpl<Entity extends BaseDto, Condition>
package com.gss.gmo.cao.hibernate.impl;
import java.util.List;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Projections;
import org.hibernate.transform.AliasToBeanResultTransformer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Required;
import com.gss.gmo.cao.base.BaseDto;
import com.gss.gmo.cao.data.impl.AbstractGenericPagingDaoImpl;
import com.gss.gmo.cao.hibernate.HibernateGenericPagingDao;
/**
* @author linus_chien
*
* @param <Entity>
* @param <Condition>
*/
public abstract class AbstractHibernateGenericPagingDaoImpl<Entity extends BaseDto, Condition> extends AbstractGenericPagingDaoImpl<Entity, Condition>
implements HibernateGenericPagingDao<Entity, Condition> {
/**
* SessionFactory instance.
*/
private SessionFactory sessionFactory;
@Autowired
@Required
@Override
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
/**
* @return Current session.
*/
protected Session getCurrentSession() {
return sessionFactory.getCurrentSession();
}
@Override
public List<Entity> findPagingResults(final Condition condition, final int firstResult, final int maxResults) {
return findPagingTransformResults(condition, firstResult, maxResults, getEntityClass());
}
@SuppressWarnings("unchecked")
@Override
public <ResultClass> List<ResultClass> findPagingTransformResults(Condition condition, int firstResult, int maxResults, Class<ResultClass> resultClass) {
Criteria criteria = getCurrentSession().createCriteria(getEntityClass(), getEntityClass().getSimpleName().toLowerCase());
initCriteria(condition, criteria);
exclusiveForPagingResults(condition, criteria);
criteria.setFirstResult(firstResult);
criteria.setMaxResults(maxResults);
if (getEntityClass() != resultClass) {
criteria.setResultTransformer(new AliasToBeanResultTransformer(resultClass));
}
return criteria.list();
}
@Override
public long getRowCount(final Condition condition) {
Criteria criteria = getCurrentSession().createCriteria(getEntityClass());
initCriteria(condition, criteria);
criteria.setProjection(Projections.rowCount());
return (Long) criteria.uniqueResult();
}
/**
* Setup criteria by generic Condition type.
*
* @param condition
* @param criteria
*/
protected abstract void initCriteria(Condition condition, Criteria criteria);
/**
* Execute this method only for findPagingResults. Do nothing by default.
*
* @param condition
* @param criteria
*/
protected void exclusiveForPagingResults(Condition condition, Criteria criteria) {
}
}
import java.util.List;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Projections;
import org.hibernate.transform.AliasToBeanResultTransformer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Required;
import com.gss.gmo.cao.base.BaseDto;
import com.gss.gmo.cao.data.impl.AbstractGenericPagingDaoImpl;
import com.gss.gmo.cao.hibernate.HibernateGenericPagingDao;
/**
* @author linus_chien
*
* @param <Entity>
* @param <Condition>
*/
public abstract class AbstractHibernateGenericPagingDaoImpl<Entity extends BaseDto, Condition> extends AbstractGenericPagingDaoImpl<Entity, Condition>
implements HibernateGenericPagingDao<Entity, Condition> {
/**
* SessionFactory instance.
*/
private SessionFactory sessionFactory;
@Autowired
@Required
@Override
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
/**
* @return Current session.
*/
protected Session getCurrentSession() {
return sessionFactory.getCurrentSession();
}
@Override
public List<Entity> findPagingResults(final Condition condition, final int firstResult, final int maxResults) {
return findPagingTransformResults(condition, firstResult, maxResults, getEntityClass());
}
@SuppressWarnings("unchecked")
@Override
public <ResultClass> List<ResultClass> findPagingTransformResults(Condition condition, int firstResult, int maxResults, Class<ResultClass> resultClass) {
Criteria criteria = getCurrentSession().createCriteria(getEntityClass(), getEntityClass().getSimpleName().toLowerCase());
initCriteria(condition, criteria);
exclusiveForPagingResults(condition, criteria);
criteria.setFirstResult(firstResult);
criteria.setMaxResults(maxResults);
if (getEntityClass() != resultClass) {
criteria.setResultTransformer(new AliasToBeanResultTransformer(resultClass));
}
return criteria.list();
}
@Override
public long getRowCount(final Condition condition) {
Criteria criteria = getCurrentSession().createCriteria(getEntityClass());
initCriteria(condition, criteria);
criteria.setProjection(Projections.rowCount());
return (Long) criteria.uniqueResult();
}
/**
* Setup criteria by generic Condition type.
*
* @param condition
* @param criteria
*/
protected abstract void initCriteria(Condition condition, Criteria criteria);
/**
* Execute this method only for findPagingResults. Do nothing by default.
*
* @param condition
* @param criteria
*/
protected void exclusiveForPagingResults(Condition condition, Criteria criteria) {
}
}
BaseDto
package com.gss.gmo.cao.base;
import java.io.Serializable;
/**
* Force DTO to implement equals(), hashCode(), toString().
*
* @author linus_chien
*
*/
public abstract class BaseDto implements Serializable {
private static final long serialVersionUID = 1L;
@Override
public abstract int hashCode();
@Override
public abstract boolean equals(Object obj);
@Override
public abstract String toString();
}
Test Case:
import java.io.Serializable;
/**
* Force DTO to implement equals(), hashCode(), toString().
*
* @author linus_chien
*
*/
public abstract class BaseDto implements Serializable {
private static final long serialVersionUID = 1L;
@Override
public abstract int hashCode();
@Override
public abstract boolean equals(Object obj);
@Override
public abstract String toString();
}
- Person
- PersonPagingDao
- PersonPagingDaoImpl
- AbstractHibernateGenericPagingDaoImplTest
- AbstractHibernateGenericPagingDaoImplTest-context.xml
Person
package com.gss.gmo.cao.hibernate.impl;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.hibernate.annotations.Filter;
import org.hibernate.annotations.Type;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import com.gss.gmo.cao.base.BaseDto;
import com.gss.gmo.cao.hibernate.audit.AuditInfo;
/**
* @author linus_chien
*
*/
@Getter
@Setter
@EqualsAndHashCode(callSuper = false, of = { "id" })
@ToString
@Entity
@Filter(name = "personFilter", condition = "name like :name and address is null")
public class Person extends BaseDto {
private static final long serialVersionUID = 1L;
@Id
private String id;
@Column
private String name;
@Column
private String address;
@Embedded
private AuditInfo auditInfo;
@Type(type = "listType")
private List<String> emails;
}
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.hibernate.annotations.Filter;
import org.hibernate.annotations.Type;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import com.gss.gmo.cao.base.BaseDto;
import com.gss.gmo.cao.hibernate.audit.AuditInfo;
/**
* @author linus_chien
*
*/
@Getter
@Setter
@EqualsAndHashCode(callSuper = false, of = { "id" })
@ToString
@Entity
@Filter(name = "personFilter", condition = "name like :name and address is null")
public class Person extends BaseDto {
private static final long serialVersionUID = 1L;
@Id
private String id;
@Column
private String name;
@Column
private String address;
@Embedded
private AuditInfo auditInfo;
@Type(type = "listType")
private List<String> emails;
}
PersonPagingDao
package com.gss.gmo.cao.hibernate;
import com.gss.gmo.cao.hibernate.impl.Person;
public interface PersonPagingDao extends HibernateGenericPagingDao<Person, String> {
}
import com.gss.gmo.cao.hibernate.impl.Person;
public interface PersonPagingDao extends HibernateGenericPagingDao<Person, String> {
}
PersonPagingDaoImpl
package com.gss.gmo.cao.hibernate.impl;
import org.hibernate.Criteria;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Restrictions;
import org.springframework.stereotype.Component;
import com.gss.gmo.cao.hibernate.PersonPagingDao;
@Component
public class PersonPagingDaoImpl extends AbstractHibernateGenericPagingDaoImpl<Person, String> implements PersonPagingDao {
@Override
protected void initCriteria(String condition, Criteria criteria) {
criteria.add(Restrictions.eq("name", condition));
}
@Override
protected void exclusiveForPagingResults(String condition, Criteria criteria) {
criteria.addOrder(Order.desc("name"));
}
}
import org.hibernate.Criteria;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Restrictions;
import org.springframework.stereotype.Component;
import com.gss.gmo.cao.hibernate.PersonPagingDao;
@Component
public class PersonPagingDaoImpl extends AbstractHibernateGenericPagingDaoImpl<Person, String> implements PersonPagingDao {
@Override
protected void initCriteria(String condition, Criteria criteria) {
criteria.add(Restrictions.eq("name", condition));
}
@Override
protected void exclusiveForPagingResults(String condition, Criteria criteria) {
criteria.addOrder(Order.desc("name"));
}
}
AbstractHibernateGenericPagingDaoImplTest
package com.gss.gmo.cao.hibernate.impl;
import static org.junit.Assert.assertEquals;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.TransactionConfiguration;
import org.springframework.transaction.annotation.Transactional;
import com.gss.gmo.cao.hibernate.PersonDao;
import com.gss.gmo.cao.hibernate.PersonPagingDao;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@TransactionConfiguration(defaultRollback = false)
@Transactional
public class AbstractHibernateGenericPagingDaoImplTest {
@Autowired
private PersonDao personDao;
@Autowired
private PersonPagingDao personPagingDao;
@Test
public void testFindPagingResults() {
Person person = new Person();
person.setId("A");
person.setName("Linus");
personDao.save(person);
person = new Person();
person.setId("B");
person.setName("Linus");
personDao.save(person);
person = new Person();
person.setId("C");
person.setName("Linus");
personDao.save(person);
List<Person> persons = personPagingDao.findPagingResults("Linus", 1, 2);
assertEquals(2, persons.size());
persons = personPagingDao.findPagingResults("A", 1, 2);
assertEquals(0, persons.size());
}
@Test
public void testGetRowCount() {
assertEquals(3, personPagingDao.getRowCount("Linus"));
assertEquals(0, personPagingDao.getRowCount("A"));
}
@Test
public void testFindPagingTransformResults() {
List<PersonWrapper> personWrappers = personPagingDao.findPagingTransformResults("Linus", 1, 2, PersonWrapper.class);
assertEquals(2, personWrappers.size());
for (PersonWrapper personWrapper : personWrappers) {
assertEquals("Linus", personWrapper.getPerson().getName());
}
}
}
import static org.junit.Assert.assertEquals;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.TransactionConfiguration;
import org.springframework.transaction.annotation.Transactional;
import com.gss.gmo.cao.hibernate.PersonDao;
import com.gss.gmo.cao.hibernate.PersonPagingDao;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@TransactionConfiguration(defaultRollback = false)
@Transactional
public class AbstractHibernateGenericPagingDaoImplTest {
@Autowired
private PersonDao personDao;
@Autowired
private PersonPagingDao personPagingDao;
@Test
public void testFindPagingResults() {
Person person = new Person();
person.setId("A");
person.setName("Linus");
personDao.save(person);
person = new Person();
person.setId("B");
person.setName("Linus");
personDao.save(person);
person = new Person();
person.setId("C");
person.setName("Linus");
personDao.save(person);
List<Person> persons = personPagingDao.findPagingResults("Linus", 1, 2);
assertEquals(2, persons.size());
persons = personPagingDao.findPagingResults("A", 1, 2);
assertEquals(0, persons.size());
}
@Test
public void testGetRowCount() {
assertEquals(3, personPagingDao.getRowCount("Linus"));
assertEquals(0, personPagingDao.getRowCount("A"));
}
@Test
public void testFindPagingTransformResults() {
List<PersonWrapper> personWrappers = personPagingDao.findPagingTransformResults("Linus", 1, 2, PersonWrapper.class);
assertEquals(2, personWrappers.size());
for (PersonWrapper personWrapper : personWrappers) {
assertEquals("Linus", personWrapper.getPerson().getName());
}
}
}
AbstractHibernateGenericPagingDaoImplTest-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config />
<context:component-scan base-package="com.gss.gmo.cao.hibernate.impl" />
<tx:annotation-driven transaction-manager="transactionManager" />
<!-- Datasource -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="org.hsqldb.jdbcDriver">
</property>
<property name="url" value="jdbc:hsqldb:mem:ExampleTest">
</property>
<property name="username" value="sa">
</property>
<property name="password" value="">
</property>
</bean>
<!-- Hibernate session factory -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource">
<ref bean="dataSource" />
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop>
<prop key="hibernate.hbm2ddl.auto">create</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
</props>
</property>
<property name="packagesToScan" value="com.gss.gmo.cao.hibernate.impl" />
<property name="annotatedPackages" value="com.gss.gmo.cao.hibernate.impl" />
</bean>
<bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
</beans>
Test case裡面的PersonDao只是一般CRUD的DAO,這裡就不浪費版面多做贅述。
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config />
<context:component-scan base-package="com.gss.gmo.cao.hibernate.impl" />
<tx:annotation-driven transaction-manager="transactionManager" />
<!-- Datasource -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="org.hsqldb.jdbcDriver">
</property>
<property name="url" value="jdbc:hsqldb:mem:ExampleTest">
</property>
<property name="username" value="sa">
</property>
<property name="password" value="">
</property>
</bean>
<!-- Hibernate session factory -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource">
<ref bean="dataSource" />
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop>
<prop key="hibernate.hbm2ddl.auto">create</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
</props>
</property>
<property name="packagesToScan" value="com.gss.gmo.cao.hibernate.impl" />
<property name="annotatedPackages" value="com.gss.gmo.cao.hibernate.impl" />
</bean>
<bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
</beans>