數據庫課程設計@美團模擬系統


完成時間:2021年1月16日

目錄

項目背景

美團模擬系統
    線上訂餐風靡初期,許多人們通過電話訂餐,這種方式雖然可省去到店里的路途時間和等待時間,但會經常出現各種問題,例如:訂餐后,飯店人員由於太過忙碌沒有看清楚顧客的要求,或是當外賣送達后,顧客消失不見……等等一系列的問題。由此可見,使用手機應用程序進行線上訂餐才能及時更新和查看外賣情況,才能同時保障顧客與商家的利益。
    隨着互聯網技術的快速發展,互聯網上誕生出這種便捷的訂餐形式,也是電子商務應用的全新體現;從另一個側面來看,網上訂餐還起到了幫助推進電子商務的普及和應用進程的作用,網上訂餐的形式,同時也在幫助加速電子商務應用的步伐。
隨着時代發展的日益加快,我們身邊每天都在發生日新月異的變化。不論在哪個行業里,用戶幾個大的根本需求永遠不會變,比如說像省錢、懶。省錢”這個需求美團團購已經做到,現在該輪到“懶”這個需求。外賣一個就足夠滿足“懶”的需求——吃飯不出門。
    目前《美團外賣》幾乎壟斷了國內外賣行業,在國內除了“餓了么”這位最大的競爭對手以外,可以說是沒有有力的競爭對手存在了,綜上,我們小組選擇了設計模擬美團外賣的數據庫課程設計,通過將現實生活中的問題轉化成實際的數據庫模型,再加以優化。

課程設計目標

  1. 學習和掌握主流數據庫設計CASE工具的使用。
  2. 加深對數據庫基礎知識的理解,熟練掌握數據庫的規范設計過程,訓練和提高數據庫設計的技能,實現理論與實踐的結合。
  3. 了解系統開發過程中組織管理、質量評審方法和數據庫性能優化措施,提高職業化水平。
  4. 學會自主性學習、研究性探索、匯報演示以及技術文檔的撰寫,促進研究能力、協作能力和創新能力的提高。
  5. 設計美團系統E-R模型圖,設計模擬《美團外賣》的數據庫系統,實現增、刪、改、查四大基礎功能。
  6. 設計觸發器實現客戶下訂單、商家出餐等功能

一、需求分析

1.1 顧客需求分析

    顧客功能模塊分為信息管理、線上訂餐、外賣查詢、評價四大功能。(1) 信息部分包括個人信息即接收地址、簽收姓名、聯系電話;(2)線上訂餐包括選擇菜品、選擇接收地址、選擇簽收人、備注信息以及付款方式;(3)外賣查詢包括查詢當前外賣定位情況,查詢騎手電話。(4) 評價包括對騎手評價和對商家評價,分五個星級進行總評。

1.2 商家需求分析

    商家功能模塊分為信息管理、訂餐確認、發出配送請求三個部分。(1)信息管理包括商店信息查詢、修改、菜品種類以及價格管理; (2)訂餐確認部分包括訂餐、出餐、確認收餐;(3)發出配送請求包括向騎手發出配送的出發點和目的地、配送費、規定配送時間。

1.3 騎手需求分析

    騎手功能模塊分為信息管理、訂單配送、超時檢測三個部分。(1)信息管理包括騎手個人信息查詢、修改;(2)訂單配送包括確認當前訂單狀態,同時自動向顧客和商家發出自身定位。(3)超時檢測包括系統檢測每個訂單完成時間,若超時則進行相應的處罰。

1.4 管理員需求分析

    管理員功能模塊分為用戶信息管理、系統維護兩個部分。(1) 用戶信息管理包括顧客注冊、商家注冊、客戶以及商家訂單記錄查詢。

1.5 性能需求分析

    該系統在性能功能上應達到如下需求:
(1)操作簡單、界面友好,完全控件式地頁面布局,使得菜品等信息地錄入工作更簡便,選擇菜品只需通過鼠標;
(2)對常見地類似網站地管理地各個方面;
(3)基本信息錄入、瀏覽、刪除、修改、搜索等方面都大體實現;
(4)對客戶預定餐飲信息地處理(包括錄入、刪除)將立即在主頁地對應欄目顯示出來;(5)達到“即時發布、即時見效”地功能;
(6)系統運行應該快速、穩定、高效和可靠;
(7)在結構上應具有很好地可擴展性,便於將來地功能擴展和維護。

二、概念設計

2.1 設計原則

(1)開放性、可擴充性、可靠性原則——開放系統是生產各種計算機產品普遍遵循地原則,遵循這種標准地產品都符合一些公共地、可以相互操作地標准,能夠融洽地在一起工作.開放系統使得各種類型地網絡和系統互連簡單、標准統一,容易擴展升級.從而適應廣大用戶需求地多變性和產品地更新換代;
(2)良好地用戶操作界面——用戶操作界面美觀、方便、實用,使用戶能在較短地時間內掌握其使用方法。
(3)實用性原則——任何系統地設計都要考慮其實用性,系統開發地目地是為了實現業務處理自動化、規范化,提高工作效率,減輕工作人員地勞動強度,減少開支;
(4)工作平台——適用於不同地網絡平台。

2.2 系統組織結構分析與設計

    結合美團外賣APP應用與現在的外賣行業發展情況來對美團外賣系統進行分析如下,首先將其分為六個必不可少的模塊,即信息管理、信息發布、意見反饋、食品管理、訂單管理、送餐管理模塊,其中每個模塊分為三個子模塊,如下圖所示。

圖2.2 系統組織結構圖
考慮到此模擬系統共有四大群體,分別是客戶、商家、騎手、管理員,接下來是針對四大群體的系統組織結構設計。

2.2.1 客戶群體的系統組織結構

圖2.2.1 客戶程序設計框圖

2.2.2 商家群體的系統組織結構

圖2.2.2商家程序設計框圖

2.2.3 騎手群體的系統組織結構

圖2.2.3騎手系統組織設計框圖

2.2.4 管理員群體的系統組織結構

圖2.2.4管理員系統組織設計框圖

2.2.5 系統的業務流程分析

圖2.2.5 美團外賣服務系統業務流程圖

2.3 系統E-R圖設計

    根據實際情況,我們區分出了五個實體,分別是客戶、商家、騎手、菜品、管理員,其中最為關鍵的是管理員,它可管理其他除訂單外所有實體,而訂單只能由商家和客戶管理。

圖2.3 ER模型圖

2.4 數據流圖設計

圖2.4 數據流圖

2.5 數據字典

2.5.1 客戶(client)

表2.5.1 客戶數據字典圖

2.5.2 管理員(admin)

表2.5.2 管理員數據字典圖

2.5.3 商家(provider)

表2.5.3 商家數據字典圖

2.5.4 菜品(dish)

表2.5.4 菜品數據字典圖

2.5.5 訂單(order)

表2.5.5 訂單數據字典圖

2.5.6 騎手(rider)

表2.5.6 騎手數據字典圖

2.5.7 管理員操作(admin_x_x)

表2.5.7 管理員操作數據字典圖

三、邏輯結構設計

3.1 E-R圖向關系初步轉換

    其中加粗的為主鍵、加p的為外鍵。)
