当前位置: 首页 > news >正文

Java JDBC 控制台记账系统:从登录到账务管理、文件上传下载

这是我学习 Java 后完成的第一个 JDBC 小项目:一个基于控制台的记账系统。

项目没有使用 Spring、MyBatis 等框架,主要通过JDBC 连接池 + DbUtils + 三层架构操作 MySQL;另外还使用 Socket 实现了账务文件和普通文件的上传、下载。

本文不贴完整源码,重点记录项目的整体流程、关键代码和实际运行效果,方便后续复习。


一、项目功能

系统包含两部分功能:

1. 本地账务管理

  • 用户注册、登录

  • 查询全部账务

  • 按日期查询账务

  • 添加账务

  • 修改账务

  • 删除单条或多条账务

  • 按类别、账户、说明、金额搜索账务

  • 导出账务到本地 txt 文件

2. Socket 文件传输

  • 上传指定日期的账务文件到服务器

  • 从服务器下载账务文件

  • 上传普通文件,例如图片、txt 文件等

  • 下载普通文件


二、项目结构

项目按照常见的三层架构划分:

src

├── app # 程序启动入口

├── view # 控制台菜单、输入和输出

├── service # 业务逻辑层

├── dao # 数据库操作层

├── domain # 实体类

├── utils # JDBC连接池工具类

├── net # Socket客户端工具

├── server # Socket服务端

└── client # 独立Socket客户端

核心调用关系如下:

用户输入

View(展示菜单、读取输入)

Service(处理业务逻辑、校验参数)

DAO(执行 SQL)

MySQL 数据库

例如用户添加一条账务时,执行流程是:

MainView.add()

ZhangWuService.add()

ZhangWuDao.add()

INSERT INTO cuit_zhangwu ...

这样分层的好处是:控制台交互、业务逻辑和 SQL 操作彼此分开,代码更容易阅读和维护。


三、数据库设计

项目使用两张表。

1. 用户表 user

CREATE TABLE `user` ( id INT PRIMARY KEY AUTO_INCREMENT, usernmae VARCHAR(20) NOT NULL UNIQUE, password VARCHAR(20) NOT NULL );

这里字段名使用的是usernmae,是项目最初建表时的命名,后续 Java 代码也保持一致。

2. 账务表 cuit_zhangwu

CREATE TABLE cuit_zhangwu ( zwid INT PRIMARY KEY AUTO_INCREMENT, flname VARCHAR(50) NOT NULL, money DOUBLE NOT NULL, zhanghu VARCHAR(50) NOT NULL, createtime DATE NOT NULL, description VARCHAR(255), username VARCHAR(20) NOT NULL );

账务表中最关键的是username字段。

因为每次查询、修改、删除账务时,都会带上当前登录用户:

WHERE zwid=? AND username=?

这样可以保证用户只能管理自己的账务数据。


四、数据库连接:DBCP 连接池

项目没有每次操作都手动创建数据库连接,而是使用 DBCP 连接池统一管理连接。

jdbc.properties

driverClassName=com.mysql.cj.jdbc.Driver

url=jdbc:mysql://localhost:3306/zhangwu_dbuseSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8

username=root

password=你的数据库密码 ​

initialSize=5

maxActive=10

maxIdle=5

minIdle=2

maxWait=3000

核心工具类代码:

static { try { Properties properties = new Properties(); Reader reader = new FileReader("jdbc.properties"); properties.load(reader); reader.close(); ​ dataSource = BasicDataSourceFactory.createDataSource(properties); } catch (Exception e) { e.printStackTrace(); } } ​ public static DataSource getDataSource() { return dataSource; }

这里的逻辑很简单:

读取 jdbc.properties

根据配置创建 DataSource 连接池

DAO 层通过 getDataSource() 获取连接池

DAO 中创建QueryRunner

private final QueryRunner runner = new QueryRunner(JDBCUtils4.getDataSource());

之后就可以通过 DbUtils 简化 JDBC 操作,不需要手动写ConnectionPreparedStatementResultSet的关闭代码。


五、用户注册和登录流程

程序从App.java启动:

String username = new LoginView(scanner).show(); ​ if (username != null) { new MainView(scanner, username).show(); }

整体流程:

启动程序

进入登录注册菜单

注册成功或登录成功

返回当前用户名

进入账务主菜单

登录使用的 SQL:

public boolean login(String username, String password) throws SQLException { Number count = runner.query( "SELECT COUNT(*) FROM user WHERE usernmae=? AND password=?", new ScalarHandler<Number>(), username, password); ​ return count.longValue() == 1; }

这里使用?占位符传入用户名和密码,而不是直接拼接 SQL 字符串。

注册时会进行基本校验:

