首先,在此我想感谢 私塾在线 的 wb 老师!
图书的(jxc)系统是我一边看wb老师的在线视频课,一边跟着做出来的项目。本博客是我的一点小小心得。
Ⅰ 内容和目标简介
本系统主要包含用户、图书、进货、销售和库存五个模块,界面采用Swing,后台采用MVC模式,主要实现功能如下:
(1)能进行操作用户的注册、登录、删除、查询和信息修改。
(2)能实现对图书基本信息的增删改查
(3)能填写进货单,一张进货单包含多条具体的货物明细,进货的书籍必须是图书数据库中已经存在的,进货的同时修改库存信息
(4)能填写销售单,一张销售单包含多条具体销售信息,销售的书籍必须是图书数据库中已经存在的,销售数量不能超过当前库存的数量,销售的同时修改库存信息
(5)对于库存,可以查看明细,可以按照条件查询某书的库存值
(6)权限方面做固定的权限控制。
Ⅱ 数据库表的设计
分为用户、图书、进货、销售和库存5个模块。
注意:每一条进货人明细都对应着多条进货书籍明细。
注意:每一个销售人明细都对应着多条销售书籍明细。
注意:表的设计过程中,要注意主键和外键。
Ⅲ 分层、分包,系统的整个框架的搭建:
–》新建java项目,组织名cn.javass , 项目名 jxc ,之后的分包规则:
整个系统的框架如上图所示:
(1)panels层调用business,business层调用dao层,三层之间model的传递用vo封装。所以,我们可以在编码的时候先写vo层,再写dao层,接着写business层,最后在panels层中调用逻辑层。
(2)界面和ebi交互,ebi中的接口负责为界面组织提供数据,界面所需要的所有数据都是ebi提供的。但ebi只是接口声明,具体实现在ebo中实现。
(3)dao层会提供最基本的增删改查实现,提供与数据库的联系。同样,dao.dao中只是接口声明,dao.Impl中主要实现接口。逻辑层在调用数据层的时候,对于简单的增删改查,ebo直接转发dao即可,如果ebo需要组织更为复杂的数据给界面,则可以直接通过操作QueryModel来实现对数据的组织。
(4)数据层主要利用vo的model实现增删改查,Ebo中主要负责为界面组织所需要的数据。
Ⅳ 接下来,我来总结User层的代码的示例写法:
1.vo层
(1)关于值对象模式 :每一个模块对应一个Model类,描述这个类的属 性都作为这个类的filed
(2)对应的get和set方法
(3)用主键来重写equals和hascode
(4)实现可序列化
(5)重写toString()方法,方便按照需要的格式显示各字段的值。
(6)用UserQueryModel.java来传model,而不是采用UserModel.java来传model.好处是当字段中包含范围值时,采用UserModel.java不方便处理。
—-》 eg: UserModel.java
package cn.javass.jxc.user.vo;
import java.io.Serializable;
public class UserModel implements Serializable {
private String uuid;
private String name;
private String pwd;
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((uuid == null) ? 0 : uuid.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
UserModel other = (UserModel) obj;
if (uuid == null) {
if (other.uuid != null)
return false;
} else if (!uuid.equals(other.uuid))
return false;
return true;
}
@Override
public String toString() {
return " [" + uuid + ", " + name + ", " + type+ ", " + pwd + "]";
}
}
//—-》用一个包含范围值的来作为实例:
——》InMainQueryModel.java
package cn.javass.jxc.in.vo;
public class InMainQueryModel extends InMainModel {
//用来传有范围的查询条件,遇到范围值就2一下,再将InMainQueryModel传给dao层的getByCodition()查询函数。
//没有范围值查询条件,InMianQueryModel()中不写内容就行,但传model的时候还是要用它来传,所以必须写!
private long inDate2;
public long getInDate2() {
return inDate2;
}
public void setInDate2(long inDate2) {
this.inDate2 = inDate2;
}
}
2.dao层:实现最基本增删改查。
—-》UserDao.java
package cn.javass.jxc.user.dao.dao;
import java.util.List;
import cn.javass.jxc.user.vo.UserModel;
import cn.javass.jxc.user.vo.UserQueryModel;
public interface UserDao {
String FILE_NAME = null;
public boolean create(UserModel user);
public boolean update(UserModel user);
public boolean delete(String uuid);
/**
* 查分为三种: (1)查全部
* (2)通过主键查一条记录
* (3)根据输入条件查询符合条件的
*/
public List getAll();
public UserModel getSingle(String uuid);
public List getCondition(UserQueryModel uqm);
//根据uuid进行精确查询
//根据username进行模糊查询
//根据type进行精确查询
//怎样查询:1.循环所有的对象
//2.反逻辑,将不符合条件的挑出来
//3.卫条件,每一步条件都保护了最后符合的条件
//4.内外层if (内层判断对象是否符合查询条件,外层判断用户的输入是不是查询条件
}
—-》UserDaoSerimpl.java
package cn.javass.jxc.user.dao.impl;
import java.util.ArrayList;
import java.util.List;
import cn.javass.framework.io.SetUtil;
import cn.javass.jxc.user.dao.dao.UserDao;
import cn.javass.jxc.user.vo.UserModel;
import cn.javass.jxc.user.vo.UserQueryModel;
public class UserDaoSerImpl implements UserDao {
private static final String FILE_NAME = "User.txt";
@SuppressWarnings("unchecked")
@Override
public boolean create(UserModel user) {
/**
* 利用序列化/反序列化create 序列化是将对象放到文件里,反序列化是将对象从文件中读出来
* 序列化和反序列化的对象是List 对于create来讲: 1.先把已经有的读出来 2.查看uuid是否已经存在
* 3.如果存在返回false 4.如果不存在则添加新的对象至List末尾,并且将添加后的序列化。
*/
// 1.
List list = SetUtil.readFromFile(FILE_NAME);
System.out.println("读出create前文件中已经存在的:\n");
System.out.println(list);
// 2.循环list,有重复
for (UserModel model : list) {
if (model.getUuid().equals(user.getUuid())) {
return false;
}
}
// 3.不重复
list.add(user);
SetUtil.writeToFile(list, FILE_NAME);
System.out.println("读出create(添加)成功后的:\n");
System.out.println(list);
return true;
// create功能成功实现
}
@SuppressWarnings("unchecked")
@Override
public boolean update(UserModel user) {
/**
* 1.先把原有的都读出来 2.查看uuid是否存在 2.1 如果不存在则返回false
* 2.2如果存在则执行Update,再序列化回去,返回true
*/
/**
* 替换。。。。该怎样替换? 要用list.set(int,
* Object)这种方式替换list中已经存在的数据,用增强型的for循环无法替代原来的model.
* 查询uuid是否存在,如果存在则可以修改这个uuid对应的其他属性,否则返回false
*/
List list = SetUtil.readFromFile(FILE_NAME);
System.out.println("读出update前文件中已经存在的:\n");
System.out.println(list);
for (int i = 0; i < list.size(); i++) {
final UserModel model = list.get(i);
if (model.getUuid().equals(user.getUuid())) {
list.set(i, user);
SetUtil.writeToFile(list, FILE_NAME);
System.out.println("update之后的结果是:");
System.out.println(list);
return true;
}
}
return false;
}
// 对于update来说,用到了user的所有字段,传参数的时候传user
// 对于delete来说,只用到了uuid这个字段,传参数的时候传uuid就行
@Override
public boolean delete(String uuid) {
/**
* 1.先把原来的都读出来 2.查看uuid是否存在 3.若存在则删除,返回true 4.否则返回false
*/
@SuppressWarnings("unchecked")
List list = SetUtil.readFromFile(FILE_NAME);
System.out.println("读出delete前文件中已经存在的:\n");
System.out.println(list);
for (int i = 0; i < list.size(); i++) {
final UserModel model = list.get(i);// 得到i
if (model.getUuid().equals(uuid)) {
list.remove(i);
// 序列化回去
SetUtil.writeToFile(list, FILE_NAME);
System.out.println("delete之后的结果是:\n");
System.out.println(list);
return true;
}
}
return false;
}
@Override
public List getAll() {
System.out.println("\n");
System.out.println("getAll文件中所有的内容:\n");
return SetUtil.readFromFile(FILE_NAME);
// getAll的功能实现
}
@Override
public UserModel getSingle(String uuid) {
List list = SetUtil.readFromFile(FILE_NAME);
// 2.循环list
for (UserModel model : list) {
if (model.getUuid().equals(uuid)) {
System.out.println("根据主键查询一条记录:\n");
return model;
}
}
return null;
}
// 完成通用的查询功能
@Override
public List getCondition(UserQueryModel uqm) {
// TODO Auto-generated method stub
// 根据uuid进行精确查询
// 根据username进行模糊查询
// 根据type进行精确查询
// 怎样查询:1.循环所有的对象
// 2.反逻辑,将不符合条件的挑出来
// 3.卫条件
// 每一个条件都是最后ret.add(model)的警卫,如果有一个警卫通不过都不能到达最后的条件
// 4.内外层if (内层判断对象是否符合查询条件,外层判断用户的输入是不是查询条件)
List list = SetUtil.readFromFile(FILE_NAME);
List ret = new ArrayList();// 结果集
for (UserModel model : list) {
if (uqm.getUuid() != null && uqm.getUuid().trim().length() > 0) {
if (!(uqm.getUuid().equals(model.getUuid())))
continue;
}
// indexOf的用法,查找uqm字段是否在model中出现,返回值为Int类型
if (uqm.getName() != null && uqm.getName().trim().length() > 0) {
if (model.getName().indexOf(uqm.getName()) == -1)
continue;
}
// 字符串判断是否为null,int型判断是否>0.设计数据库的时候要注意,对于枚举类型(enum)来讲,不能为其数据类型的默认值
// eg:type作为Int类型,默认值为0,所以在设计数据库的时候注意取值范围不能为0.
if (uqm.getType() > 0) {
if (!(uqm.getType() != model.getType()))
continue;
}
ret.add(model);
}
return ret;
}
public static void main(String[] args) {
UserDao dao = new UserDaoSerImpl();
UserModel user = new UserModel();
UserQueryModel uqm = new UserQueryModel();
uqm.setName("w");
System.out.println(dao.getAll());
System.out.println(dao.getCondition(uqm));
}
}
—-》UserDaoFactory.java
package cn.javass.jxc.user.dao.factory;
import cn.javass.jxc.user.dao.dao.UserDao;
import cn.javass.jxc.user.dao.impl.UserDaoSerImpl;
public class UserDaoFactory {
public static UserDao getUserDao() {
//返回UserDao的实现。
return new UserDaoSerImpl();
}
}
3.business层
—》UserEbi.java
package cn.javass.jxc.user.business.ebi;
import java.util.List;
import cn.javass.jxc.user.vo.UserModel;
import cn.javass.jxc.user.vo.UserQueryModel;
public interface UserEbi {
public boolean create(UserModel user);
public boolean update(UserModel user);
public boolean delete(String uuid);
/**
* 查分为三种: (1)查全部
* (2)通过主键查一条记录
* (3)根据输入条件查询符合条件的
*/
public List getAll();
public UserModel getSingle(String uuid);
public List getCondition(UserQueryModel uqm);
public List getAllIN() ;
public List getAllOUT() ;
public UserModel getUserByName(String name);
}
—-》UserEbo.java
package cn.javass.jxc.user.business.ebo;
import java.util.List;
import cn.javass.jxc.common.constance.UuidModelConstance;
import cn.javass.jxc.common.dao.dao.UuidDao;
import cn.javass.jxc.common.dao.factory.UuidDaoFactory;
import cn.javass.jxc.user.business.ebi.UserEbi;
import cn.javass.jxc.user.contance.UserTypeEnum;
import cn.javass.jxc.user.dao.dao.UserDao;
import cn.javass.jxc.user.dao.factory.UserDaoFactory;
import cn.javass.jxc.user.vo.UserModel;
import cn.javass.jxc.user.vo.UserQueryModel;
/**
* 常规的import以类、接口、枚举、注解为单位 静态导入指的是以FIeld或者method为单位进行导入
* eg: import static cn.javass.jxc.common.constance.UuidModelConstance.USER;
*/
// 本次项目Ebi较简单,直接转发dao的工作即可,但实际中很多Ebi并不是这么简单。
public class UserEbo implements UserEbi {
@Override
public boolean create(UserModel user) {
// TODO Auto-generated method stub
UuidDao uuidDao = UuidDaoFactory.getUuidDao();
user.setUuid(uuidDao.getNextNum(UuidModelConstance.USER));
UserDao dao = UserDaoFactory.getUserDao();
return dao.create(user);
}
@Override
public boolean update(UserModel user) {
// TODO Auto-generated method stub
UserDao dao = UserDaoFactory.getUserDao();
return dao.update(user);
}
@Override
public boolean delete(String uuid) {
// TODO Auto-generated method stub
UserDao dao = UserDaoFactory.getUserDao();
return dao.delete(uuid);
}
@Override
public List getAll() {
// TODO Auto-generated method stub
UserDao dao = UserDaoFactory.getUserDao();
return dao.getAll();
}
@Override
public UserModel getSingle(String uuid) {
// TODO Auto-generated method stub
UserDao dao = UserDaoFactory.getUserDao();
return dao.getSingle(uuid);
}
@Override
public List getCondition(UserQueryModel uqm) {
// TODO Auto-generated method stub
UserDao dao = UserDaoFactory.getUserDao();
return dao.getCondition(uqm);
}
//获取所有的进货员编号。在Ebo中直接筛选并且返回给表现层。
public List getAllIN() {
UserQueryModel uqm = new UserQueryModel();
uqm.setType(UserTypeEnum.IN.getType());
return getCondition(uqm);
}
//获取所有的销售员编号,在EB直接筛选并且返回给表现层。
public List getAllOUT() {
UserQueryModel uqm = new UserQueryModel();
uqm.setType(UserTypeEnum.OUT.getType());
return getCondition(uqm);
}
//通过name获取到Inuuid,即进货人的uuid
public UserModel getUserByName(String name) {
List users = this.getAll();
for(UserModel user:users) {
if(user.getName().equals(name))
return user;
}
return null;
}
}
—->UserEbiFactory.java
package cn.javass.jxc.user.business.factory;
import cn.javass.jxc.user.business.ebi.UserEbi;
import cn.javass.jxc.user.business.ebo.UserEbo;
public class UserEbiFactory {
public static UserEbi getUserEbi() {
return new UserEbo();
}
}
如上,是一个最简单的User模块的写法实例,其他模块和User模块大体类似,只是在实现不同的功能上有细微的差别。接下来我来总结差别和重要知识点。
重要知识点:
1.怎样表达一个日期+时间?用什么数据类型?
java.util.Date<—->long类型
Date里边就是一个long类型的值,记录的是1070-1-1到现在的毫秒数。
呈现给用户的是将Date转化成String的具体时间,即2001-11-25 00:00:00这种形式,但存在数据库中的是毫秒数。在项目中几乎所有的Date类型都采用的是long这种数据类型存储时间和日期。
2.本次项目中,我们的数据主要是存储在文本文件中,所以我们在对数据操作(增删改查)的过程中,需要用到序列化和反序列化的知识,即IO流和文件的基本操作。反序列化是指从文件中读出数据,在dao层中对数据操作完之后,再把更新后的数据序列化回文件中。因为序列化和反序列化过程要多次重复调用,所以抽象成Framework.io包中的一个通用类。方便dao层多次调用。
3.MainClass.java是一个判断date类型是否合法的类,主要判断是否符合2009-11-15 00:00:00这样的形式。
4.注意真实值和表现值的互相转化:
(1)枚举式
比如在用户模块中,用户的type类型就可以用枚举来表示
package cn.javass.jxc.user.contance;
//定义枚举类型,用来保存(int,String)键值对,用来完成真实值和表现值的转换
public enum UserTypeEnum {
ADMIN(1,"超级管理员"),
BOOK(2,"图书管理员"),
IN(3,"进货管理员"),
OUT(4,"销售管理员"),
STOCK(5,"库存管理员");
private final int type;
private final String name;
private UserTypeEnum(int type, String name) {
this.type = type;
this.name = name;
}
public int getType() {
return type;
}
public String getName() {
return name;
}
}
(2)日期式
若转化的原始类型为str,则采用Timestamp.valueOf(str+”00:00:00”)来转化。
若转化的原始类型为long,则采用Timestamp t =new Timestamp(long);
t.toString().substring()来转化。
Date用一个long类型来表达日期,日期真实值(50000000)表现值(2009-11-24 10:10:00)之间的转换
但是注意,在数据库中date的存储形式是毫秒数。
关于Date,如果需要在页面上让用户输入日期,他们输入的肯定是String类型,比如:2009-11-02。
但是我们需要的是Long 类型,所以涉及到类型转化。将String类型转换成long类型。
转化方法:
可以用三个下拉块让用户选择。
重新建立一个java文件,专门做转化。
(3)外键式
进货模块中:
第一次进货可能有多条明细信息
InMain(uuid=1,inUserUuid=3,inDate=50000000)
关于外键式的真实值与表现值的转换:
InDetail
(uuid=1,inUuid=1,bookUuid=1,sumNum=10,sumMoney=100)
(uuid=2,inUuid=1,bookUuid=2,sumNum=100,sumMoney=100)
Indetail中包含两个外键,inUuid指明了这个进货属于哪次进货,bookUuid指明了到底进的是哪本书。表现出来的是int数字,但是真实值应该是进货的人和书的名字。
5.关于集合框架的应用:
Map的应用—》
Map的key组成的set集合显示在界面上会出现乱序,但是按照顺序一条一条读进的话就会按顺序显示。
怎样让显示在界面的set集合按uuid的顺序排列进行显示?
–》在Model中实现可序列化的时候加入Comparable排序,并且实现其方法即可。
将HashMap改为TreeMap。HashMap和TreeMap的区别:
(1)HashMap:底层是哈希表数据结构,线程不同步
(2)HashTree:底层是二叉树数据结构,线程不同步,可以用于给Map中的键进行排序。
经验:当数据要当作一个set集合显示在页面的时候,需要立刻想到Map这种存储键值对的数据结构,并且考虑用HashMap和HashTree。区别就在于一旦需要给键排序,就要用到HashTree,普通的存储用HashMap就可以完成。
6.Swing的应用:
怎样利用swing写panels—》
(1)收集参数
(2)组织参数
(3)调用ebi方法
(4)根据ebi返回值选择下一个界面。
做panels的时候,首先要写页面的布局,注意写代码要有规矩,要将业务逻辑放在合适的地方,不能放在表现层。布局写完后,要开始写响应。写所有button的响应。
7.在.java中不重写toString方法时,打印的是全类名,不想写toString方法时,可以用反射实现相同功能。
重写toString()方法为了更好的在界面显示。
重写toString()方法:
public String toString() {
return uuid+”,”+inUuid+”,”+bookUuid+”,”+sumNumber+”,”+sumMoney
主要用于界面显示需要显示的信息。
}
将String转化成Int的方法:Integer.parseInt(String)。
8.关于进货模块:
㈠关于进货模块的显示:我们需要的数据结构是左边的JList显示左右的InMain,右边显示选中InMain所包含的所有InDetails(复数)。所以我们用Map < InMainModel,List>来表示。
㈡最后做进货模块的查询:
注意:不是简单的类似于User和Book模块的简单查询。
在做查询的时候,查出来的是Map < InMainModel, List>,现在在进货模块的查询中,组合了对InMain查询条件和对InDetails的查询条件,要求:既要符合InMain,又要符合InDetails,对于每个InMain查询出来的结果,main对应的InDetails长度不能为0.若为0则不返回。
过程如下:
getByCondition():
(1)按照InMain的条件查询,先找出合适的InMain
(2)循环每一个InMain,再按照InDetails的查询条件,找出每个InMain对应的InDetails
(3)只有当InDetails的长度不为0的时候,InMain才作为查询条件返回。
getALl():
(1)按照InMain的查询条件,先找出合适的InMain
(2)循环每一个InMain,找出对应的InDetails并返回。
结合getAll()和getByCondition()的区别,克制利用getByCondition()更符合实际情况。
9.Stock模块:
(1)开始做进货模块的stock添加:
按照表现层、数据层和逻辑层来写。
表现层不用修改,逻辑层是在循环每一个InMain的每一条InDetail的时候,将InDetail的书和每本书的数目挑出来并且加到库存中。需要在InMain中写一个方法来专门挑出书的数目,用BookUuid来判断书的类别。
在新增一个BookModel的时候,没有对应的新建一个StockModel.
当第一次in一本书的时候,相当于StockModel进行添加。
当不是第一次in这本书的时候,相当于岁StockModel进行修改。
(2)out模块只剩下和Stock模块的交互工作,即当需要销售时,Stock要检查库存是否足够,若不够则不卖,若购卖,则库存减少即可,这个需要在OutMainEbo中完成。
(3)对于最后的库存模型需要实现的功能,和别的模块的增删改查不同,它只需要按照条件查询和查询显示全部。
10.进货模块和钱息息相关,要处处小心。要利用对冲,比如买了十本,但是增添到数据库的时候增添了100本,出错!改成销售了90本就行。
这种方式就是对冲!
11.关于登录的控制:希望不同权限的用户进入系统时看到不同的权限界面:按照权限不同直接跳转界面就行。
登录界面,验证账号和密码等信息是否匹配!
在common模块中写login验证用户名和密码。
后记:
大概要总结的就是这些了,这个简单的项目我没有写swing界面,只做了后台的交互,最后代码写了4000行左右,基本的功能已经能正确实现。我学到的最重要的东西就是整个项目的分层方式以及面向对象的方法,还有对于各个模块之间的交互有了更加深刻的体会,这个项目还有更多需要完善的地方,比如没有涉及到多线程或者并发的问题,还有很多细节方面不够完善,不过如果将重心放到综合应用基础知识,设计模式,设计思想,设计方法这些上来的话,这个项目对于我还是具有比较深刻的意义。
共勉!