客戶(客戶編號,客戶昵稱,客戶聯系電話,客戶地址,密碼,狀態)
管理員(管理員編號,管理員名稱,密碼)
菜品(菜品編號,菜品名稱,菜品價格,菜品折扣)
商家(商家編號,商家狀態,商家地址,商家聯系電話號碼,信譽星級)
騎手(騎手編號,騎手聯系方式,配送費,配送狀態,騎手實名制昵稱)
訂單(訂單編號,p客戶編號,p菜品編號,p商家編號,p騎手編號,訂單時間,數量,狀態)

四、基於JAVA語言的客戶端程序實現


    對於3.1中系統組織結構分析與設計的內容,現對JAVA程序分為八大模塊,即: (1)客戶登錄窗口、(2) 客戶操作主窗口(3) 商家登錄窗口(4) 商家操作主窗口 (5) 騎手登錄窗口 (6) 騎手操作主窗口(6) 商家操作主窗口 (7) 管理員登錄窗口 (8) 管理員操作主窗口。接下來將通過代碼對重要模塊進行詳細的撰述。

4.1 客戶登錄窗口


為了設計出更友好的交互界面,我們采用了Swing包中的JFrame窗口、Jpanel面板、JButton按鈕、JLayeredPane分層面板、
CLIENT_LOAD.JAVA程序代碼如下:

import java.awt.event.*;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;

import java.sql.SQLException;

import java.text.SimpleDateFormat;

import java.util.Date;


public class CLIENT_LOAD extends JFrame {
    private int WIDTH = 400; // 窗口寬度
    private int HEIGHT = 270; // 窗口高度
    private int WIDTH_next = 400; //下個窗口的寬度
    private int HEIGHT_next = 270; //下個窗口的高度
    private String USER = ""; // 記錄賬號
    private String PASSWORD = ""; // 記錄密碼
    JLabel label = new JLabel();
    JLabel title = new JLabel("美 團 外 賣 模 擬 系 統");

    /*--------定義其他窗口----------*/
    FRAME_WAR frame_war = new FRAME_WAR(); //警告提示的窗口
                                           /*--------定義面板-------------*/
    JLayeredPane jpanel_main = new JLayeredPane(); //分層面板
    JPanel jpanel_background = new JPanel(); //專門放背景圖的面板
                                             /*-------定義按鈕-----------*/
    button login = new button("登  陸");

    /*-------定義兩個輸入框-------*/
    textUser user = new textUser("");
    textPassword password = new textPassword("");

    /*-------定義多選框---------*/
    checkOne remember = new checkOne("記住密碼");

    /*-----------定義標簽-----------*/
    JButton b_register = new JButton("注冊賬號");
    JButton b_adminLogin = new JButton("管理員登錄");
    SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    CLIENT_LOAD() {
        super("美團外賣模擬系統 ");
        init();
    }

    public void init() {
        this.setResizable(false);
        initFrame();
        initBackground();
        initPanel();
        initButton();
        initText();
        initCheckBox();

        try {
            initFile();
        } catch (IOException e) {
            e.printStackTrace();
        }

        this.setVisible(true);
    }

    public void initPanel() {
        jpanel_main.setBounds(0, 0, WIDTH, HEIGHT);
        jpanel_background.setBounds(0, -5, WIDTH, HEIGHT);
        jpanel_main.add(jpanel_background, JLayeredPane.DEFAULT_LAYER);
        jpanel_background.add(label);
        /*-------- 除了背景圖片,其他組件添加時候要加上JLayeredPane.MODAL_LAYER*/
        jpanel_main.add(login, JLayeredPane.MODAL_LAYER);
        jpanel_main.add(user, JLayeredPane.MODAL_LAYER);
        jpanel_main.add(password, JLayeredPane.MODAL_LAYER);
        jpanel_main.add(remember, JLayeredPane.MODAL_LAYER);
        jpanel_main.add(b_register, JLayeredPane.MODAL_LAYER);
        jpanel_main.add(b_adminLogin, JLayeredPane.MODAL_LAYER);
        jpanel_main.add(title, JLayeredPane.MODAL_LAYER);
    }

    public void initButton() {
        login.setActionCommand("登錄"); //  設置按鈕監聽命令
        b_register.setActionCommand("注冊"); //  設置按鈕監聽命令
        login.setBounds((WIDTH / 2) - (login.width / 2), 200, login.width,
            login.height); //設置按鈕位置
        login.setBackground(Color.ORANGE); //設置背景顏色
        login.setFont(new Font("幼圓", Font.BOLD, 15)); //設置 按鈕標題字體
        login.setFocusPainted(false); //去掉標題邊框
        login.setBorderPainted(false); //去掉按鈕邊框
        login.addActionListener(new BottonMonitor());
        b_register.addActionListener(new BottonMonitor());
        b_register.setBounds(30, 200, 100, 25);
        b_register.setFont(new Font("幼圓", 1, 14));
        b_adminLogin.setFont(new Font("幼圓", 1, 14));
        b_adminLogin.setBounds(270, 200, 110, 25);
        b_adminLogin.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    new ADMIN_LOAD();
                    dispose();
                }
            });
    }

    public void initText() {
        JLabel l_user = new JLabel("賬號:");
        JLabel l_pw = new JLabel("密碼:");
        jpanel_main.add(l_user, JLayeredPane.MODAL_LAYER);
        jpanel_main.add(l_pw, JLayeredPane.MODAL_LAYER);
        l_user.setFont(new Font("幼圓", Font.BOLD, 16));
        l_pw.setFont(new Font("幼圓", Font.BOLD, 16));
        l_pw.setBounds((WIDTH / 2) - 100, (HEIGHT / 2) - 25, 45, 40);
        l_user.setBounds((WIDTH / 2) - 100, (HEIGHT / 2) - 65, 45, 40);
        l_user.setForeground(new Color(252, 150, 25, 208));
        l_pw.setForeground(new Color(252, 150, 25, 208));
        user.setBounds((WIDTH / 2) - 60, (HEIGHT / 2) - 60, password.width,
            password.height);
        password.setBounds((WIDTH / 2) - 60, (HEIGHT / 2) - 20, user.width,
            user.height);
        user.setOpaque(false);
        password.setOpaque(false); //設置為透明
    }

    public void initCheckBox() {
        remember.setBounds(200, 145, remember.width, remember.height);
        remember.setOpaque(false);
    }

    public void initFile() throws IOException {
        File file = new File("D:" + File.separator + "log.txt");

        if (!file.getParentFile().exists()) { //父路徑不存在
            file.getParentFile().mkdirs(); //創建所有的父路徑
        }

        RandomAccessFile raf = new RandomAccessFile(file, "rw"); // 讀寫模式打開
        String[] cmd = raf.readLine().split("--");

        for (int i = 0; i < cmd.length; ++i) {
            if (cmd[i].equals("USER")) {
                if ((cmd[i + 1].length() >= 4) && (cmd[i + 1].length() <= 10)) {
                    user.setText(cmd[i + 1]);
                    USER = cmd[i + 1];
                }
            } else if (cmd[i].equals("PASSWORD")) {
                if ((cmd[i + 1].length() >= 4) && (cmd[i + 1].length() <= 10)) {
                    password.setText(cmd[i + 1]);
                    USER = cmd[i + 1];
                }
            }
        }
    }

    public void initFrame() {
        this.setSize(WIDTH, HEIGHT);
        //this.setBounds(X,Y,WIDTH,HEIGHT);
        this.setLocationRelativeTo(null); //窗口居中
        this.setResizable(false);
        this.add(jpanel_main);
    }

    public void initBackground() {
        /*------設置背景圖片------*/
        label.setIcon(new ImageIcon("src/pic/loadBackground.gif"));
        label.setBounds(0, 0, WIDTH, HEIGHT);
        title.setBounds(65, 10, (WIDTH * 3) / 2, HEIGHT / 4);
        /*--------設置標題-------*/
        title.setFont(new Font("幼圓", Font.BOLD, 25));
        title.setForeground(Color.blue);
    }

    /*-----更新下個窗口的大小信息-----*/
    public void UpdateFrame(int NextWidth, int NextHeight) {
        WIDTH = WIDTH_next;
        HEIGHT = HEIGHT_next;
        WIDTH_next = NextWidth;
        HEIGHT_next = NextHeight;
    }

    public String getTime() {
        return df.format(new Date());
    }

    class checkMore extends JCheckBox {
        int X = WIDTH;
        int Y = HEIGHT;
        int width = 80;
        int height = 35;

        checkMore(String name) {
            super(name);
        }
    }

    class checkOne extends JRadioButton {
        int X = WIDTH;
        int Y = HEIGHT;
        int width = 80;
        int height = 35;

        checkOne(String name) {
            super(name);
        }
    }

    class button extends JButton {
        public int width = 100;
        public int height = 30;

        public button(String name) {
            super(name);
        }
    }

    class textUser extends JTextField {
        public int width = 150;
        public int height = 30;
        public int X;
        public int Y;

        public textUser(String name) {
            super(name);
            this.setFont(new Font("黑體", Font.BOLD, 15));
        }
    }

    class textPassword extends JPasswordField {
        public int width = 150;
        public int height = 30;
        public int X;
        public int Y;

        public textPassword(String name) {
            super(name);

            Font font = this.getFont();
            this.setFont(new Font(font.getName(), font.getStyle(), 20));
        }
    }

    /*---------重寫按鈕被單擊監聽事件------------*/
    class BottonMonitor implements ActionListener {
        @Override
        public void actionPerformed(ActionEvent e) {
            String name = e.getActionCommand();

            if (name.equals("注冊")) {
                new CLIENT_REGISTER();
            }

            if (name.equals("登錄")) {
                /*------------記住密碼操作-------------*/
                if (remember.isSelected() == true) {
                }

                try {
                    if (PSQL_JDBC.select_client("c" + user.getText(),
                                new String(password.getPassword()))) {
                        frame_war.open(false);
                        PSQL_JDBC.setOlineClient();
                        System.out.println(getTime() + " 用戶[" + user.getText() +
                            "]登錄成功!");
                        dispose();
                    } else {
                        frame_war.open(true);
                    }
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }

            if (name.equals("找回密碼")) {
            }
        }
    }
}

