
消费一览
统计本月的消费总数,今日消费,日均消费,本月剩余,日均可用,距离月末有多少天。
同时使用一个环形进度条,这个环形进度条不是JDK自带的,需要自己设计,并且随着消费用度,颜色从绿色渐变为红色。

记一笔
记录本日的消费额度, 分类下拉框从 消费分类数据中读取,并且把经常消费的分类放在前面。
日期默认选中今天,也可以手动指定日期。

消费分类管理
对消费进行经典的CRUD 增删改查管理,同时显示一个分类下的消费次数。
这里涉及到多表关系:
消费记录和消费分类是多对一关系

月度消费报表
使用第三方chart类生成柱状报表,显示本月的消费趋势

设置预算和数据库路径
在消费一览中需要显示本月可用多少金额,都是建立在预算的基础上的。
在设置页面,设置本月的预算金额。
后续的还原和备份,都需要用到数据库的命令mysql和mysqldump,需要在这里配置mysql的安装目录

备份数据
把数据库中的所有数据,备份到.sql文件中

恢复数据
根据.sql文件还原数据库

在开始整个软件开发之前,一定是事先进行表结构设计。
把表有哪些字段搞清楚,表与表之间的关系理顺。
同时还要校验这样的表结构,是否能够支撑功能上的需要。
比如在消费一览中需要的各种数据,应该以什么样的方式去设计这些表,才能够支撑页面上的数据显示。
在专门的表结构设计章节里,会把每张表列出来,每个字段的意义,类型,限制。 表与表之间的关系,一对多关系,多对一关系如何确定与设计。主键约束,外键约束等等信息。
步骤 1 : 首先创建数据库
数据库名定为hutubill,在程序中的JDBC相关代码,都需要连接这个数据库名 hutubill
create database hutubill;
use hutubill;
步骤 2 : 确定需要哪些表
根据业务上的需要,一共要3个表
步骤 3 : 配置信息表 config
配置信息表 config有如下字段
id 主键,每个表都有一个主键 类型是 int
key_ 配置信息按照键值对的形式出现 ,类型是varchar(255)
value 配置信息的值, 类型是 varchar(255)
键值对
进一步解释一下键值对,比如要存放每个月的预算,则需要在config表中增加一条记录,key=“budget” value=“500”,就表示预算是500.
varchar(255) 表示变长字符,如果实际存放只有30个字符,那么在数据库中只占用30的空间,最多占用255
key 是关键字,不适合用于作为字段名,所以在key后面加了一个下划线 key_ 就不会有任何问题了,识别性一样很好,一眼就知道这个字段是干什么用的
ENGINE=InnoDB MySQL 有多种存储引擎,MyISAM和InnoDB是其中常用的两种, 他们之间的区别很多,如果要展开讲,就需要专门的章节了。 这里使用ENGINE=InnoDB 是因为后续要使用的外键约束只有在InnoDB中才生效。
DEFAULT CHARSET=utf8; 表示该表按照UTF-8的编码存放中文
CREATE TABLE config (
id int ,
key_ varchar(255) ,
value varchar(255)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
步骤 4 : 消费分类表 category
消费分类表 category 有如下字段
id 主键,每个表都有一个主键 类型是 int
name 分类的名称,类型是varchar(255)
CREATE TABLE category (
id int,
name varchar(255)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
步骤 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;
步骤 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);
步骤 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;
步骤 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);
**注意 : **
一般情况下,创建表格和增加约束都是一起进行的,不会分开,这里分开可以便于理解。
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;
数据库表格的设计,也可以采用数据库管理工具的可视化面板直接进行设计
以 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;
}
}
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;
}
}
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);
}
}
代码链接: