JavaWeb项目详解系列2-1(基于How2j天猫J2EE项目:表结构设计,实体类与DAO类设计 )

0项目来源与阅读建议

项目来源

http://how2j.cn/k/tmall-j2ee/tmall-j2ee-894/894.html?p=66748
注册的时候用这个链接,谢谢啦!

价格

92 * 0.8

阅读建议

  1. 本教程与站长的官方教程互为补充,并不是取而代之,也做不到取而代之。站长是以老师的身份告诉你怎么写,我是以课代表的身份告诉你,我是怎么学的。
  2. 本教程应当在你做完站长相对应部分的内容之后,再来看。也就是说,在看本教程之前,你应当对站长对应章节的内容有了一定的了解和基础,不然某些词语你可能听不懂。
  3. 我在How2j的ID名叫HuangTY,我喜欢分享大家存在的问题,所以也希望大家能够在评论区多多留言,共同进步。

1 表设计与实体类概览

表设计

在这里插入图片描述

实体类设计(第一版)

在这里插入图片描述
表设计中关系均为1对多关系。1对多关系,在表设计中通常采用主从表设计,即主表的主键作为从表的外键存在。
这个关系如何在实体类的设计中体现出来呢?即,在表设计中处于从表地位的对应实体类中必须包括处于主表地位所对应的主体类。
用大白话中就是说,在“一对多”的表设计情况下,表示“多”的实体类中必须存有表示“一”的实体类对象,以此来代表外键。
那问题来了,我需不需要在代表“一”的实体类中增加List<“多”>呢?可以放,也可以不放。我们看站长是怎么做的——

实体类设计(第二版)

在这里插入图片描述
由上图可见,一对多关系中,在“一”的实体类中是否要存放针对“多”的List,看实际需求。比如说,为什么Category中要放List<Product>呢?因为在前台显示的时候,传递一个域对象为Category c,然后在jsp中获取到了c,c中又有list,这样在前台就可以直接展示c中的全部商品了,岂不美哉?
设想一下,计入Categroy中没有List,那么就要传递两个域对象。

实体类设计(第三版)

在这里插入图片描述
这一版增加了两个业务逻辑需要使用的函数。
函数1:getStatusDesc
用处:在天猫上下的订单会存在不同的状态,比如待付款,待发货,待收货等,这个函数就是维护了这样一个状态。(其实本质上是把枚举转化成中文字符串而已)
函数2:getAnnoymousName()
用处:评价商品的时候匿名。比如账户名叫 布朗尼的犀牛,显示则为布***牛。

2 DAO类分析

2.1 CategoryDAO

CRUD

1.add()

String sql = "insert into category values(null,?)";

较高版本的mysql采用站长的代码可能存在一些问题,如下:
ResultSet rs = ps.getGeneratedKeys();
这句代码Tomcat服务器的log中可能出现报错,大概意思是如果你想要获取自动生成的主键,就需要在执行sql的时候指定,因此执行语句应该改为:

try (Connection c = DBUtil.getConnection(); PreparedStatement ps = c.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);) 

做的改动是加入了Statement.RETURN_GENERATED_KEYS。
除此之外,以上是java7的新特性,叫做try-with-resource。大概意思就是说,在try语句里面调用的资源,将会在退出和异常的时候自动关闭。
2.update()

String sql = "update category set name= ? where id = ?";

3.delete()

String sql = "delete from Category where id = " + id;

4.list()

public List<Category> list(int start, int count)

这个就是查询。这个代码的传入参数分别是,分页起始值,每页数量,对应的sql语句是:

String sql = "select * from Category order by id desc limit ?,? ";

就是从第一个?开始查询,查询数量为第二个?。
这个函数有返回值,返回的是查询到的对象集合。查询到的这个集合就可以放入域对象中,然后在jsp中显示出来。

分页相关

getTotal()
这个理解起来很简单。每页允许5个,那应该显示多少页呢?这是跟商品的总个数息息相关的。既然需要总数,那我们就用sql语句获取一下就行了

String sql = "select count(*) from Category";

id与对象的映射关系

Category get(id)
已知每个id均对应一个对象,通过get方法可以通过id得到Category的对应对象。

下面相同或类似的方法不再分析。

2.2 PropertyDAO

CRUD

这里要强调的点是,Property表示Category的从表,因此以add()为例,展现从表在CURD时候与主表的关系。

public void add(Property bean)

注意参数是一个对象,由前文,该对象存在一个Category的成员变量,这个实体类具有get/set方法,因此可以获取Category对象的id。这个id就是Product的外键(在表Property中称为cid)。