CLIENT_LOAD.java程序運行結果圖:

圖4.1 客戶登陸窗口運行圖

4.2 客戶操作主窗口

針對客戶的主要三大操作:訂餐、查詢訂單、修改信息,我們借助JAVA中的JTabbedPanel面板,將一個窗口分為三個小模塊,分別為buy, select, my,與美團外賣APP小程序對應的是三個界面,訂餐、查詢訂單、我的信息。以下是CLIANET_MAIN.java程序代碼:

package com;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;

import java.text.SimpleDateFormat;

import java.util.Date;

import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;


public class CLIENT_MAIN extends JFrame {
    static DATA d = new DATA();
    private int width = 460;
    private int height = 600;
    private int nowShop = 0;
    JTabbedPane tp_menu = new JTabbedPane(JTabbedPane.BOTTOM);
    JLayeredPane lp_buy = new JLayeredPane();
    JLayeredPane lp_select = new JLayeredPane();
    JLayeredPane lp_my = new JLayeredPane();
    PANEL_SCROLL menu_shop = new PANEL_SCROLL(20, 20, 420, 0, 200, 440, 310); //定義一個可容納20個按鈕的滾動面板
    CLIENT_MODIFY frame_modify = new CLIENT_MODIFY(getX(), getY(), getWidth(),
            getHeight());
    CLIENT_UPDATE frame_update = new CLIENT_UPDATE();
    CLIENT_MINE p_mine = new CLIENT_MINE();
    JLabel l_background = new JLabel();
    JTabbedPane[] p_shops = new JTabbedPane[20];
    PANEL_SCROLL[][] menu_dish = new PANEL_SCROLL[20][5]; // shops_menu[i][j] : 第i個商鋪的第j個菜單屬性
    int shop_num = 6;
    SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); //設置日期格式

    CLIENT_MAIN() {
        this.setResizable(false);
        init();

        tp_menu.add("首頁", lp_buy);
        tp_menu.add("訂單", lp_select);
        tp_menu.add("我的", lp_my);
        tp_menu.add("退出登錄", new JPanel());
        tp_menu.addChangeListener(new ChangeListener() {
                @Override
                public void stateChanged(ChangeEvent e) {
                    int i = tp_menu.getSelectedIndex();

                    if (i == 3) {
                        new CLIENT_LOAD();
                        dispose();
                    }
                }
            });
        tp_menu.setSelectedIndex(0);
        this.setLocationRelativeTo(null); //  窗口默認居中
        setVisible(true);
    }

    public void init() {
        this.init_Button();
        init_Panel();
        init_Head();
        loadShops();
        loadDishs();
        new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true) {
                        frame_modify.goBack(getX(), getY(), getWidth(),
                            getHeight());
                        frame_update.goBack(getX(), getY());
                    }
                }
            }).start();
        frame_update.addButtonAction(p_mine,
            new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    p_mine.update();
                }
            });
        loadMain();
        setSize(width, height);
        add(tp_menu);
    }

    /* 可按具體的寬高設置背景圖片 */
    public static ImageIcon _getIcon(String _filename, int _width, int _height) {
        ImageIcon image = new ImageIcon(_filename); //實例化ImageIcon 對象
        Image img = image.getImage();
        image.setImage(image.getImage()
                            .getScaledInstance(_width, _height,
                Image.SCALE_DEFAULT));

        return image;
    }

    public void loadShops() {
        for (int i = 0; i < shop_num; ++i)
            menu_shop.addButton(d.getShopName(i),
                "src/pic/shop" + (i + 1) + ".jpg", 150, 100, -1); //加入店鋪
    }

    public void init_Button() {
        /* 給每個商鋪按鈕分配空間  */
        for (int i = 0; i < d.getShopNum(); ++i)
            for (int j = 0; j < d.getShopMenuNum(i); ++j)
                menu_dish[i][j] = new PANEL_SCROLL(20, 15,
                        d.getDishHeight(i, j), -40, 200, 440, 310);

        /* 給每個按鈕添加監聽事件 用於實現點開店鋪后進入另一個面板*/
        for (int i = 0; i < menu_shop.getMaxNum(); ++i) {
            menu_shop.addButtonAction(i, new Listener_toShop());
        }

        /* 設置 菜單各個屬性按鈕的 格式 */
        for (int i = 0; i < d.getShopNum(); ++i) {
            for (int j = 0; j < d.getShopMenuNum(i); ++j) {
                for (int k = 0; k < d.getDishNum(i, j); ++k) {
                    menu_dish[i][j].setButtonTitle(k, d.getShopMenu(i, j));
                    menu_dish[i][j].setButtonSize(k, 120, 220);
                }
            }
        }

        p_mine.addButtonAction(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    frame_update.open();
                }
            });
    }

    public void addDish(int _dishNum, int _fileNum, String _dishName) {
    }

    public void init_Panel() {
        tp_menu.setFont(new Font("幼圓", Font.BOLD, 30));
        tp_menu.setBackground(new Color(236, 189, 14));

        int i;
        int j;

        for (i = 0; i < d.getShopNum(); ++i) {
            p_shops[i] = new JTabbedPane(JTabbedPane.LEFT);

            p_shops[i].setFont(new Font("幼圓", Font.BOLD, 20));
            p_shops[i].setBounds(0, 200, width - 20, height - 290);

            for (j = 0; j < d.getShopMenuNum(i); ++j)
                p_shops[i].add(d.getShopMenu(i, j), menu_dish[i][j]);

            p_shops[i].add("返回", new JPanel());
            p_shops[i].setSelectedIndex(0);
            p_shops[i].setBackground(new Color(219, 148, 7));
            p_shops[i].addChangeListener(new Listener_toMain());
        }
    }

    public void loadDishs() {
        for (int i = 0; i < d.getShopNum(); ++i) {
            for (int j = 0; j < d.getShopMenuNum(i); ++j) {
                for (int k = 0; k < d.getDishNum(i, j); ++k) {
                    menu_dish[i][j].addDish(frame_modify,
                        d.getDishName(i, j, k),
                        "src/pic/shop" + (i + 1) + "_" + (j + 1) + "_" +
                        (k + 1) + ".jpg", 210, 140, d.getPrice(i, j, k), i, j);
                }
            }
        }
    }

    public void loadMain() {
        l_background.setIcon(_getIcon("src/pic/background.jpg", width, 220));
        lp_buy.removeAll();
        lp_buy.add(l_background, 1);
        lp_buy.add(menu_shop, 0);
        lp_my.add(p_mine, 0);
    }

    public String getTime() {
        return df.format(new Date());
    }

    public void init_Head() {
        l_background.setIcon(_getIcon("src/pic/background.jpg", width, 220));
        l_background.setBounds(0, 0, width, 220);
    }

    public void init_Dish() {
        for (int i = 0; i < d.getShopNum(); ++i) {
            p_shops[i].setSelectedIndex(0);
        }
    }

    class Listener_toShop implements ActionListener {
        @Override
        public void actionPerformed(ActionEvent e) {
            String cmd = e.getActionCommand();
            int num = Integer.valueOf(cmd);
            nowShop = num;
            System.out.println(getTime() + " 退出了 [首頁]");
            lp_buy.removeAll();
            init_Dish();
            lp_buy.add(p_shops[num], 0);
            lp_buy.add(l_background, 1);
            l_background.setIcon(_getIcon("src/pic/shop" + (num + 1) + ".jpg",
                    width, 220));
            System.out.println(getTime() + " 進入了 [" + d.getShopName(num) +
                "] 店鋪");
        }
    }

    class Listener_toMain implements ChangeListener {
        @Override
        public void stateChanged(ChangeEvent e) {
            int chose = p_shops[nowShop].getSelectedIndex();

            if (chose == d.getShopMenuNum(nowShop)) {
                System.out.println(getTime() + " 退出了 [" +
                    d.getShopName(nowShop) + "] 店鋪");
                frame_modify.close();
                loadMain();
                System.out.println(getTime() + " 進入了 [首頁]");
            } else {
                System.out.println(getTime() + " 選擇了 " + "[" +
                    d.getMenuName(nowShop, chose) + "]");
            }
        }
    }
}

