SSH综合练习-仓库管理系统-第二天

今天的主要内容:

  1. 货物入库
    1. 页面信息自动补全回显功能:(学习目标:练习Ajax交互)
  • 根据货物简记码来自动查询回显已有货物(Ajax回显)
  • 根据货物名来自动查询补全已有货物(自动补全插件)
  1. 货物入库功能:(学习目标:练习多表插入)
  • 保存货物信息的同时记录入库的历史记录。
  • 更新货物信息的同时记录入库的历史记录。
  1. 库存管理功能(分页+多条件)
    1. 分页数据Bean的设计
    2. 要编写Pagination Bean
    3. 分页后台的代码编码
  • QBC方案—Hibernate做法
  • SQL拼接方案—传统做法(选做)
  1. 分页工具条的编写(了解)
  2. 分页代码重构优化(抽取分页代码)
  1. 历史记录查询(课后作业,涉及到多表)
  2. 出库功能(课后作业,自己思考逻辑,和入库逻辑差不多)
  3. 公共代码封装打包(了解)

课程目标:

  1. Ajax的使用和多表的插入,强化复习
  2. jQueryUI的插件的使用方法。自动补全查询。
  3. 业务条件+分页条件的综合查询
  1. 货物入库功能

    1. 根据简记码查询货物(精确匹配)

点击【入库】

业务分析:

简记码是为了方便用户快速定位已存在的货物而设计的。需要分析两个方面:

  1. 关于简记码回显的使用。

    当用户输入简记码时,通过Ajax请求,将用户输入的简记码发送到服务器,服务器判断简记码是否存在。

  • 如果存在,则说明货物也存在,则查询出货物信息,回显给表单。当点击"入库"的时候,进行更新货物的数量即可。
  • 如果不存在,则说明数据库中没有对应的货物,这是一个新的货物,需要手动输入货物的完整信息。当点击"入库"的时候,进行保存货物的所有信息。

2. 关于同一个按钮功能是走更新还是保存,服务器端如何判断调用什么方法呢?

通过主键id来判断。因此,则需要在form表单中放置一个隐藏域id,如果货物存在,则id有值,则后台只更新货物数量即可;如果货物不存在,则id没值,则插入保存一个新的货物。

开发思路:

业务一:校验数据库简记码是否存在

  1. 改造页面表单为struts标签(因为要回显一些信息)
  2. 编写页面的Ajax请求代码(事件代码)
  3. 编写后台服务器端代码,货物的数据封装为json返回到前台(fireBug调试)
  4. 完善页面的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

解决方法: 货物关联的仓库的数据是需要的,不能排除,需要加载

两种方式:

  1. 将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>

  1. 配置 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);

}

});

});

  1. 根据货物名称查询 (模糊匹配,自动补全)

目标效果参考:百度

联想提示。。。

  1. 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文件,里面有开发案例

  1. 应用一:本地数据自动补全

方法是:对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>

页面效果:

缺点:不适用于大规模数据,页面要加载全部的数据。

  1. 应用二:动态数据补全 (远程数据加载实时补全)

根据用户输入内容,实时查询,实现数据的补全。

方法是:对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";

}

第四步:业务层

  1. 接口IGoodsService类

    /**

    * 根据名称模糊查询货物列表

    * @param name

    * @return

    */

    public List<Goods> findGoodsByNameLike(String name);

  2. 实现类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);

    },

    });

    1. 货物入库功能(服务器端实现 )

    要点:

    1. 逻辑上:到底是更新还是保存?根据隐藏域的id来判断。
    2. 数据操作上:多表插入的(关联属性使用,历史记录)、快照更新

    分析:提交入库表单,需要实现商品入库逻辑(更新或保存)和操作历史记录

    第一步: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);

  3. 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);

    }

    }

    第四步:封装优化操作,简化代码的行数,将公用的方法提取出来:

  4. Dataconstant.java,用来封装常量,统一维护常量

    public class DataConstant {

    //入库常量

    public static final String OPER_TYPE_IN="1";

    //出库常量

    public static final String OPER_TYPE_OUT="2";

    }

  5. 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的注入
  1. 库存管理功能

    1. 分页数据Bean的设计思路