//null为主键,会自动添加。第一个?为cid,即外键(category的id),第二个?为name。
String sql = "insert into Property values(null,?,?)";
......
ps.setInt(1, bean.getCategory().getId());
ps.setString(2, bean.getName());

通过bean.getCategory().getId()将category的id设置为product表中的cid。

list方法

public List<Property> list(int cid, int start, int count)

看这个函数原型发现,要通过cid去查询Property表,这是为什么呢?
答案很显而易见,见如下的问答:
Q:我想找到分类A的属性,应该在那张表查询?
A:既然是分类A的属性,自然是属性表,Property啊。
Q:通过什么查询呢?
A:见要查询分类A的属性,当然是查询where cid = ?了啊。
因此,采用以下SQL语句。

String sql = "select * from Property where cid = ? order by id desc limit ?,? ";

2.3 ProductDAO

fill方法

public void fill(Category c)
public void fill(List<Category> cs)

可以看到这是一个重载的函数。下面那个是基于上面的,本质上只是多了一个循环而已。
下面用大白话解释一下这两个函数
函数1:将分类c中的所有product放入其中。
函数2:有多个分类,cs={c1,c2,…cn}。对他们都处以函数1的操作。
具体来说——
Category c,这个对象中的List<Product>这个成员变量,需要ProducDAO调用fill后,才完成赋值操作。

search方法

public List<Product> search(String keyword, int start, int count)

本质上就是模糊查询,你看sql语句就可以知道。

String sql = "select * from Product where name like ? limit ?,? ";

没学过sql语句的同学出门左转。学过sql语句的同学看到like就知道,这就是模糊匹配。

list方法

public List<Product> list(int start, int count){
	String sql = "select * from Product limit ?,? ";
}
public List<Product> list(int cid, int start, int count){
	String sql = "select * from Product where cid = ? order by id desc limit ?,? ";
}

上面两个list方法非常有趣,一个没有cid,一个有cid。我建议你再对照看一下PropertyDAO的list方法,然后就有了以下的问题:
Q:为什么这里有两个list方法?
A:想一下应用场景,你会在什么时候想要查询商品呢?情况一,已知分类查询商品,这里就用到了函数2。情况二,我就想看所有的商品,那就用函数1.
Q:那为什么PropertyDAO里面不存在函数1呢?
A:因为从业务逻辑而言,脱离了分类的属性是不存在的。也就是说,不存在一个业务场景,需要你查看所有的属性。业务场景仅为,查看一个分类下的全部属性。

与其他实体类有关联的方法

setFirstProductImage

public void setFirstProductImage(Product p) {
        List<ProductImage> pis= new ProductImageDAO().list(p, ProductImageDAO.type_single);
        if(!pis.isEmpty())
            p.setFirstProductImage(pis.get(0));
    }

可以猜测出,ProductImageDao的list方法是根据p的id查询出下面的所有标志位为“type_single”的图片,然后放入list集合中。其本质思想,和PropertyDAO的list(cid,start,count)没有任何区别。
这个方法的作用就是把这个产品下关联的所有图片中的第一张作为产品图。

setSaleAndReviewNumber

public void setSaleAndReviewNumber(Product p)
public void setSaleAndReviewNumber(List<Product> products)

这两个函数的关系,和fill的那两个函数的关系是一样的。
翻译成大白话是:
将p的售卖数量和评论数量放入其中。
因为Product对象生成的时候,一般只给了几个基本属性,至于List<>,sale和count都是用到的时候再获取的,也是为了减少内存占用吧。

2.4 ProductImageDAO

产品图片之于产品 == 属性之于分类
属性和分类的关系叫做“组合”关系,这是一种强拥有关系,即产品必须有图片,且产品没了图片也没了,他们是同生共死的关系。产品图片和产品也一样,一旦产品消失,那产品对应的图片也必须消失。
因此,我们可以对照比较Category与Property的关系,看一下Product和ProductImage的关系。
PropertyDAO:通过cid获取与cid关联的所有属性

public List<Property> list(int cid, int start, int count)

ProductImageDAO:通过产品对象p来获得对应产品下的所有图片。

public List<ProductImage> list(Product p, String type)
public List<ProductImage> list(Product p, String type, int start, int count)

区别:为什么PropertyDAO中是cid,ProductImageDAO中用的是对象p呢?
原因:站长在这里写的不统一,因为是写cid也好,写对象也好,本质上id和对象是对应关系。比如这里不传Product p而是传入int pid(product对象的id),那你只要Product.getId(pid)即可获得对象。所以两种写法本质上等价,都无所谓的。

2.5 PropertyValueDAO