下面是CLIENT_MAIN.java程序運行的效果圖:

圖4.2.1 客戶主窗口程序運行圖

圖4.2.2 客戶主窗口中“我的“窗口運行圖

五、實現JDBC + PostgreSQL數據庫

5.1 准備PostgresSQL數據庫環境

課程設計環境: windows10操作系統、PostgreSQL 13數據庫服務
postgreSQL官網: https://www.postgresql.org/
PostgreSQL windows版本下載網址: https://www.postgresql.org/download/windows/
下載成功后進行安裝,在安裝過程中需設置密碼,安裝成功后配置系統環境變量。

5.1.1 配置環境變量

步驟1: 此電腦 -> 屬性 -> 高級系統設置 -> 環境變量,在系統變量的窗口中新建一個變量存psql的安裝地址。

圖5.1.1.1 postgresql環境變量配置步驟圖1
接下來進入系統變量中的Path,雙擊打開后新建
%PostgreSQL_HOME%\bin、%PostgreSQL_HOME%\lib、%PostgreSQL_HOME%\data,接着確定即可。

圖5.1.1.2 postgresql環境變量配置步驟圖2

5.1.2 兩種方式進入psql數據庫

1.使用SQL shell工具
首次連接直接連接默認地址、默認端口、默認database即可,回車四次后需輸入用戶postgres(默認)的口令,口令即為安裝時設置的密碼。

圖5.1.2.1 通過shell連接psql圖
2.使用pgAdmin4工具
該工具會在瀏覽器中打開,初次進入需要輸入口令,和安裝時設置的密碼一致。

圖5.1.2.2 使用pgAdmin4 工具在瀏覽器中進入psql數據庫

5.2 在pgAdmin4中創建MT數據庫

    為更好的實現課程設計項目,我們將要所有的數據表存儲在自己創建的數據庫內便於區別默認的數據庫postgres,也可以在sql shell中輸入指令: create database MT; 來創建數據庫。

圖 5.2 在pgAdmin5 中創建數據庫

5.3 在sql shell中創建數據表

根據第二部分概念設計中內容,需要創建的表有如下9個。

圖 5.3 在psql中創建的表

5.3.1 創建客戶信息表

客戶(客戶編號,昵稱,密碼,聯系方式,狀態,地址,賬號余額),對於客戶的信息表為區分於商家、騎手、管理員,所以需要在ID上加一個主鍵約束,另外增加一個check約束,限制id的形式為c開頭的字符串。其中狀態1表示為在線,0表示為不在線。

CREATE TABLE client (
	c_id character varying(9) PRIMARY KEY NOT NULL CHECK (c_id LIKE 'c%'),
	c_name character varying(20) NOT NULL,
	c_pw character varying(20) NOT NULL,
	c_tel character varying(11) NOT NULL CHECK (c_tel LIKE '1%') UNIQUE,
	c_state int CHECK (c_state IN (1, 0)),
	c_add character varying(20) NOT NULL,
	c_money float CHECK (c_money >= 0)
);

5.3.2 創建商家信息表

商家(商家編號,密碼,聯系方式,店鋪名稱, 地址,信譽星級,狀態,賬號余額),其中狀態分為三種,working ——營業中,relax—— 休息, gone ——停業

1 create table provider(
 2     p_id character varying(9) primary key not null check(p_id like 'p%'),
 3     p_pw character varying(20) not null,
 4     p_tel character varying(20) not null unique check(p_tel like '1%'),
 5     p_name character varying(20) not null,
 6     p_add character varying(11) not null unique,
 7     p_lev int,
 8     p_state character varying(15) check(p_state in ('working','relax','gone')),
 9     p_money float check (p_money >= 0)
10 );

5.3.3 創建騎手信息表

騎手(騎手編號,密碼,名字,聯系電話,地址,配送星級,配送狀態,賬號余額),其中配送狀態和商家一樣分為三種: Working——配送中,relax——休息, gone——下線