分析几种查询(从查询的角度来说):

  • 条件查询:多个条件组合查询,需要将用户输入的条件,根据判断,拼接条件(sql:where条件的SQL语句。qbc:离线条件拼装)
  • 分页查询:需要得到要查询记录的索引和要查询的条数(mysql:limit 起始索引,最大记录数);或者需要得到查询记录的起始和结束的行数(Oracle)。
  • 条件查询+分页查询:需要在分页查询过程中记录原来的查询条件(url?+条件),原理见下图:

关键点(设计思想):查询条件(业务条件和分页条件)在客户端和服务器端来回传递。--封装一个bean对象(分页bean)用于存储条件。 和返回结果使用。

  1. 分页数据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>

  1. 添加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>当前位置&gt;&gt;首页&gt;&gt;货物库存</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);

}

  1. 在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);

  2. 实现类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>

    1. 编写JSP显示分页查询结果数据

    分两部分实现:

  • 分页数据列表的显示
  • 分页工具条的显示
  1. 表格分页数据显示

在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>

测试:数据是否能正常显示。

分页流程梳理 :

  1. 页面提交请求 (页码、 每页记录数、 查询条件 )
  2. 设计分页数据Bean 接收参数 Pagination
  3. 编写Action,将Pagination传递给Service业务层,让业务层进行查询和封装Pagination(引用传递,不需要返回)。
  4. 业务层service从Pagination中拿到请求参数,封装DetachedCriteria或拼接SQL语句,进行查询出总记录数和当页的数据集合,再将结果封装回Pagination。
  5. 此时,数据层Dao对应两次查询(总记录数、 当前页数据 )
  6. 将查询结果传递JSP页面 (表格数据显示、 分页工具条显示 )
  1. 分页工具条显示(了解)

页码效果分析:(前五后四)

第一步:在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;

}

  1. 在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;

    }

  2. 在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;

    }

    }

  3. 在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. 分页逻辑小结

    讲解两个问题:

    (1)分页逻辑梳理:

    (2)服务器端 分页代码逻辑

    1. 使用SQL拼接方式,实现库存查询(课后)

    目标:将service和dao中增加或修改sql方式的实现代码。

    (1)修改:业务Service实现层:

    (2)通用数据层IGenericDao接口:

  4. 通用GenericDaoImpl类的实现

    【1】查询总记录数

    【2】使用sql语句查询符合条件的分页记录

    1. 分页代码重构优化

    重构优化的思路:

    1. Action封装分页相关的请求参数 PaginationBean ----> 抽取BaseAction类
    2. Service 将参数封装DetachedCriter/ SQL ----> 不能优化 (根据业务)
    3. Service调用Dao 查询totalCount 和 pageData ----> 抽取BaseService类
    4. Dao 查询totalCount和 pageData 代码就是通用代码---->GenericDaoImpl类
    1. 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;

    }

    }

    1. 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);

    }

    }

    1. 公共代码封装打包(了解)

    企业开发小技巧(多学一招):

    一般,企业中会将核心的或公共代码进行打包封装,然后让其他项目去引用(保护源码)。

    目标:将公共代码 ,生成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

    测试,可以关联代码。

    重点:

    1. 第一天的所有内容(必须)
    2. 第二天上午内容(必须)
    3. 分页(会写即可)
    4. 理解如何编程!(开发流程、代码抽取、复用、重构)

