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. 用户表 userCREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, usernmae VARCHAR(20) NOT NULL UNIQUE, password VARCHAR(20) NOT NULL );这里字段名使用的是usernmae是项目最初建表时的命名后续 Java 代码也保持一致。2. 账务表 cuit_zhangwuCREATE 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.propertiesdriverClassNamecom.mysql.cj.jdbc.Driverurljdbc:mysql://localhost:3306/zhangwu_dbuseSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8usernamerootpassword你的数据库密码 ​initialSize5maxActive10maxIdle5minIdle2maxWait3000核心工具类代码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 中创建QueryRunnerprivate final QueryRunner runner new QueryRunner(JDBCUtils4.getDataSource());之后就可以通过 DbUtils 简化 JDBC 操作不需要手动写Connection、PreparedStatement、ResultSet的关闭代码。五、用户注册和登录流程程序从App.java启动String username new LoginView(scanner).show(); ​ if (username ! null) { new MainView(scanner, username).show(); }整体流程启动程序↓进入登录注册菜单↓注册成功或登录成功↓返回当前用户名↓进入账务主菜单登录使用的 SQLpublic boolean login(String username, String password) throws SQLException { Number count runner.query( SELECT COUNT(*) FROM user WHERE usernmae? AND password?, new ScalarHandlerNumber(), 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 ListZhangWu findAll(String username) throws SQLException { return runner.query( SELECT * FROM cuit_zhangwu WHERE username? ORDER BY zwid, new BeanListHandlerZhangWu(ZhangWu.class), username); }BeanListHandlerZhangWu会自动将数据库查询结果封装为ListZhangWu预期效果请选择3【功能3查询所有账务】共 3 条按ID从小到大排列ID 类别 金额 账户 日期 说明--------------------------------------------------------------------------------------1 吃饭支出 25.5 支付宝 2026-07-11 午饭2 工资收入 5000.0 工商银行 2026-07-11 七月工资3 交通支出 3.0 微信 2026-07-12 乘坐地铁--------------------------------------------------------------------------------------2. 添加账务添加账务时用户不需要输入 IDzwid由数据库自动递增生成。插入数据的关键代码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 层根据开始日期和结束日期是否相同执行不同 SQLif (begin.equals(end)) { return runner.query( SELECT * FROM cuit_zhangwu WHERE username? AND createtime? ORDER BY zwid, new BeanListHandlerZhangWu(ZhangWu.class), username, begin); } ​ return runner.query( SELECT * FROM cuit_zhangwu WHERE username? AND createtime BETWEEN ? AND ? ORDER BY zwid, new BeanListHandlerZhangWu(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 BeanHandlerZhangWu(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输入要编辑的账务ID1当前记录1 吃饭支出 35.0 支付宝 2026-07-11 和朋友吃饭选择编辑方式1 只改某一个字段2 重新填写全部字段请选择1选择要修改的字段1 类别 2 金额 3 账户 4 日期 5 说明请选择字段编号2当前金额35.0新金额40✔ 编辑成功5. 批量删除账务删除功能支持输入一个或多个 ID3或者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输入要删除的ID3,4,5✔ 删除成功共删除 3 条。6. 搜索账务搜索功能可以匹配类别账户说明金额核心 SQLString 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 BeanListHandlerZhangWu(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服务器IP127.0.0.1开始上传...✔ 2026-07-11.txt 上传成功上传成功2026-07-11.txt上传完成共上传 1 个文件2. 上传普通文件上传普通文件后服务端会保存到server-data/用户名/files/例如上传图片D:\workspace\ZhangWuSystem\ggz.jpg预期效果请选择11服务器IP127.0.0.1文件路径D:\workspace\ZhangWuSystem\ggz.jpg文件名ggz.jpg文件大小123456 字节正在上传...✔ 上传成功上传成功ggz.jpg3. 下载文件下载时客户端会先获取服务端文件列表1. 2026-07-11.txt2. 2026-09-01.txt输入序号或文件名直接回车取消下载后的账务文件保存到downloads/用户名/zhangwu/普通文件保存到downloads/用户名/files/预期效果请选择10服务器IP127.0.0.1正在获取服务器文件列表...1. 2026-07-11.txt2. 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 连接 MySQLDBCP 数据库连接池Apache DbUtils三层架构用户登录注册账务增删改查日期范围查询模糊搜索批量删除文件导出Socket 文件上传下载虽然项目是控制台形式但它把 JavaSE 阶段常见的数据库操作、文件操作、网络编程和分层设计串联到了一起。对刚学完 JDBC 的同学来说适合用来理解一个完整 Java 小项目是如何组织和运行的。