CREATE TABLE rider (
	r_id character varying(10) PRIMARY KEY NOT NULL CHECK (r_id LIKE 'r%'),
	r_pw character varying(20) NOT NULL,
	r_name character varying(10) NOT NULL,
	r_tel character varying(20) NOT NULL UNIQUE CHECK (r_tel LIKE '1%'),
	r_add character varying(11) NOT NULL UNIQUE,
	r_lev int,
	r_state character varying(15) CHECK (r_state IN ('working', 'relax', 'gone')),
	r_money float CHECK (r_money >= 0)
);

5.3.4 創建管理員信息表

管理員(管理員編號,名稱,密碼,聯系電話,狀態),其中狀態表示是否在線:1——在線,0——不在線

CREATE TABLE admin (
	a_id character varying(9) PRIMARY KEY NOT NULL CHECK (a_id LIKE 'a%'),
	a_name character varying(20) NOT NULL,
	a_pw character varying(20) NOT NULL,
	a_tel character varying(11) NOT NULL CHECK (a_tel LIKE '1%') UNIQUE,
	a_state int CHECK (a_state IN (1, 0))
);

5.3.5 創建菜品信息表

菜品(菜品編號,名稱,價格,折扣,提供商編號,剩余量,出售量)

CREATE TABLE dish (
	d_id int PRIMARY KEY NOT NULL,
	d_name character varying(30) NOT NULL,
	d_price float(2) NOT NULL,
	d_discount float(2),
	d_pid character varying(9) NOT NULL,
	FOREIGN KEY (d_pid) REFERENCES provider (p_id),
	d_stock int NOT NULL CHECK (d_stock >= 0),
	d_sales int NOT NULL
);

5.3.6 創建訂單信息表

訂單(訂單編號,客戶id,騎手id,菜品id,商家id,時間,數量,客戶地址,狀態)

CREATE TABLE ord (
	o_id int PRIMARY KEY NOT NULL,
	o_cid character varying(9),
	FOREIGN KEY (o_cid) REFERENCES client (c_id),
	o_rid character varying(9),
	FOREIGN KEY (o_rid) REFERENCES rider (r_id),
	o_did int,
	FOREIGN KEY (o_did) REFERENCES dish (d_id),
	o_pid character varying(9),
	FOREIGN KEY (o_pid) REFERENCES provider (p_id),
	o_time date,
	o_num int NOT NULL CHECK (o_num > 0),
	o_cadd character varying(50) NOT NULL,
	o_state character varying(15) CHECK (o_state IN ('wait', 'accept', 'cancel'))
);

5.3.7 創建管理客戶信息表

管理客戶(管理員編號,客戶編號,時間,操作類別)

CREATE TABLE admin_client (
	a_c_aid character varying(9),
	FOREIGN KEY (a_c_aid) REFERENCES admin (a_id),
	a_c_cid character varying(9),
	FOREIGN KEY (a_c_cid) REFERENCES client (c_id),
	a_c_time date,
	PRIMARY KEY (a_c_aid, a_c_cid, a_c_time),
	a_c_operation character varying(15) CHECK (a_c_operation IN ('update', 'insert', 'delete'))
);

5.3.8 創建管理商店信息表

管理商家(管理員編號,商家編號,時間,操作類別)

CREATE TABLE admin_provider (
	a_p_aid character varying(9),
	FOREIGN KEY (a_p_aid) REFERENCES admin (a_id),
	a_p_pid character varying(9),
	FOREIGN KEY (a_p_pid) REFERENCES provider (p_id),
	a_p_time date,
	PRIMARY KEY (a_p_aid, a_p_pid, a_p_time),
	a_p_operation character varying(15) CHECK (a_p_operation IN ('allow', 'delete'))
);

5.3.9 創建管理騎手信息表

管理騎手(管理員編號,騎手編號,時間,操作類別)

CREATE TABLE admin_rider (
	a_r_aid character varying(9),
	FOREIGN KEY (a_r_aid) REFERENCES admin (a_id),
	a_r_rid character varying(9),
	FOREIGN KEY (a_r_rid) REFERENCES rider (r_id),
	a_r_time date,
	PRIMARY KEY (a_r_aid, a_r_rid, a_r_time),
	a_r_operation character varying(15) CHECK (a_r_operation IN ('update', 'insert', 'delete'))
);

5.4 在IDEA中連接JAVA 與 postgreSQL數據庫

5.4.1 根據圖中的步驟在IDEA中導入數據庫相關的jar包

圖5.4.1 操作步驟1

圖5.4.2 操作步驟2

圖5.4.3 操作步驟3

圖5.4.4操作步驟4

5.5 增——以注冊功能為例實現JDBC向psql表中插入數據的功能

5.5.1 核心代碼1: 注冊按鈕的監聽事件

此監聽事件實現的主要功能是在客戶點擊注冊按鈕后,將文本框里的內容傳到PSQL_JDBC類中,在通過該check() 方法判斷注冊信息是否符合規則,如果符合則通過insert_client方法,插入數據到client 客戶表中。

b_ok.addActionListener(new ActionListener() {
       @Override
       public void actionPerformed(ActionEvent e) {
            if(check()) {
         PSQL_JDBC.insert_client(userid, username, password, address, tel);
         JOptionPane.showMessageDialog(panel, "恭喜您!注冊成功!", "消息提示:",JOptionPane.WARNING_MESSAGE); //彈出信息框
            } else JOptionPane.showMessageDialog(null, "抱歉!注冊失敗,請再次檢查您輸入的信息是否符合規范", "消息提示:",JOptionPane.ERROR_MESSAGE); //彈出信息框
       }
 });

5.5.2 核心代碼2: 連接postgresql數據庫

      public static void getConn() {
         String driver = "org.postgresql.Driver";
         //String url = "jdbc:postgresql://10.5.14.70:5432/u19211870125";
         String url = "jdbc:postgresql://10.5.14.70:5432/u19211870125";
         String username = "u19211870125";
         String password = "centos@123";
         Connection a = null;
         try {
             Class.forName(driver); // classLoader,加載對應驅動
             a = (Connection) DriverManager.getConnection(url, username, password);
         } catch (ClassNotFoundException e) {
             e.printStackTrace();
         } catch (SQLException e) {
             e.printStackTrace();
         }
         conn = a;
     }

5.5.3 核心代碼3: 通過JAVA向數據庫表插入數據

     public int insert(Client client) {
         if(conn == null) getConn();
 
         int i = 0;
         String sql = "insert into client (c_id,c_name,c_pw,c_tel,c_state,c_add,c_money) values(?,?,?,?,?,?,?)";
         PreparedStatement pstmt;
         try {
             pstmt = (PreparedStatement) conn.prepareStatement(sql);
             pstmt.setString(1, client.getId());
             pstmt.setString(2, client.getName());
             pstmt.setString(3, client.getPw());
             pstmt.setString(4, client.getTel());
             pstmt.setInt(5, client.getState());
             pstmt.setString(6, client.getAdd());
             pstmt.setDouble(7, client.getMoney());
             i = pstmt.executeUpdate();
             pstmt.close();
             //conn.close();
         } catch (SQLException e) {
             e.printStackTrace();
         }
         return i;
     }

5.6 刪——以管理員並發測試為例實現JDBC刪除psql中表的數據

