85-002-18 商城项目中商品模块的查询与增加

来源:互联网 发布:windows优化大师64位 编辑:程序博客网 时间:2024/05/22 12:58

图文版:http://note.youdao.com/yws/public/redirect/share?id=abc20790e48da5130e227d9390e4df92&type=false
资源文件下载 https://yunpan.cn/OcRaTMHR3NWIIf  访问密码 4b2c


18.1 上面已经实现了类别的功能模块,下面再实现商品模块,先定义前台页面的一个文件夹,专门存放商品页面

18.2 编写数据库sql语句
    shop.sql
DROP DATABASE  IF EXISTS shop ;
CREATE DATABASE shop DEFAULT CHARACTER SET UTF8 ;
USE shop ;
DROP TABLE IF EXISTS account ;
DROP TABLE IF EXISTS category ;
DROP TABLE IF EXISTS product ;
CREATE TABLE account(
 id INT NOT NULL AUTO_INCREMENT,
 login VARCHAR(20),
 name VARCHAR(20),
 pass VARCHAR(20),
 PRIMARY KEY(id)
);
CREATE TABLE category(
 id INT NOT NULL AUTO_INCREMENT,
 type VARCHAR(20),
 hot BOOL DEFAULT false,
 aid INT,
 PRIMARY KEY(id)
);
CREATE TABLE product(
 id INT NOT NULL AUTO_INCREMENT,
 name VARCHAR(20),
 price DECIMAL(8,2),
 pic VARCHAR(200), --商品图片
 remark LONGTEXT, --商品简单介绍
 xremark LONGTEXT, --商品详细介绍
 date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, --生产日期
 commend BOOL, --是否是推荐商品
 open BOOL, --是否为有效商品
 cid INT, --所属类别
 PRIMARY KEY(id)
);
INSERT INTO account(login,name,pass) VALUES('admin1','管理员1','admin1');
INSERT INTO account(login,name,pass) VALUES('admin2','管理员2','admin2');
INSERT INTO category(type,hot,aid) VALUES('男士休闲',true,1);
INSERT INTO category(type,hot,aid) VALUES('女士休闲',true,1);
INSERT INTO category(type,hot,aid) VALUES('儿童休闲',true,2);
INSERT INTO category(type,hot,aid) VALUES('老人休闲',true,2);
INSERT INTO product(name,price,pic,remark,xremark,commend,open,cid)
VALUES('圣得西服',2999.00,'test.jpg','简单介绍','详细介绍',true,true,1);
INSERT INTO product (name,price,pic,remark,xremark,commend,open,cid) VALUES
('罗蒙西服',1999.00,'test.jpg','这里是简单介绍','这里是详细介绍',true,true,1);
INSERT INTO product (name,price,pic,remark,xremark,commend,open,cid) VALUES
('衫衫西服',3999.00,'test.jpg','这里是简单介绍','这里是详细介绍',true,true,1);
INSERT INTO product (name,price,pic,remark,xremark,commend,open,cid) VALUES
('金利来西服',4999.00,'test.jpg','这里是简单介绍','这里是详细介绍',true,true,1);
INSERT INTO product (name,price,pic,remark,xremark,commend,open,cid) VALUES
('韩版女装',199.00,'test.jpg','这里是简单介绍','这里是详细介绍',true,true,2);
INSERT INTO product (name,price,pic,remark,xremark,commend,open,cid) VALUES
('雪地靴',299.00,'test.jpg','这里是简单介绍','这里是详细介绍',true,true,2);
INSERT INTO product (name,price,pic,remark,xremark,commend,open,cid) VALUES
('欧版女装',3999.00,'test.jpg','这里是简单介绍','这里是详细介绍',true,true,2);
INSERT INTO product (name,price,pic,remark,xremark,commend,open,cid) VALUES
('女款手套',4999.00,'test.jpg','这里是简单介绍','这里是详细介绍',true,true,2);
INSERT INTO product (name,price,pic,remark,xremark,commend,open,cid) VALUES
('佳能单反机',3998.00,'test.jpg','这里是简单介绍','这里是详细介绍',true,true,3);
INSERT INTO product (name,price,pic,remark,xremark,commend,open,cid) VALUES
('金士顿优盘',299.00,'test.jpg','这里是简单介绍','这里是详细介绍',true,true,3);
INSERT INTO product (name,price,pic,remark,xremark,commend,open,cid) VALUES
('日立硬盘',599.00,'test.jpg','这里是简单介绍','这里是详细介绍',true,true,3);
INSERT INTO product (name,price,pic,remark,xremark,commend,open,cid) VALUES
('大水牛机箱',399.00,'test.jpg','这里是简单介绍','这里是详细介绍',true,true,3);
INSERT INTO product (name,price,pic,remark,xremark,commend,open,cid) VALUES
('电脑桌',150.00,'test.jpg','这里是简单介绍','这里是详细介绍',true,true,4);
INSERT INTO product (name,price,pic,remark,xremark,commend,open,cid) VALUES
('老板椅',199.00,'test.jpg','这里是简单介绍','这里是详细介绍',true,true,4);
INSERT INTO product (name,price,pic,remark,xremark,commend,open,cid) VALUES
('空调',3599.00,'test.jpg','这里是简单介绍','这里是详细介绍',true,true,4);
INSERT INTO product (name,price,pic,remark,xremark,commend,open,cid) VALUES
('电视',699.00,'test.jpg','这里是简单介绍','这里是详细介绍',true,true,4);
SELECT * FROM account;
SELECT * FROM category;
SELECT * FROM product;