SSH综合练习-仓库管理系统-第二天的更多相关文章

  1. SSH综合练习-第1天

    SSH综合练习-仓库管理系统-第一天 综合练习的整体目的: 整合应用 Struts2 .Hibernate.Spring .Mysql . jQuery Ajax.java基础知识 熟悉企业SSH 基 ...

  2. EL&Filter&Listener:EL表达式和JSTL,Servlet规范中的过滤器,Servlet规范中的监听器,观察着设计模式,监听器的使用,综合案例学生管理系统

    EL&Filter&Listener-授课 1 EL表达式和JSTL 1.1 EL表达式 1.1.1 EL表达式介绍 *** EL(Expression Language):表达式语言 ...

  3. PDA无线数据采集器在仓库管理系统中的应用

    条码数据采集器在仓库管理系统中的应用,条码数据采集器,顾名思义就是通过扫描货物条码,对货物进行数量分类采集,方便仓库正规化管理.条码数据采集器是现代化条码仓库管理系统中不可缺少的一部分,能提升企业的整 ...

  4. 吉特仓库管理系统- 斑马打印机 ZPL语言的腐朽和神奇

    上一篇文章说到了.NET中的打印机,在PrintDocument类也暴露一些本质上上的问题,前面也提到过了,虽然使用PrintDcoument打印很方便.对应条码打印机比如斑马等切刀指令,不依赖打印机 ...

  5. QT 仓库管理系统 开放源代码

    IT 要走多久,要怎么走. IT 要走多久,要怎么走.这些问题,在我已经快毕业了一个年头的如今,又又一次浮如今我的脑海里.一边是工作的了了模块,一边是能够自己无聊打发的时间.这不是我当初要的路,如今的 ...

  6. 蓝缘管理系统第二个版本号开源了。springMVC+springSecurity3.x+Mybaits3.x 系统

    蓝缘管理系统第二个版本号开源了 继于 http://blog.csdn.net/mmm333zzz/article/details/16863543 版本号一.版本号二 对springMVC+spri ...

  7. 基于SSH框架的考勤管理系统的设计与实现

    基于SSH框架的考勤管理系统的设计与实现

  8. CAE医疗综合视听中心管理系统

    http://caehealthcare.com/eng/audiovisual-solutions/learning-space https://vimeo.com/108897296http:// ...

  9. git的介绍、git的功能特性、git工作流程、git 过滤文件、git多分支管理、远程仓库、把路飞项目传到远程仓库(非空的)、ssh链接远程仓库,协同开发

    Git(读音为/gɪt/)是一个开源的分布式版本控制系统,可以有效.高速地处理从很小到非常大的项目版本管理. [1] 也是Linus Torvalds为了帮助管理Linux内核开发而开发的一个开放源码 ...

随机推荐

  1. React入门实例

    前言 React 的核心思想是:封装组件,各个组件维护自己的状态和UI,当状态变更,自动重新渲染整个组件. 理解:react首先值得拍手称赞的是它所有的开发都基于一个组件(component),组件和 ...

  2. java框架之struts2简介

    一.Struts2简介 1.Struts2概述                    Struts2是Apache发行的MVC开源框架.注意:它只是表现层(MVC)框架. M:model-----数据 ...

  3. node.JS中配置http-server

    http-server 是一个简单的HTTP服务器, 基于 nodeJs,在nodejs命令行中配置http服务器. 项目结构:

  4. itoa()函数和atoi()函数

     1.int/float to string/array: C语言提供了几个标准库函数,可以将任意类型(整型.长整型.浮点型等)的数字转换为字符串,下面列举了各函数的方法及其说明.● itoa():将 ...

  5. Meterpreter

    监听 AutoRunScrip:自动执行脚本 如:自动执行post/windows/manage/migrate set AutoRunScript post/windows/manage/migra ...

  6. D3.js:Update、Enter、Exit

    Update.Enter.Exit 是 D3 中三个非常重要的概念,它处理的是当选择集和数据的数量关系不确定的情况. 如果数组为 [3, 6, 9, 12, 15],将此数组绑定到三个 p 元素的选择 ...

  7. [Q]手动加载菜单方法

    一般情况下,安装程序会自动安装依云软件菜单,但可能由于某些原因未能自动安装的话,您可以手动加载菜单,步骤如下: 在AoutCAD命令行输入"CUILOAD",会弹出"加载 ...

  8. Netty(7)源码-ByteBuf

    一.ByteBuf工作原理 1. ByteBuf是ByteBuffer的升级版: jdk中常用的是ByteBuffer,从功能角度上,ByteBuffer可以完全满足需要,但是有以下缺点: ByteB ...

  9. 第 2 章 Node.js 中的交互式运行环境 —— REPL

    本章内容包括: 如何使用REPL运行环境以及如何在该运行环境中测试各种JavaScript表达式. 如何定义并启动REPL运行环境. Node.js 框架中为REPL运行环境提供了哪些命令以及这些命令 ...

  10. IOS 加载网络图片2

    //1. NSData dataWithContentsOfURL // [self.icon setImage:[UIImage imageWithData:[NSData dataWithCont ...