我们知道PropertyValue与Product和Property均是多对一关系,因此实体类中需要有Product对象和Property对象。
基本关系是:一个产品的一个属性,对于一个属性值。
下面的介绍都是围绕这个基本关系来的:

public PropertyValue get(int ptid, int pid )

get方法就是利用这个基本关系获得的属性值。

public List<PropertyValue> list(int pid)

list可以查询某个商品下的全部属性值,当然也能获得property,因此在Property表中任意给出一个外键或主键查询,都能得出与之匹配的全部信息。

public void init(Product p) {
   //查询分类的全部属性(分类有属性,商品属于分类,商品有这个分类的属性及属性值)
   List<Property> pts= new PropertyDAO().list(p.getCategory().getId());
   for (Property pt: pts) {
       //匹配标准:商品id和属性id均相同的
       //即找到商品p的对应属性的属性值
       PropertyValue pv = get(pt.getId(),p.getId());
       //如果这个属性值为空,那么就新建一个属性值
       if(null==pv){
           pv = new PropertyValue();
           pv.setProduct(p);
           pv.setProperty(pt);
           this.add(pv);
       }
   }
}

init方法是比较有趣的,逻辑分析如下:
对于任意的商品p,都有n个属性,这n个属性对于n个属性值,每个属性值都称作pv好了。
对于属性值而言,具有Product和Property两个对象。因此,对于pv而言,它需要通过某种手段来set自己的Product与Property。何种手动?就是Init。先通过PropertyDAO.list(cid)获得cid分类下的所有属性。商品P有n个属性,属性的集合叫做pts,集合中的每个元素(即属性)为pt,一个p和一个pts(基本关系)决定一个propertyvalue,因此pv就可以set他的property和product了。

2.6 OrderItemDAO

Order和OrderItem中的关系

要弄清楚Order和OrderItem中的关系。Order表订单,比如我下了一笔订单叫“1号订单”,这个订单里面有多个订单项,每个订单项大致表示为[产品名,数量,…]信息。
Order看成一个集合的话,其中的元素就是OrderItem。
但是听清楚了,并不是所有OrderItem都在订单中!因为OrderItem需要下单后,才会成为订单的一部分。
坦白说一开始我就没了解这个,后面慢慢做业务的时候才理解。看一下的QA:
Q:我想加入1个产品A进入购物车。(业务逻辑上的需求)
A:那就应该把产品(对象),数量等属性封装到一个类里。
Q:这个类不就是Order类么?
A:并不是,因为Order类已经代表成为了订单,而你加入购物车说明还没有下单啊。
Q:那放入什么类里面?
A:我建立了一个类叫做OrderItem。
Q:那我懂了,是不是说,我加入购物车中的商品都是加入OrderItem类,而我直接先下单的加入Order类啊?换句话说,OrderItem是购物车类,Order类是订单类?
A:当然不是!就算你直接下订单,也是先建立一个OrderItem对象,然后将这个OrderItem指向Order(因为OrderItem实体类中存在成员变量Order)。
等到后面做业务逻辑的时候再去体味一下这些话。反正要记住,订单一定是基于订单项,而订单项本身又可以做一些与订单无关的操作。

如何区分是订单还是在购物车中

if(null==bean.getOrder())
    ps.setInt(2, -1);
else
    ps.setInt(2, bean.getOrder().getId());

在add中,如果这个订单项是刚刚创建的,那么就默认oid(代表Order的id)为-1,如果在后续操作中,这个OrderItem属于了某个Order,那么就把oid转化为那个Order的的id。

查询方法

有两种查询方法,

  1. 按照uesr查询,即查询这个用户的订单项。什么叫用户的订单项呢?就是说这个用户的购物车内容。
  2. 按照订单查询,即查询订单中包含的订单项内容
public List<OrderItem> listByUser(int uid, int start, int count)
String sql = "select * from OrderItem where uid = ? and oid=-1 order by id desc limit ?,? ";
public List<OrderItem> listByOrder(int oid, int start, int count)
String sql = "select * from OrderItem where oid = ? order by id desc limit ?,? ";

其他DAO

自己看吧,没什么特别好说的。一定要结合表结构设计的那张图来看哦。

3.本章小结

本章主要是和数据库相关的。
窃以为,J2EE这个项目给我最大的收获在于表设计,DAO,Filter+Servlet设计模式方面。至于具体业务代码,我认为脑子够用的人,总是能自己想出来的,但是这些设计方面的东西,不学还真是不知道呢。
预告:
下一章将讲解Filter与Servlet的设计模式,欢迎大家多多留言评论,共同进步。

猜你喜欢

转载自blog.csdn.net/w8253497062015/article/details/85331204
今日推荐