简介:这份「在校大学生贷款管理系统」面向高校数据库课程设计或Java实训项目场景,适合需要完成期末课设、理解业务系统从数据库建模到前后端交互全流程的学生。压缩包共46个文件,包含18个Java源码、20个编译后class文件、1个SQL数据库脚本、1份doc格式说明书及若干项目配置文件,整体仅1.1MB,结构清晰,便于对照源码与文档快速上手。系统围绕学生贷款业务设计了学生信息、贷款申请、还款记录、用户权限等数据表,并采用MVC分层模式组织代码,覆盖登录注册、贷款申请审批、还款管理、数据统计等核心模块,可帮助读者掌握Java与SQL在真实业务中的联动方式。已有1510人学习下载,适合作为课设参考模板或项目拓展基础,尤其对理解Spring、Hibernate等框架在中小型系统中的应用具有直接的借鉴价值。
1. 数据库课设选这个题:一笔贷款从申请到结清的数据流转
每到期末,数据库课程设计最头疼的不是写代码,而是选题。选图书管理太泛滥,选库存管理又看不出数据库设计功底,选这个在校大学生贷款管理系统,是因为它天然自带一条完整业务链:学生注册、贷款申请、管理员审批、放款、还款、逾期统计。一条链走下来,外键、事务、存储过程、视图、聚合查询全都能用上,期末答辩时也容易把“为什么这么设计”讲清楚。这套系统的核心不是界面多好看,而是把贷款业务里的状态流转和数据一致性管住,适合想拿高分的课程设计,也适合作为数据库综合实训的参考原型。
2. 需求分析与总体设计:角色、业务状态机与ER模型
2.1 角色与核心业务流程:谁发起、谁审批、钱怎么走
在动手建表之前,先要把业务流程理清楚,这是整套设计的出发点。系统里有三类角色:学生、管理员(审批人)、系统本身。学生提交贷款申请后,申请要经过“待审核—已通过—已放款—还款中—已结清”的状态流转,或者被驳回回到起点。这个状态机是整个系统的骨架,所有的表设计和接口调用都围绕它展开。
具体流程是这样走的:学生登录后选择贷款项目(比如学费贷款、生活应急贷款),填写金额和期限,提交后生成一条申请记录;管理员在待审核列表里看到这条记录,可以选择通过或驳回;通过后系统自动生成贷款合同和还款计划,学生可以随时查看自己的还款日历;每期到期前系统给出待还提醒,学生还款后生成还款流水,剩余本金随之更新;如果超过到期日未还,系统把该笔贷款标记为逾期,并纳入逾期统计报表。
这里有一个容易忽略的设计点:还款计划不是申请通过时临时算的,而是在放款时一次性生成所有期次的计划记录。也就是说,一笔贷款对应多条还款计划记录,每期计划有独立的“应还状态”。这样做的好处是后续查询逾期、统计坏账时,只需要针对还款计划表做筛选,不用每次动态计算,性能和数据可追溯性都好很多。
2.2 ER模型与实体关系:五张核心表的依赖关系
ER模型是数据库课设的评分重点,设计时要让实体关系和业务对应得上,而不是堆一堆孤立的表。本系统涉及的实体有:用户(含学生和管理员)、学生档案、贷款项目、贷款申请、还款计划、还款流水。其中用户表统一存放账号和角色,学生档案表存放学生的学号、院系、年级等基本信息,两者通过用户ID建立一对一关系。
实体之间的关系用文字描述就是:一个用户只有一个学生档案,一个学生可以发起多笔贷款申请,一笔申请对应一个贷款项目,一笔申请在通过后生成一份还款计划(计划内包含多条记录),一笔还款计划对应多条还款流水。主外键关系上,贷款申请表外键关联学生表和贷款项目表,还款计划表外键关联贷款申请表,还款流水表外键关联还款计划表。这五张核心表形成了清晰的父子数据链,答辩时顺着“学生—申请—计划—流水”这条链讲下去,逻辑非常顺。
ER图在说明书里的画法也有讲究。实体用矩形、属性用椭圆、关系用菱形,这些标准画法必须保留。常见翻车点是学生和贷款申请之间画成多对多,这不符合实际业务。一个学生确实可以申请多笔贷款,但每一笔贷款申请必须对应一个学生,是一对多的关系。把关系基数画错,老师一看就知道业务没想清楚。
2.3 数据表设计:字段、约束与索引
数据表设计是数据库课设的核心交付物。先给出一份可直接参考的核心表结构,后续实现都基于这套设计。用户表存储登录账号信息,包含用户ID、用户名、密码、角色、手机号、创建时间,用户名字段加唯一索引防止重复注册。学生档案表包含学号、姓名、性别、院系、专业、年级,学号做唯一约束,与用户表通过用户ID外键关联。贷款项目表包含项目名称、可贷金额上限、最长期限、年利率、项目说明、状态。
贷款申请表是最重要的一张表,它承载了业务状态。关键字段包括申请ID、学生ID、项目ID、申请金额、申请期限、申请状态、审批意见、审批时间、放款时间。申请状态建议用TINYINT类型存储数字枚举:0待审核、1已通过、2已驳回、3已放款、4已结清、5逾期中。用数字而不是字符串的好处是存储空间小、查询快,而且状态流转用数字比较不容易因为文字不一致而出错(比如“已通过”和“审核通过”其实是同一个状态,字符串容易写出两种写法)。
还款计划表每期生成一条记录,字段包括计划ID、贷款申请ID、期数、应还日期、应还本金、应还利息、应还总额、实还日期、实还金额、还款状态。还款流水表则记录每一笔实际还款操作,字段包括流水ID、计划ID、还款金额、还款方式、操作人、还款时间。这两张表的区别要讲清楚:计划表是“应该还什么”,流水表是“实际还了什么”,两者通过计划ID关联,这也是答辩时老师常问的一个点。
最后强调索引设计。所有外键字段都要建索引,这是基本要求;申请表的“状态+申请时间”需要建联合索引,因为管理员最常按状态筛选并按时间排序;还款计划表的“应还日期+状态”也要建联合索引,逾期查询会频繁用到这两个条件。字段类型上,金额一律用DECIMAL(10,2),日期用DATE,时间用DATETIME,状态用TINYINT,主键用INT自增。下面是贷款申请表的建表SQL,可直接套用。
CREATE TABLE loan_application ( id INT AUTO_INCREMENT PRIMARY KEY COMMENT '申请ID', student_id INT NOT NULL COMMENT '学生档案ID', project_id INT NOT NULL COMMENT '贷款项目ID', apply_amount DECIMAL(10,2) NOT NULL COMMENT '申请金额', apply_term INT NOT NULL COMMENT '申请期限(月)', status TINYINT NOT NULL DEFAULT 0 COMMENT '状态:0待审核 1已通过 2已驳回 3已放款 4已结清 5逾期中', approve_comment VARCHAR(255) COMMENT '审批意见', approve_time DATETIME COMMENT '审批时间', loan_time DATETIME COMMENT '放款时间', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '申请时间', CONSTRAINT fk_loan_student FOREIGN KEY (student_id) REFERENCES student_profile(id), CONSTRAINT fk_loan_project FOREIGN KEY (project_id) REFERENCES loan_project(id), INDEX idx_status_time (status, create_time) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='贷款申请表';这里有个关键设计说明:为什么申请状态不直接用CHECK约束限制取值范围?因为课程设计的MySQL版本如果低于8.0.16,CHECK约束实际上不生效,写了等于白写。更可靠的做法是在业务层(service层)用枚举或常量类控制状态的合法取值,同时配合“状态流转记录日志”的方式来保证数据可追溯。表注释和字段注释要写全,这是课设加分项,老师打开表结构一眼就能看懂设计意图。
3. 核心模块实现:从登录鉴权到还款计划的SQL落地
3.1 登录模块:密码加密与角色路由
登录模块看起来简单,其实是数据库课设里最容易写出安全漏洞的地方。正统做法是密码不存明文,用MD5再加盐的方式存储。用户在注册时输入密码,系统拼接一个固定盐值然后做哈希,把哈希结果存进用户表;登录时取出用户输入的密码做同样的哈希运算,与表中存储的值比对。这样即使数据库被导出,也无法直接拿到原始密码。下面是一个标准的密码处理示意。
// 注册时对密码做加盐哈希 String salt = "loan2024"; String hashPwd = DigestUtils.md5Hex(salt + rawPassword); // 登录时再次计算并比对 String loginHash = DigestUtils.md5Hex(salt + inputPassword); if (loginHash.equals(user.getPassword())) { // 校验通过,按角色跳转 }登录成功后的角色路由也值得设计一下。用户表里有role字段,1代表学生,2代表管理员。登录成功后不要在前端写死跳转地址,而是根据角色值动态选择不同的主界面。这样一个登录入口就能支撑两个完全不同的操作台,学生看到的是“我的贷款申请、我的还款计划”,管理员看到的是“待审核列表、全部贷款、逾期统计”。
这里有一个常见错误:有些课设把管理员账号直接写死在代码里,不经过用户表验证。这样做虽然省事,但答辩时老师问“如果管理员想改密码怎么办”,就会卡住。正确做法是管理员也是用户表里的一条记录,初始化时用SQL脚本插入一条role=2的数据,密码同样做加盐哈希。这样整个系统的鉴权逻辑是统一的,不存在特殊分支。
3.2 贷款申请与审批:状态机驱动的SQL流转
贷款申请的核心是状态机,每一次操作本质上是把记录的状态从A改成B,同时记录操作时间和操作意见。学生提交申请时,插入一条status=0的记录;管理员审批通过时,把status从0改成1,并写入审批时间和审批意见;如果驳回,则改成2。这里最重要的一条规则是:状态修改必须带前置条件判断,不能让任意状态之间随意跳转。比如一笔已经放款的贷款,不能被再次驳回。
在SQL层面实现带前置条件的状态流转,通常采用“条件更新”的方式,也就是UPDATE语句的WHERE子句里带上当前状态。这样做的效果是:如果记录已经不是期望的初始状态,这次更新影响行数为0,程序据此判断操作不合法。下面是审批通过的核心SQL示意。
-- 审批通过:仅当记录处于“待审核”状态时才允许流转 UPDATE loan_application SET status = 1, approve_comment = ?, approve_time = NOW() WHERE id = ? AND status = 0; -- 如果返回的影响行数为0,说明该申请已被处理过,需要重新加载页面为什么推荐条件更新而不是先SELECT再UPDATE?因为先查后改在并发场景下存在时间差。如果两个管理员同时打开同一个申请,A先审批通过,B后点击通过,先查后改会导致B也以为审批成功,覆盖了A的操作。把状态判断放进UPDATE语句,数据库层面保证原子性,一次命令完成“校验+更新”,这是事务并发控制里最实用的手段之一。审批通过后还要做放款动作:生成还款计划、更新申请状态为已放款。这两个操作必须放在同一个事务里,否则会出现“申请已放款但还款计划为空”的不一致数据。
3.3 还款计划生成与逾期预警:日期计算与事务
放款时生成还款计划,是整个系统里最能体现数据库功底的地方。一笔贷款期限假设是12个月,放款后要在还款计划表里生成12条记录,每条记录包含期数、应还日期、应还本金和应还利息。等额本息和等额本金两种方式任选一种即可,课设通常选等额本息,因为计算公式更体现业务理解。等额本息的每月还款额公式为:月供 = 本金 × 月利率 × (1+月利率)^期数 / ((1+月利率)^期数 - 1)。
生成计划时,应还日期从放款日期的次月同日开始计算,往后顺延期数。这里有一个日期陷阱:如果放款日是1月31日,次月是2月,2月没有31日,直接用Calendar的add(Calendar.MONTH, 1)会把日期跳到3月3日。处理办法是统一把还款日设定为每个月的固定一天,比如每月15日。放款后不管几号,计划都从下月15日开始生成,这样规则统一,也避免了月末日期错乱的bug。生成计划的循环里,每期记录的本金和利息要按公式计算,最后一期做尾差调整,保证所有期次的应还本金之和等于贷款总额,这个“尾差调整”是老师最爱问的细节。
还款计划的表结构里有一个重要字段:每期记录的“应还总额”在生成时就算好并存储,而不是在查询时临时计算。这样做的好处是:如果利率调整,历史计划不受影响;如果某期逾期,逾期费用记录在流水里,不影响计划表的原始数据。还款操作发生时,要从还款计划表和流水表同时更新,事务模板如下。
// 还款事务:更新计划状态 + 插入流水,必须同生共死 Connection conn = getConnection(); try { conn.setAutoCommit(false); // 1. 更新当前期次计划状态为已还,记录实还日期与实还金额 // 2. 插入还款流水记录 // 3. 累计查询该笔贷款所有期次状态, // 若全部已还,则把贷款申请状态改为“已结清” conn.commit(); } catch (Exception e) { conn.rollback(); // 任何一步失败,整笔还款回滚 }3.4 报表统计:按日、按月聚合查询
报表统计是课设题目里“系统管理”功能的核心。至少要实现三类查询:待审核申请统计、按贷款项目的放款总额统计、逾期记录统计。这三类查询全部用GROUP BY加聚合函数实现,能充分展现SQL编写能力。待审核统计最简单,就是SELECT COUNT(*) FROM loan_application WHERE status=0。按项目统计放款总额则要JOIN两张表,按项目名分组求和。
逾期统计相对复杂,需要从还款计划表里筛选“应还日期小于当前日期且状态不等于已还”的记录,同时关联贷款申请表和项目表,显示出是哪位学生、哪个项目、逾期了哪个期次、逾期金额多少。下面是一条完整的逾期统计SQL,可以直接放进报表模块。
-- 逾期统计:筛选已过应还日且未还的记录,按学生和项目汇总 SELECT sp.name AS student_name, sp.stu_no AS student_no, lp.name AS project_name, rp.period_no, rp.due_date, rp.due_amount, DATEDIFF(CURDATE(), rp.due_date) AS overdue_days FROM repayment_plan rp JOIN loan_application la ON rp.application_id = la.id JOIN student_profile sp ON la.student_id = sp.id JOIN loan_project lp ON la.project_id = lp.id WHERE rp.status = 0 AND rp.due_date < CURDATE() ORDER BY overdue_days DESC;这条SQL里的DATEDIFF计算逾期天数,是加分项。但是这里有个性能隐患:WHERE条件里对rp.due_date用了函数比较,如果还款计划表数据量很大,这个查询无法走索引。好在课设数据量只有几千条,影响不大。如果要优化,可以改成rp.due_date < CURDATE()且状态=0的联合索引条件,让数据库直接按索引扫描范围。答辩时主动说出这个优化点,能体现对索引原理的理解。报表模块在输出时建议做成表格视图,把“项目名称、放款笔数、放款总额、逾期笔数、逾期总额”放一并展示,一眼能看出哪个项目风险高。
4. 关键技术实现:JDBC封装与业务层的事务边界
4.1 JDBC连接池与BaseDao封装
数据库课设用Java写的话,JDBC是绕不开的。很多同学把JDBC的连接代码写在每一个界面按钮事件里,导致代码重复率极高,改一个数据库密码要改几十处。更规范的做法是把数据库连接抽到一个工具类里,统一管理连接、关闭、异常处理。课程设计阶段不需要引入完整的连接池框架,但至少要做到“一个应用只有一个获取连接的入口”。
最省事的方式是用配置文件存连接参数,工具类读取配置后建立连接。配置文件用Properties格式,把driver、url、username、password四项独立出来,将来换数据库或改密码只需要改一行。连接获取的代码封装在静态方法里,所有DAO层都调用这个方法拿连接,而不是自己new DriverManager。这套“配置外置”的思路,是答辩时说“项目可维护性”时的第一个论据。
BaseDao的设计也建议做。把增删改查提取成通用方法,比如提供一个executeUpdate(String sql, Object... params)方法执行INSERT、UPDATE、DELETE,再提供一个queryList方法执行SELECT并返回List<Map<String,Object>>。所有业务表的DAO都继承这个BaseDao,只写自己特有的SQL。这样课设代码量能减少三成,而且封装的查询结果集自动处理了字段名到Map的映射,写界面时直接按字段名取数据,不用手动遍历ResultSet。下面是一个查询封装的典型写法。
public List<Map<String, Object>> queryList(String sql, Object... params) { List<Map<String, Object>> list = new ArrayList<>(); try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { for (int i = 0; i < params.length; i++) { ps.setObject(i + 1, params[i]); // 参数从1开始 } ResultSet rs = ps.executeQuery(); ResultSetMetaData md = rs.getMetaData(); while (rs.next()) { Map<String, Object> row = new HashMap<>(); for (int i = 1; i <= md.getColumnCount(); i++) { row.put(md.getColumnLabel(i), rs.getObject(i)); } list.add(row); } } catch (SQLException e) { e.printStackTrace(); // 课设阶段打印日志即可定位问题 } return list; }这段代码有几个设计细节要说清楚。一是使用PreparedStatement而不是Statement,防止SQL注入,这也是答辩时的高频问题;二是使用try-with-resources语法,连接、PreparedStatement、ResultSet在方法结束时自动关闭,不用手动写一堆finally代码;三是getColumnLabel而不是getColumnName取列名,因为SQL里如果写了别名,Label返回的是别名,不会因为字段名大小写问题而出错。
4.2 业务service层:事务边界怎么划
很多课设的代码只有两层:界面直接调用DAO,没有service层。这在单表操作的场景下没问题,但一涉及多表事务就翻车。放款、申请、还款这些操作都是跨多张表的,必须有一个service层来协调事务边界。service层的每个方法对应一个完整的业务操作,方法内部调多个DAO方法,并由service控制事务的提交和回滚。
事务边界的划分原则是:一个业务操作一个事务。比如“审批通过并放款”是一个事务,里面包含“更新申请状态”“生成还款计划”两个数据操作;而“查看申请列表”不是事务,它只是一个查询。最典型的错误是把事务放到DAO层,每个DAO方法执行完就提交。这样导致的问题是:如果更新申请状态成功,而生成还款计划失败,前面已经提交的数据无法回滚,数据就坏了。
正确写法是在service层开启事务,把连接对象传给多个DAO方法,同一个连接执行多个SQL,最后统一提交。这里有个实现细节:DAO方法不能再内部获取新连接,而要从service层传入的连接上执行。通常做法是service层持有Connection,把它作为参数传给dao的update方法,或者用ThreadLocal保存当前线程的连接,DAO从ThreadLocal里取。课设阶段用参数传递最直观,虽然代码略啰嗦,但逻辑清晰,出了bug好排查。
事务还有一个隐藏坑:MySQL默认的隔离级别是REPEATABLE READ,在并发场景下可能出现幻读。但对课设来说,真正要防的是“丢失更新”。比如两个事务同时读取了同一笔贷款的应还状态,然后各自做了还款操作,都认为自己把最后一期还完了。解决思路是在SQL里加FOR UPDATE做行锁,或者用前面讲的条件更新加状态校验。建议课设里统一采用条件更新的方式,简单有效,不用引入复杂锁机制。
4.3 界面与数据库的交互:Swing层的数据绑定思路
如果课设要求带图形界面,Java Swing是大多数同学的选择。Swing界面最容易踩的坑不是控件用法,而是把业务逻辑写进事件监听器里。比如按钮点击事件里直接写JDBC和SQL,几百行代码塞在一个actionPerformed方法里,界面稍微复杂一点就没法维护。更合理的分层是:事件监听器只做一件事——调用service层的方法,把返回的数据填充到界面控件。
拿“还款计划列表”页面举例:页面初始化时,监听器调用PlanService.getPlanList(applicationId),拿到List之后循环构建JTable的TableModel,设置列名“期数、应还日期、应还本金、应还利息、应还总额、状态”,然后逐行填充数据。整个监听器方法里没有一条SQL,全是界面赋值逻辑。这样写的好处是,将来想改查询逻辑只需要动service层,不用动界面,答辩老师问“三层架构体现在哪”,直接翻开代码给他看监听器那道薄薄的壳就行。
Swing的数据刷新还有一个惯用手法:封装一个refreshData()方法,负责重新从service层拉数据并更新表格。当用户提交了新的还款申请或者审批通过后,回到列表页就调用一次refreshData(),而不是重新创建窗口。这个方法维护一个标志位,加载数据时清空旧行再插入新行,避免表格数据叠加显示。很多新手在这里犯迷糊,以为刷新就是重新new一个窗口,结果窗口越点越多。记住一个原则:窗口只创建一次,数据变化只做刷新,不做重建。
5. 常见问题排查与避坑:课设最容易翻车的五个点
5.1 建表失败:外键约束建不上去
现象:执行带FOREIGN KEY的建表语句时报错,提示无法添加外键约束,但单独建每一张表又都正常。
原因:两张表的引擎或字符集不一致。最常见的是父表用了InnoDB、子表用了MyISAM,或者父表是utf8、子表是utf8mb4,InnoDB要求参与外键的两张表引擎一致,且关联字段的类型、长度、字符集必须完全一致。另外,如果父表的关联字段上没有索引,MySQL不会自动建索引,也会报错。
解决:统一所有表的引擎为InnoDB,字符集统一为utf8mb4,并在父表的关联字段上先手动添加索引。建表顺序也要注意:先建父表(学生表、项目表),再建子表(申请表、计划表)。最稳妥的做法是把建表脚本按依赖顺序排列,在一个SQL文件里依次执行,避免因执行顺序错乱而失败。
5.2 金额数据错乱:double类型存钱是灾难
现象:界面显示贷款金额是99.99元,但数据库里查出来是99.989999999999。
原因:用了DOUBLE或FLOAT类型存储金额。浮点数在二进制里无法精确表示十进制小数,所以99.99在存储时会变成近似值。虽然单条数据误差很小,但一做累加,误差会越滚越大,报表统计的金额对不上。
解决:金额字段全部改成DECIMAL(10,2)。这个类型是定点数,数据库里按十进制存储,不会出现精度丢失。Java侧取数据时用BigDecimal接收,不要用double。如果之前已经用double存了数据,写一条UPDATE迁移脚本,把字段类型转成DECIMAL,再核对一遍汇总金额与原始记录是否一致。这是血泪经验,别觉得差几分钱无所谓,答辩时老师出一道精度计算题就会原形毕露。
5.3 并发审批互相覆盖:两个管理员同时操作同一笔申请
现象:A管理员和B管理员同时打开同一笔待审核申请,A先点击通过,B后点击驳回,结果这笔申请的最终状态是驳回,A的审批操作白做了。
原因:典型的丢失更新。代码里先SELECT查出状态是待审核,show界面上两个人都看到了同一个待审核记录,然后各自提交UPDATE,后提交的人覆盖了先提交的人。
解决:用条件更新,把状态判断放在UPDATE的WHERE里。UPDATE loan_application SET status=1 WHERE id=? AND status=0,如果影响行数为0,说明记录已经被别人处理过,此时弹出提示“该申请已被处理,请刷新列表”。这样在数据库层面保证了状态流转的原子性。如果还想更严格,可以在申请表里加一个version字段,每次更新时version+1,更新条件带上version,这是乐观锁的做法,两种方案选一种实现即可。
5.4 中文乱码:界面上全是问号或方框
现象:数据库里插入中文后查询出来是问号,或者通过程序写入的中文在命令行里看是乱码。
原因:字符集链路出现了断裂。这一条链路包含四个环节:数据库字符集、表的字符集、JDBC连接参数、程序文件编码。任何一个环节不是utf8,中文就会在传输或存储过程中损坏。最常见的是JDBC连接URL里没有指定characterEncoding=utf8,数据库连接串默认用的是跟服务器端字符集不一致的编码。
解决:第一,建库时指定CREATE DATABASE ... DEFAULT CHARACTER SET utf8mb4;第二,建表时也带上DEFAULT CHARSET=utf8mb4;第三,JDBC连接串末尾追加?useUnicode=true&characterEncoding=utf8;第四,明确告诉老师用的是UTF-8编码。注意,utf8mb4和utf8在MySQL里的区别是前者支持emoji和更多字符,课设推荐直接用utf8mb4,兼容性更好。如果已经出现乱码数据,只能把数据导出重新清洗导入,没有后悔药可吃,所以一开始就要把四个环节全部设定好。
5.5 事务没生效:改了数据没回滚,或者一条SQL失败后面全停
现象:在service层写了try-catch和rollback,但测试时故意在第二条SQL里写一个错误的列名,执行后第一条SQL的数据竟然没有被回滚,还是写入成功了。
原因:连接管理方式不对。最常见的错误是service层拿到Connection之后,DAO内部又通过DBUtil.getConnection()新开了一个连接。这样事务只控制了你手头那一个连接,DAO用的却是另一个连接,两个连接各自独立提交,rollback当然管不到DAO那一侧的操作。
解决:整个业务方法必须使用同一个连接。最简单的做法是service层把连接作为参数传给DAO方法,DAO方法内部不再获取新连接。检查事务是否生效也有一个笨但有效的办法:在rollback前后打印当前连接的对象标识,如果在service层和DAO层打印的是同一行地址,说明连接传对了;如果打印出两个不同地址,就是连接被重复创建了。这个黑匣子排查法在课设阶段非常管用。
6. 验收与答辩:把演示做成加分项
6.1 演示路径设计
答辩演示最忌讳东点一下西点一下,老师根本不知道你的系统能干什么。我一般会把演示路径固定成一条完整业务线,不用多,一条线讲透就够:注册一个学生账号登录,提交一笔贷款申请;退出登录,切换管理员账号,在待审核列表里通过这笔申请;系统自动生成还款计划;切回学生账号,查看自己的还款计划,执行一次还款操作;最后切回管理员,打开逾期统计和项目报表,展示数据变化。这条线走完,系统的所有核心功能都展示了,而且每一步都是前一步的结果,逻辑严密。
演示前必须准备一套“干净数据”初始化脚本。把数据库重新跑一遍建表脚本和基础数据,保证演示时列表是空的、报表从零开始。千万别在已经操作过很多次的库上演示,界面上一堆历史脏数据,老师会怀疑你的系统容错能力。另外,把SQL语句、核心代码截图放进说明书附录,老师提问时如果一时答不上来,可以直接指着代码说“这一步用了条件更新来防止并发覆盖”,比空口解释硬气得多。
6.2 答辩高频问题
答辩环节老师最爱问的几个问题,基本围绕“为什么”和“如果”:为什么用DECIMAL不用double?为什么要有还款计划表,直接每次查询时计算不行吗?如果一个人申请多笔贷款同时逾期,报表怎么统计?事务边界为什么这么划分?这些问题在实际项目中都遇到过,回答的关键是给出理由而不是背概念。比如还款计划表的问题,可以说“一次性生成计划,让每期状态独立可追踪,逾期统计只需要查计划表的状态字段”,这句话就把设计的业务价值讲清楚了。
做路演时还要准备好“失败预案”。万一演示现场连不上数据库,或者某个按钮点了没反应,最忌讳当场改代码。我的习惯是在演示机上先跑一遍完整流程,截图保存关键结果,万一现场出问题,直接切到截图页面继续讲,把注意力引到设计思想上。这也算一个答辩小技巧:让你展示的不只是运行效果,更是你对这套系统设计思路的完整理解。
6.3 说明书写作建议
说明书的结构建议按照“需求分析—概念设计—逻辑设计—物理设计—实现细节—测试结果”组织,其中逻辑设计要给足ER图和关系模式,物理设计给建表SQL和索引说明。测试部分不要只写“测试通过”,要给出一张测试用例表,包含用例编号、输入数据、预期结果、实际结果、是否通过。哪怕只写八条用例,也比一句“系统运行正常”有说服力。
记得那届课设答辩,有个同学演示时数据库突然连不上,他当场打开配置文件改密码,结果在老师面前卡了五分钟。从那以后我每做一个课设,都会强制自己走一遍“干净机演练”:找一台没装过任何环境的机器,从建库脚本开始,一步步部署到能跑完演示全流程。过程虽然麻烦,但能逼你把环境依赖彻底理清楚,也让你在答辩台上多一份从容。希望这套方案能帮到你,照着这个套路把表结构、事务和演示路径做扎实,这门课设拿高分并不难。
本文还有配套的精品资源,点击获取