JDBC 望舒客栈项目 万字详解

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS PostgreSQL,集群系列 2核4GB
简介: JDBC 第八节 望舒客栈项目 巩固Java和JDBC基础的不二之选!(代码量较大)

目录

一、前言

二、项目结构

三、准备工作

       1.建立子包 :

       2.导入jar包 :

       3.工具类 :

               1° Utility工具类

               2° JDBCUtilsDruid工具类

       4.导入配置文件 :

       5.引入BasicDAO :

四、项目主体

       1.界面显示 :

               1° 代码演示

               2° 运行测试

       2.用户登录 :

               1° 创建员工表employee

               2° 创建JavaBean类Employee

               3° 创建EmployeeDAO类

               4° 创建EmployeeService类

               5° 修改界面层WSInnView类的内容

               6° 用户登录测试

       3. 餐桌状态 :

               1° 创建餐桌表diningTable

               2° 创建JavaBean类DiningTable

               3° 创建DiningTableDAO类

               4° 创建DiningTableService类

               5° 修改界面层WSInnView类的内容

               6° 餐桌状态测试

       4.预定餐桌 :

               1° 需求分析

               2° 代码实现

               3° 修改界面层WSInnView类的内容

               4° 预定餐桌测试

       5.显示菜品 :

               1° 创建菜品表dishes

               2° 创建JavaBean类Dishes

               3° 创建DishesDAO类

               4° 创建DishesService类

               5° 修改界面层WSInnView类的内容

               6° 显示菜品测试

       6.点餐服务 :

               1° 需求分析

               2° 创建账单表bill

               3° 创建JavaBean类Bill

               4° 创建BillDAO类

               5° 创建BillService类

               6° 修改界面层WSInnView类的内容

               7° 点餐功能测试

       7.查看账单 :

               1° 需求分析

               2° 代码实现

               3° 修改界面层WSInnView类的内容

               4° 查看账单的测试

       8.结账服务 :

               1° 需求分析

               2° 代码实现

               3° 修改界面层WSInnView类的内容

               4° 结账服务的测试

五、项目扩展

       1.关于用户登录的扩展 :

       2.关于预定餐桌的扩展 :

       3.关于查看账单的扩展 :

               Δ多表查询的需求 :  

               ①多表实现思路一:

               ②多表实现思路二:

       4.关于结账服务的扩展 :

       5.关于更多新功能的扩展 :

六、最终代码 :

       1.domain包 :

       2.dao包 :

       3.service包 :

       4.view包 :

       5.utils包 :

七、总结


