尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Spring Boot记账系统实战:从数据库设计到统计SQL实现
1. 选题思路与项目定位1.1 为什么课程设计/毕业设计选记账系统每年到了课程设计或者毕业设计选题的季节总有不少同学来问我到底该做什么题目。我的建议一直很明确如果你没有特别擅长的方向又不想在答辩时翻车那就老老实实做一个个人记账系统。原因有三点都是我在实际带项目和评审答辩中总结出来的。第一需求足够明确。记账系统本质上就是记录收入、记录支出、统计汇总这三个动作业务逻辑非常清楚不需要像电商系统那样去权衡库存、订单、支付等一堆复杂状态也不太容易出现需求理解偏差。你把这个核心链路做扎实功能和深度都有了。第二技术栈覆盖得恰到好处。一个记账系统要跑起来必然涉及前端页面、后端接口、数据库存储、用户登录认证、增删改查、数据统计聚合这些环节。用Spring Boot做后端刚好能把Java Web开发的主干知识全部串起来既不会像写一个Hello World那样显得单薄也不会像做一个分布式系统那样失控。第三扩展空间非常大方便你在答辩时展示亮点。记账系统做完基础版本之后你可以很自然地往上加预算管理、账单提醒、月度统计图表、数据导出、多人家庭共享账本等模块。答辩时老师问你这个项目还有什么可改进的地方你可以直接掏出三四个扩展方向而且每一个都说得通。我见过不少选题翻车的案例有人选基于深度学习的某某识别结果连训练数据都凑不齐有人选智能推荐系统最后发现数据量和算法都撑不起深度。这类题目不是不能做而是对普通本科生来说风险太高一旦卡在数据获取或模型效果上整个项目就停滞了。记账系统没有这个问题你用Spring Boot MySQL Vue或者Thymeleaf就能完整落地数据自己造就行所见即所得。1.2 技术方案选型背后的思考这个项目的标题里已经点明了基于Spring Boot所以后端框架没什么悬念。但实际开发中具体怎么搭还是有几个关键选型问题需要认真想想这也是我在指导同学时反复强调的部分。第一个问题是持久层框架的选择。现在主流有两个方向Spring Data JPA和MyBatis-Plus。JPA的特点是实体映射自动建表写简单查询非常快适合追求极简的场景但一旦遇到多表关联、复杂统计SQLJPA的写法反而绕。MyBatis-Plus在MyBatis基础上做了增强保留XML/SQL灵活性的同时提供了BaseMapper的通用CRUD方法大多数操作你连SQL都不用写一句话就搞定。对这个项目来说我建议优先考虑MyBatis-Plus因为记账系统的核心价值有一半在统计报表上这些统计查询用原生SQL写起来更直观、更好控制而且MyBatis-Plus的文档和网上的样例都非常多遇到问题搜起来不费劲。第二个问题是前端方案的选择。如果你本身没有系统学过前端框架用Thymeleaf模板引擎配上Bootstrap和jQuery是最稳的——所有页面由Spring Boot统一渲染部署的时候打成一个Jar包就完事不需要单独处理前端工程。如果你已经会Vue那可以把前后端分离后端跑在8080端口前端用Vite起个开发服务器通过Axios调接口项目结构上更接近真实企业项目。两者没有绝对对错取决于你答辩时要强调的重点。我做指导时一般会反问同学你更怕前端样式调不明白还是更怕后端接口写不清楚怕前端的就走模板引擎怕后端的就走前后端分离——前端的组件化确实会引入不少额外工作量。第三个问题是数据库选择。MySQL是这个场景下的默认答案没有之一。PostgreSQL当然也很好但国内的环境MySQL资料更丰富Navicat、DataGrip这些工具支持也最成熟遇到字符集、时区之类的问题一搜一个准。版本建议5.7或者8.0学校机房和云服务器上基本都是这两个版本。还有一个很容易被忽略的选型点是认证授权方案。很多课程设计项目喜欢引入Spring Security JWT这本身没错但如果你对Spring Security的过滤链机制不熟配置起来可能会花掉比业务代码还多的时间。记账系统作为单机部署的Web应用完全可以先用会话Session配合拦截器实现登录控制代码量小、逻辑直观、好讲也好理解。等基础功能全部跑通了再考虑要不要把认证升级成JWT这样风险可控答辩时还能说我做了技术演进。2. 功能模块拆解与需求梳理2.1 核心业务功能清单记账系统不管做成什么样有几块功能是绕不开的。我在做需求梳理的时候习惯先把必须有的和能加分的分开列这样做项目计划和写文档都会清晰很多。必须有的核心功能我列了六大块用户注册与登录。包括注册、登录、退出登录、密码加密存储。这个模块解决的是账本归属问题每个用户只能看到和操作自己的数据。账单管理。这是记账系统的心脏。用户要能新增一笔收入或支出填写金额、分类、账户、日期、备注还要能对已有账单进行编辑和删除列表页要支持按时间范围、类型、分类进行筛选。分类管理。支出和收入都要有分类比如餐饮、交通、购物、工资、理财等。系统提前内置一批默认分类用户也可以自定义新增、修改、删除自己的分类。账户管理。这里的账户指的是钱放在哪里比如现金、银行卡、微信钱包、支付宝。每笔账单要关联到具体账户账户余额可以手动调整。统计报表。按日、按月、按年汇总收入和支出总额按分类统计支出占比。这一块是记账系统最体现设计水平的地方也是答辩时最好讲的部分。预算管理。用户可以给某个分类或总支出设一个月度预算系统对比当前已消费金额和预算金额给出提示。加分功能可以视自身时间决定账单数据导出Excel、记账提醒、多人共享账本、数据可视化图表ECharts、回收站、个人资料修改。我通常建议同学把上面六大必备功能做完做完再考虑加分项不要一上来就铺特别大的摊子。2.2 用户体系与数据隔离设计记账系统里有一个最常见的权限问题不同用户的数据必须完全隔离。也就是说A用户添加的账单B用户在登录状态下绝对不能看到也不能通过改URL参数的方式越权访问。这一点很多同学做的时候会忽略等到答辩时被老师一问就露馅。实现的思路其实很传统所有账单表都带一个user_id字段查询时永远带上WHERE user_id 当前登录用户ID这个条件。删除和更新操作也不能只按账单ID去操作必须先判断这条账单归属是不是当前用户否则就可能出现“知道ID就能删别人账”的漏洞。具体在代码层面的做法是在Service层统一处理用户身份的判断而不是让Controller层写死过滤条件。我习惯用一个工具类从Session或者ThreadLocal中取出当前登录用户ID然后传给Service做数据过滤这样Controller层不需要每个方法都重复写一遍获取用户的逻辑代码会干净很多。分类表和账户表也是同样的逻辑建表的时候必须有user_id字段。内置分类在插入时把user_id写成0作为公共数据查询时用user_id 0 OR user_id 当前用户ID来合并这样既能给新用户预置好分类又允许用户自己加分类。3. 数据库设计实战3.1 核心数据表结构拆解数据库设计做得好不好直接决定你后面写业务代码是顺畅还是痛苦。我见过不少同学一开始图省事只建一张账单表类别、账户全用字符串存结果一到做统计的时候就傻眼了——分组不知道按什么分金额类型不对数据一堆脏值。实际上账务系统一般拆成五张核心表再加一张预算表就够了。我把建表SQL整理成了一个可以直接用的版本你在Navicat里执行就能把库表结构拉起来。注意金额字段一定要用decimal不要用double或float这是金融数据的基本常识后面我会专门解释原因。-- 用户表 CREATE TABLE t_user ( id bigint(20) NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL COMMENT 用户名, password varchar(100) NOT NULL COMMENT 密码BCrypt加密, nickname varchar(50) DEFAULT NULL COMMENT 昵称, email varchar(100) DEFAULT NULL COMMENT 邮箱, create_time datetime DEFAULT NULL, update_time datetime DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表; -- 账户表 CREATE TABLE t_account ( id bigint(20) NOT NULL AUTO_INCREMENT, user_id bigint(20) NOT NULL COMMENT 所属用户, account_name varchar(50) NOT NULL COMMENT 账户名称如银行卡、微信, account_type varchar(20) DEFAULT NULL COMMENT 账户类型cash/bank/wechat/alipay, balance decimal(12,2) DEFAULT 0.00 COMMENT 当前余额, create_time datetime DEFAULT NULL, update_time datetime DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT账户表; -- 分类表 CREATE TABLE t_category ( id bigint(20) NOT NULL AUTO_INCREMENT, user_id bigint(20) NOT NULL COMMENT 用户ID0表示内置公共分类, category_name varchar(50) NOT NULL COMMENT 分类名称, type tinyint(4) NOT NULL COMMENT 1支出 2收入, icon varchar(255) DEFAULT NULL COMMENT 分类图标, create_time datetime DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT消费分类表; -- 账单表 CREATE TABLE t_bill ( id bigint(20) NOT NULL AUTO_INCREMENT, user_id bigint(20) NOT NULL, category_id bigint(20) NOT NULL, account_id bigint(20) NOT NULL COMMENT 支付账户, amount decimal(12,2) NOT NULL COMMENT 金额, type tinyint(4) NOT NULL COMMENT 1支出 2收入, remark varchar(255) DEFAULT NULL COMMENT 备注, bill_date date NOT NULL COMMENT 账单日期, create_time datetime DEFAULT NULL, update_time datetime DEFAULT NULL, PRIMARY KEY (id), KEY idx_user_billdate (user_id,bill_date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT账单明细表; -- 预算表 CREATE TABLE t_budget ( id bigint(20) NOT NULL AUTO_INCREMENT, user_id bigint(20) NOT NULL, category_id bigint(20) DEFAULT NULL COMMENT 为空表示总预算, budget_month varchar(7) NOT NULL COMMENT 预算月份格式2026-01, amount decimal(12,2) NOT NULL, create_time datetime DEFAULT NULL, update_time datetime DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT预算表;这套表结构有几个细节值得说。账单表里我把bill_date设计成了date类型而不是datetime类型因为记账只需要关心哪一天不关心具体时分秒用date还能让按天汇总的SQL更简单不需要做日期截断处理。另外我给user_id和bill_date加了联合索引因为最常用的统计是某个用户某段时间的账单这组查询条件有索引撑腰数据量大了以后性能也不会太难看。预算表里category_id允许为空表示用户设置的是一个总预算而不是某个分类的预算。这种设计通过一个可空字段实现了两种业务语义比单独拆两张预算表要简洁。查询预算执行情况的时候如果category_id为空就匹配所有账单否则只匹配该分类下的账单SQL写起来稍微绕一点但逻辑清晰。3.2 为什么金额必须用Decimal这里必须单独强调一遍金额字段一律使用decimal(12,2)千万不要用double或者float。很多开发新手第一次做项目都会用double存金额因为写起来省事但只要你随便写一段金额累加测试就会发现问题。打个比方你买一杯奶茶花了19.9元买了三次用double累加的结果可能会得到59.699999999999996而不是59.7。这在显示层面就很尴尬更不用说在统计报表里各种金额对不上的情况。原因在于浮点数在计算机内部是二进制存储十进制小数的连续加法会产生舍入误差。而decimal在MySQL底层是字符串形式的定点数存储计算时不会丢失精度。在Java实体类里对应的类型也要配套使用BigDecimal。Controller接收参数时用BigDecimal接收数据库查询结果映射时也用BigDecimal不要为了图方便转成String或者Double。前端传金额的时候后端要做一次校验必须是非负数且最多两位小数防止用户乱填数据把报表搞脏。另外还有一个小细节账单表里我同时存了amount和type两个字段似乎在category表已经有type的情况下多此一举。但实际操作中你会发现如果不冗余这个类型查询这个月一共花了多少钱的时候就不得不先把分类表join进来再从分类里判断是收入还是支出。直接在账单上冗余一个type字段让统计SQL少一次join性能更好逻辑上也更直接。这就是一种典型的空间换时间的设计。4. 核心功能实现与关键代码解析4.1 Spring Boot项目搭建与工程结构工程搭建这部分直接说结论。前往Spring Initializr生成一个Spring Boot项目Java版本选8或11Spring Boot版本建议2.7.x选2.x而不是3.x的原因是3.x要求Java 17很多学校机器的环境不一定支持而且网上关于2.x的资料量明显更大遇到问题更好查。依赖方面最基础的是Spring Web、MySQL Driver持久层引入MyBatis-Plus注意MyBatis-Plus的starter版本要与Spring Boot 2.x匹配工具层面引入Lombok。工程包结构我推荐这样分这也是贴近主流企业项目的包划分方式com.example.finance ├── FinanceApplication.java ├── common │ ├── Result.java // 统一返回结果封装 │ └── GlobalException.java // 全局异常处理 ├── config │ ├── WebMvcConfig.java // 拦截器注册、CORS配置 │ └── MybatisPlusConfig.java // 分页插件配置 ├── controller │ ├── UserController.java │ ├── BillController.java │ ├── CategoryController.java │ ├── AccountController.java │ └── StatsController.java ├── entity │ ├── User.java │ ├── Bill.java │ ├── Category.java │ ├── Account.java │ └── Budget.java ├── mapper │ ├── UserMapper.java │ ├── BillMapper.java │ └── ... ├── service │ ├── BillService.java │ ├── BillServiceImpl.java │ └── ... └── util └── UserContext.java // 获取当前登录用户这个结构没有搞太复杂的分层但已经足够演示控制层-服务层-数据访问层的经典三段式。common包里放统一返回结果和全局异常处理既能让接口返回格式统一为{code, msg, data}也方便前端统一处理错误提示。config包里放拦截器配置和CORS配置属于基础设施打包的时候会讲。在application.yml配置上有几点经验可以分享。数据库连接串里必须带上serverTimezoneAsia/Shanghai不然MySQL 8.x默认的UTC时区会让你的时间和本地时间差8小时。要开启MyBatis-Plus的下划线转驼峰映射一般默认就是开启的。逻辑删除如果要用就配置global-config里的logic-delete-field这不是必须项但如果你设计里用了is_deleted字段一定要记得配否则删除操作会变成物理删除。4.2 登录认证与密码安全实现登录认证这块我在方案选型里说过用Session 拦截器这里给出一个可落地的设计思路。密码存储必须用BCrypt加密。注册的时候对明文密码做加密登录的时候用加密后的密文和用户输入的明文去做匹配。为什么不能自己写个MD5加盐就算完事因为MD5的哈希速度太快了即使加了固定的盐攻击者用彩虹表或者暴力破解工具跑起来成本仍然很低。BCrypt算法内部会不断迭代计算单次哈希成本很高显著增加了暴力破解的代价。Spring Security的crypto包里就带了BCrypt的实现不需要把整个Spring Security引入进来只需要引入spring-security-crypto这个轻量依赖就行非常方便。注册逻辑的代码示意如下public Result register(User user) { // 1. 校验用户名是否重复 Long count userMapper.selectCount( new LambdaQueryWrapperUser() .eq(User::getUsername, user.getUsername())); if (count 0) { return Result.error(用户名已存在); } // 2. 密码加密 BCryptPasswordEncoder encoder new BCryptPasswordEncoder(); user.setPassword(encoder.encode(user.getPassword())); // 3. 保存 userMapper.insert(user); return Result.success(); }登录之后把当前用户信息存到Session中同时定义一个HandlerInterceptor在进入业务Controller之前判断Session里有没有用户。拦截器的写法上有一个细节很容易踩坑拦截器里放行路径和静态资源路径必须配置好否则登录页面和前端引用的CSS、JS全都会被拦下来页面样式错乱。我一般会把以下路径设为放行/user/login、/user/register以及static目录下的所有资源。同样的道理如果是前后端分离还需要在拦截器里给预检请求OPTIONS直接放行不然前端跨域访问时会被拦截器挡住。public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session request.getSession(); if (session.getAttribute(userId) ! null) { return true; } // 接口请求返回JSON提示页面请求直接重定向 response.setContentType(application/json;charsetUTF-8); response.getWriter().write({\code\:401,\msg\:\未登录\}); return false; } }UserContext工具类的思路是在拦截器验证登录成功后把当前用户ID塞到一个ThreadLocal里后续在Service层任何地方都能拿到当前用户ID。这样就不需要每个Controller方法都从Session里取用户再往下传业务代码会干净很多。拦截器用完记得在afterCompletion里清掉ThreadLocal否则在高并发下会有数据串台的问题。对课程设计来说虽然并发量不大但养成这个习惯总归是好的。4.3 账单核心操作与统计SQL实战账单的增删改查是标准的CRUD用MyBatis-Plus的BaseMapper自带方法就能搞定大部分操作。我重点说说两个实际开发中容易弄错的地方。第一个是分页查询带条件筛选。账单列表页一般需要根据时间范围、类型、分类ID组合筛选这时候需要自己构造LambdaQueryWrapper。日期筛选有个坑前端传来的日期范围可能是2026-01-01到2026-01-31但账单的bill_date字段是date类型直接用between查询是没问题的。如果MyBatis-Plus版本低或者你用字符串类型存日期就会遇到边界和格式的各种问题。所以这里再次印证了建表时选对字段类型有多重要。第二个是统计SQL。月度收支汇总、分类占比这两块是项目的门面写得好不好直接决定答辩效果。我用MyBatis的注解方式写了一个很常用的统计查询Mapper public interface StatsMapper { // 按月统计收支返回月份、类型、总金额 Select(SELECT DATE_FORMAT(bill_date, %Y-%m) AS month, type, SUM(amount) AS total FROM t_bill WHERE user_id #{userId} AND bill_date #{startDate} AND bill_date #{endDate} GROUP BY DATE_FORMAT(bill_date, %Y-%m), type ORDER BY month DESC) ListMapString, Object summaryByMonth(Param(userId) Long userId, Param(startDate) String startDate, Param(endDate) String endDate); // 按分类统计支出占比 Select(SELECT c.category_name AS categoryName, SUM(b.amount) AS total FROM t_bill b LEFT JOIN t_category c ON b.category_id c.id WHERE b.user_id #{userId} AND b.type 1 AND b.bill_date #{startDate} AND b.bill_date #{endDate} GROUP BY b.category_id, c.category_name ORDER BY total DESC) ListMapString, Object expenseByCategory(Param(userId) Long userId, Param(startDate) String startDate, Param(endDate) String endDate); }这两个SQL看起来简单但把记账系统核心统计的逻辑全包含了第一个用了DATE_FORMAT做月份聚合配合type字段区分收入支出第二个用LEFT JOIN关联分类表同时按分类名分组算总支出直接为饼图提供数据源。还有一点值得提醒用Map接收统计结果虽然简单但是返回给前端的数据结构不清晰。我一般会再定义一个轻量VO类比如MonthlySummaryVO包含month、income、expense三个字段在Service层把Map转成VO再返回。表面上是多写了一个类但前端拿到的是结构明确的对象和未来增加图表功能时的配合也会更顺畅。4.4 预算防超支与兜底校验预算模块的业务逻辑不复杂但有一个比较有价值的校验动作每次新增账单时如果账单类型是支出就自动检查当前分类当月累计支出是否超过预算。如果超了接口返回一个提示告诉用户你设置的餐饮预算本月已使用80%请注意。这个功能实现起来不难但是很能体现作品完整性。实现思路是BillService在新增账单后调用预算检查方法方法内部去预算表查当月有没有对应分类的预算如果有就把当月该类支出SUM出来做比较返回比例。前端拿到这个比例后可以弹提示或者加颜色警示。后端校验还有一个容易被忽略的点账单表里的category_id和account_id都要校验其归属权确保是当前用户的分类和账户。如果不校验前端把category_id改成别人的分类ID也能插入成功数据就会混乱。我的做法是在Service里用一个exists查询做校验代码量不大但属于数据完整性的关键防线。5. 项目集成与部署要点5.1 本地运行三步走项目拿到手或者自己写完后第一步就是能在本地完整跑起来。结合我自己调试项目的经验给出一个清晰的运行路径。第一步准备环境。装好JDK 8、Maven 3.6、MySQL 5.7或8.0。用Navicat创建一个数据库比如finance_db字符集选utf8mb4。然后把项目里的sql脚本导入在Navicat里直接运行即可脚本会把表结构和内置分类数据一次性建好。第二步改配置文件。打开application.yml把数据源连接信息改成你本机的账号密码。因为大部分同学本机MySQL用户名和密码都不一样这是最常见的启动失败原因。第三步启动验证。在项目根目录执行mvn spring-boot:run或者打成Jar包用java -jar跑起来。启动日志里出现Tomcat started on port 8080就说明服务正常了在浏览器访问登录页面用脚本里预置的测试账号试一下登录、记账、统计这些功能。如果项目附带的是前后端分离版本还需要在前端工程目录执行npm install安装依赖再npm run dev启动。前端工程里一般会有一个.env文件配置接口地址默认是http://localhost:8080保持不变就行。5.2 打包部署与答辩演示准备课程设计通常只需要本地演示但我还是建议把打成Jar包的过程走一遍这个能力本身也是答辩加分项。在项目根目录执行mvn clean package -DskipTests在target目录下会生成一个可执行的Jar包。部署时把这个Jar包放到服务器上执行java -jar finance.jar只要服务器上装了JDK和MySQL项目就跑起来了。答辩演示时有一个细节提醒准备一套干净的演示数据。不要拿自己天天添加调试脏数据的库去做演示。我见过太多次同学演示时一打开账单列表满屏是测试乱七八糟的记录这种感觉会大打折扣。做演示前把数据库重新初始化一遍自己花几分钟录入一组有条理的示例数据——比如1月份的工资收入、日常餐饮支出、交通充值、购物消费然后打开统计页让图表能看到清楚的数据分布。可视化图表方面如果前端用了ECharts建议演示的时候先看趋势图再看分类饼图讲的时候重点说明这两个图的数据源分别是什么SQL查出来的老师一听就知道这个项目是真正做出来了而不是只套了个壳子。6. 开发踩坑实录与排查技巧6.1 高频问题速查表写了两年代码带过不少同学做类似项目我发现很多人在同样几个坑上反复栽跟头这里直接整理成速查表遇到问题对着查就行。问题现象常见原因解决办法启动报Communications link failureMySQL地址或账号密码不对、MySQL服务未启动先ping数据库连接确认IP和端口通再核对账号密码时间字段差8小时JDBC连接串缺少serverTimezoneAsia/Shanghai在url中追加serverTimezoneAsia/Shanghai端口被占用上一次启动的进程还在或8080被其他程序占用用netstat -ano查PID执行taskkill /PID 对应PID /F或改server.port页面样式全丢静态资源未放行被登录拦截器拦了在拦截器配置中放行/static、/css、/js等路径插入账单报category_id不存在分类ID是别人用户的数据或前端选到了内置分类ID但该ID被删Service层增加分类归属校验改由接口动态拉取分类列表金额显示科学计数法/精度丢失用double或float存金额数据库字段统一改decimal(12,2)Java对象用BigDecimal点击统计报500SQL语法不兼容或字段别名问题在Navicat里单独执行SQL排查报错再改注解SQL跨域请求被拦截后端未允许跨域配置CorsFilter注意OPTIONS预检请求要先放行Mapper接口注入报空指针启动类未加MapperScan在启动类上增加MapperScan(你的mapper包路径)带条件的分页查不到数据查询条件拼接或参数格式不对检查日期字符串格式和前端传参名是否与实体字段对应6.2 时间字段、逻辑删除和事务处理的三个提醒时间字段我在前面已经反复提过这里换一个角度再多说一句。要区分业务时间和系统时间。业务时间是用户填的账单日期bill_date比如用户可能录入上个月的一笔消费这不能自动化系统时间是create_time和update_time这种记录操作痕迹的时间通常靠数据库自动填充。MyBatis-Plus里可以用TableField(fill FieldFill.INSERT)配合MetaObjectHandler实现自动填充省得每次插入、更新都要手动set日期。这个机制花十分钟配置一下后面所有实体类都能复用。逻辑删除是很多课程设计喜欢加的一个功能它的意义在于让账单删除后还能找回且统计数据不会因为误删除丢失。MyBatis-Plus开启逻辑删除很简单在实体类字段上加TableLogic注解配置全局逻辑删除字段之后你调用deleteById时底层执行的不再是DELETE语句而是UPDATE语句把deleted字段置为1。查询时框架自动追加deleted0条件。但要注意逻辑删除字段不要加到联合索引的查询范围作为主要条件它应该尽量保持简单。事务处理上也值得提一下。删除一个分类时如果这个分类下已经关联了若干账单数据库外键约束或者业务逻辑会阻止直接删除否则会出现账单分类悬空的情况。处理方式有两个要么在删除分类前先提示用户该分类下还有账单需要先转移或删除账单要么在删除分类时同时把关联账单的分类ID置为null。这两种方案都应该加上Transactional事务注解因为涉及两张表的更新操作万一中间出错事务回滚能保证数据不会出现分类删了但账单还在引用这种半截状态。6.3 我的一些额外经验最后说点比较私人的经验可能会对你做这个项目有额外启发。第一个经验是关于文档。课程设计和毕业设计不仅仅看代码还看文档。万字文档听起来吓人但你把需求分析、系统设计、数据库设计、接口设计、测试用例、运行说明这六块内容按模板写下来很容易就凑够了。写文档的时候有一个技巧是画E-R图、画用例图、画系统架构图这些图比大段文字更能体现设计能力。不要等代码写完再开始写文档最好是边开发边记录每写完一个模块就截图存进文档最后汇总会轻松很多。第二个经验是答辩陈述的思路。不要只是把功能列表念一遍而是讲清楚为什么这么设计。比如我被问到频率最高的几个问题是金额用什么类型为什么账单要冗余type字段统计报表的SQL是怎么实现的用户数据如何隔离这些问题在本文前面都解释过了你能用自己的话讲清楚来龙去脉比背100行代码都管用。老师一般只想知道你是不是真的理解了项目而不是在敷衍。第三个经验是版本管理。哪怕就一个人开发也建议用Git做版本管理在Gitee上建一个私有仓库。每完成一个功能就提交一次这样万一哪次改崩了还能回退。而且答辩时如果老师想看开发过程Git提交记录本身就是不错的证据。我个人最满意的一次指导经历是一位基础很一般的同学按照这个路线花了大概四周时间把这个项目完整做出来文档、代码、演示全部到位最后答辩效果排进了专业前几名。他不是天才就是老老实实按需求、按表设计、按模块一步步走遇到问题就查资料、纠正、记录最后整个系统的完成度非常可观。记账系统这个题目之所以我敢推荐正是因为它能让一个水平普通的开发者通过认真的工程实践产出高完成度的作品这种正反馈在课程设计阶段其实非常珍贵。
RELATED

相关推荐

基于MATLAB的电力系统状态估计与不良数据辨识实战

基于MATLAB的电力系统状态估计与不良数据辨识实战

简介:这份资源面向电力系统分析与电气工程专业学习者,聚焦状态估计这一核心课题,提供基于最小二乘法的完整MATLAB实现方案。最小二乘是电力网状态估计最基础也最常用的求解思路,而实际量测数据往往夹杂不良数据,资源在…

📅 2026/10/20 4:54:27
Node 10 到 12 与 Gulp 3 到 4 升级实战:踩坑与避坑指南

Node 10 到 12 与 Gulp 3 到 4 升级实战:踩坑与避坑指南

1. 从 Node 10 到 Node 12、Gulp 3 到 Gulp 4 的升级背景如果你手头有一个维护了两三年的前端项目,大概率会碰到这样一个局面:package.json里锁着gulp3.9.1,.nvmrc写着10.15.3,本地跑得好好的,但一旦换新电脑、升级系统…

📅 2026/10/20 4:49:27
Unity动作系统实战:受伤与死亡状态机设计与实现

Unity动作系统实战:受伤与死亡状态机设计与实现

1. 动作系统里最容易被忽视的一环:受伤与死亡做动作游戏的人都有一个共识:把角色跑起来、跳起来、打出连招,这些都不算难,真正难的是让角色“合理地倒下”。我见过太多项目,攻击判定做得花里胡哨,受击反馈却…

📅 2026/10/20 4:49:27
MORE NEWS

更多资讯

📰

Ingress Nginx 高性能调优:从内核参数到 10 万 QPS 的完整实践

1. 为什么你的 Ingress Nginx 跑不到 10 万 QPS先把话说在前面:Ingress Nginx 单机 10 万 QPS,这个目标不是玄学,但也绝不是把worker_processes改成auto、把内存调大就能碰到的。我原先帮某电商中台调过一版 Nginx Ingress,业务高…

📰

dart_scope鸿蒙适配实战:作用域驱动Flutter依赖生命周期治理

开头一个 Flutter 应用跑在鸿蒙设备上,界面渲染没问题了,但业务状态却乱成一锅粥——页面 A 创建的服务在页面 B 里串用了,退出页面后定时器还在跑,全局单例越攒越多却没人清理。这种场景,做过多端适配的团队多少都经历…

📰

扩散语言模型技术全解析:并行生成、可控性与工程实践

直接从一个技术圈的老话题切入吧——早就听人争论“语言生成到底还要不要按顺序一个字一个字往外蹦”,直到这轮“数亿元”级别的融资砸下来,全球扩散语言模型这个赛道才算真正被重新定价。说实话,当看到手机生态的头部玩家和智能终端领域的巨…

📰

YOLO11双模态改进源码解析:红外与RGB特征融合的目标检测实践

简介:一套YOLOv11多模态输入检测项目资源,面向毕业设计及科研场景,核心解决红外(IR)与RGB可见光图像共同识别问题,通过融合不同光谱信息,提升夜间、低照度、烟雾等复杂环境下的目标检测精度与鲁…

📰

Ray 分布式对象溢出调优:应对千万级长上下文的跨存储治理

在以 Ray 为分布式底层引擎构建的企业级大模型(LLM)推理、微调与长文档复杂 Agent 评测平台中,内存是整座系统最昂贵、也最容易猝死的核心战略资源。随着 GPT-6、DeepSeek-V4 等架构全面迈入 128K 乃至 1M Token 的超长上下文(Lon…

📰

基于Python的网络小说分析系统:爬虫、文本挖掘与可视化实战

最近这几年,“基于Python的网络小说分析系统”这类题目在课程设计和毕业设计里出现的频率非常高。回想我当年带学生选题的时候,每届都有好几个小组选类似的题目,原因很简单:它既有技术深度——爬虫、数据库、文本挖掘、可视化一应…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