在管理員操作主窗口中對”刪除所有客戶”的按鈕添加了監聽事件,即被單擊時會執行的代碼內容,代碼如下:
  private class buttonListener implements ActionListener {
         @Override
         public void actionPerformed(ActionEvent e) {
             int cmd = Integer.valueOf(e.getActionCommand());
             switch (cmd){
                 case 0:
                     insert_client(1000);    //插入1000k個客戶
                     break;
                 case 1:
                     insert_riders(1000);    //插入1000k個騎手
                     break;
                 case 2:
                     insert_client(10);      //插入10k個客戶
                     break;
                 case 3:
                     insert_riders(10);      //插入10k個騎手
                     break;
                 case 4:
                     System.out.println(getTime() + " 成功清除 " + PSQL_JDBC.clear_client() + " 個客戶!");
                     break;
                 case 5:
                     System.out.println(getTime() + " 成功清除 " + PSQL_JDBC.clear_rider() + " 個騎手!");
                     break;
                 case 6:
                     insert_client(1);       //插入1k個客戶
                     break;
                 case 7:
                     insert_riders(1);       //插入1k個騎手
                     break;
             }
         }
     }

在上述代碼中,根據程序的UI設計主要是在第5、6個按鈕被單擊后會執行刪除操作。在PSQL_JDBC.java程序中,刪除的方法如下:

 public static int clear_client() {
         if(!operation_client.isOK()){return -1; }
         int i = operation_client._getRow("client");
         String sql = "delete from client" ;
         PreparedStatement pstmt;
         try {
             pstmt = (PreparedStatement) operation_client.conn.prepareStatement(sql);
             pstmt.executeUpdate();
             pstmt.close();
             //operation_client.conn.close();
         } catch (SQLException e) {
             e.printStackTrace();
         }
         return i;
     }

該方法通過借助PreparedStatement類 成功執行sql語句 delete from client,並且最后會返回刪除的總個數。

5.7 改——以統計在線人數功能為例實現對表的數據進行修改

    在CLIENT_LOAD.java程序中即客戶登錄界面,當登錄按鈕被單擊后,調用JDBC類查詢密碼是否正確,如果正確則允許登陸,同時調用方法setOnlineClient(),作用為設置當前登陸的客戶的c_state屬性為1,即在線的狀態。

                 try {
                     if(PSQL_JDBC.select_client("c" + user.getText(), new String(password.getPassword()))){
                         frame_war.open(false);
                         PSQL_JDBC.setOnlineClient();
                         System.out.println(getTime() + " 用戶[" + user.getText() +  "]登錄成功!");
                         dispose();
                     }
                     else frame_war.open(true);
                 } catch (SQLException throwables) {
                     throwables.printStackTrace();
                 }

    上面的代碼主要是對登陸按鈕進行了監聽,接下來是具體實現更改操作,setType屬性主要負責更新client的表中,屬性為type,客戶編號為id,修改成text值。以此我們可以更方便的實現下一次的更改操作。

  public void setType(String type, String id, String text){
         if(conn == null) getConn();
         String sql = "update client set "+ type + "=" + text + " where c_id = \'" + id + "\'";
         PreparedStatement pstmt;
         try {
             pstmt = (PreparedStatement) conn.prepareStatement(sql);
             pstmt.executeUpdate();
             pstmt.close();
         } catch (SQLException e) {
             e.printStackTrace();
         }
     }
     public static void setOnlineClient(){
         op.setType("c_state",client.getId(),"1"); 
     }

5.8 查——查詢某一個表中的所有數據

    public Integer getAll(String table) {
         if(conn == null){ return -1;}
         String sql = "select * from " + table;
         PreparedStatement pstmt;
         try {
             pstmt = (PreparedStatement)conn.prepareStatement(sql);
             ResultSet rs = pstmt.executeQuery();
             int col = rs.getMetaData().getColumnCount();
             System.out.println("============================");
             while (rs.next()) {
                 for (int i = 1; i <= col; i++) {
                    System.out.print(rs.getString(i) + "\t");
                     if ((i == 2) && (rs.getString(i).length() < 8)) {
                         System.out.print("\t");
                     }
                 }
                 System.out.println("");
             }
             System.out.println("============================");
         } catch (SQLException e) {
             e.printStackTrace();
         }
         return null;
     }

5.9 功能模塊——客戶注冊與登陸

    對於客戶來說,注冊和登陸是必要的流程,據此我們設計的程序中包含了此功能,實質上是對數據表的基本操作,比如注冊功能實現的是在client表中插入數據,而登陸功能則是在數據表中查詢客戶的編號和密碼是否匹配。

圖5.9.1 客戶注冊賬號界面圖
如上圖所示,當點擊“確認注冊”后,JAVA程序將sql語句select count(*) from where c_id = ‘cycc’; 傳入psql數據庫中,如果返回的結果中有1那么就提示注冊失敗,請用戶重新輸入。

圖5.9.2 客戶登陸界面圖
    對於登陸界面,比如輸入的賬號是”cycc”,點擊登陸按鈕后,JAVA程序將sql語句select c_pw from client where c_id = ‘ccycc’; 傳入psql數據庫中,如果返回的pw字符串與當前密碼框輸入的字符串相同的話則成功登陸。接下來是兩個功能成功測試的動態效果圖。

圖5.9.3 實現注冊+登陸功能的動態運行圖

5.10 功能模塊——客戶點餐

    對於美團用戶來說,點餐可以說是必要的功能了,雖然時間有限,但我們依然能實現GUI層次的點餐,接下來是該模塊的核心代碼。
CLIENT_MODIFY.java程序代碼:

package com;
 
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class CLIENT_MODIFY extends JFrame {
     PANEL_SCROLL p = new PANEL_SCROLL(20,15,400,-40,200,440,310);
     private int width = 300, height = 600;
     private int money = 0;
     private int []dishSum = new int [30];  //存購物車按鈕的累計個數
     private int []dishNum = new int [30]; //存購物車第i個按鈕的id號
     private int shopNum,shopMenuNum;
     JLabel l_showMoney = new JLabel("       總價: ");
     JButton b_clear = new JButton("清空清單");
     JButton b_buy = new JButton("確認購買");
     CLIENT_MODIFY(int x, int y, int w, int h){
         this.setResizable(false);
         setBounds(x + w, y, width, height);
         p.setBounds(0,0, width, height);
         p.setMaxScorll(1200);
         p.setPanelLocation(-100, getY());
         p.setButtonTextPosition(false);
         p.setPanelLayout(new GridLayout(20,1));
         l_showMoney.setFont(new Font("幼圓", Font.BOLD,30));
         b_clear.setFont(new Font("幼圓", Font.BOLD,30));
         b_buy.setFont(new Font("幼圓", Font.BOLD,30));
         b_clear.addActionListener(new ActionListener() {
             @Override
             public void actionPerformed(ActionEvent e) {
                 p.setVisible(false);
                 clearDish();
                 p.removePanelAll();
                 p.addToPanel(b_clear);
                 p.addToPanel(b_buy);
                 p.addToPanel(l_showMoney);
                 money = 0;
                 l_showMoney.setText("       總計: " + getPrice() + " 元");
                 p.setVisible(true);
             }
         });
         b_buy.addActionListener(new ActionListener() {
             @Override
             public void actionPerformed(ActionEvent e) {
                 if(getPrice() <= PSQL_JDBC.getClientA("id", new String(PSQL_JDBC.client.getId()))){
 
                 }
             }
         });
         p.addToPanel(b_clear);
         p.addToPanel(b_buy);
         p.addToPanel(l_showMoney);
         add(p);
     }
     public void open(){setVisible(true); }
     public void close(){setVisible(false);}
     public void goBack(int x, int y,int w, int h){setBounds(x + w - 15 , y, width, height);}
     public void addPrice(int i){money += i;}
     public int getPrice(){return money;}
     public boolean canAddDish(){return p.getButtonNum() < 5; }
 
     public void setShopNum(int a) {shopNum = a;}
     public void setShopMenuNum(int a){shopMenuNum = a;}
     public void addSameDish(int i){
         dishSum[dishNum[i]]++;
     }
     public int getDishNum(int i){
         if(dishNum[i] == 0) dishNum[i] = p.getButtonNum();
         return dishSum[dishNum[i]];
     }
     public void updateDish(int i, String title){
             addSameDish(i);
             p.setButtonTitle(dishNum[i] - 1, title + " X " + dishSum[dishNum[i]]);
     }
     //判斷第i個菜餚是否已經在購物車中
     public boolean DishIsExisted(int i){ return !(dishNum[i] == 0 && dishSum[dishNum[i]] == 0); }
     public int setDishNum(int i){
         dishNum[i] = p.getButtonNum() + 1;
         dishSum[dishNum[i]] = 1;
         return 1;
    }
     public void clearDish(){
         for(int i = 0; i < p.getButtonNum(); ++i) {
             dishNum[i] = 0;
             dishSum[dishNum[i]] = 0;
         }
     }
 }