18.3 然后通过hibernate逆向工程自动生成表与对象的映射
    Product.java   

public class Product implements java.io.Serializable {
    private Integer id;
    private String name;
    private Double price;
    private String pic;
    private String remark;
    private String xremark;
    private Timestamp date;
    private Boolean commend;
    private Boolean open;
    private Category category; //由cid改为category
... //省略setter、getter

    Product.hbm.xml

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="cn.it.shop.modal">
    <class name="Product" table="product" catalog="shop">
        <id name="id" type="java.lang.Integer">
            <column name="id" />
            <generator class="native" />
        </id>
        <property name="name" type="java.lang.String">
            <column name="name" length="20" />
        </property>
        <property name="price" type="java.lang.Double">
            <column name="price" precision="8" />
        </property>
        <property name="pic" type="java.lang.String">
            <column name="pic" length="200" />
        </property>
        <property name="remark" type="java.lang.String">
            <column name="remark" />
        </property>
        <property name="xremark" type="java.lang.String">
            <column name="xremark" />
        </property>
        <property name="date" type="java.sql.Timestamp">
            <column name="date" length="19" not-null="true" />
        </property>
        <property name="commend" type="java.lang.Boolean">
            <column name="commend" />
        </property>
        <property name="open" type="java.lang.Boolean">
            <column name="open" />
        </property>
        <many-to-one name="category" class="Category">
            <column name="cid"/>
        </many-to-one>
    </class>
</hibernate-mapping>

18.4 定义后台,做一个分页查询
    ProductService.java

public interface ProductService extends BaseService<Product>{   
     public List<Product    > queryJoinCategory(String name,int page,int size) ;
}
    ProductServiceImpl.java
@SuppressWarnings("unchecked")
@Service("productService")
public class ProductServiceImpl extends BaseServiceImpl<Product> implements ProductService {
      public List<Product> queryJoinCategory(String name, int page, int size) {
        String hql = "FROM Product p LEFT JOIN FETCH p.category "
                +"WHERE p.name LIKE :name" ;
        return this.getSession().createQuery(hql)
        .setString("name", "%"+name+"%")
        .setFirstResult((page-1)*size)
        .setMaxResults(size)
        .list() ;
    }
    public Long getCount(String name) {
        String hql = "SELECT COUNT(id) FROM Product WHERE name LIKE :name" ;
        return (Long)this.getSession().createQuery(hql)
                .setString("name", "%"+name+"%")
                .uniqueResult();
    }
}

   BaseAction.java

    @Resource
    protected ProductService productService ;

18.5 然后对上面的后台实现进行JUNIT测试

        SSHTest.java

 @Resource
    private ProductService productService ;

    @Test
    public void queryJoinCategoryTest(){
        System.out.println(productService.queryJoinCategory("", 1, 5)) ;
    }
    @Test
    public void getCountTest(){
        System.out.println(productService.getCount(""));
    }

18.6 测试通过后然后定义控制层

    ProductAction.java

public String queryJoinCategory(){
        //pageMap已在baseAction中声明,其json配置也在struts.xml实现
        pageMap = new HashMap<String,Object>() ;
        pageMap.put("rows",this.productService.queryJoinCategory(this.model.getName(),this.page,this.rows)) ;
        pageMap.put("total",this.productService.getCount(this.model.getName()));
        return "jsonMap" ;
    }
18.7 然后配置product的action
    struts.xml
<action name="product_*" class="productAction" method="{1}">
            <result name="index">/index.jsp</result>
            <result name="jsonMap" type="json">
                <param name="root">pageMap</param>
                <param name="excludeProperties">
                    <!-- 此在hibernate所独有,避免其懒加载机制导致json转换出错 
                    由此,必须把category中不相关的关联对象account屏蔽掉-->
                    rows\[\d+\]\.category\.account
                </param>
            </result>
 </action>

18.8 定义前台查询的结果列表页面