if (username == null || username.trim().isEmpty() || password == null || password.isEmpty()) { return "用户名和密码不能为空"; } ​ if (!username.matches("[A-Za-z0-9_\\-\\u4e00-\\u9fa5]+")) { return "用户名只能包含中文、字母、数字、下划线或短横线"; }

预期运行效果:

==== 记账系统 ====
1 登录 2 注册 0 退出
请选择:2

新用户名:qiweikai
新密码:123456
注册成功

==== 记账系统 ====
1 登录 2 注册 0 退出
请选择:1

用户名:qiweikai
密码:123456
登录成功


六、账务管理的核心流程

登录成功后进入主菜单:

==== 当前用户:qiweikai ====
3 查询所有账务
4 按日期查询(支持单天或范围)
5 添加账务(ID自动分配)
6 编辑账务(可改一个字段或全部)
7 删除账务(支持删一条或多条)
8 搜索账务(输入关键词或金额)
9 上传账务到服务器
10 从服务器下载账务
11 上传任意文件到服务器
12 从服务器下载文件
13 导出账务到本地文件(不需要服务器)
0 退出
请选择:


1. 查询全部账务

查询时只查询当前登录用户的数据:

public List<ZhangWu> findAll(String username) throws SQLException { return runner.query( "SELECT * FROM cuit_zhangwu WHERE username=? ORDER BY zwid", new BeanListHandler<ZhangWu>(ZhangWu.class), username); }

BeanListHandler<ZhangWu>会自动将数据库查询结果封装为:

List<ZhangWu>

预期效果:

请选择:3

【功能3:查询所有账务】
共 3 条,按ID从小到大排列:

ID 类别 金额 账户 日期 说明
--------------------------------------------------------------------------------------
1 吃饭支出 25.5 支付宝 2026-07-11 午饭
2 工资收入 5000.0 工商银行 2026-07-11 七月工资
3 交通支出 3.0 微信 2026-07-12 乘坐地铁
--------------------------------------------------------------------------------------


2. 添加账务

添加账务时,用户不需要输入 ID,zwid由数据库自动递增生成。

插入数据的关键代码:

public boolean add(ZhangWu z) throws SQLException { return runner.update( "INSERT INTO cuit_zhangwu" + "(flname,money,zhanghu,createtime,description,username)" + " VALUES(?,?,?,?,?,?)", z.getFlname(), z.getMoney(), z.getZhanghu(), z.getCreatetime(), z.getDescription(), z.getUsername()) == 1; }

添加时会校验金额和日期:

double value = Double.parseDouble(text); if (value <= 0) { throw new IllegalArgumentException("金额必须是大于0的数字"); }

日期格式必须是:

yyyy-MM-dd

预期效果:

请选择:5

【功能5:添加账务】
类别:吃饭支出
金额:35
账户:支付宝
日期:2026-07-11
说明:和朋友吃饭

✔ 添加成功!


3. 按日期查询

系统支持两种日期输入方式:

2026-07-11

表示查询某一天。

2026-07-11,2026-07-20

表示查询一个日期范围。

DAO 层根据开始日期和结束日期是否相同,执行不同 SQL:

if (begin.equals(end)) { return runner.query( "SELECT * FROM cuit_zhangwu " + "WHERE username=? AND createtime=? ORDER BY zwid", new BeanListHandler<ZhangWu>(ZhangWu.class), username, begin); } ​ return runner.query( "SELECT * FROM cuit_zhangwu " + "WHERE username=? AND createtime BETWEEN ? AND ? ORDER BY zwid", new BeanListHandler<ZhangWu>(ZhangWu.class), username, begin, end);

预期效果:

请选择:4

请输入日期:2026-07-11,2026-07-12

共 2 条记录:

ID 类别 金额 账户 日期 说明
--------------------------------------------------------------------------------------
1 吃饭支出 35.0 支付宝 2026-07-11 和朋友吃饭
3 交通支出 3.0 微信 2026-07-12 乘坐地铁

--------------------------------------------------------------------------------------


4. 编辑账务

编辑前,系统会先查询该账务是否属于当前用户:

public ZhangWu findOwned(int id, String username) throws SQLException { return runner.query( "SELECT * FROM cuit_zhangwu WHERE zwid=? AND username=?", new BeanHandler<ZhangWu>(ZhangWu.class), id, username); }

编辑支持两种方式:

1. 修改某一个字段

2. 重新填写全部字段

修改单个字段时,字段名使用白名单校验:

if (!("flname".equals(field) || "money".equals(field) || "zhanghu".equals(field) || "createtime".equals(field) || "description".equals(field))) { throw new IllegalArgumentException("不允许修改该字段"); }

