Struts2泛型通用crud的action,让一切变得简单起来
2010-12-17 17:13
393 查看
对实体对象的crud操作绝对是重复性操作,不同的实体对象做着差不多的增删改。通过sturts2的一个泛型crudAction完全可以简化这些操作的。官方有个例子,http://struts.apache.org/2.2.1/docs/crud-demo-i.html ,我觉得是不给力的,有兴趣的可以看看这个不给力的demo。
泛型CrudAction的设计思路是,CrudAction提供基本的crud操作,由于是泛型的所以可以针对任意类型的实体类操作。所以,继承了CrudAction就可以让增删改的操作简单起来。
首先需要了解struts2的2个接口,需要crudAction实现:
第一个ModelDriven<T>接口,这个接口本身就是泛型的,如下:
作用是显而易见的,就是让页面直接访问实体对象。
第二个接口是Preparable,如下:
顾名思义是,在action执行前调用这个方法。这就给予了机会通过页面参数,去数据库查出实体类对象。供给crud的方法去处理。说到这个接口,很多人会结合paramsPrepareParamsStack这个拦截器使用。这是一个奇妙的拦截器,因为本来执行顺序是,preparable -> 装配参数拦截器, 应用了这个拦截器以后就变成: 装配参数 -> preparable -> 装配参数。 这样的目的是为了,在perpare方法中拿到一个装配好参数的实体对象(没有关联到数据库),以后利用参数查出数据库持久化对象,以后在执行一次装配参数向持久化对象里设置值。
但我觉得没有必要,而且这样做在有set集合的关系实体类中会有容易出错的地方。所以我并没有使用paramsPrepareParamsStack这个拦截器,而是使用了默认的。
现在让我们看代码:
可以看到crudAction是泛型的,子类继承的时候需要提供,实体类类型和主键类型。prepare()方法是关键,首先利用反射使用泛型参数new 了一个实体类型的对象,以后利用页面的 实体类对象的id 去数据库查找。 这样一来,如果有id那么entity对象就是数据库的持久化对象,如果没有id则是一个new 出来的非持久化对象。
接下来,会执行装配参数拦截器,无论是否是持久化对象,都会把页面的参数装配到entitiy中。 在后来,所有的crud方法,我们使用的就是这个entity,或保存,或删除,或怎么都可以了。
我把Crud方法里面的异常吃掉了,把方法转换成boolean返回值的了。这是为了让子类有机会去做日志记录和信息返回。
下面看一个使用的例子:
这里使用了一种比较特别的使用ajax的方法。
见这里: http://blog.csdn.net/tom_221x/archive/2010/09/03/5862267.aspx
后面贴上curdService和entityDao:
泛型CrudAction的设计思路是,CrudAction提供基本的crud操作,由于是泛型的所以可以针对任意类型的实体类操作。所以,继承了CrudAction就可以让增删改的操作简单起来。
首先需要了解struts2的2个接口,需要crudAction实现:
第一个ModelDriven<T>接口,这个接口本身就是泛型的,如下:
/** * ModelDriven Actions provide a model object to be pushed onto the ValueStack * in addition to the Action itself, allowing a FormBean type approach like Struts. * * @author Jason Carreira */ public interface ModelDriven<T> { /** * Gets the model to be pushed onto the ValueStack instead of the Action itself. * * @return the model */ T getModel(); }
作用是显而易见的,就是让页面直接访问实体对象。
第二个接口是Preparable,如下:
/** * Preparable Actions will have their <code>prepare()</code> method called if the {@link com.opensymphony.xwork2.interceptor.PrepareInterceptor} * is applied to the ActionConfig. * * @author Jason Carreira * @see com.opensymphony.xwork2.interceptor.PrepareInterceptor */ public interface Preparable { /** * This method is called to allow the action to prepare itself. * * @throws Exception thrown if a system level exception occurs. */ void prepare() throws Exception; }
顾名思义是,在action执行前调用这个方法。这就给予了机会通过页面参数,去数据库查出实体类对象。供给crud的方法去处理。说到这个接口,很多人会结合paramsPrepareParamsStack这个拦截器使用。这是一个奇妙的拦截器,因为本来执行顺序是,preparable -> 装配参数拦截器, 应用了这个拦截器以后就变成: 装配参数 -> preparable -> 装配参数。 这样的目的是为了,在perpare方法中拿到一个装配好参数的实体对象(没有关联到数据库),以后利用参数查出数据库持久化对象,以后在执行一次装配参数向持久化对象里设置值。
但我觉得没有必要,而且这样做在有set集合的关系实体类中会有容易出错的地方。所以我并没有使用paramsPrepareParamsStack这个拦截器,而是使用了默认的。
现在让我们看代码:
/* * Copyright (c) scott.cgi Rights Reserved. * Email: scott.cgi@gmail.com */ package com.scottcgi.common.struts2.action; import java.io.Serializable; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.Collections; import java.util.List; import javax.annotation.Resource; import com.opensymphony.xwork2.ModelDriven; import com.opensymphony.xwork2.Preparable; import com.scottcgi.common.exception.ServiceException; import com.scottcgi.common.spring.dao.Page; import com.scottcgi.common.spring.service.CrudService; /** * 提供CRUD操作的Action * * @author scott.Cgi * @since 2010-8-25 */ public abstract class CrudAction<T, PK extends Serializable> extends BaseAction implements ModelDriven<T>, Preparable { private static final long serialVersionUID = 7909985827753544081L; @Resource(name = "crudService") private CrudService<T, PK> crudService; // 实体对象列表 protected List<T> entityList = Collections.<T>emptyList(); // 实体对象 protected T entity; // 实体类主键 protected PK id; // 实体类的名字 protected String entityClassName = ""; // 默认分页排序,子类选择性覆盖 protected String orderBy = "id desc"; // 分页对象 protected Page<T> page; // 本页第一条数据在总数据中的位置 private String start; // 分页大小 private String limit; // 批量删除的id private String[] ids; /** * 根据泛型类型实例化对象 * * @return 泛型类型的对象 * @throws Exception */ @SuppressWarnings("unchecked") public T instanceAnnotationObject() throws Exception { Class<?> cl = this.getClass(); // 得到泛型类型参数数组就是<>里面的2个值 Type[] types = ((ParameterizedType) cl.getGenericSuperclass()).getActualTypeArguments(); try { return ((Class<T>) types[0]).newInstance(); } catch (Exception e) { log.error("crudAction的泛型类型实例化失败! 错误信息: {}", e.getMessage()); throw e; } } /* (non-Javadoc) * @see com.opensymphony.xwork2.Preparable#prepare() */ @SuppressWarnings("unchecked") @Override public void prepare() throws Exception { // 利用反射新建enity对象 this.entity = this.instanceAnnotationObject(); this.entityClassName = this.entity.getClass().getSimpleName(); String id = this.request.getParameter("id"); if(id != null) { // 根据主键从数据库查找entity try { this.id = (PK) Long.valueOf(id); T dataEntity = crudService.read((Class<T>)entity.getClass(), this.id); if(dataEntity != null) { this.entity = dataEntity; } else { this.entity = this.instanceAnnotationObject(); } } catch(NumberFormatException e) { log.error("实体对象对象[{}]的id:{}转换数字失败!", this.entityClassName, id); } catch (Exception e) { log.error("读取实体对象[{}]失败! id: {}, 错误原因 : {}",new Object[]{ this.entityClassName, id, e.getMessage() }); throw e; } } } /* (non-Javadoc) * @see com.opensymphony.xwork2.ModelDriven#getModel() */ @Override public T getModel() { return entity; } /** * 读取实体类对象 * * @return 操作是否成功 */ public boolean doRead() { // prepare已经完成此功能 // 使用getModel()获得实体类 log.info("成功读取实体对象[{}]", this.entityClassName); return true; } /** * 获得实体类分页列表 * * @return 操作是否成功 */ public boolean doListPage() { try { int pageSize = Integer.valueOf(limit); // 计算当前页面号 int pageNo = Integer.valueOf(start) / pageSize + 1; this.page = crudService.readPage(entity.getClass(), pageNo, pageSize, this.orderBy); } catch (ServiceException e) { log.error("读取实体类[{}]列表失败! 错误原因 : {}", new Object[]{ entity.getClass().getName(), e.getMessage() }); return false; } log.info("成功读取实体对象[{}]列表", this.entityClassName); return true; } /** * 获得实体类所有对象列表 * * @return 操作是否成功 */ @SuppressWarnings("unchecked") public boolean doList() { try{ this.entityList = crudService.readList((Class<T>)entity.getClass()); } catch(ServiceException e){ log.error("读取实体类[{}]列表失败! 错误原因: {}", this.entityClassName, e.getMessage()); return false; } return true; } /** * 创建实体类数据 * * @return 操作是否成功 */ public boolean doCreate() { try { crudService.createOrUpdate(entity); } catch (ServiceException e) { log.error("创建实体对象[{}]失败! 错误原因 : {}", this.entityClassName, e.getMessage()); return false; } log.info("成功创建实体对象[{}]", this.entityClassName); return true; } /** * 更新实体类数据 * * @return 操作是否成功 */ public boolean doUpdate() { try { crudService.createOrUpdate(entity); } catch (ServiceException e) { log.error("更新实体对象[{}]失败! 错误原因 : {}", this.entityClassName, e.getMessage()); return false; } log.info("成功更新实体对象[{}]", this.entityClassName); return true; } /** * 删除实体对象数据 * * @return 操作是否成功 */ public boolean doDelete() { try { crudService.delete(entity); } catch (ServiceException e) { log.error("删除实体对象[{}]失败! 错误原因 : {}", this.entityClassName, e.getMessage()); return false; } log.info("成功删除实体对象[{}]", this.entityClassName); return true; } /** * 批量删除 * * @return 操作是否成功 */ public boolean doBatchDelete() { try { StringBuilder sb = new StringBuilder(); for(String id : ids) { sb.append(id).append(","); } if(sb.lastIndexOf(",") == sb.length() - 1) { sb.deleteCharAt(sb.length() - 1); } log.info("批量删除的实体类[{}]的ids: {}", this.entityClassName, sb.toString()); crudService.batchDelete(this.entityClassName, sb.toString()); } catch(ServiceException e) { log.error("批量删除实体对象[{}]失败! 错误原因: {}", this.entityClassName, e.getMessage()); return false; } return true; } /** * @return the entity */ public T getEntity() { return entity; } /** * @param entity the entity to set */ public void setEntity(T entity) { this.entity = entity; } /** * @return the id */ public PK getId() { return id; } /** * @param id the id to set */ public void setId(PK id) { this.id = id; } /** * @return the page */ public Page<T> getPage() { return page; } /** * @param page the page to set */ public void setPage(Page<T> page) { this.page = page; } /** * @return the start */ public String getStart() { return start; } /** * @param start the start to set */ public void setStart(String start) { this.start = start; } /** * @return the limit */ public String getLimit() { return limit; } /** * @param limit the limit to set */ public void setLimit(String limit) { this.limit = limit; } /** * @return the ids */ public String[] getIds() { return ids; } /** * @param ids the ids to set */ public void setIds(String[] ids) { this.ids = ids; } }
可以看到crudAction是泛型的,子类继承的时候需要提供,实体类类型和主键类型。prepare()方法是关键,首先利用反射使用泛型参数new 了一个实体类型的对象,以后利用页面的 实体类对象的id 去数据库查找。 这样一来,如果有id那么entity对象就是数据库的持久化对象,如果没有id则是一个new 出来的非持久化对象。
接下来,会执行装配参数拦截器,无论是否是持久化对象,都会把页面的参数装配到entitiy中。 在后来,所有的crud方法,我们使用的就是这个entity,或保存,或删除,或怎么都可以了。
我把Crud方法里面的异常吃掉了,把方法转换成boolean返回值的了。这是为了让子类有机会去做日志记录和信息返回。
下面看一个使用的例子:
@Namespace("/test") public class TestAction extends CrudAction<Test, Long> { @Action("createOrUpdateTest") public String createOrUpdateTest(){ String result = "{success:true}"; // this.entity这里拿到的对象已经是装配要页面参数的了 // 调用父类方法,页面参数有id就是更新,没有id就是新增 if(this.doUpdate()) { log.info("更新Test成功!"); } else { log.error("更新Test失败!"); result = "{success:false}"; } this.setAjaxInputStream(result); return AJAX; } // 其它几个操作都差不多 }
这里使用了一种比较特别的使用ajax的方法。
见这里: http://blog.csdn.net/tom_221x/archive/2010/09/03/5862267.aspx
后面贴上curdService和entityDao:
* Copyright (c) scott.cgi Rights Reserved. package com.scottcgi.common.spring.service; import java.io.Serializable; import java.util.List; import org.springframework.stereotype.Service; import com.scottcgi.common.exception.ServiceException; import com.scottcgi.common.spring.dao.Page; /** * 提供Entity的CURD操作 * * @author scott.cgi * @since 2010-8-25 */ @Service("crudService") public class CrudService<T, PK extends Serializable> extends BaseService { /** * 读取 * * @param entityClass 实体对象类型对象 * @param id 主键 * @return 实体对象 */ public T read(Class<T> entityClass, PK id) throws ServiceException { try { return this.entityDao.get(entityClass, id); } catch(Exception e) { log.error("Dao 异常 = {}", e.getMessage()); throw new ServiceException("Dao 操作失败!"); } } /** * 读取实体类列表 * * @param entityClass 实体类的类型 * @return 实体类对象列表 * @throws ServiceException */ public List<T> readList(Class<T> entityClass)throws ServiceException { try{ return this.entityDao.loadAll(entityClass); } catch(Exception e) { log.error("Dao 异常 = {}", e.getMessage()); throw new ServiceException("Dao 操作失败!"); } } /** * 分页读取 * * @param entity 实体对象 * @param pageNo 页面号 * @param pageSize 分页大小 * @param orderBy 可选的排序字段和升序或降序关键字 * @return page对象 */ public Page<T> readPage(Class<?> entityClass, int pageNo, int pageSize, Object... orderBy) throws ServiceException { String name = entityClass.getName(); String countHql = "select count(*) from " + name; String hql = "from " + name; if(orderBy != null) { if(orderBy.length == 1) { hql += " order by " + orderBy[0]; } } try { return this.entityDao.queryPage(pageNo, pageSize, countHql, hql); } catch(Exception e) { log.error("Dao 异常 = {}", e.getMessage()); throw new ServiceException("Dao 操作失败!"); } } /** * 新建或更新 * * @param entity 实体对象 */ public void createOrUpdate(T entity) throws ServiceException { try { this.entityDao.saveOrUpdate(entity); } catch(Exception e) { log.error("Dao 异常 = {}", e.getMessage()); throw new ServiceException("Dao 操作失败!"); } } /** * 删除 * * @param entity 实体对象 */ public void delete(T entity) throws ServiceException { try { this.entityDao.delete(entity); } catch(Exception e) { log.error("Dao 异常 = {}", e.getMessage()); throw new ServiceException("Dao 操作失败!"); } } /** * 批量删除 * * @param className 实体类名 * @param ids 实体对象一个或多个id * @throws ServiceException */ public void batchDelete(String className, String ids) throws ServiceException { try { List<T> l = this.entityDao.find("from " + className + " where id in (" + ids + ")"); this.entityDao.deleteAll(l); } catch(Exception e) { log.error("Dao 异常 = {}", e.getMessage()); throw new ServiceException("Dao 操作失败!"); } } }
/* * Copyright (c) scott.cgi Rights Reserved. * Email: scott.cgi@gmail.com */ package com.scottcgi.common.spring.dao; import java.io.Serializable; import java.sql.SQLException; import java.util.Collection; import java.util.List; import org.hibernate.HibernateException; import org.hibernate.Query; import org.hibernate.Session; import org.springframework.dao.DataAccessException; import org.springframework.orm.hibernate3.HibernateCallback; import org.springframework.orm.hibernate3.support.HibernateDaoSupport; /** * 继承spring HibernateDaoSupport * 提供hibernate entity基本操作 * * @author scott.Cgi * @since 2010-08-21 * */ public class EntityDao extends HibernateDaoSupport { /** * 根据主键查找实体类,找不到返回null * * @param id 实体类主键 * @return 实体类对象 * @throws DataAccessException */ public <T, PK extends Serializable> T get(Class<T> entityClass, PK id) throws DataAccessException { return this.getHibernateTemplate().get(entityClass, id); } /** * 根据主键,加载实体类 * * @param <T> * @param <PK> * @param entity 实体类对象 * @param id 主键 * @throws DataAccessException */ public <T, PK extends Serializable> void load(T entity, PK id) throws DataAccessException { this.getHibernateTemplate().load(entity, id); } /** * 更新或创建实体对象 * * @param entity 实体对象 * @throws DataAccessException */ public <T> void saveOrUpdate(T entity) throws DataAccessException { this.getHibernateTemplate().saveOrUpdate(entity); } /** * 删除实体对象 * * @param entity 实体对象 * @throws DataAccessException */ public <T> void delete(T entity) throws DataAccessException { this.getHibernateTemplate().delete(entity); } /** * 重新装载实体对象 * * @param entity 实体对象 * @throws DataAccessException */ public <T> void refresh(T entity) throws DataAccessException { this.getHibernateTemplate().refresh(entity); } /** * 解除实体对象与hibernate session的关系 * * @param entity 实体对象 * @throws DataAccessException */ public <T> void evict(T entity) throws DataAccessException { this.getHibernateTemplate().evict(entity); } /** * 根据查询语句查询 * * @param <T> * 实体类的类型 * @param hql * 查询语句 * @param values * 查询参数 * @return 查询结果集 * @throws DataAccessException * */ @SuppressWarnings("unchecked") public <T> List<T> find(String hql, Object... values) throws DataAccessException { return this.getHibernateTemplate().find(hql, values); } /** * 根据hibernate注解或映射文件的查询语句查询 * * @param <T> * 实体类的类型 * @param queryName * 查询语句的名称 (语句包含?占位符) * @param values * 参数名称 (?占位符的值) * @return 结果集 * @throws DataAccessException * */ @SuppressWarnings("unchecked") public <T> List<T> findByNamedQuery(String queryName, Object... values) throws DataAccessException { return this.getHibernateTemplate().findByNamedQuery(queryName, values); } /** * 根据hql语句进行更新或删除 * * @param hql * 查询语句 * @param values * 查询参数 * @return 操作影响的行数 * @throws DataAccessException */ public int update(String hql, Object... values) throws DataAccessException { return this.getHibernateTemplate().bulkUpdate(hql, values); } /** * 执行回调接口,获得使用原生hibernate sessoin的能力 * * @param <T> * 泛型类型 * @param action * hibernate回调接口 * @return 泛型结果 * @throws DataAccessException */ public <T> T execute(HibernateCallback<T> action) throws DataAccessException { return this.getHibernateTemplate().execute(action); } /** * 根据查询语句分页 * * @param <T> 泛型参数 * @param pageNo 当前页数 * @param pageSize 每页容量 * @param countHql 计算总记录数的查询语句 * @param hql 查询语句 * @param values 查询参数 * @return 分页结果集 * @throws DataAccessException */ @SuppressWarnings("unchecked") public <T> Page<T> queryPage(final int pageNo, final int pageSize, String countHql, final String hql, final Object... values) throws DataAccessException { List<Long> countlist = this.getHibernateTemplate().find(countHql, values); long totalCount = countlist.get(0); final int startIndex = (pageNo - 1) * pageSize; if (totalCount < 1) { return new Page<T>(); } List<T> result = this.getHibernateTemplate().execute( new HibernateCallback<List<T>>() { @Override public List<T> doInHibernate(Session session) throws HibernateException, SQLException { Query query = session.createQuery(hql); if (values != null) { for (int i = 0; i < values.length; i++) { query.setParameter(i, values[i]); } } return query.setFirstResult(startIndex) .setMaxResults(pageSize).list(); } }); return new Page<T>(totalCount, pageNo, pageSize, result); } /** * 刷新hibernate seesion为触发的数据库操作 * * @throws DataAccessException */ public void flush() throws DataAccessException { this.getHibernateTemplate().flush(); } /** * 移除hibernate session cache的所有对象 * 取消所有为触发的数据库操作 * * @throws DataAccessException */ public void clean() throws DataAccessException { this.getHibernateTemplate().clear(); } /** * 删除实体对象集合 * * @param <T> 实体类的类型 * @param entities 实体对象集合 * @throws DataAccessException */ public <T> void deleteAll(Collection<T> entities) throws DataAccessException { this.getHibernateTemplate().deleteAll(entities); } /** * 保存或更新实体对象集合 * * @param <T> 实体类的类型 * @param entities 实体对象集合 * @throws DataAccessException */ public <T> void saveOrUpdateAll(Collection<T> entities) throws DataAccessException { this.getHibernateTemplate().saveOrUpdateAll(entities); } /** * 获得所有实体类对象 * * @param <T> 泛型参数 * @param entityClass 实体类的类型 * @return 实体类对象列表 * @throws DataAccessException */ public <T> List<T> loadAll(Class<T> entityClass) throws DataAccessException { return this.getHibernateTemplate().loadAll(entityClass); } }
相关文章推荐
- spring2+hibernate3+struts2 真的让一切都变得简单
- Goolge让一切变得简单
- 让一切都变得简单--构建本地YUM源服务器!
- Struts2的简单使用(三)action跳转
- 并发在一定程度上使一切变得简单
- Struts2-Action-6-系列问题(乱码问题解决、简单数据校验)
- 也来写个struts2 CURD的例子-Move CRUD Operations into the same Action
- Java 理论与实践: 并发在一定程度上使一切变得简单
- Java 理论与实践: 并发在一定程度上使一切变得简单
- Java 理论与实践: 并发在一定程度上使一切变得简单
- Java:并发使一切变得简单
- Struts2、Hibernate、Spring整合的泛型DAO,以及通用的分页技术
- struts2 lesson two 主要配置文件,及简单验证,Tomcat中的乱码,default-action-ref
- Struts2的简单使用(四)action向页面传值
- 使用Struts2和jQuery EasyUI实现简单CRUD系统(七)——数据分页处理
- 超简单方法解决Struts2中一个action处理多个请求
- 用泛型写Struts2的BaseAction出错
- 使用Struts2和jQuery EasyUI实现简单CRUD系统(八)——Struts与EasyUI使用JSON进行交互
- 使用Struts2和jQuery EasyUI实现简单CRUD系统(三)——ajax,struts2使用json格式的交互
- 简单理解Struts2 action中动态方法及通配符