JDBC 望舒客栈项目 万字详解
目录
一、前言
- 第八节内容,up打算和大家分享一个模拟项目——望舒客栈;算是对之前所学知识的一个应用和巩固。
- 该项目用到的相关知识有——Java,MySQL,JDBC(Druid连接池);主要完成登录、预定、点餐、结账等功能。
- 注意事项——①代码中的注释也很重要;②不要眼高手低,自己跟着过一遍才有收获;③点击文章的侧边栏目录或者文章开头的目录可以进行跳转。
- 良工不示人以朴,所有文章都会适时补充完善。大家如果有问题都可以在评论区进行交流或者私信up。 感谢阅读!
二、项目结构
在上一小节软件分层设计的理念——各司其职,各尽其责。PS : BasicDAO示意图如下 :
中,我们通过一张BasicDAO的示意图,简单解释了当然,我们当时也说了,这张图仅仅是为了加深大家对BasicDAO的理解而做的一个简单阐释;up只划分了三部分,真正开发中,很可能会有5个,6个甚至更多部分(比如业务层,界面层等等)。
那么,今天的望舒客栈项目,我们终于可以在该图结构的基础上,再加入一些新的东西。望舒客栈项目框架图如下 :
其实,软件分层强调的是一个逻辑概念;根据分层设计的理念,我们可以将开发目标细分化,例如可以创建不同的子包来存放对应的类;借助项目的框架图,我们可以清晰地明白项目各层之间的调用关系(界面层--> 业务层--> DAO层--> Domain层);当然,实际编写程序时,是按自下到上的顺序编写的(先写底层)。
三、准备工作
1.建立子包 :
首先新键一个项目WangshuInn(望舒客栈),然后根据望舒客栈的项目框架图,在inn包下新建几个子包,存放相应的类,如下图所示 :
dao包用户存放BasicDAO类以及项目需要用到的一些自定义DAO类;
domain包就是存放JavaBean类;
service包用于存放业务层相关的类或接口;
utils包存放工具类(德鲁伊连接池的工具类,用户输入控制的工具类);
view包则存放界面层相关的类或接口。
2.导入jar包 :
在WangshuInn项目下新建一个libs目录,用于存放jar包;将需要用到的德鲁伊连接池的jar包,mysql的jar包,以及dbutils的jar包导入到libs目录下,如下图所示 :
3.工具类 :
1° Utility工具类
Utility工具类专门负责处理用户的输入,比方说限制用户输入数据的长度,类型等。up使用的Utility工具类,是首先定义了一个readKeyBoard方法来读取一个经过限制的字符串;然后以该方法为基础,去定义其他的读取方法。
该工具类本身代码没有什么难度,都是把之前学过的一些API拿来套用,封装一下;并且整个Utility类中只定义了零星几个方法,仍具有很强的扩展性;我在代码中也做了很详细的注释,保证大家一看就懂。
Utility工具类代码如下 : (注释很重要)
2° JDBCUtilsDruid工具类
这可是老面孔了吧?咱在
JDBCUtilsDruid工具类代码如下 :
4.导入配置文件 :
JDBCUtilsDruid工具类中用到了druid.properties配置文件,因此需要将druid.properties配置文件导入到当前项目的src目录下,如下图所示 :
up的druid.properties文件如下 :
如果你要使用该配置文件,需要自行更改url,以及用户的登录信息。
5.引入BasicDAO :
关于BasicDAO,我们也在
BasicDAO类代码如下 : (无注释版本)
四、项目主体
1.界面显示 :
该项目的目标仅仅是巩固Java基础和JDBC基础,因此界面不是重点。我们只使用控制台来实现界面显示。
定义WSInnView类来实现界面显示的功能,因为望舒客栈的员工是在界面处选择使用系统的不同功能,底层肯定会调用到很多其他的方法;所以我们此处先把界面的框架打出来,让它可以完整地运行,随着我们继续编写程序,只需修改WSInnView类中调用其他方法处的代码即可。
1° 代码演示
WSInnView类代码如下 :
2° 运行测试
在WSInnView类中临时定义一个main方法,在main方法中通过创建WSInnView类对象来调用显示菜单界面的mainMenu方法,如下图所示 :
运行效果如下GIF图 :
2.用户登录 :
1° 创建员工表employee
先来创建一张保存了望舒客栈所有员工信息的员工表employee。大家可以新建一个数据库专门存放“望舒客栈”项目所用的表;因为up的JDBC系列博文所用的表都在jdbc_ex数据库中,就直接在该数据库下创建了。
创建employee表的代码如下 :
employee表效果如下 :
2° 创建JavaBean类Employee
我们可以令employee表对应的JavaBean类为Employee类,建在domain包下。
Employee类代码如下 :
3° 创建EmployeeDAO类
根据项目框架图,下一步我们就应该编写Employee类对应的DAO了,幸而我们已经在BasicDAO类中实现了基本的crud,而且暂时也没有特殊业务需求,所有我们可以直接令EmployeeDAO类去继承BasicDAO类。
在dao包下新建一个EmployeeDAO类,代码如下 :
4° 创建EmployeeService类
根据表---JavaBean---DAO---Service的对应关系,employee表还对应有一个EmployeeService类,该类只负责调用EmployeeDAO类,以完成对employee表的操作,真正执行操作是EmployeeDAO类。但要注意,EmployeeService类中的代码十分关键,它负责组织要执行的sql,直接决定了要实现怎样的业务需求。
在service包下创建EmployeeService类,代码如下 : (注意看注释!)
5° 修改界面层WSInnView类的内容
既然employee表相关的JavaBean类,DAO类,以及服务类都已经完工,并且我们还在EmployeeService类中定义了验证用户登录的方法getEmployee,那么,接下来便可以修改WSInnView类中关于用户登录的部分了。
首先,还是老规矩,在WSInnView类中创建一个EmployeeService对象,用于调用该类中的方法。如下图所示 :
然后,在登录相关部分(case "1"),利用创建好的EmployeeService对象,调用该类的getEmployee方法得到一个Employee对象,用于验证要登陆的用户是否合法(是否存在于数据库employee表中),然后在if语句中进行判断。若得到的对象非空,表明employee表中是存在该用户的。
如下图所示 :
6° 用户登录测试
up先输入一个employee表中不存在的用户来登录;然后再输入一个存在的用户来登录。
测试过程如下GIF图 :
3. 餐桌状态 :
1° 创建餐桌表diningTable
餐桌表diningTable中,要存放当前望舒客栈所有餐桌的信息,包括餐桌的编号,状态,预定餐桌人的姓名,预定餐桌人的电话。代码如下 :
餐桌表diningTable效果如下 : (初始状态每个餐桌均为空)
2° 创建JavaBean类DiningTable
DiningTable类代码如下 :
3° 创建DiningTableDAO类
DiningTableDAO类代码如下:
4° 创建DiningTableService类
DiningTableService类代码如下 :
5° 修改界面层WSInnView类的内容
还是老规矩,先在界面类中创建一个DiningTableService类的对象,如下图所示 :
然后在WSInnView类中新定义一个showTableList方法(封装的思想),用于显示所有的餐桌状态。 showTableList方法如下 :
接着,我们只需要在符合“餐桌状态”的case处调用showTableList方法即可,如下所示 :
6° 餐桌状态测试
餐桌状态的测试如下GIF图所示 :
4.预定餐桌 :
1° 需求分析
预定餐桌需要考虑两个问题——①要预定的餐桌是否存在?②若存在,该餐桌是否已被预定?
针对第一个问题,我们可以让用户输入要预定的餐桌的编号,然后去餐桌表diningTable中检索该餐桌编号是否存在;针对第二个问题,可以根据已存在的餐桌编号去diningTable表中查询其对应的那条记录,检查该餐桌的状态是否为empty。
此外,由于预定餐桌不是小事儿(要花💴的啦~),因此当用户选择预定餐桌的功能时,我们要向用户确认是否真的要当韭菜(bushi)预定餐桌。
2° 代码实现
由于在“餐桌状态”功能中,我们已经把餐桌表diningTable,包括它对应的JavaBean, DAO类,以及服务层类给一套打通了,因此我们现在只需要编写——可以实现“预定餐桌”功能的方法即可。既然是关于“餐桌”的一个具体业务,我们当然要将该方法定义到DiningTableService类中。getDiningTable方法代码如下 :
如果返回的DiningTable对象不为空(在界面中判断),说明要预定的餐桌存在;并且,如果存在的餐桌状态为empty,表示该餐桌可以预定。
因此,我们还需要在DiningTableService类中另定义一个方法,用来完成“预定餐桌”的任务,其实就是修改对应编号的餐桌的状态,以及更新预定人的信息(姓名,电话)。
orderDiningTable方法代码如下 :
3° 修改界面层WSInnView类的内容
由于我们之前已经在WSInnView类中定义了DiningTableService对象,因此这里只需要根据封装的思想,在WSInnView类定义一个方法用于接收用户的输入,并且调用我们刚才写得服务层中的orderDiningTable方法(这个方法是真正实现了预定餐桌的功能,因为其修改了数据库中表的数据)。
我们之前看过不少Java的底层源码了,相信大家对“包皮结构”已然司空见惯,这里我们就小小致敬一下,把这个方法也命名为orderDiningTable🤗,然后在该方法内去调用服务层中的orderDiningTable方法😋。因为淋过雨,所以要把别人🌂给撕烂(bushi)。好的,外层包皮的orderDiningTable方法代码如下 :
处理预定餐桌的方法完成后,我们只需要直接在符合“预定餐桌”的case处调用orderDiningTable方法即可,如下图所示 :
4° 预定餐桌测试
预定餐桌的测试如下GIF图所示 :
我们还可以进一步在数据库中查询diningTable表,查看预定人的信息是否已经成功登记,如下图所示 :
由此可见,我们已成功实现“预定餐桌”的功能。
5.显示菜品 :
1° 创建菜品表dishes
创建菜品表dishes的代码如下 :
菜品表dishes效果如下 :
2° 创建JavaBean类Dishes
同样地,dishes表对应一个自己的JavaBean类Dishes,放在domain包下。
Dishes类代码如下 :
3° 创建DishesDAO类
DishesDAO类代码如下 :
4° 创建DishesService类
DishesService类代码如下 :
5° 修改界面层WSInnView类的内容
仍然是先定义一个DishesService对象作为一个私有属性,如下图所示 :
然后根据封装的思想,再单独定义一个listDishes方法用于显示菜品, 代码如下 :
然后在符合“显示菜品”的case处调用listDishes方法即可,如下图所示 :
6° 显示菜品测试
显示菜品的测试如下GIF图所示 :
6.点餐服务 :
1° 需求分析
要想完成点餐的服务,我们必须明确顾客的两个需求——①在哪儿吃(坐哪个餐桌),②吃什么(点哪些个菜)。
而要想完成这两个需求,又需要做以下的一些更细节的判断——
①顾客要坐的餐桌存不存在?如果存在,是不是已经有人预定了?
②顾客要点的菜大厨(或者小厨)能不能做?如果能做,要做几份?
这么来看,点餐前主要围绕“餐桌编号”和“菜品编号”两个字段来进行业务处理。
那么,点餐后呢?如果点餐成功我们要做些什么?
首先,既然顾客指定要坐哪个桌了,那么根据“先来先服务”的原则,这桌暂时是不能给其他人坐了,因此我们要将该餐桌的状态修改为booked(已预定)。
其次,点餐成功后,你客栈肯定得指定顾客一共点了多少摩拉的菜吧,不然你怎么收钱?因此,我们要设法生成一张账单,记录顾客一共点了那些个菜,一共要交多少摩拉。
2° 创建账单表bill
哎,既然都提到生成账单了,这不得创建一张表?水到渠成的结果。up这里创建账单表的思路是,每点一份菜,都会对应账单表中的一条记录;若同一张餐桌上点了不同的菜,就让对应多条记录的“餐桌编号”字段相同。
创建账单表bill的代码如下 :
账单表bill效果如下 :
3° 创建JavaBean类Bill
Bill类代码如下 :
4° 创建BillDAO类
BillDAO类代码如下 :
5° 创建BillService类
对于BillService类中的代码,首先我们肯定是要定义一个方法来生成账单的,但是账单中有一个字段total代表了菜品总价,而菜品总价 = 菜品单价 * 菜品数量;菜品数量我们可以令用户指定,在方法形参列表显式传入,但是菜品单价只能通过菜品编号到dishes表中查询。
因此,我们需要先补充一下DishesService类中的内容,新定义一个“可以根据菜品的编号来返回一个对应的菜品对象”的getDish方法,代码如下 :
继续,既然我们要在BillService类中调用DishesService类中的方法,那就得在BillService类中定义一个DishesService对象了(业务层内的协调工作),如下图所示 :
通过该对象就可以得到菜品的总价了,如下图所示 :
而对于账单表中的state(账单状态)和payment(付款方式)这两个字段,我们可以默认传入"unpaid"(未付款)和"unknown"(未知),因为现在还在点餐嘛,等你结账时才知道你是怎么付的款。OK,这下账单的事情我们可以解决了。
But,还有一个问题!—— 怎么修改对应餐桌的状态?
诶,上面你都请过DishesService出山了,此处你再去请DiningTableService也出山帮个忙不久好啦!Yes,所以接下来我们要在DiningTableService类中再补充一个方法,用于修改指定编号餐桌的状态。代码如下 :
这下我们可算是能回到BillService类了。不得感慨:兜兜转转,还得是人脉😭(bushi)。BillService类代码如下:
6° 修改界面层WSInnView类的内容
还是老规矩,先来定义一个BillService属性,如下图所示 :
然后,定义一个用于实现接收用户点餐信息的方法orderDishes,代码如下 :
最后,在符合“点餐功能”的case语句处调用该方法即可,如下图所示 :
7° 点餐功能测试
点餐功能的测试如下GIF图所示 :
在GIF图演示中,我们成功地在三号桌点了3份腌笃鲜和2份素鲍鱼,美汁儿汁儿!
7.查看账单 :
1° 需求分析
要查看账单,我们自然要打印出bill表中的一些信息。在BillService类中定义一个打印账单信息的方法,自然,该方法内肯定要用到BillDAO对象,通过BillDAO对象完成对bill表的操作。
2° 代码实现
在BillService类中定义listBills方法,代码如下 :
3° 修改界面层WSInnView类的内容
在WSInnView类中定义方法来显示账单信息,代码如下 :
然后,在符合"显示账单" 的case语句处调用该方法,如下图所示 :
4° 查看账单的测试
查看账单的测试如下GIF图所示 :
8.结账服务 :
1° 需求分析
根据我们日常生活中的经验,吃饭结账时一般都是按桌来结的,某某桌某某桌结账。因此结账服务中,首先我们要确定要结账的餐桌编号;自然,我们就需要对给出的餐桌编号进行校验,判断它是否存在以及该餐桌是否有账单需要结账。
然后,我们还需要对结账的方式进行记录(现金,微信,支付宝),并且要修改对应账单的state字段(账单状态要从unpaid修改为paid)和payment字段(付款方式)。最后,将结账餐桌的状态修改为empty。
注意——
①只有处于"dining"(就餐)状态的餐桌才可以进行结账操作,结账后将该餐桌的状态从dining设置为empty。
②已有的bill,diningTable表及其对应的DAO和Service足以支撑我们开展“结账服务”的业务,我们只需要在这些个Service中增加新的业务逻辑即可。
2° 代码实现
首先,在BillService类中定义一个方法,该方法可以根据传入的餐桌编号来确定该餐桌有无需要结账的账单。hasUnpaid方法代码如下 :
当返回的bill对象不为空,说明对应编号的餐桌可以结账。
结账后,需要修改bill表中的信息,将已结账账单的state修改为"paid",payment修改为客户的实际付款方式。同时,我们还需要修改该餐桌的状态,从“dining”修改为“empty”。
这就需要我们另定义一个方法用户修改bill和diningTable的状态,代码如下 :
3° 修改界面层WSInnView类的内容
在WSInnView类中定义checkOut方法用于接收用户录入的结账信息,并调用我们之前定义的方法, 完成结账功能。checkOut方法代码如下 :
接着,在符合“结账服务”的case语句处调用checkOut方法即可,如下图所示 :
4° 结账服务的测试
结账服务的测试如下GIF图所示 :
五、项目扩展
1.关于用户登录的扩展 :
up给出的employee表只有五个字段(id, name, empId, empPwd, career);实际上,在我们之前的MySQL系列博文中,up创建的员工表都有8个甚至更多字段。因此,我们可以考虑扩大员工表的规模,将其增加到更多的字段;再比如说,可以将不同职务的员工再划分出不同的部门,以及不同的员工要发的工资也不相同。这就引申出了工资表和部门表,可以帮助我们实现更加复杂的业务。
2.关于预定餐桌的扩展 :
不知大家发现没有,在上文中,我们确实是通过“餐桌是否存在”以及“餐桌是否已经被预定”这两个因素实现了“预定餐桌”的功能。但是,我们没有考虑到预定时间的问题;预定人预约的餐桌是几点钟要来吃?万一不来了那不是占着茅坑不拉屎么?还有,假设有客户已经预约了餐桌,但是想退订了,我们可没有实现退订的功能。
以上两个问题都可以进行优化。比方说,扩展餐桌表的内容,增加预定时间的字段;或者说,将被预定的餐桌单独存放一张表,或者以视图的方式形成映射关系。对于退订的问题,我们可以在DiningTableService类中再定义一个实现“退订功能”的方法,将餐桌的状态设置为empty,然后清除预定人的信息。对应地在界面层WSInnView类中,我们则可以增加一个新的功能“退订餐桌”,然后判断该餐桌是否已经预定,如果已经预定了就可以退订;或者说,直接在“预定餐桌”的模块进行扩展,让用户自行选择是预定餐桌还是退订餐桌。具体的退订实现就是在WSInnView类中再单独定义一个要退订的方法,接收用户输入的信息,做出判断后,调用DiningTableService中“退订餐桌”的方法,思路和我们上面说的其实是一致的。
3.关于查看账单的扩展 :
Δ多表查询的需求 :
不知道大家发现没有,我们上面所有功能的实现,全部是单表查询!这可对不起我们学习了的那么多的SQL语句。
其实,我们上面这一大堆,主要目的是先把项目的功能需求给实现了,也就是先把房子的框架打好了,相当于是个毛胚房。你没发现吗,我们顺着“望舒客栈项目框架图”的思路,编写代码时都是按着JavaBean --> DAO --> Service --> 修改View的顺序进行的,而实际调用确实反着来的。跟着框架图一套打下来,的确我们把几个功能都给实现了。
但是,毛胚房可住不了人。我们得把房子好好装修一下。
打个比方,查看账单时,我们无法直观地看到这份账单是对应哪个菜的!而只能通过菜品编号,去dishes表中查找对应的菜品,这不是骑驴找马?太费事了,为什么不能直接在显示账单时,就把对应的菜品名也打印出来?
①多表实现思路一:
我们可以新定义一个JavaBean类,但这个JavaBean类不与任何表有直接对应关系,而是将你想获取到的多张表中的多个字段定义在一块儿。当然,我们还需要定义其对应的DAO类,根据实际需求,也可能需要另定义Service类。那我们靠这张"杂糅"的表又如何实现多表查询呢?
很简单,以“显示账单时打印出菜品”为例,我们只需要在BillService中另定义一个方法专门返回含有菜品名称的账单信息,这就需要我们修改SQL语句;并且泛型要定义为这个杂糅类。SQL语句就是我们学过的多表查询的语句,注意字段匹配条件即可。
②多表实现思路二:
在一个表对应的JavaBean类中追加一个新属性,该属性的类型就是另一个JavaBean类型。使用ApacheDBUtils的MapListHandler来保存数据。那么在遍历集合时,我们便可以通过这个属性的setter方法来修改另一个JavaBean类中的属性。
4.关于结账服务的扩展 :
我估计大家在完成结账服务的模块是怀着疑虑的。为什么?nnd结账服务居然整个流程没有涉及到米的问题!没错,我们只考虑了结账前和结账后的问题。结账前我们要校验桌号的合法性;结账后我们要修改餐桌的状态以及该餐桌对应的账单的信息(账单状态,账单付款方式)。虽然每一份菜品对应的账单中标出了该菜品的总价,但是我们没有计算出一个餐桌结账时的总价。这就很不实用了,难道员工查询账单后,难道还要拿把计算器把某一个餐桌的账单算一下?显然,如果我们在BillService类中专门定义一个方法用来计算结账时某一个餐桌一共要交多少钱,就省去了员工的这个麻烦。
5.关于更多新功能的扩展 :
我在上面就说了,咱们这个项目就是个毛胚房。你要说基本功能吧,有水有电,但是你要说住吧,装修还不如住别人家厕所。所以,除了上文up提到的“退订餐桌”的新功能外,我们还可以扩展很多新的独立功能,比方说,仓库管理,如果登录用户是仓库主管,他就可以查看仓库的信息,包括存货信息等;再比如说,活动管理,假设海灯节什么大节日的到了,客栈大概率是要打折的,这时候需要记录活动的具体内容。再比如说,人事管理,工资管理,账目管理等等功能。而且,别忘了,望舒客栈大老板非尔戈黛特应该是可以访问系统的所有模块的,并且具有给不同用户授予不同权限的权力,其实也算人事管理功能了。
以上,都是大家可以考虑的内容。
六、最终代码 :
1.domain包 :
domain包下最终定义了Employee, DiningTable, Dishes, Bill四个类,如下图所示 :
全部源码up已经以代码包的形式上传,找到domain目录即可。
2.dao包 :
dao包下共定义了BasicDAO,EmployeeDAO,DiningTableDAO,DishesDAO,BillDAO五个类,如下图所示 :
全部源码up已经以代码包的形式上传,找到dao目录即可。
3.service包 :
service包下共定义了EmployeeService, DiningTableService, DishesService, BillService四个类,如下图所示 :
全部源码up已经以代码包的形式上传,找到service目录即可。
4.view包 :
view包下只有WSInnView一个类,全部源码up已经以代码包的形式上传,找到view目录即可。
5.utils包 :
utilis包下即我们一开始导入的两个工具类,一个是Utility,用于处理用户的输入信息;还有一个是JDBCUtilsDruid,即德鲁伊连接池的封装工具类。全部源码up已经以代码包的形式上传,找到utils目录即可。
七、总结
- 🆗,以上就是JDBC系列博文第八节的全部内容了。
- 总结一下,整个望舒客栈的项目,我们都是围绕一开始的“望舒客栈项目框架图”来进行的,即表的定义--> 对应JavaBean的定义--> 对应DAO的定义--> 对应Service的定义--> 对界面层的修改和补充。其实,整个项目你要说有什么难度吧,其实代码本身是几乎没有难度的,而且我们这仅仅是个模拟项目,啥都没用上呢!不过,整个项目对于我们业务分析的能力和代码的熟练程度还是有较大益处的。
- 下一节内容——暂时无了哈哈,JDBC暂时告一段落🍐。感谢阅读!
System.out.println("END---------------------------------------------------------------------------");
- 点赞
- 收藏
- 关注作者
评论(0)