这样避免将任意字段名直接拼接到 SQL 中。

预期效果:

请选择:6

输入要编辑的账务ID:1

当前记录:
1 吃饭支出 35.0 支付宝 2026-07-11 和朋友吃饭

选择编辑方式:
1 只改某一个字段
2 重新填写全部字段
请选择:1

选择要修改的字段:
1 类别 2 金额 3 账户 4 日期 5 说明
请选择字段编号:2

当前金额:35.0
新金额:40

✔ 编辑成功!


5. 批量删除账务

删除功能支持输入一个或多个 ID:

3

或者:

3,4,5

关键 SQL 是动态拼接IN条件:

StringBuilder sql = new StringBuilder( "DELETE FROM cuit_zhangwu WHERE username=? AND zwid IN ("); ​ for (int i = 0; i < ids.size(); i++) { if (i > 0) sql.append(','); sql.append('?'); } ​ sql.append(')');

最终效果类似:

DELETE FROM cuit_zhangwu WHERE username=? AND zwid IN (?, ?, ?)

预期效果:

请选择:7

输入要删除的ID:3,4,5

✔ 删除成功,共删除 3 条。


6. 搜索账务

搜索功能可以匹配:

  • 类别

  • 账户

  • 说明

  • 金额

核心 SQL:

String like = "%" + text + "%"; ​ return runner.query( "SELECT * FROM cuit_zhangwu WHERE username=? AND " + "(flname LIKE ? OR zhanghu LIKE ? OR description LIKE ? " + "OR CAST(money AS CHAR) LIKE ?) ORDER BY zwid", new BeanListHandler<ZhangWu>(ZhangWu.class), username, like, like, like, like);

预期效果:

请选择:8

输入搜索内容:吃饭

共 1 条匹配记录:

ID 类别 金额 账户 日期 说明
--------------------------------------------------------------------------------------
1 吃饭支出 40.0 支付宝 2026-07-11 和朋友吃饭

--------------------------------------------------------------------------------------


七、账务导出流程

功能13可以把账务导出为本地 txt 文件,不需要连接服务器。

导出逻辑:

选择导出全部账务或指定日期范围

从数据库查询账务

将账务格式化为文本

写入 exports 文件夹

导出文件的核心代码:

byte[] content = format(rows).getBytes(StandardCharsets.UTF_8); ​ FileOutputStream fos = new FileOutputStream(outFile); try { fos.write(content); } finally { fos.close(); }

预期效果:

请选择:13

选择导出范围:
1 导出全部账务
2 按日期范围导出
请选择:1

✔ 导出成功!
文件路径:D:\workspace\ZhangWuSystem\exports\qiweikai_全部账务_2026-07-11.txt
共导出 3 条记录

导出的 txt 内容:

ID 类别 金额 账户 日期 说明
1 吃饭支出 40.0 支付宝 2026-07-11 和朋友吃饭
2 工资收入 5000.0 工商银行 2026-07-11 七月工资


八、Socket 文件上传下载流程

除了 JDBC 功能外,项目还实现了一个简单的 Socket 文件服务器。

服务端启动类:

server.ServerApp

启动后监听12000端口:

服务器监听:0.0.0.0:12000
数据目录:D:\workspace\ZhangWuSystem\server-data

客户端和服务端的通信格式大致如下:

客户端连接服务器

发送 command 命令

发送 username 用户名

发送 filename 文件名

发送 length 文件长度

发送文件字节内容

服务端保存文件并返回结果

项目中定义的命令:

命令功能
9上传账务文件
10下载账务文件
11上传普通文件
12下载普通文件
13账务自动同步

1. 上传账务文件

上传账务时,系统会根据日期将账务生成 txt 文件。

例如上传2026-07-11的账务:

2026-07-11.txt

服务端保存位置:

server-data/qiweikai/zhangwu/2026-07-11.txt

客户端发送数据的关键代码:

out.writeInt(command); out.writeUTF(username); out.writeUTF(new File(filename).getName()); out.writeLong(bytes.length); out.write(bytes); out.flush();

预期效果:

请选择:9

请输入日期:2026-07-11
服务器IP:127.0.0.1

开始上传...
✔ 2026-07-11.txt 上传成功:上传成功:2026-07-11.txt

上传完成,共上传 1 个文件


2. 上传普通文件

上传普通文件后,服务端会保存到:

server-data/用户名/files/

例如上传图片:

D:\workspace\ZhangWuSystem\ggz.jpg

预期效果:

请选择:11

服务器IP:127.0.0.1
文件路径:D:\workspace\ZhangWuSystem\ggz.jpg

文件名:ggz.jpg
文件大小:123456 字节
正在上传...

✔ 上传成功:上传成功:ggz.jpg