    query.jsp

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
  <head>
   <%@ include file="/public/head.jspf" %>
   <style type="text/css">
    .searchbox{
    margin:-3px ;
  }
   </style>
   <script type="text/javascript">
 $(function(){
  $('#dg').datagrid({  
   queryParams:{name:''},
   //列中所出现的数据的请求地址
        url:'product_queryJoinCategory.action',  
        //所显示列的具体字段,其中字段名称field与json中对应的key相对应
       width:500,
        //冻结列使用,类似于xls表格中的列行冻结
         striped:true,
         idField:'id' ,//指定上下分页操作时绑定的勾选字段不至丢失
         /* 数据加载等待提示信息 */
         //只允许选择单行
         singleSelect:false,
         loadMsg:'wait.......',
         //数据超出表格边界是否换行
         nowrap:true,
       fitColumns:true,
       //显示分页栏目
       pagination:true,
       pageSize:5,
       pageList:[5,10,15,20,25],
       
      toolbar: [{
     iconCls: 'icon-add',
     text:"添加商品" ,
     handler: function(){
      parent.$('#win').window({    
          width:300,    
          height:200,  
          title:"添加商品",
          content:'<iframe border="0" width="100%" height="100%" src="send_product_add.action"/>'
      });  
     }
    },'-',{
     iconCls: 'icon-edit',
     text:"更新商品" ,
     handler: function(){
      //以数组的形式放回被选中的数据
      var rows = $('#dg').datagrid("getSelections");
      if(rows.length != 1){
       //利用EasyUI的消息提示
       $.messager.show({
        title:'更新操作提示',
        msg:'必须选中一行数据',
        timeout:3000,
        showType:'slide'
       });
      }else{
       parent.$('#win').window({    
           width:300,    
           height:250,  
           title:"更新商品",
           content:'<iframe border="0" width="100%" height="100%" src="send_product_update.action"/>'
       });  
      }
     }
    },'-',{
     iconCls: 'icon-remove',
     text:"删除商品" ,
     handler: function(){
      //以数组的形式放回被选中的数据
      var rows = $('#dg').datagrid("getSelections");
      if(rows.length == 0){
       //利用EasyUI的消息提示,显示用户没有选中数据
       $.messager.show({
        title:'删除操作提示',
        msg:'没有选中被操作的数据',
        timeout:3000,
        showType:'slide'
       });
      }else{
       $.messager.confirm('确认对话框', '您确定要删除吗?', function(r){
        if (r){
            var ids = "" ;
            for(var x=0 ;x<rows.length;x++){
             ids += rows[x].id +"," ;
            }
            ids = ids.substring(0, ids.lastIndexOf(",")) ;
            //alert(ids) ;
            //发送AJAX请求
            $.post("product_deleteByIds.action",{ids:ids},function(obj){
             if(obj.trim()=="true"){
              //在easyUI里面同时要清除被删除行的选中状态
              $("#dg").datagrid("uncheckAll") ;
              //刷新页面继续回到删除的当前页
              $('#dg').datagrid("reload");
             }else{
              $.messager.show({
             title:'删除异常',
             msg:'删除有误,检查一下',
             timeout:1000,
             showType:'slide'
            });
             }
            },"text");
        }
       });
      }
     }
    },'-',{
     text:"<input id='ss' name='search'/>"
    }],
        frozenColumns:[[
                     {field:'随便写',checkbox:true},
                   {field:'id',title:'编号',width:100}
                       ]],
        columns:[[    
            {field:'id',title:'编号',width:100},    
            {field:'name',title:'商品名称',width:100},    
            {field:'price',title:'价格',width:100,align:'right'
            },
          {field:'category.type',title:'商品所属类别',width:100,
             formatter: function(value,row,index){
              if(row.category!=null&&row.category.type!=null){
               return row.category.type ;
              }
       }  
          }
        ]]    
    });  
  $('#ss').searchbox({
   searcher:function(value,name){
    //alert(value + "," + name) ;
    $('#dg').datagrid('load',{
     //需要查询的字段
     type:value
    });
   },
   prompt:'Please Input Value'
   });
 });
   </script>
  </head>
  <body>
   <table id="dg"></table>  
  </body>
</html>

 修改