在該模塊中若要實現點餐的功能,需要判斷客戶的賬號余額是否足夠,在足夠的情況下才會發起訂單,接着商家可以接受到訂單,否則就點餐失敗,那么接下來給出獲取某個客戶賬號余額的關鍵代碼:

  public double getClientA(String type, String id){
         if(conn == null) getConn();
         double i = 0;
         String sql = "select "+ type +" from client where c_id = " + "\'" + id+"\'";
        PreparedStatement pstmt;
         try {
             pstmt = (PreparedStatement) conn.prepareStatement(sql);
             ResultSet rs = pstmt.executeQuery();
             while(rs.next())
                 i = Double.valueOf(rs.getString(1));
             pstmt.close();
         } catch (SQLException e) {
            e.printStackTrace();
         }
         return i;
     }

在上述代碼基礎下調用getClientA("c_money",client.getId())就可實現查詢客戶余額。

圖5.10 點餐測試Gif圖

5.11 功能模塊——管理員添加百萬客戶

    該功能模塊可以很好的測試postgreSQL數據庫的性能,不過在添加百萬客戶的同時需要有產生隨機字符串的方法,接下來是相關代碼:

     public static String getRandomString(int length){
         String str="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
         Random random=new Random();
         StringBuffer s=new StringBuffer();
         for(int i=0;i<length;i++){
             int number=random.nextInt(62);
             s.append(str.charAt(number));
         }
         return s.toString();
     }
     public static String getRandomTel(){
         String str="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ00123456789";
         Random random = new Random();
         StringBuffer s = new StringBuffer();
         s.append(1);
         for(int i=0;i<10;i++){
             int number=random.nextInt(62);
             s.append(str.charAt(number));
         }
         return s.toString();
     }
     public void insert_client(int sum){
         String name[] = new String[1000];
         String tel[] = new String[1000];
         String id[] = new String[1000];
         String sql[] = new String[1000];
         int num = 0;
         for(int k = 1; k <= sum;++k) {
             for (int i = 0; i < 1000;++num, ++i) {
                 name[i] = getRandomString(5);
                 id[i] = getRandomString(5);
                 tel[i] = getRandomTel();
                 sql[i] = "insert into client (c_id,c_name,c_pw,c_tel,c_state,c_add,c_money) values(\'c"+ id[i] +"\',\'"+name[k] +"\',\'123456\',\'" + tel[i] + "\',0,\'wenzhoudaxue\',0);";
             }
             PSQL_JDBC.insert_clients(sql);
             System.out.println(getTime() + " 第 " + k + " 次操作: 插入 " + num + " 個客戶 已完成!");
         }
     }

    在上述代碼的基礎上能獲取到100W個隨機數據,但是沒有判斷重復的機制,因為在數據量很大的情況下,同時判斷是否重復會給服務器增加負擔,所以采取的策略便是100W個數據分成1000次連續插入1000個數據,這樣能保證有效的插入也能防止一次性插入太多而卡頓。接下來是jdbc類中實現插入功能的關鍵代碼:

     public int inserts(String []sql) {
         if(conn == null) getConn();
         int i = 0;
         Statement pstmt;
         try {
             pstmt = conn.createStatement();
             for(int k = 0; k < sql.length;++k)
             pstmt.addBatch(sql[k]);
             pstmt.executeBatch();
             pstmt.close();
             //conn.close();
         } catch (SQLException e) {
             e.printStackTrace();
         }
         return i;
     }

圖5.11 添加百萬客戶功能測試運行動態圖

5.12 觸發器設計

5.12.1 保證客戶的余額始終大於0

    在整個模擬系統中,當客戶進行訂餐后,數據庫系統將對客戶的余額進行查詢,同時會更新余額,此時就需要保證余額一定要大於0,這個小功能可以用sql語句來實現,sql代碼如下:

CREATE FUNCTION update_client_money()
returns trigger as $$
begin
    update client set c_money = 0 where c_money < 0;
    return old;
end;$$
language plpgsql;

create triiger U_C_M
after update on client
for each row execute procedure update_client_money();

5.12.2 保證訂單的id是遞增的序列
    由於各個商家以及各個客戶是m對n的關系,所以肯定會產生許多不同的訂單,此時為了更好的區分不同的訂單,則采取設置主鍵o_id為遞增序列的形式,首先創建一個序列,接着創建對應的執行函數、還有觸發器。sql代碼如下:

create sequence s_ord
start with 1
increment by 1
create function insert_ord()
returns trigger as $$
begin
    select s_ord.nextval into new.o_id from dual;
    return new;
end;$$
language plpgsql;
CREATE TRIGGER I_O 
    before insert on ord
    for each row execute procedure insert_ord();

5.12.3 添加新訂單

    對於新訂單的產生,我們設想當客戶的余額有減少的時候,就說明客戶在客戶端進行了點餐,與此同時可以添加一個觸發器對客戶表client的update操作進行觸發,觸發事件則插入新的訂單到ord表中,代碼如下:

create function insert_ord2()
returns trigger as $$
begin
    insert into ord(o_cid, o_num, o_cadd) values (old.c_id, 1, old.c_add);
    return new;
end;$$
language plpgsql;

CREATE TRIGGER I_NewO
    after update of c_money on client
    for each row execute procedure insert_ord2();

五、實踐總結與體會

    本次課程設計主要是模擬《美團外賣》程序來設計數據庫系統,在經過一學期對《數據庫原理與應用》的學習,從最開始了解數據庫概念在到后來的數據庫系統設計,我們感受到了當代數據庫存在的必要性以及應用廣泛性,通過不斷地學習與討論,首先是需求分析、可行性分析,接着又是概念設計、邏輯結構設計、最后就是實現,包括基於JAVA的客戶端實現以及JDBC+postgreSQL的實現,成功嘗試了用JAVA程序連接psql數據庫,並且最終實現了簡要的數據庫系統。
    設計過程中,我們也會遇到各種各樣的問題,首先是關於ER模型的建立,起初我們設計的模型還存在許多問題,在多次向老師請教之后才有了正確的方向,我們小組每位成員都承擔着重要的角色,整個過程中,我們通過積極配合、積極討論、積極學習來推進課程設計的發展,在這個過程中我們學到了團隊合作,並收獲了許多數據庫系統設計的相關知識,嘗試了使用PowerDesigner工具、postgreSQL工具、IDEA編輯器,並初次通過psql連接到了校園數據庫系統里分配給我們的數據庫。