一、前言

    • 第八节内容,up打算和大家分享一个模拟项目——望舒客栈;算是对之前所学知识的一个应用和巩固。
    • 该项目用到的相关知识有——Java,MySQL,JDBC(Druid连接池);主要完成登录、预定、点餐、结账等功能。
    • 注意事项——代码中的注释也很重要;不要眼高手低,自己跟着过一遍才有收获;点击文章的侧边栏目录或者文章开头的目录可以进行跳转。
    • 良工不示人以朴,所有文章都会适时补充完善。大家如果有问题都可以在评论区进行交流或者私信up。 感谢阅读!

    二、项目结构

                   在上一小节BasicDAO详解中,我们通过一张BasicDAO的示意图,简单解释了软件分层设计的理念——各司其职,各尽其责PS : BasicDAO示意图如下 :

    image.gif编辑

                   当然,我们当时也说了,这张图仅仅是为了加深大家对BasicDAO的理解而做的一个简单阐释;up只划分了三部分,真正开发中,很可能会有5个,6个甚至更多部分(比如业务层,界面层等等)。

                   那么,今天的望舒客栈项目,我们终于可以在该图结构的基础上,再加入一些新的东西。望舒客栈项目框架图如下 :

    image.gif编辑

                   其实,软件分层强调的是一个逻辑概念;根据分层设计的理念,我们可以将开发目标细分化,例如可以创建不同的子包来存放对应的类;借助项目的框架图,我们可以清晰地明白项目各层之间的调用关系(界面层--> 业务层--> DAO层--> Domain层);当然,实际编写程序时,是按自下到上的顺序编写的(先写底层)。


    三、准备工作

           1.建立子包 :

                   首先新键一个项目WangshuInn(望舒客栈),然后根据望舒客栈的项目框架图,在inn包下新建几个子包,存放相应的类,如下图所示 :

    image.gif编辑

           dao包用户存放BasicDAO类以及项目需要用到的一些自定义DAO类;

           domain包就是存放JavaBean类;

           service包用于存放业务层相关的类或接口;

           utils包存放工具类(德鲁伊连接池的工具类,用户输入控制的工具类);

           view包则存放界面层相关的类或接口。

           2.导入jar包 :

                   在WangshuInn项目下新建一个libs目录,用于存放jar包;将需要用到的德鲁伊连接池的jar包,mysql的jar包,以及dbutils的jar包导入到libs目录下,如下图所示 :

    image.gif编辑

           3.工具类 :

                   1° Utility工具类

                           Utility工具类专门负责处理用户的输入,比方说限制用户输入数据的长度,类型等。up使用的Utility工具类,是首先定义了一个readKeyBoard方法来读取一个经过限制的字符串;然后以该方法为基础,去定义其他的读取方法。

                           该工具类本身代码没有什么难度,都是把之前学过的一些API拿来套用,封装一下;并且整个Utility类中只定义了零星几个方法,仍具有很强的扩展性;我在代码中也做了很详细的注释,保证大家一看就懂。

                           Utility工具类代码如下 : (注释很重要

    package inn.utils;
    import java.util.Scanner;
    /**
         Utility工具类用于处理各种情况下用户的输入;
         并且可以由程序员手动设置参数。
     */
    @SuppressWarnings("all")
    public class Utility {
      //静态属性
        private static Scanner scanner = new Scanner(System.in);
        /**
         * @function : 读取用户输入的字符串;
         *             该方法用于辅助其他方法,并不单独调用,因此设为private类型。
         * @param limit : 限制读取的长度;若输入为空, 或者输入长度大于limit, 提示重新输入
         * @param blankReturn : true———可以接收空字符串;
         *                      false———不允许空字符串。
         * @return : 返回读取后的字符串
         */
        private static String readKeyBoard(int limit, boolean blankReturn) {
            String line = "";
            while (scanner.hasNextLine()) {
                line = scanner.nextLine();
                /*
                    检查用户输入的字符串是否为空;
                    PS : windows操作系统下,如果直接输入Enter等效于/r/n
                 */
                if (line.length() == 0) {
                    if (blankReturn) {
                        return line;
                    }
                    else {
                        continue;
                        //若不允许为空,一直循环下去,直到用户输入非空字符串。
                    }
                }
                //检查用户输入的字符串的长度是否合法
                if (line.length() < 1 || line.length() > limit) {
                    System.out.print("字符串的长度应该在(0," + limit + "]之间,请重新输入:");
                    continue;
                }
                break;
            }
            return line;
        }
        /**
         * @function : 通过设置limit = 1,限制用户仅能输入一个字符;判断该字符
         *             是否为[1,5]之间的一个数字,若不是,提示重新输入;若是,返回该字符;
         *             调用该方法的目的是 : 可以根据返回字符的不同,做不同的业务处理
         * @return : 返回输入的字符,范围是[1,5]。PS : 范围可以由程序⚪手动指定
         */
      public static char readMenuSelection() {
            char c;
            //for形式的死循环,直到满足break的条件才跳出循环!
            for (; ; ) {
                String str = readKeyBoard(1, false);
                //类型转换(String --> char)
                c = str.charAt(0);
                if (c != '1' && c != '2' && 
                    c != '3' && c != '4' && c != '5') {
                    System.out.print("选择错误,请重新输入:");
                } else {
                    break;
                }
            }
            return c;
        }
        /**
         * @function : 相比于上一个方法的区别是———不再限制用户的输入;仅要求输入一个字符即可
         * @return : 返回char类型的一个字符
         */
        public static char readChar() {
            String str = readKeyBoard(1, false);
            return str.charAt(0);
        }
        /**
         * @function : 相比于上一个方法的区别是————允许用户输入空字符串
         * @param defaultValue : 指定的默认值
         * @return : 若用户输入了空串;返回指定的默认值。
         *           若用户输入的字符串(长度仍然限制为 1)非空,返回该字符串的字符类型。
         */
        public static char readChar(char defaultValue) {
            String str = readKeyBoard(1, true);//要么是空字符串,要么是一个字符
            return (str.length() == 0) ? defaultValue : str.charAt(0);
        }
        /**
         * @function : 返回用户输入的整型数据(用parseInt方法实现类型转换);
         *             要求整型数据的长度在(0,limit]区间内,否则令其重新输入。
         * @return : 返回合法的整数
         */
        public static int readInt() {
            int n;
            for (; ; ) {
                String str = readKeyBoard(10, false);//一个整数,长度<=10位
                try {
                    //类型转换(String --> Int)
                    n = Integer.parseInt(str);
                    break;
                } catch (NumberFormatException e) {
                    System.out.print("数字输入错误,请重新输入:");
                }
            }
            return n;
        }
        /**
         * @function : 与上一个方法的区别是———允许输入空字符串;
         * @param defaultValue : 指定的默认值
         * @return : 若用户输入空字符串,返回指定的默认值;
         *           若用户输入非空字符串,返回输入的合法整型数据。
         */
        public static int readInt(int defaultValue) {
            int n;
            for (; ; ) {
                String str = readKeyBoard(10, true);
                if (str.equals("")) {
                    return defaultValue;
                }
                try {
                    n = Integer.parseInt(str);
                    break;
                } catch (NumberFormatException e) {
                    System.out.print("数字输入错误,请重新输入:");
                }
            }
            return n;
        }
        /**
         * @function : 返璞归真,直接调用readKeyBoard方法
         * @param limit : 限制字符串的长度范围———(0,limit]
         * @return : 返回非空的,合法的字符串
         */
        public static String readString(int limit) {
            return readKeyBoard(limit, false);
        }
        /**
         * @function : 与上一个方法的区别在于———允许输入空字符串
         * @param limit : 限制字符串的最大长度
         * @param defaultValue : 指定的默认值
         * @return : 若用户输入的字符串为空,返回默认值;
         *           否则返回合法的字符串
         */
        public static String readString(int limit, String defaultValue) {
            String str = readKeyBoard(limit, true);
            return str.equals("")? defaultValue : str;
        }
        /**
         * @function : 与之前的readMenuSelection类似,令用户输入一个具体的字符;
         *             区别在于,readConfirmSelection方法是指定输入Y或者N,表示Yes和No
         * @return : 返回Y 或者 N
         */
        public static char readConfirmSelection() {
            System.out.println("请输入你的选择(Y/N): ");
            char c;
            //for形式的死循环,直到满足break的条件才跳出循环!
            for (; ; ) {
                //String类常用转换功能的方法之一———toUpperCase() : 转为大写
                String str = readKeyBoard(1, false).toUpperCase();
                c = str.charAt(0);
                if (c == 'Y' || c == 'N') {
                    break;
                } else {
                    System.out.print("选择错误,请重新输入:");
                }
            }
            return c;
        }
    }

    image.gif

                  2° JDBCUtilsDruid工具类

                   这可是老面孔了吧?咱在JDBC 连接池刚讲过的。

                   JDBCUtilsDruid工具类代码如下 :

    package inn.utils;
    import com.alibaba.druid.pool.DruidDataSourceFactory;
    import javax.sql.DataSource;
    import java.io.FileInputStream;
    import java.sql.Connection;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.sql.Statement;
    import java.util.Properties;
    /**
        基于Druid连接池的工具类
     */
    public class JDBCUtilsDruid {
        private static DataSource dataSource;
        static {
            Properties properties = new Properties();
            try {
                properties.load(new FileInputStream("src/druid.properties"));
                dataSource = DruidDataSourceFactory.createDataSource(properties);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        public static Connection getConnection() throws SQLException {
            return dataSource.getConnection();
        }
        public static void close(ResultSet resultSet, Statement statement, Connection connection) {
            try {
                if (resultSet != null) {
                    resultSet.close();
                }
                if (statement != null) {
                    statement.close();
                }
                if (connection != null) {
                    connection.close();
                }
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    }

    image.gif

           4.导入配置文件 :

                   JDBCUtilsDruid工具类中用到了druid.properties配置文件,因此需要将druid.properties配置文件导入到当前项目的src目录下,如下图所示 :

    image.gif编辑

                   up的druid.properties文件如下 :

    driverClassName=com.mysql.cj.jdbc.Driver
    url=jdbc:mysql://localhost:3306/jdbc_ex?rewriteBatchedStatements=true
    username=root
    password=RA9_Cyan
    initialSize=10
    minIdle = 5
    maxActive=50
    maxWait=5000

    image.gif

                   如果你要使用该配置文件,需要自行更改url,以及用户的登录信息。

           5.引入BasicDAO :

                   关于BasicDAO,我们也在上一小节中讲过了,这里直接拿来用即可,放在dao包下。

                  BasicDAO类代码如下 : (无注释版本)

    package inn.dao;
    import inn.utils.JDBCUtilsDruid;
    import org.apache.commons.dbutils.QueryRunner;
    import org.apache.commons.dbutils.handlers.BeanHandler;
    import org.apache.commons.dbutils.handlers.BeanListHandler;
    import org.apache.commons.dbutils.handlers.ScalarHandler;
    import java.sql.Connection;
    import java.sql.SQLException;
    import java.util.List;
    /**
        BasicDAO类,完成所有DAO通用的crud操作。
     */
    public class BasicDAO<T> {
        private QueryRunner queryRunner = new QueryRunner();
        public int update(String sql, Object... params) {
            int affectedRows = 0;
            Connection connection = null;
            try {
                connection = JDBCUtilsDruid.getConnection();
                affectedRows = queryRunner.update(connection,sql,params);
                return affectedRows;
            } catch (SQLException e) {
                throw new RuntimeException(e);
            } finally {
                JDBCUtilsDruid.close(null, null, connection);
            }
        }
        public List<T> queryMultiply(String sql, Class<T> clazz, Object... params) {
            Connection connection = null;
            try {
                connection = JDBCUtilsDruid.getConnection();
                List<T> query = queryRunner.query(connection,sql,new BeanListHandler<T>(clazz),params);
                return query;
            } catch (SQLException e) {
                throw new RuntimeException(e);
            } finally {
                JDBCUtilsDruid.close(null, null, connection);
            }
        }
        public T querySingle(String sql, Class<T> clazz, Object... params) {
            Connection connection = null;
            try {
                connection = JDBCUtilsDruid.getConnection();
                return queryRunner.query(connection, sql, new BeanHandler<T>(clazz), params);
            } catch (SQLException e) {
                throw new RuntimeException(e);
            } finally {
                JDBCUtilsDruid.close(null, null, connection);
            }
        }
        public Object queryScalar(String sql, Object... params) {
            Connection connection = null;
            try {
                connection = JDBCUtilsDruid.getConnection();
                return queryRunner.query(connection, sql, new ScalarHandler<>(), params);
            } catch (Exception e) {
                throw new RuntimeException(e);
            } finally {
                JDBCUtilsDruid.close(null,null,connection);
            }
        }
    }

    image.gif


    四、项目主体

           1.界面显示 :

                   该项目的目标仅仅是巩固Java基础和JDBC基础,因此界面不是重点。我们只使用控制台来实现界面显示。

                   定义WSInnView类来实现界面显示的功能,因为望舒客栈的员工是在界面处选择使用系统的不同功能,底层肯定会调用到很多其他的方法;所以我们此处先把界面的框架打出来,让它可以完整地运行,随着我们继续编写程序,只需修改WSInnView类中调用其他方法处的代码即可。

                   1° 代码演示

                   WSInnView类代码如下 :

    package inn.view;
    import inn.utils.Utility;
    public class WSInnView {
        //控制菜单的显示和退出
        private boolean loop = true;
        //接收用户的输入
        private String key = "";
        //显示主菜单
        public void mainMenu() {
            while (loop) {
                System.out.println("===============望舒客栈===============");
                System.out.println("\t\t 1.登录望舒客栈");
                System.out.println("\t\t 2.退出望舒客栈");
                System.out.print("请输入你的选择:");
                key = Utility.readString(1);
                switch (key) {
                    case "1" :
                        System.out.print("请输入员工号:");
                        String empID = Utility.readString(64);
                        System.out.print("请输入密  码:");
                        String empPwd = Utility.readString(64);
                        if ("数据库判断" != null) {
                            System.out.println("===============登陆成功===============\n");
                            while (loop) {
                                System.out.println("============望舒客栈(二级菜单)============");
                                System.out.println("\t\t 1.餐桌状态");
                                System.out.println("\t\t 2.预定餐桌");
                                System.out.println("\t\t 3.显示菜品");
                                System.out.println("\t\t 4.点餐服务");
                                System.out.println("\t\t 5.查看账单");
                                System.out.println("\t\t 6.结账服务");
                                System.out.println("\t\t 9.退出系统");
                                System.out.print("请输入你的选择:");
                                key = Utility.readString(1);
                                switch (key) {
                                    case "1" :
                                        System.out.println("餐桌状态");
                                        break;
                                    case "2" :
                                        System.out.println("预定餐桌");
                                        break;
                                    case "3" :
                                        System.out.println("显示菜品");
                                        break;
                                    case "4" :
                                        System.out.println("点餐服务");
                                        break;
                                    case "5" :
                                        System.out.println("查看账单");
                                        break;
                                    case "6" :
                                        System.out.println("结账服务");
                                        break;
                                    case "9" :
                                        loop = false;
                                        break;
                                    default :
                                        System.out.println("选择不合法,请重新输入!");
                                        break;
                                }
                            }
                        } else {
                            System.out.println("===============登陆失败===============");
                        }
                        break;
                    case "2" :
                        loop = false;
                        break;
                    default :
                        System.out.println("选择不合法,请重新输入!");
                }
            }
            System.out.println("已退出望舒客栈系统...");
        }
    }

    image.gif

                  2° 运行测试

                   在WSInnView类中临时定义一个main方法,在main方法中通过创建WSInnView类对象来调用显示菜单界面的mainMenu方法,如下图所示 :

    image.gif编辑

                   运行效果如下GIF图 :  

    image.gif编辑

          2.用户登录 :

                   1° 创建员工表employee

                   先来创建一张保存了望舒客栈所有员工信息的员工表employee。大家可以新建一个数据库专门存放“望舒客栈”项目所用的表;因为up的JDBC系列博文所用的表都在jdbc_ex数据库中,就直接在该数据库下创建了。

                   创建employee表的代码如下 :

    CREATE TABLE IF NOT EXISTS employee(
        id INT PRIMARY KEY AUTO_INCREMENT,          #员工id
        `name` VARCHAR(64) NOT NULL DEFAULT '',       #员工姓名
        empId VARCHAR(64) UNIQUE NOT NULL DEFAULT '', #员工号(账号)
        empPwd CHAR(32) NOT NULL DEFAULT '',        #员工密码
        career VARCHAR(255) NOT NULL DEFAULT ''       #员工职位
    ) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin ENGINE INNODB;
    #向表中加入员工数据
    INSERT INTO employee 
        VALUES 
        (NULL, '王小美', '9290118', MD5('wangxiaomei118'), '超级保洁'),
        (NULL, '王大美', '9290119', MD5('wangdamei119'), '弟弟保洁'),
        (NULL, '香菱', '9290120', MD5('xiangling120'), '大厨'),
        (NULL, '锅巴', '9290121', MD5('guoba121'), '小厨'),
        (NULL, '胡桃', '9290122', MD5('hutao122'), '牛逼服务员'),
        (NULL, '菲尔戈黛特', '0000001', MD5('boss'), '老板(不是老板娘)');
    SELECT * FROM employee;

    image.gif

                   employee表效果如下 :

    image.gif编辑

                   2° 创建JavaBean类Employee

                   我们可以令employee表对应的JavaBean类为Employee类,建在domain包下。

                   Employee类代码如下 :

    package inn.domain;
    public class Employee {
        private Integer id;
        private String name;
        private String empId;
        private String empPwd;
        private String career;
        public Employee() {
        }
        public Employee(Integer id,String name,String empId,String empPwd,String career) {
            this.id = id;
            this.name = name;
            this.empId = empId;
            this.empPwd = empPwd;
            this.career =career;
        }
        public Integer getId() {
            return id;
        }
        public void setId(Integer id) {
            this.id = id;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public String getEmpId() {
            return empId;
        }
        public void setEmpId(String empId) {
            this.empId = empId;
        }
        public String getEmpPwd() {
            return empPwd;
        }
        public void setEmpPwd(String empPwd) {
            this.empPwd = empPwd;
        }
        public String getCareer() {
            return career;
        }
        public void setCareer(String career) {
            this.career = career;
        }
        @Override
        public String toString() {
            return "\nEmployee{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", empId='" + empId + '\'' +
                    ", empPwd='" + empPwd + '\'' +
                    ", career='" + career + '\'' +
                    '}';
        }
    }

    image.gif

                  3° 创建EmployeeDAO类

                   根据项目框架图,下一步我们就应该编写Employee类对应的DAO了,幸而我们已经在BasicDAO类中实现了基本的crud,而且暂时也没有特殊业务需求,所有我们可以直接令EmployeeDAO类去继承BasicDAO类。

                   在dao包下新建一个EmployeeDAO类,代码如下 :

    package inn.dao;
    import inn.domain.Employee;
    public class EmployeeDAO extends BasicDAO<Employee> {
        //1.注意泛型!
        //2.EmployeeDAO中也可以有自己的特有方法。
    }

    image.gif

                   4° 创建EmployeeService类

                   根据表---JavaBean---DAO---Service的对应关系,employee表还对应有一个EmployeeService类,该类只负责调用EmployeeDAO类,以完成对employee表的操作,真正执行操作是EmployeeDAO类。但要注意,EmployeeService类中的代码十分关键,它负责组织要执行的sql,直接决定了要实现怎样的业务需求

                   在service包下创建EmployeeService类,代码如下 : (注意看注释!)

    package inn.service;
    import inn.dao.EmployeeDAO;
    import inn.domain.Employee;
    /**
     * 通过创建EmployeeDAO对象,来完成对employee表的相关操作。
     */
    public class EmployeeService {
        private EmployeeDAO employeeDAO = new EmployeeDAO();
        /*
            根据传入的账号密码判断该员工是否存在;
            若存在,返回一个Employee对象(查到了employee表中的某条记录);否则返回null(什么都没查到)
            PS : 对于员工密码的判断条件,一定要采用MD5函数进行加密操作,否则查无此人!
         */
        public Employee getEmployee(String empId, String empPwd) {
            String sql = "SELECT * FROM employee " +
                                "WHERE empId = ? AND empPwd = MD5(?);";
            Employee employee = employeeDAO.querySingle(sql, Employee.class, empId, empPwd);
            return employee;
        }
    }

    image.gif

                  5° 修改界面层WSInnView类的内容

                   既然employee表相关的JavaBean类,DAO类,以及服务类都已经完工,并且我们还在EmployeeService类中定义了验证用户登录的方法getEmployee,那么,接下来便可以修改WSInnView类中关于用户登录的部分了。

                   首先,还是老规矩,在WSInnView类中创建一个EmployeeService对象,用于调用该类中的方法如下图所示 :

    image.gif编辑

                   然后,在登录相关部分(case "1"),利用创建好的EmployeeService对象,调用该类的getEmployee方法得到一个Employee对象,用于验证要登陆的用户是否合法(是否存在于数据库employee表中),然后在if语句中进行判断。若得到的对象非空,表明employee表中是存在该用户的

                   如下图所示 :

    image.gif编辑

                   6° 用户登录测试

                   up先输入一个employee表中不存在的用户来登录;然后再输入一个存在的用户来登录。

                   测试过程如下GIF图 :

    image.gif编辑

           3. 餐桌状态 :

                   1° 创建餐桌表diningTable

                   餐桌表diningTable中,要存放当前望舒客栈所有餐桌的信息,包括餐桌的编号,状态,预定餐桌人的姓名,预定餐桌人的电话。代码如下 :

    CREATE TABLE IF NOT EXISTS diningTable(
        id INT PRIMARY KEY AUTO_INCREMENT,      #餐桌编号
        state VARCHAR(64) NOT NULL DEFAULT '',    #餐桌状态
        orderName VARCHAR(64) NOT NULL DEFAULT '',  #餐桌预定人的姓名
        orderPhone VARCHAR(64) NOT NULL DEFAULT ''  #餐桌预定人的电话
    ) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin ENGINE INNODB;
    INSERT INTO diningTable 
        VALUES 
        (NULL, 'empty', '', ''),
        (NULL, 'empty', '', ''),
        (NULL, 'empty', '', ''),
        (NULL, 'empty', '', '');
    SELECT * FROM diningTable;

    image.gif

                   餐桌表diningTable效果如下 : (初始状态每个餐桌均为空)

    image.gif编辑

                   2° 创建JavaBean类DiningTable

                  DiningTable类代码如下 :

    package inn.domain;
    public class DiningTable {
        private Integer id;
        private String state;
        private String orderName;
        private String orderPhone;
        public DiningTable() {
        }
        public DiningTable(Integer id, String state, String orderName, String orderPhone) {
            this.id = id;
            this.state = state;
            this.orderName = orderName;
            this.orderPhone = orderPhone;
        }
        public Integer getId() {
            return id;
        }
        public void setId(Integer id) {
            this.id = id;
        }
        public String getState() {
            return state;
        }
        public void setState(String state) {
            this.state = state;
        }
        public String getOrderName() {
            return orderName;
        }
        public void setOrderName(String orderName) {
            this.orderName = orderName;
        }
        public String getOrderPhone() {
            return orderPhone;
        }
        public void setOrderPhone(String orderPhone) {
            this.orderPhone = orderPhone;
        }
        @Override
        public String toString() {
            return String.format("%d\t\t%s", id, state);
        }
    }

    image.gif

                   3° 创建DiningTableDAO类

                  DiningTableDAO类代码如下:

    package inn.dao;
    import inn.domain.DiningTable;
    public class DiningTableDAO extends BasicDAO<DiningTable> {
    }

    image.gif

                  4° 创建DiningTableService类

                  DiningTableService类代码如下 :

    package inn.service;
    import inn.dao.DiningTableDAO;
    import inn.domain.DiningTable;
    import java.util.List;
    public class DiningTableService {
        //定义一个DiningTableDAO类对象
        private DiningTableDAO diningTableDAO = new DiningTableDAO();
        /*
            该方法返回当前所有餐桌的信息(餐桌编号和餐桌状态),
            以List类型作接收,注意泛型!
         */
        public List<DiningTable> getDiningTables() {
            String sql = "SELECT id,state FROM diningTable;";
            List<DiningTable> diningTables = diningTableDAO.queryMultiply(sql, DiningTable.class);
            return diningTables;
        }
    }

    image.gif

                   5° 修改界面层WSInnView类的内容

                   还是老规矩,先在界面类中创建一个DiningTableService类的对象,如下图所示 :

    image.gif编辑

                   然后在WSInnView类中新定义一个showTableList方法(封装的思想),用于显示所有的餐桌状态。 showTableList方法如下 :

    public void showTableList() {
            List<DiningTable> diningTables = diningTableService.getDiningTables();
            System.out.println("\n餐桌编号\t\t餐桌状态");
            for (DiningTable diningTable : diningTables) {
                System.out.println(diningTable);
            }
            System.out.println("============餐桌状态显示完毕============\n");
        }

    image.gif

                   接着,我们只需要在符合“餐桌状态”的case处调用showTableList方法即可,如下所示 :

    image.gif编辑

                   6° 餐桌状态测试

                   餐桌状态的测试如下GIF图所示 :

    image.gif编辑

          4.预定餐桌 :

                   1° 需求分析

                   预定餐桌需要考虑两个问题——要预定的餐桌是否存在?若存在,该餐桌是否已被预定?

                   针对第一个问题,我们可以让用户输入要预定的餐桌的编号,然后去餐桌表diningTable中检索该餐桌编号是否存在针对第二个问题,可以根据已存在的餐桌编号去diningTable表中查询其对应的那条记录,检查该餐桌的状态是否为empty

                   此外,由于预定餐桌不是小事儿(要花💴的啦~),因此当用户选择预定餐桌的功能时,我们要向用户确认是否真的要当韭菜(bushi)预定餐桌。

                   2° 代码实现

                   由于在“餐桌状态”功能中,我们已经把餐桌表diningTable,包括它对应的JavaBean, DAO类,以及服务层类给一套打通了,因此我们现在只需要编写——可以实现“预定餐桌”功能的方法即可。既然是关于“餐桌”的一个具体业务,我们当然要将该方法定义到DiningTableService类中。getDiningTable方法代码如下 :

    /*
            该方法返回一个餐桌对象。若返回null,说明该餐桌不存在。
            PS : 餐桌编号是餐桌表中的自增长型的主键,不可能重复,因此考虑使用id来查询。
         */
        public DiningTable getDiningTable(int id) {
            String sql = "SELECT * FROM diningTable WHERE id = ?;";
            DiningTable diningTable = diningTableDAO.querySingle(sql, DiningTable.class, id);
            return diningTable;
        }

    image.gif

                   如果返回的DiningTable对象不为空(在界面中判断),说明要预定的餐桌存在;并且,如果存在的餐桌状态为empty,表示该餐桌可以预定。

                   因此,我们还需要在DiningTableService类中另定义一个方法,用来完成“预定餐桌”的任务,其实就是修改对应编号的餐桌的状态,以及更新预定人的信息(姓名,电话)。

                   orderDiningTable方法代码如下 :

    /*
            该方法用于修改餐桌的信息(状态,预定人姓名,预定人电话)
         */
        public boolean orderDiningTable(int id, String orderName, String orderPhone) {
            String sql = "UPDATE diningTable " +
                                "SET state = ?, orderName = ?, orderPhone = ? " +
                                "WHERE id = ?;";
            int affectedRows = diningTableDAO.update(sql, "booked",orderName, orderPhone, id);
            return (affectedRows > 0) ? true : false;
        }

    image.gif

                   3° 修改界面层WSInnView类的内容

                   由于我们之前已经在WSInnView类中定义了DiningTableService对象,因此这里只需要根据封装的思想,在WSInnView类定义一个方法用于接收用户的输入,并且调用我们刚才写得服务层中的orderDiningTable方法(这个方法是真正实现了预定餐桌的功能,因为其修改了数据库中表的数据)。

                   我们之前看过不少Java的底层源码了,相信大家对“包皮结构”已然司空见惯,这里我们就小小致敬一下,把这个方法也命名为orderDiningTable🤗,然后在该方法内去调用服务层中的orderDiningTable方法😋。因为淋过雨,所以要把别人🌂给撕烂(bushi)。好的,外层包皮的orderDiningTable方法代码如下 :

    //预定餐桌的方法
        public void orderDiningTable() {
            System.out.println("============预定餐桌============");
            System.out.print("请选择要预定餐桌的编号(-1退出):");
            int orderId = Utility.readInt();
            //判断用户是否是点错了
            if (orderId == -1) {
                System.out.println("============取消预定餐桌============\n");
                return;
            }
            System.out.println("-----请确认是否预定餐桌(输入Y或N)!-----");
            char c = Utility.readConfirmSelection();
            if (c == 'Y') {
                DiningTable diningTable = diningTableService.getDiningTable(orderId);
                //判断餐桌是否存在
                if (diningTable == null) {
                    System.out.println("餐桌编号不合法,请重新预定餐桌!");
                    return;
                }
                //判断餐桌是否已经被预定
                if (!("empty".equals(diningTable.getState()))) {
                    System.out.println("该编号的餐桌已经被预定,请重新预定餐桌!");
                    return;
                }
                //接收用户输入的预定信息
                System.out.print("请输入预定人姓名:");
                String orderName = Utility.readString(64);
                System.out.print("请输入预定人电话:");
                String orderPhone = Utility.readString(64);
                //修改对应编号的餐桌信息
                boolean b = diningTableService.orderDiningTable(orderId, orderName, orderPhone);
                System.out.println((b == true) ? "预定餐桌成功!\n" : "预定餐桌失败\n");
            } else {
                System.out.println("============取消预定餐桌============\n");
                return;
            }
        }

    image.gif

                   处理预定餐桌的方法完成后,我们只需要直接在符合“预定餐桌”的case处调用orderDiningTable方法即可,如下图所示 :

    image.gif编辑

                   4° 预定餐桌测试

                   预定餐桌的测试如下GIF图所示 :

    image.gif编辑

                   我们还可以进一步在数据库中查询diningTable表,查看预定人的信息是否已经成功登记,如下图所示 :

    image.gif编辑

                   由此可见,我们已成功实现“预定餐桌”的功能。

           5.显示菜品 :

                   1° 创建菜品表dishes

                   创建菜品表dishes的代码如下 :

    CREATE TABLE IF NOT EXISTS dishes (
        id INT PRIMARY KEY AUTO_INCREMENT,        #菜肴编号
        `name` VARCHAR(64) NOT NULL DEFAULT '',     #菜肴名称
        type CHAR(16) NOT NULL DEFAULT '',        #菜肴类型
        price DECIMAL(10,1) NOT NULL DEFAULT 0.0    #菜肴价格(单位:摩拉)
    ) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin ENGINE INNODB;
    INSERT INTO dishes 
        VALUES 
        (NULL, '素鲍鱼', '正常料理', 2500),
        (NULL, '腌笃鲜', '正常料理', 5000),
        (NULL, '乾坤摩拉肉', '特殊料理', 10000),
        (NULL, '幽幽大行军', '特殊料理', 9999),
        (NULL, '金丝虾球', '正常料理', 3500),
        (NULL, '文心豆腐', '活动料理', 2000),
        (NULL, '仙跳墙', '正常料理', 100000),
        (NULL, '派蒙', '应急食品', 1);
    SELECT * FROM dishes;

    image.gif

                  菜品表dishes效果如下 :

    image.gif编辑

                   2° 创建JavaBean类Dishes

                   同样地,dishes表对应一个自己的JavaBean类Dishes,放在domain包下。

                   Dishes类代码如下 :

    package inn.domain;
    /**
     * dishes表(菜品表)对应的JavaBean类
     */
    public class Dishes {
        private Integer id;
        private String name;
        private String type;
        private Double price;
        public Dishes() {
        }
        public Dishes(Integer id, String name, String type, Double price) {
            this.id = id;
            this.name = name;
            this.type = type;
            this.price = price;
        }
        public Integer getId() {
            return id;
        }
        public void setId(Integer id) {
            this.id = id;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public String getType() {
            return type;
        }
        public void setType(String type) {
            this.type = type;
        }
        public Double getPrice() {
            return price;
        }
        public void setPrice(Double price) {
            this.price = price;
        }
        @Override
        public String toString() {
            return String.format("%d\t\t\t%s\t\t\t%s\t\t\t%.1f", id,name,type,price);
        }
    }

    image.gif

                   3° 创建DishesDAO类

                    DishesDAO类代码如下 :

    package inn.dao;
    import inn.domain.Dishes;
    public class DishesDAO extends BasicDAO<Dishes> {
    }

    image.gif

                   4° 创建DishesService类

                   DishesService类代码如下 :

    package inn.service;
    import inn.dao.DishesDAO;
    import inn.domain.Dishes;
    import java.util.List;
    public class DishesService {
        //定义一个DishesDAO类对象
        private DishesDAO dishesDAO = new DishesDAO();
        /*
            该方法返回所有菜品的信息
         */
        public List<Dishes> getDishes() {
            String sql = "SELECT * FROM dishes;";
            List<Dishes> dishes = dishesDAO.queryMultiply(sql, Dishes.class);
            return dishes;
        }
    }

    image.gif

                   5° 修改界面层WSInnView类的内容

                   仍然是先定义一个DishesService对象作为一个私有属性,如下图所示 :

    image.gif编辑

                   然后根据封装的思想,再单独定义一个listDishes方法用于显示菜品, 代码如下 :

    //显示菜品的方法
        public void listDishes() {
            List<Dishes> dishes = dishesService.getDishes();
            System.out.println("\n菜品编号\t\t菜品名称\t\t\t菜品类型\t\t\t菜品价格");
            for (Dishes dish : dishes) {
                System.out.println(dish);
            }
            System.out.println("============菜品列表显示完毕============\n");
        }

    image.gif

                   然后在符合“显示菜品”的case处调用listDishes方法即可,如下图所示 :

    image.gif编辑

                   6° 显示菜品测试

                   显示菜品的测试如下GIF图所示 :

    image.gif编辑

          6.点餐服务 :

                  1° 需求分析

                   要想完成点餐的服务,我们必须明确顾客的两个需求——在哪儿吃(坐哪个餐桌),吃什么(点哪些个菜)

                   而要想完成这两个需求,又需要做以下的一些更细节的判断——

                   顾客要坐的餐桌存不存在?如果存在,是不是已经有人预定了?

                   顾客要点的菜大厨(或者小厨)能不能做?如果能做,要做几份?

                   这么来看,点餐前主要围绕“餐桌编号”和“菜品编号”两个字段来进行业务处理。

                   那么,点餐后呢?如果点餐成功我们要做些什么?

                   首先,既然顾客指定要坐哪个桌了,那么根据“先来先服务”的原则,这桌暂时是不能给其他人坐了,因此我们要将该餐桌的状态修改为booked(已预定)。

                   其次,点餐成功后,你客栈肯定得指定顾客一共点了多少摩拉的菜吧,不然你怎么收钱?因此,我们要设法生成一张账单,记录顾客一共点了那些个菜,一共要交多少摩拉。

                   2° 创建账单表bill

                   哎,既然都提到生成账单了,这不得创建一张表?水到渠成的结果。up这里创建账单表的思路是,每点一份菜,都会对应账单表中的一条记录;若同一张餐桌上点了不同的菜,就让对应多条记录的“餐桌编号”字段相同。

                   创建账单表bill的代码如下 :

    CREATE TABLE IF NOT EXISTS bill (
        id INT PRIMARY KEY AUTO_INCREMENT,      #编号(自增长主键)
        billId VARCHAR(32) UNIQUE NOT NULL,     #特定账单编号(可自定义)
        tableId INT NOT NULL DEFAULT 0,       #餐桌编号
        dishId INT NOT NULL DEFAULT 0,        #菜品编号
        amount TINYINT NOT NULL DEFAULT 0,      #菜品数量
        total DECIMAL(10,1) NOT NULL DEFAULT 0.0, #菜品总价
        billDate DATETIME NOT NULL,         #账单生成日期(年月日 时分秒)
        state CHAR(16) NOT NULL DEFAULT '',     #账单状态(已结账,未结账,坏账)
        payment CHAR(32) DEFAULT ''         #付款方式(现金,支付宝,微信)
    ) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin ENGINE INNODB;
    SELECT * FROM bill;

    image.gif

                   账单表bill效果如下 :

    image.gif编辑

                   3° 创建JavaBean类Bill

                   Bill类代码如下 :

    package inn.domain;
    import java.time.LocalDateTime;
    public class Bill {
        private Integer id;                     //编号
        private String billId;                  //特定账单编号
        private Integer tableId;                //餐桌编号
        private Integer dishId;                 //菜品编号
        private Integer amount;                 //菜品数量
        private Double total;                   //菜品总价
        private LocalDateTime billDate;         //账单生成日期
        private String state;                   //账单状态
        private String payment;                 //付款方式
        public Bill() {
        }
        public Bill(Integer id, String billId, Integer tableId, Integer dishId, Integer amount, Double total, LocalDateTime billDate, String state, String payment) {
            this.id = id;
            this.billId = billId;
            this.tableId = tableId;
            this.dishId = dishId;
            this.amount = amount;
            this.total = total;
            this.billDate = billDate;
            this.state = state;
            this.payment = payment;
        }
        public Integer getId() {
            return id;
        }
        public void setId(Integer id) {
            this.id = id;
        }
        public String getBillId() {
            return billId;
        }
        public void setBillId(String billId) {
            this.billId = billId;
        }
        public Integer getTableId() {
            return tableId;
        }
        public void setTableId(Integer tableId) {
            this.tableId = tableId;
        }
        public Integer getDishId() {
            return dishId;
        }
        public void setDishId(Integer dishId) {
            this.dishId = dishId;
        }
        public Integer getAmount() {
            return amount;
        }
        public void setAmount(Integer amount) {
            this.amount = amount;
        }
        public Double getTotal() {
            return total;
        }
        public void setTotal(Double total) {
            this.total = total;
        }
        public LocalDateTime getBillDate() {
            return billDate;
        }
        public void setBillDate(LocalDateTime billDate) {
            this.billDate = billDate;
        }
        public String getState() {
            return state;
        }
        public void setState(String state) {
            this.state = state;
        }
        public String getPayment() {
            return payment;
        }
        public void setPayment(String payment) {
            this.payment = payment;
        }
        @Override
        public String toString() {
            return String.format("%d\t\t\t%d\t\t\t%d\t\t\t%d\t\t\t%.1f\t\t\t%s\t\t%s\t\t%s", id,tableId,dishId,amount,total,billDate.toString(),state,payment);
        }
    }

    image.gif

                  4° 创建BillDAO类

                  BillDAO类代码如下 :

    package inn.dao;
    import inn.domain.Bill;
    public class BillDAO extends BasicDAO<Bill> {
    }

    image.gif

                  5° 创建BillService类

                   对于BillService类中的代码,首先我们肯定是要定义一个方法来生成账单的,但是账单中有一个字段total代表了菜品总价,而菜品总价 = 菜品单价 * 菜品数量;菜品数量我们可以令用户指定,在方法形参列表显式传入,但是菜品单价只能通过菜品编号到dishes表中查询。

                   因此,我们需要先补充一下DishesService类中的内容新定义一个“可以根据菜品的编号来返回一个对应的菜品对象”的getDish方法代码如下 :

    /*
            该方法可以根据菜品编号(id),来返回一个对应的菜品对象dishes
         */
        public Dishes getDish(int id) {
            String sql = "SELECT * FROM dishes WHERE id = ?;";
            Dishes dishes = dishesDAO.querySingle(sql, Dishes.class, id);
            return dishes;
        }

    image.gif

                   继续,既然我们要在BillService类中调用DishesService类中的方法,那就得在BillService类中定义一个DishesService对象了(业务层内的协调工作),如下图所示 :

    image.gif编辑

                   通过该对象就可以得到菜品的总价了,如下图所示 :

    image.gif编辑

                   而对于账单表中的state(账单状态)和payment(付款方式)这两个字段,我们可以默认传入"unpaid"(未付款)和"unknown"(未知),因为现在还在点餐嘛,等你结账时才知道你是怎么付的款。OK,这下账单的事情我们可以解决了。

                   But,还有一个问题!—— 怎么修改对应餐桌的状态?

                   诶,上面你都请过DishesService出山了,此处你再去请DiningTableService也出山帮个忙不久好啦!Yes,所以接下来我们要在DiningTableService类中再补充一个方法,用于修改指定编号餐桌的状态。代码如下 :

    /*
            该方法用于修改指定编号餐桌的状态
         */
        public boolean updateDiningTableState(int id, String state) {
            String sql = "UPDATE diningTable " +
                                "SET state = ? " +
                                "WHERE id = ?;";
            int affectedRows = diningTableDAO.update(sql, state, id);
            return affectedRows > 0;
        }

    image.gif

                   这下我们可算是能回到BillService类了。不得感慨:兜兜转转,还得是人脉😭(bushi)。BillService类代码如下:

    package inn.service;
    import inn.dao.BillDAO;
    import java.util.UUID;
    public class BillService {
        //定义一个BillDAO类对象
        private BillDAO billDAO = new BillDAO();
        //定义一个DishesService类对象
        private DishesService dishesService = new DishesService();
        //定义一个DiningTableService类对象
        private DiningTableService diningTableService = new DiningTableService();
        /*
            该方法用于实现点餐的功能,需要生成一份账单(对应bill表中的一条记录),
            然后更新餐桌表diningTable中餐桌的状态。
         */
        public boolean orderDishes(int tableId, int dishId, int amount ) {
            //借助UUID的randomUUID方法得到一个随机字符串,作为账单号(注意这里不要超过字段定义的范围)
            String billId = UUID.randomUUID().toString().substring(0, 17);
            //利用定义的DishesService类对象,计算菜品总价
            Double total = dishesService.getDish(dishId).getPrice() * amount;
            String sql = "INSERT INTO bill " +
                                "VALUES " +
                                "(NULL, ?,?,?,?,?,NOW(),?,?);";
            int affectedRows = billDAO.update(sql, billId, tableId, dishId, amount, total, "unpaid", "unknown");
            if (affectedRows <= 0) {
                return false;
            }
            //修改餐桌状态(empty --> dining)
            return diningTableService.updateDiningTableState(tableId, "dining");
        }
    }

    image.gif

                   6° 修改界面层WSInnView类的内容

                   还是老规矩,先来定义一个BillService属性,如下图所示 :

    image.gif编辑

                   然后,定义一个用于实现接收用户点餐信息的方法orderDishes,代码如下 :

    //点餐功能的方法
        public void orderDishes() {
            System.out.println("============点餐服务:============");
            System.out.print("请输入餐桌编号(-1退出):");
            int tableKey = Utility.readInt();
            if (tableKey == -1) {
                return;
            }
            System.out.print("请输入菜品编号(-1退出):");
            int dishKey = Utility.readInt();
            if (dishKey == -1) {
                return;
            }
            System.out.print("请输入菜品数量(-1退出):");
            int dishAmount = Utility.readInt();
            if (dishAmount == -1) {
                return;
            }
            //对餐桌编号以及菜品编号进行校验
            DiningTable diningTable = diningTableService.getDiningTable(tableKey);
            if (diningTable == null) {
                System.out.println("餐桌编号不合法,请重新进行点餐!");
                return;
            } else if ("booked".equals(diningTable.getState())) {
                System.out.println("该餐桌已被预定啦,请重新进行点餐!");
                return;
            }
            Dishes dish = dishesService.getDish(dishKey);
            if (dish == null) {
                System.out.println("菜品编号不合法,请重新进行点餐!");
                return;
            }
            //开始点餐
            boolean b = billService.orderDishes(tableKey, dishKey, dishAmount);
            if (b) {
                System.out.println("============点餐成功!============\n");
            } else {
                System.out.println("============点餐失败!============\n");
            }
        }

    image.gif

                   最后,在符合“点餐功能”的case语句处调用该方法即可,如下图所示 :

    image.gif编辑

                   7° 点餐功能测试

                   点餐功能的测试如下GIF图所示 :

    image.gif编辑

                   在GIF图演示中,我们成功地在三号桌点了3份腌笃鲜和2份素鲍鱼,美汁儿汁儿!

           7.查看账单 :

                   1° 需求分析

                   要查看账单,我们自然要打印出bill表中的一些信息。在BillService类中定义一个打印账单信息的方法,自然,该方法内肯定要用到BillDAO对象,通过BillDAO对象完成对bill表的操作。

                   2° 代码实现

                   在BillService类中定义listBills方法,代码如下 :

    /*
            该方法用于返回bill表中的所有账单
         */
        public List<Bill> listBills() {
            String sql = "SELECT * FROM bill;";
            List<Bill> bills = billDAO.queryMultiply(sql, Bill.class);
            return bills;
        }

    image.gif

                   3° 修改界面层WSInnView类的内容

                   在WSInnView类中定义方法来显示账单信息,代码如下 :

    //显示账单的方法
        public void listBills() {
            System.out.println("编号\t\t餐桌号\t\t菜品号\t\t菜品数量\t\t菜品总额\t\t\t账单日期\t\t\t\t\t账单状态\t\t支付方式");
            List<Bill> bills = billService.listBills();
            for (Bill bill : bills) {
                System.out.println(bill);
            }
            System.out.println("============账单列表显示完毕============\n");
        }

    image.gif

                   然后,在符合"显示账单" 的case语句处调用该方法,如下图所示 :

    image.gif编辑

                  4° 查看账单的测试

                   查看账单的测试如下GIF图所示 :

    image.gif编辑

           8.结账服务 :

                   1° 需求分析

                   根据我们日常生活中的经验,吃饭结账时一般都是按桌来结的,某某桌某某桌结账。因此结账服务中,首先我们要确定要结账的餐桌编号;自然,我们就需要对给出的餐桌编号进行校验,判断它是否存在以及该餐桌是否有账单需要结账。

                   然后,我们还需要对结账的方式进行记录(现金,微信,支付宝),并且要修改对应账单的state字段(账单状态要从unpaid修改为paid)和payment字段(付款方式)。最后,将结账餐桌的状态修改为empty。

                  注意——

                   只有处于"dining"(就餐)状态的餐桌才可以进行结账操作,结账后将该餐桌的状态从dining设置为empty

                   已有的bill,diningTable表及其对应的DAO和Service足以支撑我们开展“结账服务”的业务,我们只需要在这些个Service中增加新的业务逻辑即可。

                   2° 代码实现

                   首先,在BillService类中定义一个方法,该方法可以根据传入的餐桌编号来确定该餐桌有无需要结账的账单hasUnpaid方法代码如下 :

    /*
            该方法用于判断对应编号的餐桌有无需要结账的账单。
            为什么使用LIMIT 0,1?因此当前方法只是判断某个餐桌需不需要结账;
            而只要某个餐桌有一条账单是处于“unpaid”状态的,就可以认为该餐桌需要结账。
         */
        public boolean hasUnpaid(int tableId) {
            String sql = "SELECT * FROM bill " +
                                "WHERE tableId = ? AND state = 'unpaid'" +
                                "LIMIT 0,1;";
            Bill bill = billDAO.querySingle(sql, Bill.class, tableId);
            return bill != null;
        }

    image.gif

                   当返回的bill对象不为空,说明对应编号的餐桌可以结账。

                   结账后,需要修改bill表中的信息,将已结账账单的state修改为"paid",payment修改为客户的实际付款方式。同时,我们还需要修改该餐桌的状态,从“dining”修改为“empty”。

                   这就需要我们另定义一个方法用户修改bill和diningTable的状态代码如下 :

    /*
            该方法用于修改bill表和diningTable表中的信息,完成结账的服务。
         */
        public boolean checkOut(int tableId, String payment) {
            //1.修改bill表的信息
            String sql = "UPDATE bill " +
                                "SET state = 'paid',payment = ? " +
                                "WHERE tableId = ? AND state = 'unpaid';";
            int changeKey1 = billDAO.update(sql, payment, tableId);
            if (changeKey1 <= 0) {
                return false;
            }
            //2.修改diningTable表的信息
            boolean changeKey2 = diningTableService.updateDiningTableState(tableId, "empty");
            if (!changeKey2) {
                return false;
            }
            return true;
        }

    image.gif

                   3° 修改界面层WSInnView类的内容

                   在WSInnView类中定义checkOut方法用于接收用户录入的结账信息,并调用我们之前定义的方法, 完成结账功能。checkOut方法代码如下 :

    //结账功能的方法
        public void checkOut() {
            //录入餐桌编号
            System.out.println("============结账服务============");
            System.out.print("请选择结账餐桌(-1退出):");
            int tableId = Utility.readInt();
            if (tableId == -1) {
                System.out.println("============退出结账============\n");
                return;
            }
            //验证餐桌编号
            DiningTable diningTable = diningTableService.getDiningTable(tableId);
            if (diningTable == null) {
                System.out.println("该餐桌不存在,请重新进行结账!");
                return;
            }
            //验证餐桌是否有账单需要结账
            boolean b = billService.hasUnpaid(tableId);
            if (b == false) {
                System.out.println("该餐桌没有未结账账单,请重新进行结账!");
                return;
            }
            //录入付款方式
            System.out.print("请选择结账方式(WeChat,Alipay,Cash),回车表示退出");
            String payment = Utility.readString(16, "");
            if ("".equals(payment)) {
                System.out.println("付款方式不能为空,请重新进行结账!");
                return;
            }
            //询问用户是否确认结账
            System.out.print("是否确认结账?");
            char c = Utility.readConfirmSelection();
            if (c == 'Y') {
                boolean checkOutKey = billService.checkOut(tableId, payment);
                if (checkOutKey) {
                    System.out.println("============结账成功!============\n");
                } else {
                    System.out.println("============结账失败!============\n");
                }
            } else {
                System.out.println("============退出结账============\n");
                return;
            }
        }

    image.gif

                   接着,在符合“结账服务”的case语句处调用checkOut方法即可,如下图所示 :

    image.gif编辑

                  4° 结账服务的测试

                   结账服务的测试如下GIF图所示 :

    image.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四个类,如下图所示 :

    image.gif编辑

                   全部源码up已经以代码包的形式上传,找到domain目录即可。

           2.dao包 :

                   dao包下共定义了BasicDAO,EmployeeDAO,DiningTableDAO,DishesDAO,BillDAO五个类,如下图所示 :

    image.gif编辑

                   全部源码up已经以代码包的形式上传,找到dao目录即可。

          3.service包 :

                   service包下共定义了EmployeeService, DiningTableService, DishesService, BillService四个类,如下图所示 :

    image.gif编辑

                   全部源码up已经以代码包的形式上传,找到service目录即可。

           4.view包 :

                   view包下只有WSInnView一个类,全部源码up已经以代码包的形式上传,找到view目录即可。

           5.utils包 :

                   utilis包下即我们一开始导入的两个工具类,一个是Utility,用于处理用户的输入信息;还有一个是JDBCUtilsDruid,即德鲁伊连接池的封装工具类。全部源码up已经以代码包的形式上传,找到utils目录即可。


    七、总结

      • 🆗,以上就是JDBC系列博文第八节的全部内容了。
      • 总结一下,整个望舒客栈的项目,我们都是围绕一开始的“望舒客栈项目框架图”来进行的,即表的定义--> 对应JavaBean的定义--> 对应DAO的定义--> 对应Service的定义--> 对界面层的修改和补充。其实,整个项目你要说有什么难度吧,其实代码本身是几乎没有难度的,而且我们这仅仅是个模拟项目,啥都没用上呢!不过,整个项目对于我们业务分析的能力和代码的熟练程度还是有较大益处的。
      • 下一节内容——暂时无了哈哈,JDBC暂时告一段落🍐。感谢阅读!

             System.out.println("END---------------------------------------------------------------------------");

      相关实践学习
      如何在云端创建MySQL数据库
      开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
      全面了解阿里云能为你做什么
      阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
      目录
      相关文章
      |
      6月前
      |
      关系型数据库 MySQL Java
      启动项目出现com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException异常解决方法
      启动项目出现com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException异常解决方法
      |
      6月前
      |
      消息中间件 存储 缓存
      开源一个教学型分库分表示例项目 shardingsphere-jdbc-demo
      在笔者心中,**消息队列**,**缓存**,**分库分表**是高并发解决方案三剑客。 分库分表之所以被广泛使用,因为工程相对简单,但分库分表并不仅仅是分片,还是需要考虑如何扩缩容(全量同步、增量同步、数据校验等)。
      开源一个教学型分库分表示例项目 shardingsphere-jdbc-demo
      |
      Java 数据库连接 数据库
      大学的第一个项目,JDBC实现图书管理系统
      大学的第一个项目,JDBC实现图书管理系统
      127 1
      |
      JSON 前端开发 Java
      【前端+后端项目】 - 论坛信息管理系统(Web+servlet+MySQL+JDBC)
      现在我们可以基于模板的方式,通过服务器把数据渲染到页面中,然后直接返回完整的页面给浏览器。
      467 0
      |
      Java 关系型数据库 MySQL
      #java项目#《水果库存系统1.0》(java(jdbc)+mysql)(二)
      3.4FruitDAOimpl类实现FruitDAO 3.5Menu类 3.5.1showMainMenu()显示主菜单 3.5.2showFruitInfo()查看特定水果信息 3.5.3delFruit()水果下架 3.5.4showFruitList()查看水果列表; 3.5.5addFruit()添加水果库存信息 3.5.6exit()退出
      324 0
      #java项目#《水果库存系统1.0》(java(jdbc)+mysql)(二)
      |
      设计模式 Java 关系型数据库
      #java项目#《水果库存系统1.0》(java(jdbc)+mysql)(一)
      水果库存系统1.0 一:水果库存系统简介: 二:前置知识 三结构说明 3.1Client类 3.2fruit类 3.3FruitDAO接口
      308 0
      #java项目#《水果库存系统1.0》(java(jdbc)+mysql)(一)
      |
      存储 druid Java
      ❀ 开发带数据的javaWeb项目的步骤:【jdbc+servlet+jsp】
      ❀ 开发带数据的javaWeb项目的步骤:【jdbc+servlet+jsp】
      158 0
      |
      Java 数据库连接 Spring
      第四章:Spring项目对JDBC的支持
      欢迎查看Java开发之上帝之眼系列教程,如果您正在为Java后端庞大的体系所困扰,如果您正在为各种繁出不穷的技术和各种框架所迷茫,那么本系列文章将带您窥探Java庞大的体系。本系列教程希望您能站在上帝的角度去观察(了解)Java体系。
      1484 0