于是,我们想要用一个工具类来把上面的代码进行优化。
针对上面的问题,我们发现手写拼接SQL很容易出错。那我们可以在工具类里面拼接,使用的时候调用方法获取就行啦。查询的对象写死了,我们要可以处理任何的查询。。
我们能够找到如下的规律:
FROM Info WHERE title like ? and state = ? order by createTime,state 条件查询(QueryHelper): 1、查询条件语句hql: from 子句:必定出现;而且只出现一次 where 子句:可选;但关键字where 出现一次;可添加多个查询条件 order by子句:可选;但关键字order by 出现一次;可添加多个排序属性 2、查询条件值集合: 出现时机:在添加查询条件的时候,?对应的查询条件值
- 工具类代码:
package zhongfucheng.core.utils; import java.util.ArrayList; import java.util.List; /** * Created by ozc on 2017/6/7. */ public class QueryHelper { private String fromClause = ""; private String whereClause = ""; private String orderbyClause = ""; private List<Object> objectList; public static String ORDER_BY_ASC = "asc"; public static String ORDER_BY_DESC = "desc"; //FROM子句只出现一次 /** * 构建FROM字句,并设置查询哪张表 * @param aClass 用户想要操作的类型 * @param alias 别名 */ public QueryHelper(Class aClass, String alias) { fromClause = " FROM " + aClass.getSimpleName() + " " + alias; } //WHERE字句可以添加多个条件,但WHERE关键字只出现一次 /** * 构建WHERE字句 * @param condition * @param objects * @return */ public QueryHelper addCondition(String condition, Object... objects) { //如果已经有字符了,那么就说明已经有WHERE关键字了 if (whereClause.length() > 0) { whereClause += " AND " + condition; } else { whereClause += " WHERE" + condition; } //在添加查询条件的时候,?对应的查询条件值 if (objects == null) { objectList = new ArrayList<>(); } for (Object object : objects) { objectList.add(object); } return this; } /** * * @param property 要排序的属性 * @param order 是升序还是降序 * @return */ public QueryHelper orderBy(String property, String order) { //如果已经有字符了,那么就说明已经有ORDER关键字了 if (orderbyClause.length() > 0) { orderbyClause += " , " + property +" " + order; } else { orderbyClause += " ORDER BY " + property+" " + order; } return this; } /** * 返回HQL语句 */ public String returnHQL() { return fromClause + whereClause + orderbyClause; } /** * 得到参数列表 * @return */ public List<Object> getObjectList() { return objectList; } }
- Action处理:
public String listUI() { QueryHelper queryHelper = new QueryHelper(Info.class, "i"); //根据info是否为null来判断是否是条件查询。如果info为空,那么是查询所有。 if (info != null) { if (StringUtils.isNotBlank(info.getTitle())) { queryHelper.addCondition(" i.title like ? ", "%" + info.getTitle() + "%"); } } queryHelper.orderBy("i.createTime", QueryHelper.ORDER_BY_DESC); infoList = infoServiceImpl.findObjects(queryHelper); //infoList = infoServiceImpl.findObjects(hql,objectList); ActionContext.getContext().getContextMap().put("infoTypeMap", Info.INFO_TYPE_MAP); return "listUI"; }
- 最后在dao、service层中添加一个queryHelper参数的方法就行了。
数据回显
前面我们已经完成了条件查询的功能,可以根据用户给出的条件进行查询数据。但是呢,还是有一些小毛病的。我们来看看:
当我们查询数据时候,对查询出来的数据进行操作。操作完毕后,它回到的不是我们查询后的数据,而是我们的初始化数据。这明显是不合适的,当用户操作完后,我们应该返回的还是条件查询出来的数据。
还有一点的就是:我们的分页还没写……因此,下面主要解决这两个问题。
首先,我们来分析一下为什么我们操作完毕后,得到的是初始化的数据。我们按照用户的操作来看看到底哪里出了问题。
- 用户按条件查询数据,显示查询后的数据
- 用户点击编辑/删除对查询后的数据操作,交给Action处理
- Action返回给显示页面jsp
- JSP页面提交请求到Action中,Action进行处理
- 最后Action重定向到listUI
那么在这个过程,我们遇到什么问题呢???
处理1.0
在Action中使用一个变量封装着查询条件
/************获取查询条件的值*************************/ private String selectCondition; public String getSelectCondition() { return selectCondition; } public void setSelectCondition(String selectCondition) { this.selectCondition = selectCondition; }
当请求到Action时,我们将查询条件的值取出来,发给对应的JSP页面
public String editUI() { //得到所有的信息类型 ActionContext.getContext().getContextMap().put("infoTypeMap", Info.INFO_TYPE_MAP); //外边已经传了id过来了,我们要找到id对应的Info if (info != null && info.getInfoId() != null) { //把查询条件发给JSP页面 ActionContext.getContext().getContextMap().put("selectCondition", info.getTitle()); //直接获取出来,后面JSP会根据Info有getter就能读取对应的信息! info = infoServiceImpl.findObjectById(info.getInfoId()); } return "editUI"; }
JSP页面把发送过来的值存储好,通过隐藏域发送给Action
<%--把查询条件带过去给Action--%> <s:hidden name="selectCondition"/>
重新抵达到Action的时候,由于Struts2有自动封装的功能,所以可以把查询条件封装到selectCondition变量中。最后操作完重定向到listUI界面
由于是重定向,所以我们需要在struts配置文件中把我们的查询条件带过去:
<result name="list" type="redirectAction"> <param name="actionName">info_listUI</param> <!--重定向回去的时候,把查询条件带上--> <param name="info.title">${selectCondition}</param> </result>
当然啦,在删除的时候,把查询条件记录下来就行了。
//删除 public String delete() { selectCondition = info.getTitle(); String id = info.getInfoId(); infoServiceImpl.delete(id); return "list"; }
处理2.0
上面我们的确解决了查询后数据回显的情况,但是如果我们的查询条件是中文的话,会怎么样??
变成了乱码了…..在解决它之前,我们又来分析一下为什么出现乱码了….
- 我们知道Struts2使用post提交表单的数据,内部会自动帮我们转化编码的。也就是说,我们的乱码肯定不是在表单传输的过程中搞乱的。
- 那就是在重定向的时候,中文参数的数据搞乱了。
- 我们在配置文件上,要传递参数的时候,设置编码:
<!--传输数据的时候需要编码--> <param name="encode">true</param>
- 在Action中读取这个数据的时候,我们解码就行了.
if (info != null) { if (StringUtils.isNotBlank(info.getTitle())) { selectCondition = URLDecoder.decode(info.getTitle(),"UTF-8"); info.setTitle(selectCondition); queryHelper.addCondition(" i.title like ? ", "%" + info.getTitle() + "%"); } }
分页
分页对我们来说也不是陌生的事情了,我曾经在写JDBC博文的时候就讲解过分页了:http://blog.csdn.net/hon_3y/article/details/53790092
分页的复用代码http://blog.csdn.net/hon_3y/article/details/70051541
我们这一次还是使用回我们的分页复用代码,具体不同的需求,在上面修改即可了。
在dao和daoImpl中添加方法
- dao方法
/** * * @param queryHelper 查询助手,条件查询都交给查询助手来干 * @param currentPage 当前页数 * @return */ PageResult getPageResult(QueryHelper queryHelper, int currentPage);
- 在查询助手queryHelper中添加一个查询总记录数的sql语句
/** * * @return 返回查询总记录数的sql语句 */ public String getTotalRecordSql() { return "SELECT COUNT(*) " + fromClause + whereClause; }
- daoImpl实现:
- 先查询总记录数
- 初始化Page对象
- 查询分页数据,将分页数据设置到Page对象中
- 返回Page对象
public PageResult getPageResult(QueryHelper queryHelper, int currentPage) { //查询总记录数 Query queryCount = getSession().createQuery(queryHelper.getTotalRecordSql()); if (queryHelper.getObjectList() != null) { int i =0; for (Object o : queryHelper.getObjectList()) { queryCount.setParameter(i, o); i++; } } Long totalRecord = (Long) queryCount.uniqueResult(); //初始化PageResult对象 PageResult pageResult = new PageResult(currentPage, totalRecord); //查询具体模块的数据【有查询条件的也可以处理】 Query query = getSession().createQuery(queryHelper.returnHQL()); if (queryHelper.getObjectList() != null) { int i =0; for (Object o : queryHelper.getObjectList()) { query.setParameter(i, o); i++; } } //设置分页开始和末尾 query.setFirstResult(pageResult.getStartIndex()); query.setMaxResults(pageResult.getLineSize()); List dataList = query.list(); //将条件查询出来的数据设置到Page对象中 pageResult.setList(dataList); return pageResult; }
在service和serviceImpl中添加方法
- baseService
PageResult getPageResult(QueryHelper queryHelper, int currentPage);
- baseServiceImpl
public PageResult getPageResult(QueryHelper queryHelper, int currentPage) { return baseDao.getPageResult(queryHelper, currentPage); }
在Action中调用service的方法
设置我们需要用到的分页属性。
private int currentPageCount; private PageResult pageResult; public int getCurrentPageCount() { return currentPageCount; } public void setCurrentPageCount(int currentPageCount) { this.currentPageCount = currentPageCount; } public PageResult getPageResult() { return pageResult; } public void setPageResult(PageResult pageResult) { this.pageResult = pageResult; }
判断我们的当前页是否为0【如果为0,就代表着刚初始化值,我们设置为1】,调用service的方法得到分页对象
//当前页数没有值,那么赋值为1 if (currentPageCount == 0) { currentPageCount = 1; } pageResult = infoServiceImpl.getPageResult(queryHelper,currentPageCount);
在JSP页面中,我们遍历分页对象的集合就可以获取分页的数据了。
<s:iterator value="pageResult.list" status="st">
抽取属性
我们的分页属性和查询条件数据不单单只有Info模块用的,于是我们将分页数据抽取到BaseAction中
/************分页属性*************************/ protected int currentPageCount; protected PageResult pageResult; public int getCurrentPageCount() { return currentPageCount; } public void setCurrentPageCount(int currentPageCount) { this.currentPageCount = currentPageCount; } public PageResult getPageResult() { return pageResult; } public void setPageResult(PageResult pageResult) { this.pageResult = pageResult; } /************获取查询条件的值*************************/ protected String selectCondition; public String getSelectCondition() { return selectCondition; } public void setSelectCondition(String selectCondition) { this.selectCondition = selectCondition; }
修改其他的模块,也能够拥有条件查询和分页查询这两个功能:以用户模块为例。
- 将查询的对象设置为User,根据用户名的名字来进行条件查询。
//抛出Action异常 public String listUI() throws ServiceException, UnsupportedEncodingException { QueryHelper queryHelper = new QueryHelper(User.class, "u"); //根据info是否为null来判断是否是条件查询。如果info为空,那么是查询所有。 if (user != null) { if (org.apache.commons.lang.StringUtils.isNotBlank(user.getName())) { selectCondition = URLDecoder.decode(user.getName(),"UTF-8"); user.setName(selectCondition); queryHelper.addCondition(" u.name like ? ", "%" + user.getName() + "%"); } } //当前页数没有值,那么赋值为1 if (currentPageCount == 0) { currentPageCount = 1; } pageResult = userServiceImpl.getPageResult(queryHelper,currentPageCount); return "listUI"; }
- 在JSP页面遍历的是分页对象,导入我们的分页下标下表JSP
<s:iterator value="pageResult.list"> <jsp:include page="/common/pageNavigator.jsp"/>
处理查询后数据回显的问题:
- 在跳转到编辑页面之前,把查询条件记录下来。不然就会被覆盖掉了。
public String editUI() { //把所有的角色查询出来,带过去给JSP页面显示 ActionContext.getContext().getContextMap().put("roleList", roleServiceImpl.findObjects()); //外边已经传了id过来了,我们要找到id对应的User if (user != null &&user.getId() != null ) { //得到查询条件 selectCondition = user.getName(); //直接获取出来,后面JSP会根据User有getter就能读取对应的信息! user = userServiceImpl.findObjectById(user.getId()); //通过用户的id得到所拥有UserRole List<UserRole> roles = userServiceImpl.findRoleById(user.getId()); //把用户拥有角色的id填充到数组中,数组最后回显到JSP页面 int i=0; userRoleIds = new String[roles.size()]; for (UserRole role : roles) { userRoleIds[i++] = role.getUserRoleId().getRole().getRoleId(); } } return "editUI"; }
- 在显示编辑页面的JSP上,把查询条件带过去给Action
<%--把查询条件通过隐藏域带过去给Action--%> <s:hidden name="selectCondition"></s:hidden>
- 最后,当编辑完,重定向的时候,也要将查询条件带过去。预防着中文的问题,我们对其进行编码
<action name="user_*" class="zhongfucheng.user.action.UserAction" method="{1}"> <result name="{1}" >/WEB-INF/jsp/user/{1}.jsp</result> <!--返回列表展示页面,重定向到列表展示--> <result name="list" type="redirectAction"> <param name="actionName">user_listUI</param> <param name="user.name">${selectCondition}</param> <param name="encode">true</param> </result> </action>
- 在删除的时候,也要把查询条件记录下来。
//删除 public String delete() { if (user != null && user.getId() != null) { //记载着查询条件 selectCondition = user.getName(); userServiceImpl.delete(user.getId()); } return "list"; }
总结
- 如果页面上的数据是写死的,那么我们就考虑一下是不是可以在域对象把数据带过去了。
- 使用Ueditor来做富文本编辑器
- 在页面上定位一个标签,我们可以使用特殊的前缀+上我们的Id。
- 由于Service的代码重复性太高了,我们也将Service进行抽取。抽取成一个BaseService接口
- BaseServiceImpl实现BaseService接口,但他要使用BaseDao对象来对实现的方法进行调用
- 此时,BaseServiceImpl是一个抽象类,它本身不能实例化了。那怎么将BaseDao实例化呢??
- 当我们的InfoServiceImpl继承继承着BaseServiceImpl时,本身就需要用到InfoDao来对该模块的业务进行调用。
- 在InfoServiceImpl对InfoDao实例话是很容易的,可以使用自动装配,set方法注入等等。这次我们使用set方法注入!
- set方法有什么好处??能够在InfoServiceImpl注入InfoDao对象的同时,还能进行其他操作。比如:调用父类的set方法!!!!!!!
- 我们只要在BaseServiceImpl提供一个setBaseDao方法,子类再把自身的Dao传递进去。那么我们的BaseDao就被实例化了!
- 由于我们查询条件的不确定性,要对查询条件字符串进行拼接。这样不安全和很容易出错。我们就封装了一个查询助手对象。专门用于查询的。
- 在条件查询的过程中,如果我们不将查询条件保留下来。那么这个条件就会丢失。等我们操作完之后,它返回的列表页面是没有带查询条件的。
- 例如:我们的编辑操作就经历了好几个步骤:请求交由editUI()处理,如果我们不把查询条件记录下来。它就被原本的属性给覆盖掉了。
- 接着跳转到编辑页面,如果我们不将查询条件通过隐藏域交给Action,那么查询条件在页面上就丢失了。
- 最后,我们重定向到list页面时,要么通过URL添加参数来把条件重定向到list()方法上,要么就使用Struts2的参数传递。其中,如果带中文的话,记得要编码啊!
- 对于分页数据我们已经做得很多了。最后将我们Action中通过的数据封装到BaseAction中就行了。