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

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({

                //形式参数,不是servletapi

                //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({

    //形式参数,不是servletapi

                //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 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);        

        

    }

 

  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. 理解如何编程!(开发流程、代码抽取、复用、重构)
posted @ 2017-01-10 22:22  beyondcj  阅读(785)  评论(0编辑  收藏  举报