SSH综合练习-仓库管理系统-第二天
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> {
}
传递参数:客户端会传过来哪些条件?
-
当前页:查询第几页,比如第二页,如果第一次,默认应该是第一页。
-
查询的业务条件:用户在页面填写的参数条件,可以是单条件,也可以是组合的多条件。
返回数据:服务端可以返回哪些数据呢?
-
数据列表:根据客户端传过来的组合条件和页码查询出的数据list。
-
总页数:给客户端显示有多少页,或者根据这个总页数来判断客户端实际要显示多少个分页数字按钮。
-
总的记录数:可以给客户端显示,也用来计算总页数。
// 分页bean
// 封装请求和响应的相关数据
// 业务层:要:业务条件+分页条件
// 业务层:提供:数据列表+分页结果(总记录数)
public class Pagination<T> {
//--------请求的条件
private int page = 1;//当前页码,默认第一次页码是1
private int pageSize = 3;//每页最大记录数,我们的这个业务没有,默认给出3
//业务条件
// 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实现层:
-
-
通用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到这个工程中。
现在开始打包:
(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
测试,可以关联代码。
重点:
-
第一天的所有内容(必须)
-
第二天上午内容(必须)
-
分页(会写即可)
-
理解如何编程!(开发流程、代码抽取、复用、重构)
-