• 一本糊涂账(Java 基础实战项目)


    1. 项目简介

    在这里插入图片描述

    1. 需要知识

    • 面向对象、字符串数字、日期
    • 异常、集合、JDBC、反射机制、I/O、Swing、利用 TableModel 更新数据、图形界面的皮肤
    • 图标 chart 动态生成、数据库的备份与恢复、自定义圆形进度条

    2. 设计思想

    • 单例模式
    • 面板类与监听器类松耦合
    • Entity 层设计
    • Service 层设计

    3. 模块功能

    消费一览
    统计本月的消费总数,今日消费,日均消费,本月剩余,日均可用,距离月末有多少天。

    同时使用一个环形进度条,这个环形进度条不是JDK自带的,需要自己设计,并且随着消费用度,颜色从绿色渐变为红色。

    在这里插入图片描述
    记一笔
    记录本日的消费额度, 分类下拉框从 消费分类数据中读取,并且把经常消费的分类放在前面。

    日期默认选中今天,也可以手动指定日期。
    在这里插入图片描述
    消费分类管理
    对消费进行经典的CRUD 增删改查管理,同时显示一个分类下的消费次数。
    这里涉及到多表关系:

    消费记录和消费分类是多对一关系
    在这里插入图片描述
    月度消费报表
    使用第三方chart类生成柱状报表,显示本月的消费趋势
    在这里插入图片描述
    设置预算和数据库路径
    在消费一览中需要显示本月可用多少金额,都是建立在预算的基础上的。

    在设置页面,设置本月的预算金额。

    后续的还原和备份,都需要用到数据库的命令mysql和mysqldump,需要在这里配置mysql的安装目录
    在这里插入图片描述
    备份数据
    把数据库中的所有数据,备份到.sql文件中
    在这里插入图片描述
    恢复数据
    根据.sql文件还原数据库
    在这里插入图片描述

    2. 开发流程

    1. 表结构设计

    在开始整个软件开发之前,一定是事先进行表结构设计。

    把表有哪些字段搞清楚,表与表之间的关系理顺。

    同时还要校验这样的表结构,是否能够支撑功能上的需要。

    比如在消费一览中需要的各种数据,应该以什么样的方式去设计这些表,才能够支撑页面上的数据显示。

    在专门的表结构设计章节里,会把每张表列出来,每个字段的意义,类型,限制。 表与表之间的关系,一对多关系,多对一关系如何确定与设计。主键约束,外键约束等等信息。

    步骤 1 : 首先创建数据库
    数据库名定为hutubill,在程序中的JDBC相关代码,都需要连接这个数据库名 hutubill

    create database hutubill;
    use hutubill;
    
    • 1
    • 2

    步骤 2 : 确定需要哪些表
    根据业务上的需要,一共要3个表

    1. 配置表信息 config 用于保存每月预算和Mysql的安装路径( 用于备份还原用)
    2. 消费分类表 category 用于保存消费分类,比如餐饮,交通,住宿
    3. 消费记录表 record 用于存放每一笔的消费记录,并且会用到消费分类

    步骤 3 : 配置信息表 config
    配置信息表 config有如下字段
    id 主键,每个表都有一个主键 类型是 int
    key_ 配置信息按照键值对的形式出现 ,类型是varchar(255)
    value 配置信息的值, 类型是 varchar(255)

    1. 键值对
      进一步解释一下键值对,比如要存放每个月的预算,则需要在config表中增加一条记录,key=“budget” value=“500”,就表示预算是500.

    2. varchar(255) 表示变长字符,如果实际存放只有30个字符,那么在数据库中只占用30的空间,最多占用255

    3. key 是关键字,不适合用于作为字段名,所以在key后面加了一个下划线 key_ 就不会有任何问题了,识别性一样很好,一眼就知道这个字段是干什么用的

    4. ENGINE=InnoDB MySQL 有多种存储引擎,MyISAM和InnoDB是其中常用的两种, 他们之间的区别很多,如果要展开讲,就需要专门的章节了。 这里使用ENGINE=InnoDB 是因为后续要使用的外键约束只有在InnoDB中才生效。

    5. DEFAULT CHARSET=utf8; 表示该表按照UTF-8的编码存放中文

    CREATE TABLE config (
      id int ,
      key_ varchar(255) ,
      value varchar(255)
    )  ENGINE=InnoDB  DEFAULT CHARSET=utf8;
    
    • 1
    • 2
    • 3
    • 4
    • 5

    步骤 4 : 消费分类表 category
    消费分类表 category 有如下字段
    id 主键,每个表都有一个主键 类型是 int
    name 分类的名称,类型是varchar(255)

    CREATE TABLE category (
      id int,
      name varchar(255)
    )   ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    • 1
    • 2
    • 3
    • 4

    步骤 5 : 消费记录表 record
    消费记录表 record 有如下字段:
    id 主键,每个表都有一个主键 类型是 int
    spend 本次花费,类型是int
    cid 对应的消费分类表的中记录的id, 类型是int
    comment 备注,比如分类是娱乐,但是你希望记录更详细的内容,啪啪啪,那么就存放在这里。
    date 日期,本次记录发生的时间。

    CREATE TABLE record (
      id int,
      spend int,
      cid int,
      comment varchar(255) ,
      date Date
    )   ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    步骤 6 : 添加主键
    每个表都应该有主键,主键约束自带非空和唯一性属性。

    alter table category 表示修改表category
    add constraint 增加约束
    pk_category_id 约束名称 pk 是primary key的缩写,category是表名, id表示约束加在id字段上。约束名称是可以自己定义的,你可以写成abc,但是尽量使用好的命名,使得一眼就能够看出来这个约束是什么意思。 能够降低维护成本。
    primary key 约束类型是主键约束
    (id) 表示约束加在id字段上

    alter table category add constraint pk_category_id primary key (id);
    alter table record add constraint pk_record_id primary key (id);
    alter table config add constraint pk_config_id primary key (id);
    
    • 1
    • 2
    • 3

    步骤 7 : 设置id为自增长
    设置id为自增长是常用的插入数据策略。 换句话说,插入消费分类数据的时候,只用提供分类名称即可,不需要自己提供id, id由数据库自己生成。

    alter table category 表示修改表category
    change id 表示修改字段 id
    id int auto_increment; 修改后的id是 int类型,并且是auto_increment(修改之前仅仅是int类型,没有auto_increment)

    alter table category change id id int auto_increment;
    alter table record change id id int auto_increment;
    alter table config change id id int auto_increment;
    
    • 1
    • 2
    • 3

    步骤 8 : 外键
    外键约束的作用是保持数据的一致性
    比如增加一条消费记录,金额是500,cid是5。
    但是cid=5在分类表category中找不到对应的数据,那么这就破坏了数据的一致性,会带来一系列的后续问题,比如根据分类进行统计,就会找不到这条数据。

    增加外键约束之前首先确定record表的外键是cid,指向了category表的id主键。
    所以增加外键之前,必须把category的id字段设置为主键。从而保证cid=5的数据在category中只能找到一条,而不是找到多条。

    alter table record 修改表record
    add constraint 增加约束
    fk_record_category 约束名称fk_record_category,fk是foreign key的缩写,record_category 表示是从record表指向category表的约束。 与主键一样,约束名称也是可以自己定义的,比如写成abc. 不过依然建议使用可读性好的命名方式。
    foreign key 约束类型,外键
    (cid) references category(id) 本表record的字段 cid 指向category表的字段id

    alter table record add constraint fk_record_category foreign key (cid) references category(id);
    
    • 1

    **注意 : **
    一般情况下,创建表格和增加约束都是一起进行的,不会分开,这里分开可以便于理解。

    create database hutubill;
     
    use  hutubill;
      
    CREATE TABLE config (
      id int AUTO_INCREMENT,
      key_ varchar(255) ,
      value varchar(255) ,
      PRIMARY KEY (id)
    )  DEFAULT CHARSET=utf8;
      
    CREATE TABLE category (
      id int AUTO_INCREMENT,
      name varchar(255) ,
      PRIMARY KEY (id)
    )  DEFAULT CHARSET=utf8;
      
    CREATE TABLE record (
      id int AUTO_INCREMENT,
      spend int,
      cid int,
      comment varchar(255) ,
      date Date,
      PRIMARY KEY (id),
      CONSTRAINT `fk_record_category` FOREIGN KEY (`cid`) REFERENCES `category` (`id`)
    )  DEFAULT CHARSET=utf8;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    数据库表格的设计,也可以采用数据库管理工具的可视化面板直接进行设计

    2. entity类和dao类的实现

    以 category 为例

    package entity;
    
    /**
     * 实体类 Category
     */
    
    public class Category {
        private int id;
        private String name;
    
        private int recordNumber;
    
        public Category() {
        }
    
        public Category(int id, String name) {
            this.id = id;
            this.name = name;
        }
    
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getRecordNumber() {
            return recordNumber;
        }
    
        public void setRecordNumber(int recordNumber) {
            this.recordNumber = recordNumber;
        }
    
        @Override
        public String toString() {
            return name;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    package dao;
    
    import entity.Category;
    import util.DBUtil;
    
    import java.sql.*;
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * DAO类-Category,对category表进行增改删查操作,还做了一些冗余的方法,不再过多注释
     */
    
    public class CategoryDAO {
        public void add(Category category) {
            String sql = "insert into category (`name`) values (?)";
            try (Connection c = DBUtil.getConnection();
                 PreparedStatement ps = c.prepareStatement(sql)) {
                ps.setString(1, category.getName());
                ps.execute();
                ResultSet rs = ps.getGeneratedKeys();
                if (rs.next()) {
                    int id = rs.getInt(1);
                    category.setId(id);
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    
        public int update(Category category) {
            String sql = "update category set name = ? where id = ?";
            int result = 0;
            try (Connection c = DBUtil.getConnection();
                 PreparedStatement ps = c.prepareStatement(sql)) {
                ps.setString(1, category.getName());
                ps.setInt(2, category.getId());
                result = ps.executeUpdate();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            return result;
        }
    
        public int delete(int id) {
            String sql = "delete from category where id = ?";
            int result = 0;
            try (Connection c = DBUtil.getConnection();
                 PreparedStatement ps = c.prepareStatement(sql)) {
                ps.setInt(1, id);
                result = ps.executeUpdate();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            return result;
        }
    
        public Category get(int id) {
            Category category = null;
            String sql = "select * from category where id = ?";
            try (Connection c = DBUtil.getConnection();
                 PreparedStatement ps = c.prepareStatement(sql)) {
                ps.setInt(1, id);
                ResultSet result = ps.executeQuery();
                if (result.next()) {
                    category = new Category(result.getInt("id"),
                            result.getString("name"));
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
            return category;
        }
    
        public Category getByKey(String key) {
            Category category = null;
            String sql = "select * from category where key_ = ?";
            try (Connection c = DBUtil.getConnection();
                 PreparedStatement ps = c.prepareStatement(sql)) {
                ps.setString(1, key);
                ResultSet result = ps.executeQuery();
                if (result.next()) {
                    category = new Category(result.getInt("id"),
                            result.getString("name"));
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
            return category;
        }
    
        public List<Category> list(int start, int count) {
            String sql = "select * from category order by id desc limit ?,?";
            List<Category> categories = new ArrayList<>();
            try (Connection c = DBUtil.getConnection();
                 PreparedStatement ps = c.prepareStatement(sql)) {
                ps.setInt(1, start);
                ps.setInt(2, count);
                ResultSet rs = ps.executeQuery();
                while (rs.next()) {
                    Category category = new Category(rs.getInt("id"),
                            rs.getString("name"));
                    categories.add(category);
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
            return categories;
        }
    
        public List<Category> list() {
            return list(0, Short.MAX_VALUE);
        }
    
        public int getTotal() {
            String sql = "select count(*) from category";
            try (Connection c = DBUtil.getConnection();
                 Statement s = c.createStatement()) {
                ResultSet rs = s.executeQuery(sql);
                if (rs.next()) {
                    return rs.getInt(1);
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
            return 0;
        }
    
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131

    3. service 层的实现

    service 主要是对 entity 类的业务进行封装

    package service;
    
    import dao.CategoryDAO;
    import dao.RecordDAO;
    import entity.Category;
    import entity.Record;
    
    
    import java.util.List;
    
    /**
     * 业务类 CategoryService 对category数据库的业务进行封装
     */
    
    public class CategoryService {
        private CategoryDAO categoryDAO = new CategoryDAO();
        private RecordDAO recordDAO = new RecordDAO();
    
        public List<Category> list() {
            List<Category> cs = categoryDAO.list();
            for (Category c : cs) {
                List<Record> rs = recordDAO.list(c.getId());
                c.setRecordNumber(rs.size());
            }
            //lambda表达式实现comparator接口
            cs.sort((c1, c2) -> c2.getRecordNumber() - c1.getRecordNumber());
            return cs;
        }
    
        public void add(String name) {
            Category c = new Category();
            c.setName(name);
            categoryDAO.add(c);
        }
    
        public void update(int id, String name) {
            Category c = new Category();
            c.setId(id);
            c.setName(name);
            categoryDAO.update(c);
        }
    
        public void delete(int id) {
            categoryDAO.delete(id);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    3. 完整代码

    代码链接:

  • 相关阅读:
    Java线程学习入门(三)
    Java在云原生的破局利器——AOT(JIT与AOT)
    电容笔好还是触屏笔好?便宜又好用的电容笔推荐
    Ansys Speos | 助力汽车按键开关设计与优化
    Java中时间工具类
    Android 10.0 禁止弹出系统simlock的锁卡弹窗功能实现
    STC 51单片机43——看门狗
    Jackson ObjectNode JsonNode --> FastJson JSONObject
    差值结构的加法
    MFC 实现延时,并且进行消息分发,不阻塞
  • 原文地址:https://blog.csdn.net/qq_52354698/article/details/127674290