zoukankan      html  css  js  c++  java
  • 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="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="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="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. 理解如何编程!(开发流程、代码抽取、复用、重构)
  • 相关阅读:
    事件的解密
    C#世界中的委托
    这次是C#中的接口
    完全二叉树的建立和翻转
    全排列的应用
    网易笔试-按位或运算
    柱状图的最大矩形--单调栈
    Linux将线程绑定到CPU内核运行
    Windows多线程与线程绑定CPU内核
    B+树介绍
  • 原文地址:https://www.cnblogs.com/beyondcj/p/6271066.html
Copyright © 2011-2022 走看看