3. 下载文件

下载时,客户端会先获取服务端文件列表:

1. 2026-07-11.txt
2. 2026-09-01.txt
输入序号或文件名(直接回车取消):

下载后的账务文件保存到:

downloads/用户名/zhangwu/

普通文件保存到:

downloads/用户名/files/

预期效果:

请选择:10

服务器IP:127.0.0.1

正在获取服务器文件列表...
1. 2026-07-11.txt
2. 2026-09-01.txt

输入序号或文件名:1

✔ 下载成功,文件保存到:
D:\workspace\ZhangWuSystem\downloads\qiweikai\zhangwu\2026-07-11.txt


九、项目完整运行流程

最后用一张流程图总结整个项目:

启动 App

注册 / 登录

获取当前登录用户名

进入账务主菜单

选择账务操作
├── 查询:DAO 执行 SELECT
├── 添加:DAO 执行 INSERT
├── 修改:DAO 执行 UPDATE
├── 删除:DAO 执行 DELETE
├── 搜索:DAO 执行 LIKE 查询
└── 导出:查询账务后写入本地 txt

如果选择上传或下载文件

SocketClient 连接 ServerApp

通过 12000 端口传输文件

文件保存到 server-data 或 downloads 目录


十、总结

这个项目是一个以 JDBC 为核心的控制台记账系统,完成了从用户登录到账务 CRUD 的完整流程。

项目中重点练习了:

JDBC 连接 MySQL
DBCP 数据库连接池
Apache DbUtils
三层架构
用户登录注册
账务增删改查
日期范围查询
模糊搜索
批量删除
文件导出
Socket 文件上传下载

虽然项目是控制台形式,但它把 JavaSE 阶段常见的数据库操作、文件操作、网络编程和分层设计串联到了一起。对刚学完 JDBC 的同学来说,适合用来理解一个完整 Java 小项目是如何组织和运行的。

http://www.jsqmd.com/news/1339897/

相关文章:

  • 免费鼠标连点器和光标美化工具MouseClick:从入门到精通的完整指南
  • 抖音下载终极指南:免费批量下载无水印视频的完整解决方案
  • 从零自制纯净WinPE启动盘:老毛桃核心构建与U盘部署实战
  • 暗黑破坏神2存档编辑器终极解决方案:可视化编辑深度剖析
  • CRUD矩阵分析:从数据实体到系统设计的结构化思维与实践指南
  • Project Graph终极指南:快速构建思维网络的免费可视化工具
  • Unity异步编程:Thread.Sleep为何导致构建死锁及Task.Delay解决方案
  • SAP-ABAP:ABAP调试常见误区与踩坑点汇总——10类典型调试错误与规避方案
  • 免费商标查询网站怎么选?用权大师App查询文字、图形和近似商标
  • 快稳铷原子钟技术解析与应用,模块原子钟,铷原子钟
  • 掌握高效工作神器:Linux桌面便签工具实战指南
  • 北京创业扶持机构哪家适合小微企业:【博亚信诚】业界认可 - 18102756859
  • 低空飞行器质量监管收紧,维修行业规范加快构建
  • 《深入理解 AI Agent》之学习笔记-DAY 4(提示工程 + Agent Skills + 状态栏 + 上下文压缩)
  • Sticky便签:让Linux桌面效率翻倍的3个实用技巧
  • 【国家级政务AI治理白皮书首发解读】:3大红线、4级风险评估模型与2024年强制审计新规倒计时
  • 太阳能BLE信标设计:实现免维护物联网节点的低功耗方案
  • 3个技巧让Mac窗口置顶工具Topit提升你的工作效率300%
  • 【计算机毕业设计】基于 flask 的校园失物招领系统设计与实现小程序
  • G-Helper完全指南:5步掌控你的华硕笔记本性能调校
  • Wand-Enhancer终极指南:5分钟解锁WeMod专业版所有功能
  • MOS晶体管漏电流的六大成因与低功耗设计实战解析
  • URL编码 常见编码文本 图片隐写加解密
  • 基于荣培数据平台构建上级下发与下级上报模板:从表单搭建到数据上报全流程
  • WandEnhancer完整指南:本地客户端配置增强工具的技术解析与使用教程
  • 什么样的GEO公司算正规?五道门槛帮你把风险挡在签约之前 - 品牌前沿专家
  • 竞价推广托管公司核心服务标准与企业选型能力要求解析:丽鸿科技行业实践洞察
  • 国家中小学智慧教育平台电子课本下载工具:3分钟极速获取PDF教材
  • Unity资源逆向分析利器UABEA:从原理到实战的完整指南
  • Poppins字体终极指南:免费开源几何字体如何轻松驾驭多语言设计