    aindex.jsp

....
<script type="text/javascript">
      $(function(){
          //对于a元素点击事件只对含有属性title的a元素点击有效
          $("a[title]").click(function(){ 
              var text = $(this).text() ;    //获取元素a中的text文本
              var href= $(this).attr("title") ;
              //判断指定内容(比如‘类别管理’)的tabs标签是否存在
              if($("#tt").tabs("exists",text)){
                  /* 如果存在指定tabs的对象则切换到这个tabs上 */
                  $("#tt").tabs("select",text) ;
              }else{
                  $("#tt").tabs("add",{
                      title:text,
                      content:'<iframe title='+text+' src='+href+' width="100%" height="100%" frameborder="0"/>',
                      closable:true 
                  }) ;
              }
          });
      });
      </script>
。。。
<div id="menu" class="easyui-accordion" data-options="fit:true">   
            <div title="基本操作">   
                <ul>
                      <li><a href="#" title="send_category_query.action">类别菜单</a></li>
                      <li><a href="#" title="send_product_query.action">商品菜单</a></li>
                  </ul>
            </div>
            <div title="其他操作">   
                <ul>
                      <li><a href="#">类别菜单</a></li>
                      <li><a href="#">商品菜单</a></li>
                  </ul>
            </div>
        </div> 
。。。


18.9 实现商品添加功能,定义一个添加页面用来专门弹出添加的窗口
    add.jsp
<body>
      <form id="ff" method="post" action="">   
        <div>   
            <label for="type">商品名称:</label>   
            <input type="text" name="name" value="添加测试"/>   
        </div>
        <div>   
            <label for="type">商品价格:</label>   
            <input type="text" name="price" value="9.9"/>   
        </div>   
         <div>
             图片上传:<input type="file" name="upload"/>
         </div> 
         <div>   
            <label for="category.id">所属类别:</label>   
           <select name="category.id" id="cc">
           </select>
        </div>  
        <div>   
            <label for="commend">推荐产品:</label>   
            <input type="radio" value="true" name="commend" >是</input>  
            <input type="radio" value="false" name="commend" checked>否</input>
        </div> 
        <div>   
            <label for="open">是否上架:</label>   
            <input type="radio" value="true" name="open" >是</input>  
            <input type="radio" value="false" name="open" checked>否</input>
        </div> 
        <div>
            <label>简单描述</label>
            <input type="text" name="remark" size="20" maxlength="20" value="添加描述测试"/>
        </div>
        <div>
            <label>详细描述</label>
            <textarea rows="3" cols="10" name="xremark">添加详细描述测试</textarea>
        </div>
        <div>
            <a id="btn" href="#" class="easyui-linkbutton">添加</a> 
            <a id="btn" href="#" class="easyui-linkbutton">重置</a> 
        </div>  
    </form>  
  </body>

18.10 对商品添加列表框中的表单的每一项定义相应的验证规则

    add.jsp

<script type="text/javascript">
      //在jQuery环境下自定义验证方法,向validatebox.defaults.rules注册新方法
        $.extend($.fn.validatebox.defaults.rules, {  
            //利用json来实现:一个函数与一个消息
            format: {    
                validator: function(value,param){  
                    //取得当前上传文件后缀名
                    var ext = value.substring(value.lastIndexOf(".")+1) ;
                    //取得预定义的上传文件后缀名
                    var arr = param[0].split(",") ;
                    for(var x=0;x<arr.length;x++){
                        if(arr[x]==ext){
                            return true ;
                        }
                    }
                    return false;    
                },    
                //利用占位符可以取得参数
                message: '格式必须为{0}'   
            }    
        }); 

        $(function(){
            $("input[name=name]").validatebox({    
                required: true,    
                missingMessage:"请输入商品名称"   
            });  
            $("input[name=price]").numberbox({    
                required: true,    
                missingMessage:"请输入商品价格",
                min:0,
                /* 保留两位小数 */
                precision:2,
                prefix:'$'
            }); 
            $("input[name=upload]").validatebox({    
                required: true,    
                missingMessage:"请指定上传图片",
                validType:'format["gif,jpg,jpeg,png"]'
            }); 
            //动态联动下拉列表
            $('#cc').combobox({    
                url:'category_query.action',    
                valueField:'id',    
                textField:'type',
                editable:false 
            });  

            $("input[name='remark']").validatebox({    
                required: true,    
                missingMessage:"请输入简单描述"   
            });  
});

18.11 现在实现下拉列表的联动绑定,定义后台类别数据的获取

    CategoryAction.java

public String query(){
        jsonList = this.categoryService.query() ;
        return "jsonList" ;
    }