優點:
(1) 使用Java語言與postgresql數據庫連接,通過java程序對數據庫進行了增、刪、改、查的操作。
(2) 管理員登陸簡便,只要賬號和密碼輸入正確就可在后台管理客戶賬號。
(3) 設計的數據流圖、字典邏輯清晰、簡潔明了。
(4) 程序界面設計友好,比如客戶、管理員窗口可進行互相轉換。
缺點:
(1) 使用到的觸發器較少,對於數據庫原理與應用課程設計,偏向的角度應該為數據庫方面的學習,而我們大多時間花費在了JAVA程序設計上,由於是初次使用JAVA連接postgreSQL數據庫,所以總是會出現各種各樣的問題。
(2)在設計關系模型時,我們沒有推算到更高的范式,由於屬性錯綜復雜,經過小組多次討論,決定以當前最合理的關系模型為主,若忽略部分屬性,最多可達到3NF范式的要求,但是要達到更高范式就需要對關系模型進一步的分析了,所以有的屬性是多余的存在。
(3) java程序功能較少,大多時候還得通過后台連接數據庫進行操作。
(4) 缺少數據庫維護的功能,由於時間有限,我們沒有使用到數據庫中的視圖、模式、觸發器,以此來實現數據庫維護,以及事務日志也還未准備。
任務分配情況:
A = 需求分析
B = 可行性分析
C = 概念設計
D = 邏輯結構設計
E = 基於JAVA的客戶端程序實現
F = 實現JDBC + psql數據庫
G = 撰寫課程設計報告
H = 修改、完善報告
I = 測試JAVA程序、調試功能
J = 制作匯報PPT
K = 修改、完善匯報PPT
本人(ABCDEFGK)

六、附錄:代碼、語句和命令等匯總(可以另外附加項目文件)

項目文件在同一個文件夾內
1.創建客戶表

create table client(
    c_id character varying(9) primary key not null check(c_id like 'c%'),
    c_name character varying(20) not null,
    c_pw character varying(20) not null,
    c_tel character varying(11) not null check (c_tel like '1%') unique,
    c_state int check (c_state in (1,0)),    
    c_add character varying(20) not null,
    c_money float check (c_money >= 0)
);

2.創建商家表

create table provider(
    p_id character varying(9) primary key not null check(p_id like 'p%'),
    p_pw character varying(20) not null,
    p_tel character varying(20) not null unique check(p_tel like '1%'),
    p_name character varying(20) not null,
    p_add character varying(11) not null unique,
    p_lev int,
    p_state character varying(15) check(p_state in ('working','relax','gone')),
    p_money float check (p_money >= 0)
);

3.創建騎手表

create table rider(
    r_id character varying(9) primary key not null check(r_id like 'r%'),
    r_pw character varying(20) not null,
    r_name character varying(10) not null,
    r_tel character varying(20) not null unique check(r_tel like '1%'),
    r_add character varying(11) not null,
    r_lev int,
    r_state character varying(15) check(r_state in ('working','relax','gone')),
    r_money float check(r_money >= 0)
);

4.創建管理員表

create table admin(
    a_id character varying(9) primary key not null check(a_id like 'a%'),
    a_name character varying(20) not null,
    a_pw character varying(20) not null,
    a_tel character varying(11) not null check (a_tel like '1%') unique,
    a_state int check (a_state in (1,0))
);

5.創建菜品表

create table dish(
    d_id int primary key not null,
    d_name character varying(30) not null,
    d_price float(2) not null,
    d_discount float(2),
    d_pid character varying(9) not null,
    foreign key(d_pid) references provider(p_id),
    d_stock int not null check (d_stock >= 0),
    d_sales int not null
);

6.創建訂單

create table ord(
    o_id int primary key not null,
    o_cid character varying(9),
    foreign key(o_cid) references client(c_id),
    o_rid character varying(9),
    foreign key(o_rid) references rider(r_id),
    o_did int,
    foreign key(o_did) references dish (d_id),
    o_pid character varying(9),
    foreign key(o_pid) references provider(p_id),
    o_time date,
    o_num int not null check(o_num > 0),
    o_cadd character varying(50) not null,
    o_state character varying(15) check (o_state in ('wait','accept','cancel')),
);

7.管理客戶

create table admin_client(
    a_c_aid character varying(9),
    foreign key (a_c_aid) references admin(a_id),
    a_c_cid character varying(9),
    foreign key (a_c_cid) references client(c_id),
    a_c_time date,
    primary key (a_c_aid, a_c_cid, a_c_time),
    a_c_operation character varying(15) check (a_c_operation in('update','insert','delete'))
);

8.管理商店

create table admin_provider(
    a_p_aid character varying(9),
    foreign key (a_p_aid) references admin(a_id),
    a_p_pid character varying(9),
    foreign key (a_p_pid) references provider(p_id),
    a_p_time date,
    primary key (a_p_aid, a_p_pid, a_p_time),
    a_p_operation character varying(15) check (a_p_operation in('allow','delete'))
);

9.管理騎手

create table admin_rider(
    a_r_aid character varying(9),
    foreign key (a_r_aid) references admin(a_id),
    a_r_rid character varying(9),
    foreign key (a_r_rid) references rider(r_id),
    a_r_time date,
    primary key (a_r_aid, a_r_rid, a_r_time),
    a_r_operation character varying(15) check (a_r_operation in('update','insert','delete'))
);

1.刪除約束
alter table {TABLENAME} drop constraint if EXISTS {CHECKNAME};
2.刪除客戶的money約束
alter table client drop constraint if EXISTS client_c_money_check
3.級聯刪除執行函數
DROP FUNCTION {NAME}() CASCADE;
4.刪除某個表中的觸發器
drop trigger {triggerName} on {tableName};
5.設置序列下個的值
select setval('{NAME}', 0);

觸發器設計的sql指令

--------------------------------------------
【功能:當客戶的余額小於0時設置為0】
--------------------------------------------
-> 1. 創建觸發的執行函數

CREATE FUNCTION update_client_money()
returns trigger as $$
begin
    update client set c_money = 0 where c_money < 0;
    return old;
end;$$
language plpgsql;

-> 2. 創建觸發器 U_C_M

create triiger U_C_M
after update on client
for each row execute procedure update_client_money();

--------------------------------------------
【功能:插入訂單時,訂單id號自動遞增】
--------------------------------------------
-> 1.創建訂單遞增序列

create sequence s_ord
start with 1
increment by 1

--------------------------------------------
-> 2.創建觸發的執行函數

create function insert_ord()
returns trigger as $$
begin
    select s_ord.nextval into new.o_id from dual;
    return new;
end;$$
language plpgsql;

--------------------------------------------
--> 3. 創建觸發器 I_O
CREATE TRIGGER I_O 
    before insert on ord
    for each row execute procedure insert_ord();
--------------------------------------------
【功能:客戶余額減少時,添加訂單】
-> 1.創建觸發的執行函數
create function insert_ord2()
returns trigger as $$
begin
    insert into ord(o_cid, o_num, o_cadd) values (old.c_id, 1, old.c_add);
    return new;
end;$$
language plpgsql;
-> 2.創建觸發器 
CREATE TRIGGER I_NewO
    after update of c_money on client
    for each row execute procedure insert_ord2();


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM