浅析FreeMaker + Ajax分页组件
昨天学习了下FreeMaker,体会到了它的强大用途后,我想到了可以用它来实现分页组件,为了加大难度以及加强用户体验,我决定实现Ajax分页组件,昨晚构思了下,把思路理清楚了,今天下班一回家就开始完成我的构想,结果成功咯,我感觉效果还不错,虽然代码还需要继续优化。
首先我说说我起初的想法,刚开始我有了利用FreeMaker写分页组件时,也是先网上Google了一番,发现几乎都是利用模版里定义宏,然后用ftl替代jsp,在ftl里动态导入分页自定义宏所在模版ftl,那时,我设想能不能在jsp里导入ftl,因为毕竟实际项目中前端试图层我们用的绝大多数还是JSP,把项目里需要用到分页组件的jsp都替换成ftl,显然不现实。可惜,经过我实验,jsp里导入ftl方案不可行,因为我们知道,ftl需要经过FreeMaker.servle拦截处理后才能被解析为html,直接导入到jsp是无法被解析的。但是我又不想全部是ftl实现,因为那样实现后,应用不广泛,所以我决定另辟蹊径,不能局限在别人的思维里。既然ftl需要经过FreeMaker.servle拦截处理后才能解析,所以我们后台Action执行完后需要跳转到ftl模版,幸好struts2的导航结果类型内置就包含了freemaker,所以freemaker解析不是问题了。然后就是考虑FreeMaker模版数据来源问题,通过FreeMaker的官方开发指南,我了解到,我们只需将数据存入request作用域,然后ftl模版里就能通过插入值${}一种类似于EL表达式的方式获取数据,所以数据这块也不是问题,由于是Ajax分页,所以action设置导航到ftl后,ftl被解析为html,然后解析后得到的html内容被当作结果返回给浏览器,然后我们回调函数里把返回的html内容赋值给页面元素的innerHTML,即可实现分页组件的ajax方式动态展现。上面就是我整个分页组件的实现思路。下面我一步步展示代码实现过程:
首先MyEclipse里新建个web Project,我取名为freemakerDemo,然后就是导入SSH所需的jar,下面我导入的所有jar截图:

其中itext-1.1.2.jar和poi-2.5.1.jar不是必需的,因为我有空准备封装个报表数据接口,简化数据导入导出PDF、Excel、word操作,还在计划中。特此说明。
然后就是web.xml里struts,hibernate,spring的一些相关配置,不详说了,直接贴代码了:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" >
<!-- spring注册 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<!--
spring配置文件放在WEB-INF目录下
<param-value>/WEB_INF/app*.xml</param-value>
-->
<!-- spring配置文件放在src自定义目录下 -->
<param-value>
classpath:/com/yida/config/spring/app*.xml
</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<!-- struts2注册 -->
<filter>
<filter-name>struts2</filter-name>
<filter-class>
org.apache.struts2.dispatcher.FilterDispatcher
</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>*.do</url-pattern>
<dispatcher>FORWARD</dispatcher>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>*.jsp</url-pattern>
</filter-mapping>
<filter>
<filter-name>struts-cleanup</filter-name>
<filter-class>
org.apache.struts2.dispatcher.ActionContextCleanUp
</filter-class>
</filter>
<filter-mapping>
<filter-name>struts-cleanup</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 设置请求参数的字符编码 -->
<filter>
<filter-name>encoding</filter-name>
<filter-class>
org.springframework.web.filter.CharacterEncodingFilter
</filter-class>
<init-param>
<param-name>encode</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>freemarker</servlet-name>
<servlet-class>
freemarker.ext.servlet.FreemarkerServlet
</servlet-class>
<init-param>
<param-name>NoCache</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>ContentType</param-name>
<param-value>text/html</param-value>
</init-param>
<!-- FreeMarker settings: -->
<init-param>
<param-name>default_encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>locale</param-name>
<param-value>zh_CN</param-value>
</init-param>
<init-param>
<param-name>number_format</param-name>
<param-value>0.##########</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>freemarker</servlet-name>
<url-pattern>*.ftl</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>JspSupportServlet</servlet-name>
<servlet-class>
org.apache.struts2.views.JspSupportServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet>
<servlet-name>StudentServletTest</servlet-name>
<servlet-class>
com.yida.servlet.StudentServletTest
</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>StudentServletTest</servlet-name>
<url-pattern>/StudentServletTest</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>StudentServletTest2</servlet-name>
<servlet-class>
com.yida.servlet.StudentServletTest2
</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>StudentServletTest2</servlet-name>
<url-pattern>/StudentServletTest2</url-pattern>
</servlet-mapping>
<jsp-config>
<taglib>
<taglib-uri>/FreeMaker</taglib-uri>
<taglib-location>/WEB-INF/freemaker.tld</taglib-location>
</taglib>
</jsp-config>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
其中FreeMaker的tld标签库文件注册也不是必须的,因为当初我是设想在jsp中使用FreeMaker标签,所以才在web-inf目录下引入了freemaker.tld文件,实验证明,方案不可行。
然后就是dao底层的一个封装了,因为我以前已经封装过了,所以我一般都是copy过来,哦,我先贴下我的项目组织结构。你们可以按你们自己的喜好进行包的结构组织。我的仅供参考。




项目组织结构大概就是那样,因为我电脑显示器尺寸小,所以我截了多张图。
下面贴下通用dao的代码封装吧:
package com.yida.common;
import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.HibernateTemplate;
import com.yida.utils.PageData;
/**
* 通用dao封装
* @author Lanxiaowei
* @createTime 2011-03-11
*/
public class GerneralDaoImpl<K,T> implements GerneralDao<K,T> {
private Class entityClass;
private HibernateTemplate hibernateTemplate;
private JdbcTemplate jdbcTemplate;
public JdbcTemplate getJdbcTemplate() {
return jdbcTemplate;
}
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public Class getEntityClass() {
return entityClass;
}
public void setEntityClass(Class entityClass) {
this.entityClass = entityClass;
}
public HibernateTemplate getHibernateTemplate() {
return hibernateTemplate;
}
public void setHibernateTemplate(HibernateTemplate hibernateTemplate) {
this.hibernateTemplate = hibernateTemplate;
}
public GerneralDaoImpl() {
this.entityClass = (Class)((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments()[1];
}
/**
* 根据Id加载实体
*
* @param id
* @return
*/
public T get(K id) {
return (T) getHibernateTemplate().get(entityClass, (Serializable) id);
}
/**
* 根据Id加载实体
* @param id
* @return
*/
public T load(K id){
return (T) getHibernateTemplate().load(entityClass, (Serializable) id);
}
/**
* 更新实体
*
* @param entity
*/
public int update(T entity) {
if(null == entity){
return 0;
}
getHibernateTemplate().clear();//清理Session缓存(很关键)
getHibernateTemplate().update(entity);
return 1;
}
/**
* 批量更新实体
* @param entities 要更新的对象集合
* @return 返回受影响的行数
*/
public int bulkUpdate(List<T> entities){
int count = 0; //统计受影响的行数
if(null == entities || entities.size() == 0){
return count;
}
for(T t : entities){
if(count % 20 == 0){
getHibernateTemplate().flush(); //清理Hibernate缓存,防止内存溢出
}
getHibernateTemplate().update(t);
count++;
}
return count;
}
/**
* 更新实体Merge方式
*
* @param entity
*/
public void merge(T entity) {
getHibernateTemplate().merge(entity);
}
/**
* 删除指定实体
*/
public int del(T entity){
if(null == entity){
return 0;
}
getHibernateTemplate().delete(entity);
return 1;
}
/**
* 根据Id删除某个实体
*
* @param id
*/
public int delete(K id) {
Object object = get(id);
if(null == object){
return 0;
}
getHibernateTemplate().clear();//清理Session缓存(很关键)
getHibernateTemplate().delete(object);
return 1;
}
/**
* 保存实体
*
* @param entity
*/
public Long save(T entity) {
return (Long) getHibernateTemplate().save(entity);
}
/**
* 批量保存实体
* @param entites 要保存的实体对象集合
* @return 返回保存的对象主键id集合
*/
public List<K> bulkSave(List<T> entites){
List<K> list = new ArrayList<K>();
int index = 0;
for (T t : entites) {
if(index % 20 == 0){
getHibernateTemplate().flush();//清理Hibernate缓存
}
K id = (K)getHibernateTemplate().save(t);
list.add(id);
index++;
}
return list;
}
/**
* 保存或更新实体
*
* @param entity
*/
public void saveOrUpdate(T entity) {
getHibernateTemplate().saveOrUpdate(entity);
}
/**
* 查询所有
*
* @return List<T>
*/
public List<T> findAll(Class targetClass) {
return (List<T>) getHibernateTemplate().loadAll(targetClass);
}
/**
* 根据给定hql查询
*/
public List findForHql(String hql) {
return getHibernateTemplate().find(hql);
}
/**
* 根据给定hql查询
*/
public List<T> findByHql(String hql) {
return (List<T>) getHibernateTemplate().find(hql);
}
/**
* 根据给定hql查询(带参)
*/
public List<T> findByHql(String hql, Object[] condition) {
return (List<T>) getHibernateTemplate().find(hql, condition);
}
/**
* 根据给定hql查询(带参)--重载
*/
public List<T> findByHql(String hql, List condition) {
return findByHql(hql,condition.toArray());
}
/**
* hql分页查询
*
* @return
*/
public PageData findForPage(final String hql, final int currentPage,
final int pageSize) {
return findForPage(hql, new Object[]{}, currentPage, pageSize);
}
/**
* hql分页查询(带参)
*
* @return
*/
public PageData findForPage(final String hql, final Object[] condition,
final int currentPage, final int pageSize) {
int total = getTotal(getTotalHql(hql), condition).intValue();
List list = getHibernateTemplate().executeFind(new HibernateCallback() {
public Object doInHibernate(Session session)
throws HibernateException, SQLException {
Query query = session.createQuery(hql);
query = setParameters(query, condition);
return query.setFirstResult((currentPage - 1) * pageSize)
.setMaxResults(pageSize).list();
}
});
return new PageData(list, total, currentPage, pageSize);
}
/**
* hql分页查询(带参)重载
*
* @return
*/
public PageData findForPage(final String hql, final List condition,
final int currentPage, final int pageSize){
return findForPage(hql,condition.toArray(),currentPage,pageSize);
}
/**
* hql批量删除更新(hql类名不能有别名� where xx in(?,?,?,?))
*
* @param hql
* ,array
*/
public int batchUpdateOrDelete(final String hql, final Object[] condition) {
return (Integer)getHibernateTemplate().execute(new HibernateCallback() {
public Object doInHibernate(Session session)
throws HibernateException, SQLException {
Query query = session.createQuery(hql);
query = setParameters(query, condition);
return query.executeUpdate();
}
});
}
/**
* hql批量删除更新(重载1)
*
* @param hql
* map
*/
public int batchUpdateOrDelete(final String hql, final Map condition) {
return batchUpdateOrDelete(hql, condition.values().toArray());
}
/**
* hql批量删除更新(重载2)
*
* @param hql
* ,List
*/
public int batchUpdateOrDelete(final String hql, final List condition) {
return batchUpdateOrDelete(hql, condition.toArray());
}
/**
* hql批量删除更新
*
* @param hql
* ,array
*/
public int bulkUpdateOrDelete(final String hql, final Object[] condition) {
return getHibernateTemplate().bulkUpdate(hql, condition);
}
/**
* hql批量删除更新
*
* @param hql
* ,array
*/
public int bulkUpdateOrDelete(final String hql, final Map condition) {
return bulkUpdateOrDelete(hql, condition.values().toArray());
}
/**
* hql批量删除更新
*
* @param hql
* ,array
*/
public int bulkUpdateOrDelete(final String hql, final List condition) {
return bulkUpdateOrDelete(hql, condition.toArray());
}
/**
* hql批量删除更新(hql类名不能有别名 wher id in(:condition)) 推荐使用此方法
*
* @param hql
* ,array
*/
public int bulkUpdateOrDeleteNew(final String hql, final Object[] condition) {
return (Integer)getHibernateTemplate().execute(new HibernateCallback() {
public Object doInHibernate(Session session)
throws HibernateException, SQLException {
Query query = session.createQuery(hql);
if (null != condition && condition.length > 0) {
query.setParameterList("condition", condition);
}
return query.executeUpdate();
}
});
}
/**
* hql批量删除更新(hql类名不能有别名 wher id in(:condition))
*
* @param hql
* ,array
*/
public int bulkUpdateOrDeleteNew(final String hql, final Map condition) {
return bulkUpdateOrDeleteNew(hql,condition.values().toArray());
}
/**
* hql批量删除更新(hql类名不能有别名 wher id in(:condition))
*
* @param hql
* ,array
*/
public int bulkUpdateOrDeleteNew(final String hql, final List condition) {
return bulkUpdateOrDeleteNew(hql,condition.toArray());
}
/**
* 根据传入id集合批量加载对象集合
* hql:wher id in(:idList)
*/
public List bulkFindByIdList(final String hql,final List idList){
return bulkFindByIdList(hql,idList.toArray());
}
/**
* 根据传入id集合批量加载对象集合(重载)
* hql:wher id in(:idList)
*/
public List bulkFindByIdList(final String hql,final Object[] idList){
return getHibernateTemplate().executeFind(new HibernateCallback() {
public Object doInHibernate(Session session)
throws HibernateException, SQLException {
Query query = session.createQuery(hql);
if (null != idList && idList.length > 0) {
query.setParameterList("idList", idList);
}
return query.list();
}
});
}
/**
* 根据给定Id判定某个实体是否存在
*
* @param id
* @return
*/
public boolean has(K id) {
if (id == null || id.equals("")) {
return false;
}
return (get(id) != null);
}
/**
* 查询数据总条数
*
* @param hql
* @param condition
* @return
*/
public Long getTotal(String hql) {
return (Long) getHibernateTemplate().find(hql).get(0);
}
/**
* 查询数据总条数(带参)
*
* @param hql
* @param condition
* @return
*/
public Long getTotal(String hql, Object[] condition) {
return (Long) getHibernateTemplate().find(hql, condition).get(0);
}
/**
* 设置参数
*
* @param query
* @param condition
* @return Query
*/
private Query setParameters(Query query, Object[] condition) {
if (null != condition && condition.length != 0) {
for (int i = 0; i < condition.length; i++) {
query.setParameter(i, condition[i]);
}
}
return query;
}
/************************************** 华丽的分割线 begin ********************************************/
/************************************* JDBC原生态sql封装封装 ******************************************/
/************************************** 华丽的分割线 end *********************************************/
/**
* 适用于增删改操作(不推荐使用此方法,因为此方法会破坏Hibernate的二级缓存体系)
*
* @param sql
* 例如 delete from tableName where id = ?
* @param condition
* @return 是否执行成功 true/false
*/
public boolean excuteUpdate(String sql, Object[] condition) {
return getJdbcTemplate().update(sql, condition) > 0;
}
/**
* 返回单列值(适用于查询总数、平均数、最大值、最小值)
*
* @param sql
* 例如:select count(*) from user where age > ?
* @param condition
* @return
*/
public int queryForInt(String sql, Object[] condition) {
return getJdbcTemplate().queryForInt(sql, condition);
}
/**
* 返回单个对象(适用于主键查询)
*
* @param sql
* 例如: select * from user where id = ?
* @param condition
* @return
*/
public T queryForObject(String sql, Object[] condition) {
return (T) getJdbcTemplate()
.queryForObject(sql, condition, entityClass);
}
/**
* 返回对象集合
*
* @param sql
* @param condition
* @return
*/
public List<T> queryForList(String sql, Object[] condition) {
return (List<T>) getJdbcTemplate().queryForList(sql, condition,
entityClass);
}
/**
* JDBC分页查询
*
* @param sql
* 初始sql
* @param condition
* 查询参数
* @param currentPage
* 当前第几页
* @param pageSize
* 每页大小
* @return
*/
public PageData queryForPage(String sql, Object[] condition,
int currentPage, int pageSize, String dataBaseName) {
// 总记录数
int total = queryForInt(getTotalSql(sql), null);
if (total <= 0) {
return null;
}
String pageSql = getPageSql(sql, currentPage, pageSize, dataBaseName);
List list = getJdbcTemplate().queryForList(pageSql, condition);
return new PageData(list, total, currentPage, pageSize);
}
/**
* 获取分页sql
*
* @param sql
* 初始sql
* @param currentPage
* 当前页�
* @param pageSize
* 每页大小
* @param dataBaseName
* 数据库类型�:sqlserver/mysql/orcale
* @return sql
*/
private String getPageSql(String sql, int currentPage, int pageSize,
String dataBaseName) {
if (null == sql || sql.equals("")) {
return "";
}
StringBuffer stringBuffer = new StringBuffer();
if (currentPage <= 0) {
currentPage = 1;
}
if (pageSize <= 0) {
pageSize = 10;
}
if (dataBaseName.toLowerCase().equals("sqlserver")) {
stringBuffer
.append("select top ")
.append(pageSize)
.append(" * from (")
.append("select top ")
.append(currentPage * pageSize)
.append(" * from (")
.append(sql)
.append(") as a order by a.id desc) as b order by b.id asc");
} else if (dataBaseName.toLowerCase().equals("mysql")) {
stringBuffer.append("select * from (").append(sql).append(
") limit ").append((currentPage - 1) * pageSize)
.append(",").append(pageSize);
} else if (dataBaseName.toLowerCase().equals("orcale")) {
stringBuffer.append("select * from(").append(
"select a.*,rownum r from(").append(sql).append(
") as a where r <=").append(currentPage * pageSize).append(
") where r >").append((currentPage - 1) * pageSize);
}
return stringBuffer.toString();
}
/**
* 获取查询总记录数sql
*
* @param hql
* 初始sql
* @return
*/
private String getTotalSql(String sql) {
return "select count(*) from (" + removeByKey(sql,"order by") + ")";
}
/**
* 获取查询总记录数的hql
*
* @param hql
* 初始hql
*/
private String getTotalHql(String hql) {
String s = removeSubSelect(hql);
s = removeByKey(s,"order by");
int from = s.indexOf("from");
return "select count(*) " + s.substring(from);
}
/**
* 移除key指定的子句,如order by,group by
*/
private String removeByKey(String hql,String key){
int index = hql.indexOf(key.toLowerCase());
if(index == -1){
return hql;
}
return hql.replace(hql.substring(index),"");
}
/**
* 移除distinct关键字
*/
private String removeDistinct(String hql){
if(hql.indexOf("distinct") == -1){
return hql;
}
return hql.replace("distinct","");
}
/**
* 移除select部分子查询
* @param hql 传入的hql语句
*/
private String removeSubSelect(String hql){
String regex = "[^in\\s]+\\([^)]*([^(^)]*?)\\)";
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(hql);
while(m.find()){
String sub = m.group();
hql = hql.replace(sub, "");
}
return hql;
}
}
然后是通用Service接口代码:
package com.yida.common;
import com.yida.dao.IStudentDao;
import com.yida.utils.SSHFactory;
/**
* @author Lanxiaowei
* @createTime 2011-3-27
*/
public class GerneralServiceImpl implements GerneralService{
protected SSHFactory sshFactory;
public SSHFactory getSshFactory() {
return sshFactory;
}
public void setSshFactory(SSHFactory sshFactory) {
this.sshFactory = sshFactory;
}
public IStudentDao getStudentDao() {
return this.getSshFactory().getBean(IStudentDao.class);
}
}
然后就是SSHFactory工厂工具类代码(提供通用的getBean方法来获取spring里注册的bean):
package com.yida.utils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
/**
* Bean工厂类(使用spring依赖注入实现)
* @author Lanxiaowei
* @createTime Jul 30, 2011 5:03:53 PM
*/
public class SSHFactory implements ApplicationContextAware{
private ApplicationContext ctx;
public void setApplicationContext(ApplicationContext ctx) throws BeansException {
this.ctx = ctx;
}
private ApplicationContext getContext(){
return ctx;
}
/**
* 获取bean实例
* @param 接口的class
* @return 返回接口实现类
*/
public <T>T getBean(Class<T> clazz){
String simpleName = clazz.getSimpleName();
return (T)getContext().getBean(simpleName);
}
}
然后就是通用Action的封装,主要是提供一些常用对象的获取方法,如ActionContext,request,response,session,PageContext
等等,具体看代码:
package com.yida.common;
import java.io.IOException;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.servlet.jsp.PageContext;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.util.StrutsUtil;
import com.yida.utils.SSHFactory;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;
/**
* 当前包下所有Action的工厂类
* @author Lanxiaowei
* @createTime 2011-3-27
*/
public class GerneralActionImpl extends ActionSupport implements GerneralAction{
protected SSHFactory sshFactory;
public SSHFactory getSshFactory() {
return sshFactory;
}
public void setSshFactory(SSHFactory sshFactory) {
this.sshFactory = sshFactory;
}
/**
* 获取Struts上下文
* @return
*/
public ActionContext getActionContext(){
return ActionContext.getContext();
}
/**
* 获取HttpSession对象
* @return Map
*/
@SuppressWarnings("unchecked")
public Map getSession(){
return this.getActionContext().getSession();
}
/**
* 获取HttpSession对象
* @return HttpSession
*/
@SuppressWarnings("unchecked")
public HttpSession getHttpSession(){
return this.getRequest().getSession();
}
/**
* 获取request对象
*/
public HttpServletRequest getRequest(){
return (HttpServletRequest)this.getActionContext().get(ServletActionContext.HTTP_REQUEST);
}
/**
* 获取response对象
*/
public HttpServletResponse getResponse(){
return (HttpServletResponse)this.getActionContext().get(ServletActionContext.HTTP_RESPONSE);
}
/**
* 获取PageContext对象
*/
public PageContext getPageContext(){
return ServletActionContext.getPageContext();
}
/**
* 获取项目的basePath */
public String getBasePath(){
return getRequest().getScheme() + "://"+getRequest().getServerName() +
":"+getRequest().getServerPort() + getRequest().getContextPath() + "/";
}
/**
* 设置页面不缓存并且向客户端输出Json
* @param message 要输出的json字符串
*/
public void setNoCacheAndWriteJson(String message) throws IOException{
getResponse().setCharacterEncoding("UTF-8");
getResponse().setHeader("Pragma","No-cache");
getResponse().setHeader("Cache-Control","no-cache");
getResponse().setDateHeader("Expires", 0);
getResponse().setContentType("application/json");
getResponse().getWriter().write(message);
}
}
上面都是些准备工作,下面我就以学生的增删改查为例子来说明吧,虽然只是为了实现个ajax分页,但是我贯穿展示SSH增删改查等常用操作的实现。如果代码还有不够精炼的,欢迎大家指正!
首先是建实体类和编写hbm配置文件,get set我就省了
package com.yida.entity;
import java.io.Serializable;
import java.util.Date;
/**
* @author Lanxiaowei
* @createTime 2011-11-13 下午09:37:09
*/
public class Student implements Serializable{
/**
* 主键id
*/
private Long id;
/**
* 姓名
*/
private String stuName;
/**
* 性别 1/男 0/女
*/
private String sex;
/**
* 生日
*/
private Date birth;
/**
* 爱好
*/
private String hobby;
/**
* 电话
*/
private String phone;
/**
* 邮箱
*/
private String email;
/**
* 学历
*/
private String degree;
/**
* 联系地址
*/
private String address;
student.hbm.xml内容编辑如下:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"">
<!--
Mapping file autogenerated by MyEclipse - Hibernate Tools
-->
<hibernate-mapping package="entity">
<class name="com.yida.entity.Student" table="student">
<!-- 映射属性 -->
<id name="id" column="id" type="java.lang.Long">
<!--
主键值生成方式
native,identity自增
assigned 编程给定
increment hibernate使用select max(主键列)+1的方式生成
-->
<generator class="identity"></generator>
</id>
<!-- 基本属性 -->
<property name="stuName" column="stuName" type="java.lang.String" length="30" not-null="true"></property>
<property name="sex" column="sex" type="java.lang.String" length="1" not-null="true"></property>
<property name="birth" column="birth" type="java.util.Date" not-null="true"></property>
<property name="hobby" column="hobby" type="java.lang.String" length="80"></property>
<property name="phone" column="phone" type="java.lang.String" length="20"></property>
<property name="degree" column="degree" type="java.lang.String" length="20"></property>
<property name="email" column="email" type="java.lang.String" length="30" not-null="true"></property>
<property name="address" column="address" type="java.lang.String" length="200"></property>
</class>
</hibernate-mapping>
为了简便起见,就没弄什么一对多或者多对多关系,全部是一些基本属性配置,应该没什么大问题,OK,继续!
接着就是编写StudentDao及其实现类了:我只贴实现类,接口的代码就没必要贴了
package com.yida.dao.impl;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import com.yida.action.vo.StudentVO;
import com.yida.common.GerneralDaoImpl;
import com.yida.dao.IStudentDao;
import com.yida.entity.Student;
import com.yida.utils.Hql;
import com.yida.utils.PageData;
import com.yida.utils.Where;
/**
* 学生dao接口实现类
* @author Lanxiaowei
* @createTime 2011-11-13 下午10:53:33
*/
public class StudentDaoImpl extends GerneralDaoImpl<Long,Student> implements IStudentDao {
/**
* 学生分页组合查询
* @param studentVO 查询条件
* @param pageData 分页参数
* @return PageData 一页数据
* @throws ParseException
*/
public PageData findStudents(StudentVO studentVO) throws ParseException{
Hql hql = Hql.start("from Student s");
if(null != studentVO){
if(null != studentVO.getId() && studentVO.getId().intValue() != 0){
hql.addWhere("where", Where.get()
.equal("s.id", studentVO.getId()));
} else{
DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
Date from = null;
Date to = null;
if(null != studentVO.getBirthFrom() && null != studentVO.getBirthTo() && !studentVO.getBirthFrom().equals("") && !studentVO.getBirthTo().equals("")){
from = df.parse(studentVO.getBirthFrom());
to = df.parse(studentVO.getBirthTo());
}
hql.addWhere("where", Where.get()
.like("s.stuName", studentVO.getStuName())
.equal("s.sex", studentVO.getSex())
.gt("s.birth", from)
.lt("s.birth", to)
.like("s.hobby", studentVO.getHobby())
.equal("s.phone", studentVO.getPhone())
.equal("s.email", studentVO.getEmail())
.equal("s.degree", studentVO.getDegree())
.like("s.address", studentVO.getAddress())
);
}
PageData pageData = studentVO.getPageData();
if(null == studentVO.getPageData()){
pageData = new PageData();
}
return findForPage(hql.getSqlstr(), hql.getParams(), pageData.getCurrentPage(), pageData.getPageSize());
}
List<Student> students = findByHql(hql.getSqlstr(), hql.getParams());
return new PageData(students.size(),students);
}
/**
* 根据id集合加载学生集合
* @param idList id集合
* @return List<Student> 学生集合
*/
public List<Student> findStudentByIdList(Long[] idList){
Hql hql = Hql.start("from Student s where s.id in(:idList)");
return (List<Student>)bulkFindByIdList(hql.getSqlstr(), idList);
}
/**
* 批量删除学生
* @param idList 待删除学生id集合
* @return 是否执行成功
*/
public boolean bulkDeleteStudent(Long[] idList){
Hql hql = Hql.start("delete from Student s where s.id in(:condition)");
boolean result = true;
try {
int count = bulkUpdateOrDeleteNew(hql.getSqlstr(),idList);
result = (count == 1);
} catch (Exception e) {
result = false;
} finally{
return result;
}
}
}
然后就是StudentService接口及其实现类的编写:
package com.yida.service.impl;
import java.text.ParseException;
import java.util.List;
import com.yida.action.vo.StudentVO;
import com.yida.common.GerneralServiceImpl;
import com.yida.entity.Student;
import com.yida.service.IStudentService;
import com.yida.utils.PageData;
public class StudentServiceImpl extends GerneralServiceImpl implements IStudentService {
/**
* 学生分页组合查询
* @param studentVO 查询条件
* @param pageData 分页参数
* @return PageData 一页数据
*/
public PageData findStudents(StudentVO studentVO) throws ParseException{
return getStudentDao().findStudents(studentVO);
}
/**
* 根据主键id加载某个特定的学生对象
* @param id 主键id
* @return Student对象
*/
public Student findStudentById(Long id){
return getStudentDao().load(id);
}
/**
* 根据id集合加载学生集合
* @param idList id集合
* @return List<Student> 学生集合
*/
public List<Student> findStudentByIdList(Long[] idList){
return getStudentDao().findStudentByIdList(idList);
}
/**
* 新增学生
* @param student 待添加的学生
* @return 是否执行成功
*/
public boolean addStudent(Student student){
boolean result = true;
try {
getStudentDao().save(student);
} catch (Exception e) {
result = false;
} finally{
return result;
}
}
/**
* 更新学生
* @param student 待编辑的学生
* @return 是否执行成功
*/
public boolean updateStudent(Student student){
boolean result = true;
try {
getStudentDao().update(student);
} catch (Exception e) {
result = false;
} finally{
return result;
}
}
/**
* 删除单个学生
* @param id 待删除的学生id
* @return 是否执行成功
*/
public boolean deleteStudent(Long id){
boolean result = true;
try {
int count = getStudentDao().delete(id);
result = (count == 1);
} catch (Exception e) {
result = false;
} finally{
return result;
}
}
/**
* 批量删除学生
* @param idList 待删除学生id集合
* @return 是否执行成功
*/
public boolean bulkDeleteStudent(Long[] idList){
return getStudentDao().bulkDeleteStudent(idList);
}
}
然后就是StudentAction编写了,由于我是基于Struts2的模型驱动开发的,所以还需要StudentModel,由于不希望model里属性太过于庞大,所以我把一些actionModel的属性都封装到StudentVO里,这样StudentModel里就简化了许多了。
package com.yida.action.model;
import com.yida.action.vo.StudentVO;
import com.yida.common.PageModel;
public class StudentModel extends PageModel{
private StudentVO studentVO;
public StudentVO getStudentVO() {
if(null == studentVO){
studentVO = new StudentVO();
}
return studentVO;
}
public void setStudentVO(StudentVO studentVO) {
this.studentVO = studentVO;
}
}
StudentVO.java代码如下:
package com.yida.action.vo;
import java.util.Date;
import java.util.List;
import com.yida.entity.Student;
import com.yida.utils.PageData;
public class StudentVO {
/**
* 主键id
*/
private Long id;
/**
* 姓名
*/
private String stuName;
/**
* 性别 1/男 0/女
*/
private String sex;
/**
* 生日
*/
private Date birth;
/**
* 爱好
*/
private String hobby;
/**
* 电话
*/
private String phone;
/**
* 邮箱
*/
private String email;
/**
* 学历
*/
private String degree;
/**
* 联系地址
*/
private String address;
/**
* 生日(开始时间)
*/
private String birthFrom;
/**
* 生日(结束时间)
*/
private String birthTo;
/**
* 分页数据
*/
private PageData pageData;
/**
* 批量操作时传递的id字符串,逗号分割
*/
private String idListString;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getStuName() {
return stuName;
}
public void setStuName(String stuName) {
this.stuName = stuName;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Date getBirth() {
return birth;
}
public void setBirth(Date birth) {
this.birth = birth;
}
public String getHobby() {
return hobby;
}
public void setHobby(String hobby) {
this.hobby = hobby;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getDegree() {
return degree;
}
public void setDegree(String degree) {
this.degree = degree;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getBirthFrom() {
return birthFrom;
}
public void setBirthFrom(String birthFrom) {
this.birthFrom = birthFrom;
}
public String getBirthTo() {
return birthTo;
}
public void setBirthTo(String birthTo) {
this.birthTo = birthTo;
}
public PageData getPageData() {
if(null == pageData){
pageData = new PageData();
}
return pageData;
}
public void setPageData(PageData pageData) {
this.pageData = pageData;
}
public String getIdListString() {
return idListString;
}
public void setIdListString(String idListString) {
this.idListString = idListString;
}
}
然后编写StudentAction方法处理请求:
package com.yida.action;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import com.sun.org.apache.xerces.internal.impl.xpath.regex.ParseException;
import com.yida.action.model.StudentModel;
import com.yida.action.vo.StudentVO;
import com.yida.common.GerneralSessionAction;
import com.yida.entity.Student;
import com.yida.utils.PageData;
/**
* 学生Action
* @author Lanxiaowei
*
*/
public class StudentAction extends GerneralSessionAction<StudentModel>{
/**
* 查询学生列表(非Ajax分页)
*/
public String searchStudents() throws Exception{
StudentVO studentVO = this.getModel().getStudentVO();
PageData pageData = getStudentService().findStudents(studentVO);
studentVO.setPageData(pageData);
getRequest().setAttribute("total", pageData.getData().size());
return "studentManager";
}
/**
* ajax分页
*/
public String ajaxSearch()throws Exception{
StudentVO studentVO = this.getModel().getStudentVO();
studentVO.getPageData().setCurrentPage(Integer.parseInt(getRequest().getParameter("currentPage")));
studentVO.getPageData().setPageSize(Integer.parseInt(getRequest().getParameter("pageSize")));
PageData pageData = getStudentService().findStudents(studentVO);
pageData.setUrl(getRequest().getRequestURL().toString());
studentVO.setPageData(pageData);
getRequest().setAttribute("total", pageData.getTotal());
getRequest().setAttribute("currentPage", pageData.getCurrentPage());
getRequest().setAttribute("pageSize", pageData.getPageSize());
getRequest().setAttribute("totalPage", pageData.getTotalPage());
getRequest().setAttribute("url", pageData.getUrl());
getRequest().setAttribute("students", pageData.getData());
return "ftl";
}
/**
* 初始化数据
*/
public String initalData() throws Exception{
List list = getStudentService().findStudents(null).getData();
if(null == list || list.size() == 0){
DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
for (int i = 0; i < 1008; i++) {
String sex = (i%3 == 0)?"1":"0";
Date birth = df.parse("1980-09-15");
Student student = new Student(
new Long(i+1),"学生" + (i+1),sex,birth,
"爱好"+(i+1),"","",
"学历"+(i+1),"暂住地址"+(i+1)
);
getStudentService().addStudent(student);
}
}
return null;
}
}
其中initalData方法只是为了初次运行程序进行数据初始化,主要是为了方便测试,因为要测试分页,必须要一定量的数据,手动插入不现实。
下面是对一页数据的包装类代码:为了大家阅读时的连贯性,我还是有必要贴出来:
package com.yida.utils;
import java.util.List;
import com.yida.common.Globarle;
/**
* 一页的数据
* @author lanxiaowei
*
*/
public class PageData {
public PageData(List data, int total, int currentPage, int pageSize) {
if(pageSize <= 0){
pageSize = Globarle.PAGESIZE;
}
this.data = data;
this.total = total;
this.currentPage = currentPage;
this.pageSize = pageSize;
}
public PageData(){
this.currentPage = 1;
this.pageSize = Globarle.PAGESIZE;
}
public PageData(int currentPage, int pageSize){
this.currentPage = currentPage;
this.pageSize = pageSize;
}
public PageData(int total, List data) {
this.total = total;
this.data = data;
}
//查询结果
private List data;
//总记录数
private int total;
//当前页
private int currentPage;
//每页大小
private int pageSize;
//后台请求地址
private String url;
public void setCurrentPage(int currentPage) {
this.currentPage = currentPage;
}
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public int getFromRow(){
int num = (currentPage - 1) * pageSize + 1;
if(num > total)
num = total;
return num;
}
public int getEndRow(){
int num = getFromRow() + pageSize;
if(num >= total){
return total;
}
return num - 1;
}
public int getTotalPage(){
int num = total / pageSize;
if(total % pageSize != 0)
num ++;
return num;
}
public List getData() {
return data;
}
public int getTotal() {
return total;
}
public int getCurrentPage() {
if(currentPage > getTotalPage()){
return getTotalPage();
}
if(currentPage <= 0){
return 1;
}
return currentPage;
}
public int getPageSize() {
if(pageSize <= 0){
return Globarle.PAGESIZE;
}
return pageSize;
}
}
然后就是程序显示主页面studentmanager.jsp编写,由于CSS技术有限,写的不是很好看,就讲究着看吧。
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@taglib prefix="s" uri="/struts-tags"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path;
request.setAttribute("basePath",basePath);
%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "">
<html xmlns="">
<head>
<base href="<%=basePath%>">
<title>学生管理</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
<link rel="stylesheet" type="text/css" href="${basePath}/css/style.css">
<script type="text/javascript" src="${basePath}/js/xmlhttprequest.js"></script>
</head>
<body>
<script type="text/javascript" src="${basePath}/My97DatePicker/WdatePicker.js"></script>
<div id="left">
学生信息管理
</div>
<div id="right">
<div id="top">
<form name="studentForm" method="post" action="${basePath}/studentAction!searchStudents.do">
<input type="hidden" name="studentVO.idListString" value=""/>
<table align="center" cellpadding="2" cellspacing="1">
<tr>
<th colspan="4">学生信息查询</th>
</tr>
<tr>
<td width="20%" align="right">姓名:</td>
<td width="30%" align="left">
<input type="text" name="studentVO.stuName" value="${studentVO.stuName}"/>
</td>
<td width="20%" align="right">性别:</td>
<td width="30%" align="left">
<input type="radio" name="studentVO.sex" value="1" <s:if test='%{studentVO.sex == "1"}'>checked="checked"</s:if>/>男
<input type="radio" name="studentVO.sex" value="0" <s:if test='%{studentVO.sex == "0"}'>checked="checked"</s:if>/>女
</td>
</tr>
<tr>
<td width="20%" align="right">生日从:</td>
<td width="30%" align="left">
<input id="start" type="text" name="studentVO.birthFrom" value="${studentVO.birthFrom}"/><img onclick="WdatePicker({el:'start',lang:'zh-cn',maxDate:'#F{$dp.$D(\'end\')}',readOnly:true,dateFmt:'yyyy-MM-dd'})" src="${basePath}/My97DatePicker/skin/datePicker.gif" width="16" height="22" align="absmiddle">
</td>
<td width="20%" align="right">至:</td>
<td width="30%" align="left">
<input id="end" type="text" name="studentVO.birthTo" value="${studentVO.birthTo}"/><img onclick="WdatePicker({el:'end',lang:'zh-cn',minDate:'#F{$dp.$D(\'start\')}',readOnly:true,dateFmt:'yyyy-MM-dd'})" src="${basePath}/My97DatePicker/skin/datePicker.gif" width="16" height="22" align="absmiddle">
</td>
</tr>
<tr>
<td colspan="4">
<input type="button" name="a" value="Ajax查询" class="btn" onclick="aSearch('${basePath}/studentAction!ajaxSearch.do',1,10)"/>
<input type="submit" name="search" value="查询" class="btn"/>
<input type="reset" name="reapte" value="重填" class="btn"/>
</td>
</tr>
</table>
</form>
</div>
<div id="bottom">
<table align="center" cellpadding="2" cellspacing="1">
<tr>
<th colspan="9">学生信息列表</th>
</tr>
<tr>
<td width="9%">
序号<input type="checkbox" id="checkall" onclick="reverseAll()"/>
</td>
<td width="9%">姓名</td>
<td width="6%">性别</td>
<td width="12%">生日</td>
<td width="8%">爱好</td>
<td width="14%">联系电话</td>
<td width="14%">Email</td>
<td width="8%">学历</td>
<td width="12%">暂住地址</td>
</tr>
<tr>
<td id="t" colspan="9"></td>
</tr>
<tr>
<td colspan="9">
<input type="button" name="add" value="新增" class="btn"/>
<input type="button" name="deleteMore" value="批量删除" class="btn"/>
</td>
</tr>
</table>
</div>
</div>
<script type="text/javascript">
function reverseAll(){
var ckall = document.getElementById("checkall");
var boxs = document.getElementsByName("idList");
for(var i = 0; i < boxs.length; i++){
if(boxs[i].type == "checkbox"){
boxs[i].checked = ckall.checked;
}
}
}
function aSearch(url,currentPage,pageSize){
var params = "currentPage=" + currentPage + "&pageSize=" + pageSize;
var xmlHttp = createXmlHttpRequest();
xmlHttp.open('post', url, false);
xmlHttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
xmlHttp.send(params);
if (xmlHttp.readyState == 4){
if (xmlHttp.status == 200){
var data = xmlHttp.responseText;
document.getElementById("t").innerHTML = data;
}
}
}
</script>
</body>
</html>
studentManager.jsp里主要是ajax分页查询表单的编写,其中出生日期输入我采用了My97组件。其他就是ajax的XmlHttpRequest对象的获取我是封装到一个xmlhttprequest.js文件里,然后编写了一个aSearch函数用于发送ajax请求,这个方法除了回调函数里这句代码document.getElementById("t").innerHTML = data;这里的id t是强制的,至于id需不需要作为函数的参数传递过来,这个不是很重要,你只要在页面定一个id为t的dom元素即可,我觉得这个改动也不是很大,如果你有兴趣也可以在函数添加的id参数,然后后台在返回数据包装到pageData对象里时,你可以再在pageData类里添加个id属性,在返回PageData对象时获取前端传递过来的id参数值并设置进去即可。
然后就是action执行完后跳转后模版编写:
模版page.ftl主要是进行数据循环展示和分页组件的显示:
<table>
<#list students as student>
<tr>
<td>
${student_index+1}
<input type="checkbox" name="idList" value="${student.id}"/>
</td>
<td>${student.stuName}</td>
<td>
<#if student.sex == "1">
男
<#else>
女
</#if>
</td>
<td>
${student.birth?string("yyyy-MM-dd")}
</td>
<td>${student.hobby}</td>
<td>${student.phone}</td>
<td>${student.email}</td>
<td>${student.degree}</td>
<td>${student.address}</td>
</tr>
</#list>
<tr>
<td colspan="9" style="text-align:right;padding-right:10px;">
<div style="font-size:14px;text-decoration:none;text-align:right;width:356px;height:20px;">
共:${total}条 页码:${currentPage}/${totalPage}页
<#if currentPage == 1>
首页 上一页
<#else>
<a href="javascript:void(0);" onclick="aSearch('${url}',1,${pageSize});return false;">首页</a>
<a href="javascript:void(0);" onclick="aSearch('${url}',${currentPage-1},${pageSize});return false;">上一页</a>
</#if>
<#if currentPage == totalPage>
下一页 尾页
<#else>
<a href="javascript:void(0);" onclick="aSearch('${url}',${currentPage+1},${pageSize});return false;">下一页</a>
<a href="javascript:void(0);" onclick="aSearch('${url}',${totalPage},${pageSize});return false;">尾页</a>
</#if>
</div>
</td>
</tr>
</table>
到此整个编码过程就完成了,剩下就是部署项目然后启动Tomcat服务器测试效果,下面我的测试效果图:

到此我的FreeMaker+ajax分页组件就完工了。其实最最关键的就是page.ftl和页面上的aSearch()函数,其实aSearch()函数你可以独立到一个js文件,以后直接引入即可,你可能会说page.ftl里代码不通用,但是你要知道,修改ftl文件不需要重新编译的,再说前端展示界面是千变万化的,不可能做到不用进行一丝修改,再说修改page.ftl很简单,把你数据展示部分和分页显示部分html代码copy进来,需要动态读取的数据用freemaker插入值替换。而你后台只需每次查询后把数据设置到request作用域即可。也许你会问,为什么我要把数据循环展示部分也纳入到page.ftl,感觉page.ftl不能够重用很不爽,但是不把数据循环展示部分加进去,你前端回调里怎么拿到数据列表,要知道这是ajax请求,如果是非ajax请求可以用struts标签搞定,可是你是异步请求,struts标签或者jstl标签不取不到数据的,想法很好,可惜我试过了,不可行。所以为了重用性,你最好把你前端数据展示部分设计的能通用,如果你是一个页面一个样式,那就多写一套模版吧,也只能这样了。如果是非ajax方式,你ftl不能直接导入jsp或者你action跳转到ftl模版做法又不可取,因为毕竟jsp用的还是比较多,用ftl做前端视图组件的项目还是很少的。
所以我也只能这样处理,如果你有更好的电子能让page.ftl通用,请留言告知,谢谢!如我有哪里写的不好的,也请你多多拍砖!
本文来源 我爱IT技术网 http://www.52ij.com/jishu/95.html 转载请保留链接。
- 评论列表(网友评论仅供网友表达个人看法,并不表明本站同意其观点或证实其描述)
-