    同时在struts.xml配置json传输
    struts.xml
<action name="category_*" class="categoryAction" method="{1}">
          。。。
            <result name="jsonList" type="json">
                <!-- 转换成JSON格式的数据 -->
                <param name="root">jsonList</param>
                <param name="excludeProperties">
                    \[\d+\]\.account
                </param>
            </result>
            <result name="stream" type="stream">
                <param name="inputName">inputStream</param>
            </result>
        </action>

18.12 最后完成添加页面的所有功能,实现向后台进行数据传入的功能

    add.jsp

... 
$("#btn1").click(function(){
                $('#ff').form('reset') ;
            });
            $("#btn").click(function(){
                //点击提交按钮时进行表单验证
                $("#ff").form("enableValidation") ;
                //首先进行表单验证的判断
                if($('#ff').form('validate')){ //如果验证通过
                    //开始表单提交
                    $('#ff').form('submit', {
                        url: 'product_save.action',
                        success: function(){
                            //成功之后关闭弹出的窗口
                            parent.$("#win").window("close") ;
                            //下面的方式是通过jquery取得对象,有些浏览器不支持
                            //var dg = parent.$("iframe[title='类别管理']").contents().find("#dg");
                            //下面的一部分是通过DOM解析取得对象,支持度更高
                            var dg = parent.$("iframe[title='商品管理']").get(0).contentWindow.$("#dg") ;
                            //利用easyUI中的方法reload重新加载datagrid表
                            dg.datagrid("reload") ;
                        }
                    });
                }
            }) ;
...

    同时完成后台控制层的数据接收与保存(注意文件上传的实现)

18.13 由于文件上传是其他很多模块的共有功能,可以定义一个上传文件类(专门用来封装各种上传文件的属性),然后把它放到baseAction中去

    UploadFile.java
//注意下面的setter方法是属性名无关
public class UploadFile{
          private File file;
          private String contentType;
          private String filename;

          public void setUpload(File file) {
             this.file = file;
          }

          public void setUploadContentType(String contentType) {
             this.contentType = contentType;
          }

          public void setUploadFileName(String filename) {
             this.filename = filename;
          }

        public File getFile() {
            return file;
        }

        public String getContentType() {
            return contentType;
        }

        public String getFilename() {
            return filename;
        }
}

    BaseAction.java

protected UploadFile uploadFile ;
 public UploadFile getUploadFile() {
  return uploadFile;
 }
 public void setUploadFile(UploadFile uploadFile) {
  this.uploadFile = uploadFile;
 }

    修改前台页面的上传入口

<div>
<!--注意此处的name属性将根据UploadFile中的setter方法来定,而不是属性  -->
             图片上传:<input type="file" name="uploadFile.upload"/> 
</div> 

18.14 把上传文件的功能实现(业务逻辑)放到一个专门的工具类中

    FileUploadUtil.java

@Component("fileUpload") //Component代表工具类
public class FileUploadUtil implements FileUpload {
 private String filePath = "c:/";
 private String getFileExt(String filename){ //根据上传的文件名获取文件扩展名
  return FilenameUtils.getExtension(filename) ;
 }
 public String newFileName(UploadFile uploadFile){ //根据上的文件保存到本地,并且重新生成一个文件名
  String newFileName = UUID.randomUUID().toString()+"."+getFileExt(uploadFile.getFilename());
  //实现上传
  try {
   FileUtil.copyFile(uploadFile.getFile(),new File(filePath,newFileName));
   return newFileName ;
  } catch (IOException e) {
   throw new RuntimeException(e) ;
  }finally{
   uploadFile.getFile().delete() ;
  }
 }
}
18.15 为了方便把这个工具类交给spring管理,需要把上面的实现类 FileUploadUtil抽取出来作为一个接口使用

  FileUpload.java

public interface FileUpload {

    public abstract String newFileName(UploadFile uploadFile);

}

    FileUploadUtil.java

@Component("fileUpload") //Component代表工具类
public class FileUploadUtil implements FileUpload {
    。。。
}

    然后利用spring在baseAction中注册此接口

    BaseAction.java 

@Resource
 protected FileUpload fileUpload ;

18.16 最后测试运行出现一个异常

org.hibernate.PropertyValueException: not-null property references a null or transient value: cn.it.shop.modal.Product.date
    经过排查发现是因为在前台没有为date没有传值,也就是为null(mysql中此字段是自动添加当前时间所以没有设值),但是Product.hbm.xml文件中却配置如下
    Product.hbm.xml
<property name="date" type="java.sql.Timestamp">
            <column name="date" length="19" not-null="true" />
</property>

    需要改成

    Product.hbm.xml

<property name="date" type="java.sql.Timestamp">
            <column name="date" length="19" not-null="false" />
</property>

18.17 通过以上的操作就基本实现了产品保存的功能


0 0
原创粉丝点击