SSH综合练习-仓库管理系统-第二天
今天的主要内容:
-
货物入库
- 页面信息自动补全回显功能:(学习目标:练习Ajax交互)
- 根据货物简记码来自动查询回显已有货物(Ajax回显)
- 根据货物名来自动查询补全已有货物(自动补全插件)
- 货物入库功能:(学习目标:练习多表插入)
- 保存货物信息的同时记录入库的历史记录。
- 更新货物信息的同时记录入库的历史记录。
-
库存管理功能(分页+多条件)
- 分页数据Bean的设计
- 要编写Pagination Bean
- 分页后台的代码编码
- QBC方案—Hibernate做法
- SQL拼接方案—传统做法(选做)
- 分页工具条的编写(了解)
- 分页代码重构优化(抽取分页代码)
- 历史记录查询(课后作业,涉及到多表)
- 出库功能(课后作业,自己思考逻辑,和入库逻辑差不多)
- 公共代码封装打包(了解)
课程目标:
- Ajax的使用和多表的插入,强化复习
- jQueryUI的插件的使用方法。自动补全查询。
- 业务条件+分页条件的综合查询
-
货物入库功能
根据简记码查询货物(精确匹配)
点击【入库】
业务分析:
简记码是为了方便用户快速定位已存在的货物而设计的。需要分析两个方面:
-
关于简记码回显的使用。
当用户输入简记码时,通过Ajax请求,将用户输入的简记码发送到服务器,服务器判断简记码是否存在。
- 如果存在,则说明货物也存在,则查询出货物信息,回显给表单。当点击"入库"的时候,进行更新货物的数量即可。
- 如果不存在,则说明数据库中没有对应的货物,这是一个新的货物,需要手动输入货物的完整信息。当点击"入库"的时候,进行保存货物的所有信息。
2. 关于同一个按钮功能是走更新还是保存,服务器端如何判断调用什么方法呢?
通过主键id来判断。因此,则需要在form表单中放置一个隐藏域id,如果货物存在,则id有值,则后台只更新货物数量即可;如果货物不存在,则id没值,则插入保存一个新的货物。
开发思路:
业务一:校验数据库简记码是否存在
- 改造页面表单为struts标签(因为要回显一些信息)
- 编写页面的Ajax请求代码(事件代码)
- 编写后台服务器端代码,货物的数据封装为json返回到前台(fireBug调试)
- 完善页面的Ajax请求代码,完善回调函数,进行页面数据的回显。
第一步:改造页面表单为struts标签,修改jsps/save/save.jsp
<s:form action="goods_instore" namespace="/" method="post" name="select">
</s:form>
save.jsp页面
<tr>
<td>
简记码:
</td>
<td>
<s:textfield name="nm" cssClass="tx"/>
<!-- 隐藏域:一会来识别是更新还是插入:是一个存在货物还是新的货物 -->
<s:hidden name="id" cssClass="tx"/>
</td>
</tr>
<tr>
<td>
货物名称:
</td>
<td>
<s:textfield name="name" cssClass="tx"/>
</td>
</tr>
<tr>
<td>
计量单位:
</td>
<td>
<s:textfield name="unit" cssClass="tx"/>
</td>
</tr>
<tr>
<td>
入库数量:
</td>
<td>
<s:textfield name="amount" cssClass="tx"/>
</td>
</tr>
<tr>
<td>
选择仓库:
</td>
<td>
<select class="tx" style="width:120px;" name="store.id" id="store_id">
</select>
(此信息从数据库中加载)
</td>
</tr>
提示:注意关联属性对象的数据是如何封装的。
测试:改造完页面后,测试页面是否正常。
第二步:编写页面的Ajax请求代码(事件代码):
用户输入简记码 ,使用失去焦点的事件blur(离焦事件),发起Ajax请求。验证简记码在数据库中是否存在。
//简记码绑定一个离焦事件
$("input[name='nm']").blur(function(){
//请求服务器,获取货物信息
$.post("${pageContext.request.contextPath}/goods_findByNmAjax.action",{"nm":$(this).val()},function(data){
//data:返回的json对象,一个(简记码和货物是一对一关系)
alert(data)
});
});
第三步:编写后台服务器端代码(Action类),货物的数据封装为json返回到前台(fireBug调试)
在服务器端接收到的简记码,进行逻辑处理,将结果转换为json返回。
创建GoodsAction.java 代码
//货物的表现层
public class GoodsAction extends BaseAction<Goods>{
//注入service
private IGoodsService goodsService;
public void setGoodsService(IGoodsService goodsService) {
this.goodsService = goodsService;
}
//根据简记码查询货物(ajax)
public String findByNmAjax(){
Goods goods = goodsService.findGoodsByNm(model.getNm());
//将goods对象转换成json字符串
//压入栈顶
pushValueStackRoot(goods);
return "json";
}
}
第四步:编写业务层代码
编写IGoodsService 接口
//货物的业务层接口
public interface IGoodsService {
/**
* 根据简记码查询货物
* @param nm
* @return
*/
public Goods findGoodsByNm(String nm);
}
实现类 GoodsServiceImpl
//货物的业务层实现
public class GoodsServiceImpl extends BaseService implements IGoodsService{
//注入dao
private IGenericDao<Goods, String> goodsDao;
public void setGoodsDao(IGenericDao<Goods, String> goodsDao) {
this.goodsDao = goodsDao;
}
public Goods findGoodsByNm(String nm) {
//qbc
DetachedCriteria criteria = DetachedCriteria.forClass(Goods.class)
.add(Restrictions.eq("nm", nm));
List<Goods> list = goodsDao.findByCriteria(criteria);
return list.isEmpty()?null:list.get(0);
}
}
第五步:配置struts.xml
<!-- 货物管理 -->
<action name="goods_*" class="goodsAction" method="{1}">
<!-- json无需配置结果集了 -->
</action>
代码优化:
通过配置代码发现,返回json的结果集配置一样,那么可以将其抽取为全局的结果集配置:
<package name="default" namespace="/" extends="json-default">
<!-- 全局结果集 -->
<global-results>
<!-- json结果集类型 -->
<result name="json" type="json"></result>
</global-results>
</package>
提示:后面只要需要转换为json,直接返回json结果集即可。
第六步:配置applicationContext.xml
<!--通用的DAO类 -->
<bean id="goodsDao" class="cn.itcast.storemanager.dao.impl.GenericDaoImpl">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<!-- service -->
<bean id="goodsService" class="cn.itcast.storemanager.service.impl.GoodsServiceImpl">
<property name="goodsDao" ref="goodsDao"/>
</bean>
<!-- action -->
<bean id="goodsAction" class="cn.itcast.storemanager.web.action.GoodsAction" scope="prototype">
<property name="goodsService" ref="goodsService"/>
</bean>
第七步:测试:
在数据库中手动插入一个货物,然后使用firebug进行抓包测试。
Ajax加载出错:延迟加载错误(一对多)
Caused by: org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: cn.itcast.storemanager.domain.Goods.histories, no session or session was closed
解决方法: 对于不需要转换返回的数据,用@JSON排除集合属性Histories
//排除histories历史集合中的属性转换json
@JSON(serialize=false)
public Set getHistories() {
return this.histories;
}
Ajax加载仍然出错:延迟加载错误(多对一)
由于save.jsp页面中使用:
$("#store_id").val(data.store.id);//从货物关联到仓库
所以要求货物中关联仓库。
所以报错
Caused by: org.hibernate.LazyInitializationExcep
Caused by: org.hibernate.LazyInitializationException: could not initialize proxy - no Session
解决方法: 货物关联的仓库的数据是需要的,不能排除,需要加载
两种方式:
- 将store改为立即加载(hbm---class标签lazy属性改为false)
修改:Goods.hbm.xml文件:
<many-to-one name="store" class="cn.itcast.storemanager.domain.Store" fetch="select" lazy="false">
<column name="storeid" length="32" />
</many-to-one>
- 配置 OpenSessionInView来解决,这里注意使用OpenSessionInView过滤器一定要放置到struts2的过滤器的前面。
在web.xml容器中添加:
<!-- openSessionInView处理器,必须配置在stuts的过滤器前面 -->
<filter>
<filter-name>OpenSessionInView</filter-name>
<filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>OpenSessionInView</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
使用火狐的firebug调试,测试结果(js对象):
第八步:完善页面的Ajax请求代码,完善回调函数,进行页面数据的回显。
编写回调函数内容,将页面的字段元素赋值。
//简记码绑定一个离焦事件
$("input[name='nm']").blur(function(){
//请求服务器,获取货物信息
$.post("${pageContext.request.contextPath}/goods_findByNmAjax.action",{"nm":$(this).val()},function(data){
//data:返回的json对象,一个(简记码和货物是一对一关系)
if(data ==null){
//没有匹配的货物,清除表单
$("input[name='name']").val("");
$("input[name='unit']").val("");
$("#store_id").val("");
//隐藏域
$("input[name='id']").val("");//id属性很重要,用来判断是修改已有的货物还是新增一个货物
//解禁
$("input[name='name']").removeAttr("disabled");
$("input[name='unit']").removeAttr("disabled");
$("#store_id").removeAttr("disabled");
}else{
//填充
$("input[name='name']").val(data.name);
$("input[name='unit']").val(data.unit);
$("#store_id").val(data.store.id);//从货物关联到仓库
//隐藏域
$("input[name='id']").val(data.id);//id属性很重要,用来判断是修改已有的货物还是新增一个货物
//禁用表单元素
$("input[name='name']").attr("disabled",true);
$("input[name='unit']").attr("disabled",true);
$("#store_id").attr("disabled",true);
}
});
});
根据货物名称查询 (模糊匹配,自动补全)
目标效果参考:百度
联想提示。。。
jQuery UI的autocomplete插件介绍和引入
autocomplete插件是jQuery官方提供了一些免费开源的插件集中的一个插件。
下载jquery-ui-1.9.2.custom.zip
将插件集解压到硬盘:
查看插件的功能:点击index.html,可以看到效果
下面我们开始开发实现:
第一步:引入jquery ui开发的js和css
只引用插件相关的组件js
缺点:组建js必须要很熟悉才能导入。--不推荐
另外一种方式:全部引入,推荐,我们就采用这种,具体方法见下面:
插件的引入方式(两步):
1)导入相关文件:jQuery核心、插件的js和CSS样式
2)在页面中引入jQuery、插件js、插件的css
导入jqueryui插件的js和css
<!-- 引入jquery库 -->
<script type="text/javascript" src="${pageContext.request.contextPath }/js/jquery-1.8.3.js"></script>
<script type="text/javascript" src="${pageContext.request.contextPath }/js/jqueryui/jquery-ui-1.9.2.custom.js"></script>
<link rel="stylesheet" type="text/css" href="${pageContext.request.contextPath }/js/jqueryui/smoothness/jquery-ui-1.9.2.custom.css"></link>
插件如何使用?
可以参考自带的示例或api文档。
该补全插件主要有两种应用:本地数据自动补全和动态数据补全。
第二步:如果开发呢?
点击:autocomplete.html文件,里面有开发案例
应用一:本地数据自动补全
方法是:对source属性绑定一个数组。
//对货物名称进行自动补全功能
//方案一:-----数据源是固定的,如果数据源非常大,那么这里要加载所有的大量数据
$( "input[name='name']" ).autocomplete({
source: [ "c++", "java", "php", "coldfusion", "javascript", "asp", "ruby" ]
});
针对货物名称的输入框:
<tr>
<td>
货物名称:
</td>
<td>
<s:textfield name="name" cssClass="tx"/>
</td>
</tr>
页面效果:
缺点:不适用于大规模数据,页面要加载全部的数据。
应用二:动态数据补全 (远程数据加载实时补全)
根据用户输入内容,实时查询,实现数据的补全。
方法是:对source绑定一个function(request,response),通过request.term获取输入的值,通过response包装要显示的值。
第一步:编写save.jsp
//方案二:-----自定义数据源,从数据库中查询,动态加载数据
$( "input[name='name']" ).autocomplete({
//request.term:文本框输入的值
//response(json数组结果)
source: function( request, response ) {
$.post("${pageContext.request.contextPath}/goods_findByNameLikeAjax.action",{"name":request.term},function(data){
//data:json数组
response(data);
});
},
});
第三步:编写GoodsAction的findByNameLikeAjax方法
//根据名称模糊匹配(ajax)
public String findListByNameAjax(){
List<Goods> list = goodsService.findGoodsByNameLike(model.getName());
pushValueStackRoot(list);
return "json";
}
第四步:业务层
-
接口IGoodsService类
/**
* 根据名称模糊查询货物列表
* @param name
* @return
*/
public List<Goods> findGoodsByNameLike(String name);
-
实现类GoodsServiceImpl类
s public List<Goods> findGoodsByNameLike(String name) {
//qbc
DetachedCriteria criteria = DetachedCriteria.forClass(Goods.class)
.add(Restrictions.like("name", "%"+name+"%"));
return goodsDao.findByCriteria(criteria);
}
第五步:配置struts.xml 结果集返回 (略,使用全局结果集)
<package name="default" namespace="/" extends="json-default">
<!-- 全局结果集 -->
<global-results>
<!-- json结果集类型 -->
<result name="json" type="json">
</result>
</global-results>
</package>
第六步:页面测试:
问题:下拉列表中没有显示值。
查看火狐浏览器,看到返回的结果:
分析:
如果使用简单的数组数据是没有问题的:
原因:数据格式不对。我们用的是复杂的json数据格式。
复杂数据怎么办?
点击:autocomplete.html文件
选择"source"
需要在数组的元素对象中指定label或者value才能显示。如果只指定一个,那么另外一个的值默认会等于这个值,即:你指定lable:"阿司匹林",如果没有value的话那么值也是"阿司匹林"。
那么如何指定label呢?
就让生成json的时候有这个getter属性就行了。
操作:
两种方法:
方法一:修改Goods实体类 ,添加getLabel方法 :(采用方案)
//增加label属性来显示下拉列表,增加label为key的getter方法
public String getLabel(){
return name + "("+ store.getName() +")";
}
public String getValue() {
return this.name;
}
测试页面:ok。
此时查看火狐浏览器
【继续优化一】:
根据选择的label来显示其他货物的信息。
分析:需要添加选中列表的事件。
查看API的event章节:
我们找到select事件:
说明:select是要指定更改的事件名字,ui.item是从列表中选中的js对象(我们这里是goods数据对象)。
完善save.jsp页面代码:
//方案二:-----自定义数据源,从数据库中查询,动态加载数据
$( "input[name='name']" ).autocomplete({
//形式参数,不是servlet的api
//request:可以用来获取文本框输入的值,request.term,可以进行异步请求查询,返回data
//response:用来包装返回的数据的(接受普通数组或json数组),用法将数据放入即可response(data)
//例如:
//request.term:文本框输入的值
//response(json数组结果)
source: function( request, response ) {
$.post("${pageContext.request.contextPath}/goods_findByNameLikeAjax.action",{"name":request.term},function(data){
//data:json数组
response(data);
});
},
//选择的事件
select:function(event,ui){
//填充其他字段
//ui.item是当前选中的js对象,这里是货物goods的js数据内容
$("input[name='id']").val(ui.item.id);
$("input[name='nm']").val(ui.item.nm);
//$("input[name='name']").val(ui.item.name);//可选
$("input[name='unit']").val(ui.item.unit);
$("#store_id").val(ui.item.store.id);
},
});
这里注意:要填充id的值。
【继续优化二】:
在查询前将要填充的字段置空。否则显示的值不对
最终完整代码:
//方案二:-----自定义数据源,从数据库中查询,动态加载数据
$( "input[name='name']" ).autocomplete({
//形式参数,不是servlet的api
//request:可以用来获取文本框输入的值,request.term,可以进行异步请求查询,返回data
//response:用来包装返回的数据的(接受普通数组或json数组),用法将数据放入即可response(data)
//例如:
//request.term:文本框输入的值
//response(json数组结果)
source: function( request, response ) {
//清除表单
$("input[name='nm']").val("");
$("input[name='unit']").val("");
$("#store_id").val("");
//隐藏域
$("input[name='id']").val("");
//解禁
$("input[name='unit']").removeAttr("disabled");
$("#store_id").removeAttr("disabled");
$.post("${pageContext.request.contextPath}/goods_findByNameLikeAjax.action",{"name":request.term},function(data){
//data:json数组
response(data);
});
},
//选择的事件
select:function(event,ui){
//填充其他字段
//ui.item是当前选中的js对象,这里是货物goods的js数据内容
$("input[name='id']").val(ui.item.id);
$("input[name='nm']").val(ui.item.nm);
//$("input[name='name']").val(ui.item.name);//可选
$("input[name='unit']").val(ui.item.unit);
$("#store_id").val(ui.item.store.id);
//禁用表单元素
$("input[name='unit']").attr("disabled",true);
$("#store_id").attr("disabled",true);
},
});
货物入库功能(服务器端实现 )
要点:
- 逻辑上:到底是更新还是保存?根据隐藏域的id来判断。
- 数据操作上:多表插入的(关联属性使用,历史记录)、快照更新
分析:提交入库表单,需要实现商品入库逻辑(更新或保存)和操作历史记录
第一步:save.jsp页面,表单提交
<s:form action="goods_instore" namespace="/" method="post" name="select">
<table width="100%" border="0" cellpadding="0" cellspacing="0" class="tx" align="center">
<colgroup>
<col width="20%" align="right">
<col width="*%" align="left">
</colgroup>
<tr>
<td bgcolor="a0c0c0" style="padding-left:10px;" colspan="2" align="left">
<b>货物入库登记:</b>
</td>
</tr>
<tr>
<td>
简记码:
</td>
<td>
<s:textfield name="nm" cssClass="tx"/>
<!-- 隐藏域:一会来识别是更新还是插入:是一个存在货物还是新的货物 -->
<s:hidden name="id" cssClass="tx"/>
</td>
</tr>
<tr>
<td>
货物名称:
</td>
<td>
<s:textfield name="name" cssClass="tx"/>
</td>
</tr>
<tr>
<td>
计量单位:
</td>
<td>
<s:textfield name="unit" cssClass="tx"/>
</td>
</tr>
<tr>
<td>
入库数量:
</td>
<td>
<s:textfield name="amount" cssClass="tx"/>
</td>
</tr>
<tr>
<td>
选择仓库:
</td>
<td>
<!-- name改成store.id,用来使用模型驱动,直接为Goods对象中的store属性的id属性赋值 -->
<select class="tx" style="width:120px;" name="store.id" id="store_id">
</select>
(此信息从数据库中加载)
</td>
</tr>
<tr>
<td colspan="2" align="center" style="padding-top:10px;">
<input class="tx" style="width:120px;margin-right:30px;" type="button" onclick="document.forms[0].submit();" value="入库">
<input class="tx" style="width:120px;margin-right:30px;" type="reset" value="取消">
</td>
</tr>
</table>
</s:form>
在save.jsp中,使用ajax对隐藏域id赋值,用来判断执行的是向货物表中存放数据,还是通过简记码更新货物表的数量
//简记码绑定一个离焦事件
$("input[name='nm']").blur(function(){
//请求服务器,获取货物信息
$.post("${pageContext.request.contextPath}/goods_findByNmAjax.action",{"nm":$(this).val()},function(data){
//data:返回的json对象,一个(简记码和货物是一对一关系)
if(data ==null){
//没有匹配的货物,清除表单
$("input[name='name']").val("");
$("input[name='unit']").val("");
$("#store_id").val("");
//隐藏域
$("input[name='id']").val("");
//解禁
$("input[name='name']").removeAttr("disabled");
$("input[name='unit']").removeAttr("disabled");
$("#store_id").removeAttr("disabled");
}else{
//填充
$("input[name='name']").val(data.name);
$("input[name='unit']").val(data.unit);
$("#store_id").val(data.store.id);//从货物关联到仓库
//隐藏域
$("input[name='id']").val(data.id);
//禁用表单元素
$("input[name='name']").attr("disabled",true);
$("input[name='unit']").attr("disabled",true);
$("#store_id").attr("disabled",true);
}
});
});
第二步:在GoodsAction 添加save方法
//入库操作
public String instore(){
//将数据传递给业务层
goodsService.saveStore(model);
//从业务角度,继续录入,跳回入库页面
return "savejsp";
}
第三步:业务层
(1)IGoodsService代码
/**
* 入库
* @param goods
*/
public void saveStore(Goods goods);
-
GoodsServiceImpl类代码,操作入库的同时,操作历史,所以需要goodsDao和historyDao
//入库:有两个逻辑,一个插入,一个更新
public void saveStore(Goods goods) {
//货物id
String id = goods.getId();
//当前登录人
Userinfo loginUser = ServletUtils.getLoginUserFromSession();
//根据id判断是插入还是更新
if(StringUtils.isBlank(id)){
//插入:字段有没有都填充
goodsDao.save(goods);//持久态oid,抢占id
//插入历史记录(一般没有更新):瞬时态--》持久态
History history = new History();
history.setAmount(goods.getAmount());//操作的数量
history.setRemain(goods.getAmount());//操作后的剩余数量
//将其封装常量
// history.setType("1");//操作类型1:入库,2:出库
history.setType(DataConstant.OPER_TYPE_IN);//操作类型1:入库,2:出库
history.setGoods(goods);//货物外键:只要对象有oid即可
//注意:当前用户没有登录的情况下,空指针。解决方案;登录拦截器
history.setUser(loginUser.getName());//操作人:一般当前登陆人,
// history.setDatetime(new Date().toLocaleString());//操作时间
history.setDatetime(DateUtils.getCurrentDateString());//操作时间
historyDao.save(history);
}else{
//更新:
//hibernate两种方式:update方法(更新所有字段),快照更新(部分字段)
// goodsDao.update(goods);
//快照更新
Goods persistGoods = goodsDao.findById(Goods.class, id);
//更改一级缓存
persistGoods.setAmount(persistGoods.getAmount()+goods.getAmount());
//等待快照更新
//插入历史记录(一般没有更新):瞬时态--》持久态
History history = new History();
history.setAmount(goods.getAmount());//操作的数量
history.setRemain(persistGoods.getAmount());//操作后的剩余数量
// history.setType("1");//操作类型1:入库,2:出库
history.setType(DataConstant.OPER_TYPE_IN);//操作类型1:入库,2:出库
history.setGoods(persistGoods);//货物外键:只要对象有oid即可
history.setUser(loginUser.getName());//操作人:一般当前登陆人
// history.setDatetime(new Date().toLocaleString());//操作时间
history.setDatetime(DateUtils.getCurrentDateString());//操作时间
historyDao.save(history);
}
}
第四步:封装优化操作,简化代码的行数,将公用的方法提取出来:
-
Dataconstant.java,用来封装常量,统一维护常量
public class DataConstant {
//入库常量
public static final String OPER_TYPE_IN="1";
//出库常量
public static final String OPER_TYPE_OUT="2";
}
-
DateUtils.java,日期类封装,获取当前日期,将日期类型转换成String类型
//操作日期的:
//将日期转换成各种各样的字符串:日期,时间
//网上
public class DateUtils {
//获取到当前日期的字符串表示形式
public static String getCurrentDateString(){
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return df.format(new Date());
}
}
第五步:配置struts.xml 入库后跳转的页面
<!-- 货物管理 -->
<action name="goods_*" class="goodsAction" method="{1}">
<!-- json无需配置结果集了 -->
<!--入库 -->
<result name="savejsp" type="redirect">/jsps/save/save.jsp</result>
</action>
第六步:配置applicationContex.xml 在 GoodsService注入HistoryDAO
<!--通用的DAO类 -->
<bean id="historyDao" class="cn.itcast.storemanager.dao.impl.GenericDaoImpl">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<!-- service -->
<bean id="goodsService" class="cn.itcast.storemanager.service.impl.GoodsServiceImpl">
<property name="goodsDao" ref="goodsDao"/>
<property name="historyDao" ref="historyDao"></property>
</bean>
注意:
- 多表的插入(对象状态,持久态对象不能关联瞬时态对象),在多的一端需要关联持久对象
- 保存或更新的判断(使用页面的隐藏域id)
- 一个Service可以多个dao的注入
-
库存管理功能
分页数据Bean的设计思路
分析几种查询(从查询的角度来说):
- 条件查询:多个条件组合查询,需要将用户输入的条件,根据判断,拼接条件(sql:where条件的SQL语句。qbc:离线条件拼装)
- 分页查询:需要得到要查询记录的索引和要查询的条数(mysql:limit 起始索引,最大记录数);或者需要得到查询记录的起始和结束的行数(Oracle)。
- 条件查询+分页查询:需要在分页查询过程中记录原来的查询条件(url?+条件),原理见下图:
关键点(设计思想):查询条件(业务条件和分页条件)在客户端和服务器端来回传递。--封装一个bean对象(分页bean)用于存储条件。 和返回结果使用。
分页数据Bean的设计实现
第一步:库存管理,修改main.jsp
<s:a action="goods_listPage" namespace="/" target="content" id="left1002" >
[库存管理]
</s:a>
第二步:改造页面(form部分):
jsps/store/remain.jsp
(1)修改标签
<s:form action="goods_listPage" namespace="/" method="post" name="select">
<table width="100%" border="0" cellpadding="0" cellspacing="0" class="tx" align="center">
<colgroup>
<col width="20%" align="right">
<col width="*%" align="left">
</colgroup>
<tr>
<td bgcolor="a0c0c0" style="padding-left:10px;" colspan="2" align="left">
<b>查询条件:</b>
</td>
</tr>
<tr>
<td>
简记码:
</td>
<td>
<s:textfield name="nm" cssClass="tx"/>
</td>
</tr>
<tr>
<td>
货物名称:
</td>
<td>
<s:textfield name="name" cssClass="tx"/>
</td>
</tr>
<tr>
<td>
选择仓库:
</td>
<td>
<select class="tx" style="width:120px;" name="store.id" id="store_id">
<option value="">--请选择--</option>
</select>
</td>
</tr>
<tr>
<td colspan="2" align="right" style="padding-top:10px;">
<input class="tx" style="width:120px;margin-right:30px;" type="button" onclick="document.forms[0].submit();" value="查询">
</td>
</tr>
</table>
</s:form>
-
添加ajax
<!-- 引入jquery库 -->
<script type="text/javascript" src="${pageContext.request.contextPath }/js/jquery-1.8.3.js"></script>
<script type="text/javascript">
$(function(){
//$.post(请求url,请求参数,回调函数,返回的类型);
$.post("${pageContext.request.contextPath}/store_listAjax.action",function(data){
//data:转换后的对象:json对象数组-dom对象
$(data).each(function(){
//this每一个对象json
//this.id
var option=$("<option value='"+this.id+"'>"+this.name+"</option>");
//添加到下拉列表中
$("#store_id").append(option);
});
//回显下拉选择,手动给select列表选中一个选中的值,从隐藏域中获取选中的值。
//alert($("#storeId").val());
$("#store_id").val($("#storeId").val());
});
});
</script>
(3)通过传递store.id用来完成下拉框的回显
<table border="0" class="tx" width="100%">
<tr>
<td>当前位置>>首页>>货物库存</td>
<input type="hidden" id="storeId" value="${store.id }">
</tr>
</table>
第三步:设计服务器分页查询数据Bean
新建一个包cn.itcast.storemanager.page,创建类Pagination.java存放数据分页Bean。
思考:
Pagination要通用,所以引入泛型。
//分页bean
//封装请求和响应的相关数据
// * 业务层:要:业务条件+分页条件
// 业务层:提供:数据列表+分页结果(总记录数)
public class Pagination<T> {
}
传递参数:客户端会传过来哪些条件?
- 当前页:查询第几页,比如第二页,如果第一次,默认应该是第一页。
- 每页最多显示的记录数:该值可以是用户自定义的,也可以是系统默认的,我们这里默认为3。
- 查询的业务条件:用户在页面填写的参数条件,可以是单条件,也可以是组合的多条件。
返回数据:服务端可以返回哪些数据呢?
- 数据列表:根据客户端传过来的组合条件和页码查询出的数据list。
- 总页数:给客户端显示有多少页,或者根据这个总页数来判断客户端实际要显示多少个分页数字按钮。
- 总的记录数:可以给客户端显示,也用来计算总页数。
// 分页bean
// 封装请求和响应的相关数据
// 业务层:要:业务条件+分页条件
// 业务层:提供:数据列表+分页结果(总记录数)
public class Pagination<T> {
//--------请求的条件
private int page = 1;//当前页码,默认第一次页码是1
//业务条件
// private T t;//页面查询的业务条件,经常可能不是数据库的字段(实体类的属性)
// 获取所有参数的方法ServletActionContext.getRequest().getParameterMap()
private Map<String, String[]> parameterMap;//最灵活,可以封装任何的参数条件
//--------响应的结果
//不管用什么技术,所有的分页组合条件查询,都需要查询两次:一次总记录数,一次查询数据列表
//结果数据列表
private List<T> resultList;//数据库查询的结果集列表
private long totalCount;//总记录数,数据库查询的总记录数
private long totalPage;//总页码数(计算出来的,不是查询出来)
}
提示:需要添加getter和setter方法。
分析:
一般参数条件用map更灵活,可以放置任何条件。泛型参数类型的设计根据request.getParameterMap来设计,后台通过这个方法取到的数据直接封装到这里就行了。
代码完善:
计算总页数:
总页数=(总的记录数+每页最多显示的记录数-1)/每页最多显示的记录数。
public void setTotalCount(long totalCount) {
//计算一些东西
//计算总页码数:
totalPage=(totalCount+pageSize-1)/pageSize;
//计算页面的页码中"显示"的起始页码和结束页码
//一般显示的页码叫好的效果是最多显示10个页码
//算法是前5后4,不足补10
//计算显示的起始页码(根据当前页码计算):当前页码-5
begin = page-5;
if(begin<1){//页码修复
begin=1;
}
//计算显示的结束页码(根据开始页码计算):开始页码+9
end=begin+9;
if(end>totalPage){//页码修复
end=totalPage;
}
//起始页面重新计算(根据结束页码计算):结束页码-9
begin=end-9;
if(begin<1){
begin=1;
}
System.out.println(begin +"和" +end);
this.totalCount = totalCount;
}
每页第一条记录的索引:(新增方法)
//设置两个getter属性
//起始索引
public int getFirstResult(){
//计算起始索引
return (page-1)*pageSize;
}
每页要查询的最大记录数:(新增方法)
//最大记录数
public int getMaxResults(){
return pageSize;
}
第四步:编写Action代码,准备分页参数
在GoodsAction 中添加listPage方法
//分页列表综合查询
//思路:
/*
* 将业务条件和分页条件都封装到一个分页bean中,
* 将分页bean传递给业务层,处理逻辑(拼接sql,调用dao查询数据)
* 业务层:要:业务条件+分页条件
* 业务层:提供:数据列表+分页结果(总记录数)
* 所有数据,全部都封装分页bean中。
* 表现层:将条件装到bean,将已经有结果的bean,给页面(值栈),在页面显示
*
*
*/
public String listPage(){
//1.将条件装到分页bean中
//实例化一个分页bean
Pagination<Goods> pagination = new Pagination<Goods>();
//封装业务条件
pagination.setParameterMap(ServletActionContext.getRequest().getParameterMap());
//封装页码和最大记录数?page=2&pageSize=3
//思路:获取这两个参数:1。可以从参数中直接获取2。使用struts值栈来封装数据
//获取值可以使用:方案一:ServletActionContext.getRequest().getParameter("page");
//获取值可以使用:方案二:struts2的属性驱动
//如果第一次查询,则会出现page为0的情况
if(page>0){
pagination.setPage(page);
}
if(pageSize>=1){
pagination.setPageSize(pageSize);
}
//2.将分页bean传递给业务层,注意:业务层查询出来的数据,再封装回分页bean
//对象引用的知识点
//pagination=goodsService.findGoodsListPage(pagination);
goodsService.findGoodsListPage(pagination);
//3.将分页bean
//放入栈顶
result =pagenation ; //action的属性
//跳转到列表页面
return "remainjsp";
}
//值栈封装属性的值,使用属性驱动获取页面传递的当前页(page)和当前页最多存放的记录数(pageSize)
//action的初始化的,struts拦截器会自动调用同名属性(page--->SetPage(2))
private int page;//int默认是0
private int pageSize;
public void setPage(int page) {
this.page = page;
}
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
分析:Action类使用Pagination用来传递参数和获取返回结果
引用传值Pagination
第五步:分页代码Service、Dao实现 (方案一:先使用QBC查询)
分析:
任何分页查询技术在Dao层都必须查询两次:即:当前页的数据和总的记录数。
因此,我们的业务层service需要调用两次dao,dao中需要对应两个方法来满足需要(查总记录数和查当前页数据)。
(1)接口IGoodsService 代码
/**
* 分页条件综合查询
* @param pagination
*/
public void findGoodsListPage(Pagination<Goods> pagination);
(2)实现类GoodsServiceImpl类代码
//分页条件综合查询:
//从分页bean中取出条件,拼接sql条件,调用dao查询数据库,返回结果,封装回分页bean
public void findGoodsListPage(Pagination<Goods> pagination) {
//1.....条件的取出和拼接
//QBC方案
DetachedCriteria criteria = DetachedCriteria.forClass(Goods.class);//根查询,主查询对象
//先获取业务条件
Map<String, String[]> parameterMap = pagination.getParameterMap();
//添加业务条件:每个业务都不一样
//简记码
// String nm=parameterMap.get("nm")==null?null:parameterMap.get("nm")[0];
String nm = getValueFromParameterMap(parameterMap, "nm");
if(StringUtils.isNotBlank(nm)){
criteria.add(Restrictions.eq("nm", nm));
}
//货物名称
String name = getValueFromParameterMap(parameterMap, "name");
if(StringUtils.isNotBlank(name)){
criteria.add(Restrictions.like("name","%"+name+"%"));
}
//仓库:注意参数名称,
String storeId = getValueFromParameterMap(parameterMap, "store.id");
if(StringUtils.isNotBlank(storeId)){
//外键的条件---单表
//两种方式
//1.直接指定关联对象的id--外键:底层原理是第二种方式
criteria.add(Restrictions.eq("store.id", storeId));
//2。直接传对象,自动将对象主键作为外键
// Store store = new Store();
// store.setId(storeId);
// criteria.add(Restrictions.eq("store", store));
}
findListPage(pagination, criteria,goodsDao);
}
-
在BaseService类中,抽出来的取参数值方法:
//业务层的父类:用来复用公用代码
//抽象类
public abstract class BaseService<T,ID extends Serializable> {
//从参数map中获取参数值
protected String getValueFromParameterMap(Map<String, String[]> parameterMap,String key){
return parameterMap.get(key)==null?null:parameterMap.get(key)[0];
}
//分页条件查询
protected void findListPage(Pagination<T> pagination,
DetachedCriteria criteria,IGenericDao<T, ID> dao) {
//2.....调用dao查询数据和结果的封装
//2.1查询总记录数
long totalCount = dao.findCountByCriteria(criteria);
//直接封装到分页bean
pagination.setTotalCount(totalCount);
//2.2计算后,查询数据列表(分页查询)
// int firstResult=(pagination.getPage()-1)*pagination.getPageSize();//属于分页的业务,在分页bean中计算
//分析:结果集封装策略发生了变化,要做:重置结果集封装策略
//将投影设置为null
criteria.setProjection(null);
//将封装策略手动更改为ROOT_ENTITY
criteria.setResultTransformer(criteria.ROOT_ENTITY);
List<T> resultList = dao.findListPageByCriteria(criteria, pagination.getFirstResult(), pagination.getMaxResults());
//直接封装到分页bean中
pagination.setResultList(resultList);
}
}
第六步:分页代码Service、Dao实现 (方案一:先使用QBC查询)
(1)修改IGenericDao接口:添加两个方法
/**
* 查询记录数
* @param criteria
* @return
*/
public long findCountByCriteria(DetachedCriteria criteria);
/**
* 条件分页查询列表
* @param criteria
* @param firstResult
* @param maxResults
* @return
*/
public List<T> findListPageByCriteria(DetachedCriteria criteria, int firstResult, int maxResults);
-
实现类GenericDaoImpl类
//查询总记录数
public long findCountByCriteria(DetachedCriteria criteria) {
//添加记录数查询的投影
//投影查询,会改变默认的结果集封装策略为PROJECTION,而原来默认是ROOT_ENTITY
criteria.setProjection(Projections.rowCount());
//只能有一个元素:记录数
List<Long> list = getHibernateTemplate().findByCriteria(criteria);
return list.isEmpty()?0:list.get(0);
}
//查询当前分页的数据集合
public List<T> findListPageByCriteria(DetachedCriteria criteria,
int firstResult, int maxResults) {
return getHibernateTemplate().findByCriteria(criteria, firstResult, maxResults);
}
第七步:struts.xml文件的配置:
<!-- 货物管理 -->
<action name="goods_*" class="goodsAction" method="{1}">
<!-- json无需配置结果集了 -->
<!--入库 -->
<result name="savejsp" type="redirect">/jsps/save/save.jsp</result>
<!-- 列表页面 -->
<result name="remainjsp">/jsps/store/remain.jsp</result>
</action>
编写JSP显示分页查询结果数据
分两部分实现:
- 分页数据列表的显示
- 分页工具条的显示
表格分页数据显示
在remain.jsp中遍历结果集
<table class="store">
<tr style="background:#D2E9FF;text-align: center;">
<td>简记码</td>
<td>名称</td>
<td>计量单位</td>
<td>库存数量</td>
<td>所在仓库</td>
<td>操作</td>
</tr>
<s:iterator value="result.resultList" >
<tr>
<td><s:property value="nm"/> </td>
<td><s:property value="name"/></td>
<td><s:property value="unit"/></td>
<td><s:property value="amount"/></td>
<td><s:property value="store.name"/></td>
<td>
<a href="<c:url value='/jsps/save/save.jsp'/>">入库</a>
<a href="<c:url value='/jsps/out/out.jsp'/>">出库</a>
<a href="<c:url value='/jsps/his/his.jsp'/>">历史记录</a>
</td>
</tr>
</s:iterator>
</table>
测试:数据是否能正常显示。
分页流程梳理 :
- 页面提交请求 (页码、 每页记录数、 查询条件 )
- 设计分页数据Bean 接收参数 Pagination
- 编写Action,将Pagination传递给Service业务层,让业务层进行查询和封装Pagination(引用传递,不需要返回)。
- 业务层service从Pagination中拿到请求参数,封装DetachedCriteria或拼接SQL语句,进行查询出总记录数和当页的数据集合,再将结果封装回Pagination。
- 此时,数据层Dao对应两次查询(总记录数、 当前页数据 )
- 将查询结果传递JSP页面 (表格数据显示、 分页工具条显示 )
分页工具条显示(了解)
页码效果分析:(前五后四)
第一步:在Pagination.java中定义分页的逻辑
(1)在Pagination 添加开始页码begin和结束页码end:
//工具条使用的结果
private long begin;//起始页码数
private long end;//结束的页码数
public long getBegin() {
return begin;
}
public void setBegin(long begin) {
this.begin = begin;
}
public long getEnd() {
return end;
}
public void setEnd(long end) {
this.end = end;
}
-
在Pagination.java中找个位置计算获取begin和end的值:
public void setTotalCount(long totalCount) {
//计算一些东西
//计算总页码数:
totalPage=(totalCount+pageSize-1)/pageSize;
//计算页面的页码中"显示"的起始页码和结束页码
//一般显示的页码叫好的效果是最多显示10个页码
//算法是前5后4,不足补10
//计算显示的起始页码(根据当前页码计算):当前页码-5
begin = page-5;
if(begin<1){//页码修复
begin=1;
}
//计算显示的结束页码(根据开始页码计算):开始页码+9
end=begin+9;
if(end>totalPage){//页码修复
end=totalPage;
}
//起始页面重新计算(根据结束页码计算):结束页码-9
begin=end-9;
if(begin<1){
begin=1;
}
System.out.println(begin +"和" +end);
this.totalCount = totalCount;
}
-
在Pagination.java中添加计算页面获取查询条件参数字符串方法,点击首页、上一页、下一页、末页、具体页需要传递查询条件。
//获取条件的字符串标识形式:map解析为字符串&name=xxx&age=xxx
public String getParameterStr(){
String paramterStr="";
Set<String> keySet = parameterMap.keySet();
for (String key : keySet) {
//排除page和pageSize参数
if(!key.equals("page")&&!key.equals("pageSize")){
String[] values = parameterMap.get(key);
if(values!=null &&StringUtils.isNotBlank(values[0])){
paramterStr+="&"+key+"="+values[0];
}
}
}
return paramterStr;
}
页面显示分页工具条
第二步:在remain.jsp中添加分页条,由于分页操作属于超链接,需要使用Get请求的方式传递值
<div align="right">
<!-- 显示页码 -->
<!-- 首页和上一页
当前页码是否大于1显示
-->
<s:if test="result.page>1">
<a href="${pageContext.request.contextPath }/goods_listPage.action?page=1${result.parameterStr}">首页</a>
<a href="${pageContext.request.contextPath }/goods_listPage.action?page=${result.page-1}${result.parameterStr}">上一页</a>
</s:if>
<!-- 中间页码 -->
<s:iterator begin="result.begin" end="result.end" var="num">
<a href="${pageContext.request.contextPath }/goods_listPage.action?page=${num}${result.parameterStr}">[${num}]</a>
</s:iterator>
<!-- 下一页和尾页 -->
<s:if test="result.page<result.totalPage">
<a href="${pageContext.request.contextPath }/goods_listPage.action?page=${result.page+1}${result.parameterStr}">下一页</a>
<a href="${pageContext.request.contextPath }/goods_listPage.action?page=${result.totalPage}${result.parameterStr}">尾页</a>
</s:if>
<input type="text" size="2" name="page"/>
<input type="button" value="go" size="2" />
</div>
在remain.jsp中,代码完善和效果优化:中间页面的显示添加样式
<!-- 中间页码遍历 -->
<s:iterator begin="result.begin" end="result.end" var="num">
<a href="${pageContext.request.contextPath }/goods_listPage.action?page=${num}${result.parameterStr}">
<%--[${num}]--%>
<s:if test="#num==1">
<span style="color:red">[${num}]</span>
</s:if>
<s:else>
<span style="color:blue">[${num}]</span>
</s:else>
</a>
</s:iterator>
第三步:查询条件乱码问题:
输入货物名称,会产生乱码
原因:因为页码上请求参数使用 ?拼接,中文会使用get方式提交,参数会出现中文乱码。
解决方案:
(1) 修改或新增tomcat中的server.xml 的编码为:URIEncoding="UTF-8"
原因:tomcat的编码iso8859-1,导致从tomcat获取数据的时候,编码不一致(程序用utf-8)
tomcat8:底层编码已经变成utf-8,但是我们使用的是tomcat7
(2) 手动编码, GenericEncodingFilter (过滤器可参考课前资料)
GenericEncodingFilter.java 代码:
/**
* 解决get和post请求 全部乱码
*
*/
public class GenericEncodingFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// 转型为与协议相关对象
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
// 对request包装增强
HttpServletRequest myrequest = new MyRequest(httpServletRequest);
chain.doFilter(myrequest, response);
}
public void init(FilterConfig filterConfig) throws ServletException {
}
public void destroy() {
}
}
// 自定义request对象
class MyRequest extends HttpServletRequestWrapper {
private HttpServletRequest request;
private boolean hasEncode;
public MyRequest(HttpServletRequest request) {
super(request);// super必须写
this.request = request;
}
// 对需要增强方法 进行覆盖
public Map getParameterMap() {
// 先获得请求方式
String method = request.getMethod();
if (method.equalsIgnoreCase("post")) {
// post请求
try {
// 处理post乱码
request.setCharacterEncoding("utf-8");
return request.getParameterMap();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
} else if (method.equalsIgnoreCase("get")) {
// get请求
Map<String, String[]> parameterMap = request.getParameterMap();
if (!hasEncode) { // 确保get手动编码逻辑只运行一次
for (String parameterName : parameterMap.keySet()) {
String[] values = parameterMap.get(parameterName);
if (values != null) {
for (int i = 0; i < values.length; i++) {
try {
// 处理get乱码
values[i] = new String(values[i]
.getBytes("ISO-8859-1"), "utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
}
hasEncode = true;
}
return parameterMap;
}
return super.getParameterMap();
}
@Override
public String getParameter(String name) {
Map<String, String[]> parameterMap = getParameterMap();
String[] values = parameterMap.get(name);
if (values == null) {
return null;
}
return values[0]; // 取回参数的第一个值
}
public String[] getParameterValues(String name) {
Map<String, String[]> parameterMap = getParameterMap();
String[] values = parameterMap.get(name);
return values;
}
}
-
在web.xml设置过滤器的配置
<!-- 乱码过滤器 -->
<filter>
<filter-name>GenericEncodingFilter</filter-name>
<filter-class>cn.itcast.storemanager.web.filter.GenericEncodingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>GenericEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
注意:乱码过滤器要往前面放,放置到struts2的过滤器的前面。
注意:tomcat编码和过滤器编码两者不要同时使用。
分页逻辑小结
讲解两个问题:
(1)分页逻辑梳理:
(2)服务器端 分页代码逻辑
使用SQL拼接方式,实现库存查询(课后)
目标:将service和dao中增加或修改sql方式的实现代码。
(1)修改:业务Service实现层:
(2)通用数据层IGenericDao接口:
-
通用GenericDaoImpl类的实现
【1】查询总记录数
【2】使用sql语句查询符合条件的分页记录
分页代码重构优化
重构优化的思路:
- Action封装分页相关的请求参数 PaginationBean ----> 抽取BaseAction类
- Service 将参数封装DetachedCriter/ SQL ----> 不能优化 (根据业务)
- Service调用Dao 查询totalCount 和 pageData ----> 抽取BaseService类
- Dao 查询totalCount和 pageData 代码就是通用代码---->GenericDaoImpl类
- Action的数据封装
【1】BaseAction类:
//action的父类:用来存放action的重复代码的
//通用:泛型
public abstract class BaseAction<T> extends ActionSupport implements ModelDriven<T>{
//实例化数据模型T,模型驱动必须实例化
// private T t = new T();
//子类可见
protected T model;//没有初始化
public T getModel() {
return model;
}
//在默认的构造器初始化数据模型
public BaseAction() {
//在子类初始化的时候,默认会调用父类的构造器
//反射机制:获取具体的类型
//得到带有泛型的类型,如BaseAction<Userinfo>
Type superclass = this.getClass().getGenericSuperclass();
//转换为参数化类型
ParameterizedType parameterizedType = (ParameterizedType) superclass;
//获取泛型的第一个参数的类型类,如Userinfo
Class<T> modelClass = (Class<T>) parameterizedType.getActualTypeArguments()[0];
//实例化数据模型类型
try {
model = modelClass.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
//封装值栈的操作的方法
//root栈:栈顶map,可以通过key获取value
protected void setToValueStackRoot(String key,Object value){
ActionContext.getContext().getValueStack().set(key, value);
}
//root栈:栈顶对象(匿名)
protected void pushToValueStackRoot(Object value){
ActionContext.getContext().getValueStack().push(value);
}
//map栈:--推荐,可以通过key获取value
protected void putToValueStackMap(String key,Object value){
ActionContext.getContext().put(key, value);
}
//root栈:action的属性
protected Object result;
public Object getResult() {//action在root栈,因此,result也在root栈
return result;
}
}
- Service 代码
将通用分页查询代码,抽取BaseService
//业务层的父类:用来复用公用代码
//抽象类
public abstract class BaseService<T,ID extends Serializable> {
//从参数map中获取参数值
protected String getValueFromParameterMap(Map<String, String[]> parameterMap,String key){
return parameterMap.get(key)==null?null:parameterMap.get(key)[0];
}
//分页条件查询
protected void findListPage(Pagination<T> pagination,
DetachedCriteria criteria,IGenericDao<T, ID> dao) {
//2.....调用dao查询数据和结果的封装
//2.1查询总记录数
long totalCount = dao.findCountByCriteria(criteria);
//直接封装到分页bean
pagination.setTotalCount(totalCount);
//2.2计算后,查询数据列表(分页查询)
// int firstResult=(pagination.getPage()-1)*pagination.getPageSize();//属于分页的业务,在分页bean中计算
//分析:结果集封装策略发生了变化,要做:重置结果集封装策略
//将投影设置为null
criteria.setProjection(null);
//将封装策略手动更改为ROOT_ENTITY
criteria.setResultTransformer(criteria.ROOT_ENTITY);
List<T> resultList = dao.findListPageByCriteria(criteria, pagination.getFirstResult(), pagination.getMaxResults());
//直接封装到分页bean中
pagination.setResultList(resultList);
}
}
3:Dao代码,封装了操作数据库通用的CRUD方法
//通用dao的实现
//hibernate模版类操作,继承daosupport类
public class GenericDaoImpl<T,ID extends Serializable> extends HibernateDaoSupport implements IGenericDao<T, ID> {
//保存对象
public void save(Object domain) {
getHibernateTemplate().save(domain);
}
//修改对象
public void update(Object domain) {
getHibernateTemplate().update(domain);
}
//删除对象
public void delete(Object domain) {
getHibernateTemplate().delete(domain);
}
//使用主键ID查询
public T findById(Class<T> domainClass, ID id) {
return getHibernateTemplate().get(domainClass, id);
}
//查询所有
public List<T> findAll(Class<T> domainClass) {
return getHibernateTemplate().loadAll(domainClass);
}
//命名查询
public List<T> findByNamedQuery(String queryName, Object... values) {
return getHibernateTemplate().findByNamedQuery(queryName, values);
}
//QBC
public List<T> findByCriteria(DetachedCriteria criteria) {
return getHibernateTemplate().findByCriteria(criteria);
}
//查询总记录数
public long findCountByCriteria(DetachedCriteria criteria) {
//添加记录数查询的投影
//投影查询,会改变默认的结果集封装策略为PROJECTION,而原来默认是ROOT_ENTITY
criteria.setProjection(Projections.rowCount());
//只能有一个元素:记录数
List<Long> list = getHibernateTemplate().findByCriteria(criteria);
return list.isEmpty()?0:list.get(0);
}
//查询当前分页的数据集合
public List<T> findListPageByCriteria(DetachedCriteria criteria,
int firstResult, int maxResults) {
return getHibernateTemplate().findByCriteria(criteria, firstResult, maxResults);
}
}
公共代码封装打包(了解)
企业开发小技巧(多学一招):
一般,企业中会将核心的或公共代码进行打包封装,然后让其他项目去引用(保护源码)。
目标:将公共代码 ,生成jar包, 后期直接导入去使用
准备工作:先将未打包的代码打包一份备份。
新建java项目storemanager-core,将原来项目中与业务无关的公用代码和依赖的jar都copy到这个工程中。
现在开始打包:
(1)选择项目,右键,点击Export。
(2)选择JAR file
配置JAR Export
导出的jar:在E盘目录下,发现storemanager-core.jar
原项目不需要这么多代码 ,只需要引用基础包
复制一个storemanager项目,命名为storemanager-new,删除公用的代码,即刚刚抽取的代码
发现报错了,我们导入storemanager-core.jar包
没有错误了,我们尝试发布一下!右键项目,点击Web,修改Web Context-root为storemanager-new,表示我们发布的项目为storemanager-new
访问页面:http://localhost:8080/storemanager-new
最后,访问页面,进行测试,一切OK。
【问题】
如何查询源代码呢?
重复刚才的步骤:导出源码包
(1)选择项目,右键,点击Export。
(2)选择JAR file
配置JAR Export(选择第三个,Export Java Source files and resources)
导出的源码jar:在E盘目录下,发现storemanager-core-resource.jar
测试,可以关联代码。
重点:
- 第一天的所有内容(必须)
- 第二天上午内容(必须)
- 分页(会写即可)
- 理解如何编程!(开发流程、代码抽取、复用、重构)
SSH综合练习-仓库管理系统-第二天的更多相关文章
-
SSH综合练习-第1天
SSH综合练习-仓库管理系统-第一天 综合练习的整体目的: 整合应用 Struts2 .Hibernate.Spring .Mysql . jQuery Ajax.java基础知识 熟悉企业SSH 基 ...
-
EL&;Filter&;Listener:EL表达式和JSTL,Servlet规范中的过滤器,Servlet规范中的监听器,观察着设计模式,监听器的使用,综合案例学生管理系统
EL&Filter&Listener-授课 1 EL表达式和JSTL 1.1 EL表达式 1.1.1 EL表达式介绍 *** EL(Expression Language):表达式语言 ...
-
PDA无线数据采集器在仓库管理系统中的应用
条码数据采集器在仓库管理系统中的应用,条码数据采集器,顾名思义就是通过扫描货物条码,对货物进行数量分类采集,方便仓库正规化管理.条码数据采集器是现代化条码仓库管理系统中不可缺少的一部分,能提升企业的整 ...
-
吉特仓库管理系统- 斑马打印机 ZPL语言的腐朽和神奇
上一篇文章说到了.NET中的打印机,在PrintDocument类也暴露一些本质上上的问题,前面也提到过了,虽然使用PrintDcoument打印很方便.对应条码打印机比如斑马等切刀指令,不依赖打印机 ...
-
QT 仓库管理系统 开放源代码
IT 要走多久,要怎么走. IT 要走多久,要怎么走.这些问题,在我已经快毕业了一个年头的如今,又又一次浮如今我的脑海里.一边是工作的了了模块,一边是能够自己无聊打发的时间.这不是我当初要的路,如今的 ...
-
蓝缘管理系统第二个版本号开源了。springMVC+springSecurity3.x+Mybaits3.x 系统
蓝缘管理系统第二个版本号开源了 继于 http://blog.csdn.net/mmm333zzz/article/details/16863543 版本号一.版本号二 对springMVC+spri ...
-
基于SSH框架的考勤管理系统的设计与实现
基于SSH框架的考勤管理系统的设计与实现
-
CAE医疗综合视听中心管理系统
http://caehealthcare.com/eng/audiovisual-solutions/learning-space https://vimeo.com/108897296http:// ...
-
吉特仓库管理系统(开源)-如何在网页端启动WinForm 程序
在逛淘宝或者使用QQ相关的产品的时候,比如淘宝我要联系店家点击旺旺图标的时候能够自动启动阿里旺旺进行聊天.之前很奇怪为什么网页端能够自动启动客户端程序,最近在开发吉特仓储管理系统的时候也遇到一个类似的 ...
随机推荐
-
ip二进制计算,与运算算网段
每8位二进制,各位从左到右对应的权值分别是 128,64,32,16, 8,4,2,1 .(即2的n-1次方,n是从右到左当前位的位数) 所以随便拿一个256以内的数给你化为二进制,都可以分解为权值 ...
-
objdump 分析
objdump -H 显示如下: 一般常用的是 objdump -x 显示文件头信息 objdump -d 反汇编代码段代码 objdump -D 反汇编所有代码 用法:objdump <选项& ...
-
kafka生产消息的速度跟什么有关?
kafka的吞吐量很大,在保证带宽的情况下,网上的一些测试表明3台broker,没有replication,6个partition的情况下,一般的写入速度可以达到300MB/s.参考:kakfa测试 ...
-
iOS高性能图片架构与设计
版权声明:本文由柯灵杰原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/157 来源:腾云阁 https://www.qclo ...
-
使用Web Application Stress Tool 进行压力测试
1.在测试客户端机器上启动Web Application Stress Tool,在弹出的“建立新脚本”对话框中选择“Record”按钮: 2.在“Record”参数设置第一步中,所有的checkbo ...
-
ue4音效、动画结合实例
在游戏中,许多音效需要在动画恰当的时机出现,例如行走.奔跑,就需要恰好在足部落地瞬间播放. 而AnimNotify就能非常方便地处理此类问题. AnimNotify,顾名思义就是动画通知,能在特定的动 ...
-
开源博客系统使用springmvc
https://github.com/Zephery/newblog http://www.wenzhihuai.com/index.html
-
wsgi&;nginx-理解
WSGI协议 首先弄清下面几个概念:WSGI:全称是Web Server Gateway Interface,WSGI不是服务器,python模块,框架,API或者任何软件,只是一种规范,描述web ...
-
PHP遍历数组常用方式(for,foreach,while,指针等等)
1使用for循环遍历数组 count($arr)用于统计数组元素个数 for循环只能用于遍历,纯索引数组!!如果存在关联数组,count统计两种数组的总个数 使用for ...
-
微服务之springCloud-docker-comsumer(三)
简介 上一节,我们讲了创建spring cloud生产者,并利用docker-compose部署到swarm集群中,这节我们讨论一下最restTemlate调用生产者服务 一.创建模块(micros ...