ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于Java SSM与Python Django的教务系统设计与实现

基于Java SSM与Python Django的教务系统设计与实现 如果你正在为课程设计或毕业设计选题发愁或者已经选了“教务系统”这个方向却不知道怎么把学生信息、课程安排、成绩查询、选课这些功能落成一套可运行的代码那这篇内容应该能帮到你。我用JavaSSM和PythonDjango各实现了一套完整的教务信息平台从数据库设计到核心业务逻辑再到部署调试都整理成了可以直接参考的源码和配套文档。这套项目包含学生信息管理、课程安排、成绩查询、教学管理、学籍管理、考试安排、选课系统等核心模块既覆盖了教务业务的主要场景也兼顾了两种主流Web技术栈的工程量。无论你是Java方向还是Python方向或者两个方向都有所涉及这套双栈实现都能当作用一个业务需求来对比理解框架设计差异的活教材。1. 项目整体设计与技术选型1.1 这套系统的定位与目标用户教务信息平台是个非常经典的业务系统选题因为它包含了业务管理系统里几乎所有常见场景用户认证与权限区分、基础数据维护、多条件查询与分页、外键关联和事务处理、甚至还有一点简单的排课约束逻辑。该说不说选这个题目做课程设计或者毕业设计性价比确实高业务需求容易向评审老师讲清楚功能边界又足够撑起一篇完整的系统设计文档。这套项目的目标用户很清晰。一类是正在做毕业设计的本科生需要跑通一个完整系统并写出对应的设计和说明文档另一类是刚学完SSM或Django基础、想通过一个完整项目把框架知识串起来的自学开发者。我不会把内容写得太教科书化更多的是一步步把“这个项目到底怎么做出来”的细节讲透。哪怕你没亲手写过SSM的XML配置或者对Django的ORM只停留在会用objects.filter的程度照着这套代码思路往下走也能在几天内把系统跑起来并且能说出每个模块背后的设计原因。1.2 为什么选“SSM打底 Django翻版”这套组合很多同学会问为什么一个教务系统要做两套技术栈不是增加工作量吗这里我得说清楚这个项目的初衷不是追求“最快的开发效率”而是要同时覆盖Java和Python两个方向的招聘和技术热门方向。Java后端和Python后端在就业市场上都是主流选择很多人在两者之间纠结。与其纠结不如用一个相同业务需求分别用SSM和Django各实现一遍在对比中自然就理解了框架的设计取舍。从技术难度和框架学习曲线来看SSM是Java系里最经典的组合Spring管对象和事务、SpringMVC管请求分发、MyBatis管数据库操作。这套组合的灵活性高但配置相对繁琐适合用来理解Web框架的底层协作方式。而Django走的是“全家桶”路线自带ORM、Admin后台、认证体系和模板引擎开发效率高适合快速搭建业务原型。同一个教务平台用这两套实现能很直观地对比出“SSM中需要手动配置的东西Django里到底替你做了什么”。从最终交付的角度看源码加调试文档加讲解的配套形式也方便在答辩时展示。你可以先讲Java版的SSM实现突出事务管理、Mapper接口和SpringMVC请求流再切到Django版演示Admin后台几行代码就生成增删改查界面顺便讲透两套方案的适用场景。这样整个答辩逻辑是自洽的我不仅会写代码还知道技术方案何时该选哪种。1.3 功能范围与页面结构速览我整理了一下这套系统覆盖的功能清单包含六个主体模块学生信息管理学生档案的增删改查、按学号/姓名/班级/专业/入学年份条件组合查询课程安排课程基础信息维护、授课教师关联、上课时间地点设置成绩管理平时成绩和期末成绩录入、加权总评计算、按课程和学生维度查询成绩教学管理教师授课任务分配、教学班管理、教师信息的维护学籍管理学生入学、休学、复学、毕业等状态管理考试安排考试时间地点分配、监考教师安排选课系统学生自主选课、时间冲突检测、退课操作页面结构上两种技术栈都采用传统的服务端渲染模式。Java端用JSP借Bootstrap做页面样式Django端用模板继承和自带模板语法。前台用户界面和学生登录界面分开后台管理只对教师和管理员角色开放。菜单层级不超过三层保证操作路径短也方便在文档里画功能结构图。2. SSM侧核心设计细节2.1 Controller / Service / Mapper 三层是怎么分工的SSM项目的代码组织方式基本都遵循三层架构这也是Java面试题里反复被拷问的点。在教务系统里我把三层职责拆得非常明确Controller层只接收请求、解析参数、调用Service、返回视图或JSON数据。比如成绩查询接口GradeController负责从HttpServletRequest里拿到当前页、每页条数、课程ID这些参数然后交给Service层最后把分页结果塞进ModelAndView。Controller里不出现任何SQL和业务判断尽量保持“薄”。Service层承载全部业务逻辑包括事务控制、数据校验、冲突检测。比如选课时的冲突检测就在CourseSelectionService里完成先查出该学生已选的所有课程时间段再和目标课程逐条比对如果发生重叠就抛业务异常由全局异常处理器转成页面提示。Mapper层只做数据访问。MyBatis的Mapper接口定义方法XML文件里写SQL。这里有个经验多表关联查询不要全堆在主Mapper里比如成绩列表需要关联课程名和学生名我建议单独建一个GradeCustomMapper只负责组装复杂列表查询的SQL这样基础的CRUD和报表类查询互不干扰。分层的好处不只是好写文档更重要的是调试的时候能快速定位问题。我踩过一次坑成绩排名的SQL算出的结果是错的页面展示却正常最后定位发现是Controller里传参顺序和Service层接收顺序对不上把班级ID和课程ID传反了。如果是分层清晰的结构这种问题在Service入口加个日志就能立刻看出来。2.2 MyBatis动态SQL与应用场景教务系统的查询条件非常灵活光是一个学生列表就可能组合出十几个查询条件学号模糊匹配、姓名模糊匹配、学院下拉筛选、专业下拉筛选、入学年份范围、性别选项。如果为每种组合写一条SQL工作量会爆炸。MyBatis的动态SQL就是解决这个问题的核心技术。以一个典型的多条件查询为例我的学生列表Mapper大概是这样的select idfindByCondition resultMapStudentResultMap SELECT s.*, c.class_name, m.major_name FROM tb_student s LEFT JOIN tb_class c ON s.class_id c.id LEFT JOIN tb_major m ON c.major_id m.id where if teststuNo ! null and stuNo ! AND s.stu_no LIKE CONCAT(%, #{stuNo}, %) /if if testname ! null and name ! AND s.name LIKE CONCAT(%, #{name}, %) /if if testclassId ! null AND s.class_id #{classId} /if if testmajorId ! null AND c.major_id #{majorId} /if /where ORDER BY s.enroll_date DESC /select这里的核心要点是where标签会自动处理开头的AND如果所有条件都为空生成的SQL就是一个不带WHERE的全表查询不会出现语法错误。if标签则按条件拼接片段。这种写法比在Java代码里拼SQL字符串安全得多因为参数始终走预处理能有效避免SQL注入风险。我还建议学一下foreach标签它在批量插入和批量删除场景里很常用。比如批量导入学生数据时List参数通过foreach循环拼成多组VALUES一次INSERT就能搞定性能比单条循环插入高很多。需要注意的一个坑foreach的集合类型在XML里要写清楚如果是List参数collection属性写list如果是使用Param注解命名的参数就写注解名这个写错了会直接报异常。2.3 权限控制与Session处理教务系统里至少有三种角色学生、教师、管理员不同的角色能看到的菜单和能执行的操作天差地别。学生能看自己的成绩和课表但不能改教师能录成绩但不能改学生个人信息管理员什么都行但也不能乱改成绩。我把权限控制放在了两个层面来做。第一层是登录后的Session标记。用户登录成功后把用户ID、姓名、角色代码存进Session。页面上的菜单选项根据角色代码动态渲染管理员登录后才有“学籍管理”“考试安排”入口学生登录后就没有“成绩录入”这个按钮。这层过滤做得简单直接代码维护成本低也方便在答辩时讲解。第二层是SpringMVC的拦截器用来保护URL级别的访问路径。我写了一个LoginInterceptor注册到SpringMVC配置里排除掉登录页、验证码、静态资源这些公开路径其余所有业务URL都走拦截。同时有一个AdminInterceptor只过滤/admin/**路径用于校验管理员权限。这样即使有人猜到了某个管理接口的URL不登录也进不去。权限控制的延伸设计里还涉及一个刚学的Transactional注解的使用。比如教师录入成绩时需要先检查该教师是否确实教这门课再写入成绩表同时更新该学生的总评数据。这个流程里两步操作必须同时成功或同时失败所以我在Service方法上加Transactional(rollbackFor Exception.class)配合Spring的声明式事务管理。如果不加事务可能成绩明细写进去了总评更新失败数据就出现不一致。这一点在文档里的“功能测试用例”部分值得重点强调一下。3. Django侧核心设计细节3.1 MTV结构在教务场景里的映射Django的项目组织方式和SSM差别挺大它是按app来划分模块的每个功能域独立成一个app。这个教务系统我把app按业务拆成了五个users管登录和用户信息、students管学生档案和学籍、courses管课程和排课、grades管成绩与统计、selects管选课和冲突检查。每个app都有自己的models、views、urls、admin职责边界比Java端的包结构还要直观。写Django工程量最大的其实不是业务代码而是理解“请求到底是怎么路由到视图函数的”。我刚开始用Django时经常搞混path和re_path后来才总结出规律对一个REST风格的URL比如/student/2021001/path里用转换器来捕获变量path(student/int:stu_id/, views.student_detail)如果需要更灵活的正则匹配就用re_path配合命名分组。一个容易犯的错是把stu_id和int:stu_id混写后者带类型转换Django会自动把参数转成int而前者转出来是字符串和后续数据库查询条件一比对就会出错。模板层的设计我走的是“继承”路线写一个base.html放导航栏、CSS和JS引用、用户登录信息展示子模板用{% extends base.html %}继承只填充{% block content %}部分。这样改一次导航栏结构所有页面同步更新比SSM里每个JSP都要独立维护头尾要省心得多。对于列表分页Django原生有Paginator类在视图里分页、模板里渲染页码再配合Bootstrap的分页样式效果和Java端的PageHelper差不多。3.2 ORM难点多表关联和冲突查询Django最吸引人的地方就是它的ORM。教务系统里最复杂的查询场景是查一个学生选的所有课、同时显示课程的授课老师和上课时间地点。如果用原生SQL写要连三张表但在Django ORM里只需要在模型上把外键关系定义好然后利用双下划线跨表查询。class CourseSelection(models.Model): student models.ForeignKey(Student, on_deletemodels.CASCADE, related_nameselections) course models.ForeignKey(Course, on_deletemodels.CASCADE, related_nameselections) created_at models.DateTimeField(auto_now_addTrue) # 查询学生张三的所有选课及课程信息 selections CourseSelection.objects.filter( student__name张三 ).select_related(course__teacher)这里的student__name就是双下划线跨表过滤的经典写法你可以一层一层往深处关联course__teacher__name也能用。select_related则是在SQL层面用JOIN把关联对象一次性查出来避免循环访问数据库。一个常见的性能坑在模板里循环展示选课时如果没有select_related每显示一行课程信息都会额外执行一次查询几十条记录就是几十条SQLDjango调试工具里能明显看到查询次数暴涨。选课冲突检测是另一个有代表性的ORM用法。在Java端我用Java代码判断时间段重叠在Django端可以直接用ORM构造起止区间交叉查询conflicts Course.objects.filter( schedules__day_of_weektarget_schedule.day_of_week, schedules__start_section__lttarget_schedule.end_section, schedules__end_section__gttarget_schedule.start_section, selections__studentcurrent_student )这段逻辑表达的是“两个时间段存在重叠区域的充要条件是一方开始时间小于另一方结束时间、且一方结束时间大于另一方开始时间”对于处理上课节次、会议室预订这类重叠判断这是个可以反复使用的套路。3.3 Admin后台与权限管理Django自带一个Admin后台这是它相比Java系框架的一个巨大优势。我在models里注册了全部业务表之后再在admin.py里配置列表显示字段和搜索字段比如admin.register(Course) class CourseAdmin(admin.ModelAdmin): list_display [course_code, course_name, credit, teacher, course_type] search_fields [course_code, course_name] list_filter [course_type]系统运行起来后访问/admin/就能直接对课程、学生、班级这些基础数据进行可视化维护。用Admin后台来完成“基础数据初始化”非常方便比在Java端一点点敲SQL省事得多。但要注意Admin后台最好只给管理员用不能放给学生身份。我通过has_module_permission方法对某些模型做了管控确保普通用户进入不了这些页面。Django的权限体系自身就有三要素User、Group、Permission。利用内置的login_required装饰器能保护视图函数利用user_passes_test可以自定义校验函数比如检查当前用户是否是教师角色。对于班级辅导员只能看本班学生的需求我写了一个自定义校验def is_class_counselor(user): return user.is_authenticated and user.role counselor把它挂到学生列表视图上非辅导员角色直接重定向到无权限页。这个方案比自定义装饰器简单还能和Django自带的登录系统无缝配合。有一点要特别注意Django默认开启了CSRF防护所有POST表单模板里都要写{% csrf_token %}这一点和Java端SpringMVC默认不开CSRF是不同的刚转过来时容易漏掉导致403。4. 数据库设计与核心功能逻辑4.1 十几张表怎么串起整个教务流程教务系统的数据库设计是整个项目的地基表结构如果设计不好后面写业务代码会非常痛苦。我最终的库里有这么几张核心表表名用途关键字段tb_user用户账号username, password, roletb_student学生档案stu_no, name, gender, class_id, enroll_date, statustb_teacher教师信息name, title, dept_id, phonetb_major专业major_name, dept_idtb_class行政班级class_name, major_id, grade, counselor_idtb_course课程course_code, course_name, credit, hours, course_typetb_schedule排课表course_id, teacher_id, classroom, day_of_week, start_section, end_sectiontb_selection选课记录student_id, course_id, created_attb_grade成绩记录student_id, course_id, regular_score, exam_score, total_score, semestertb_exam考试安排course_id, exam_date, start_time, end_time, room, invigilator这十来张表的关系用一个类比来理解tb_course是菜单tb_schedule是餐厅的营业时间和座位安排tb_selection是客户下的单tb_grade是下单之后的口味评价。学生选课产生的记录在tb_selection成绩表单独存这样即使退课重新选历史成绩也不会被覆盖。设计成绩表时我特意把regular_score和exam_score分成两个字段而不是只存一个总评。原因有二一是教师录成绩时经常先录平时分后录期末分分开存方便分阶段录入二是后期如果要算“期末不及格但平时分很高的学生名单”分开查直接有效。总评分total_score是一个冗余字段由加权公式在Service层计算出来再写入查询时不需每次现算。冗余字段在这个数据量级上完全可以接受反而能显著提升成绩列表页的响应速度。4.2 选课冲突检测的实现思路选课系统里最有技术含量的功能就是冲突检测。我设置了一个上课时间段模型一周7天每天12节课每节课有对应的节次编号。一门课由若干节次的排课记录组成比如“周一34节、周四56节”。学生选课时要检查目标课的每个时间段是否和已选课程的时间段重叠。冲突判断的逻辑我前文提到了区间交叉条件这里补充设计的原因为什么不比较“开始节次相等”这种简单条件因为存在一门课是2节连上、另一门课是3节连上的情况两个区间部分重叠只比较头尾的等值条件会漏判。区间重叠判断则能覆盖所有场景。Java端我选择先把学生已选课程的时间段查出来在Service层遍历判断。这样代码逻辑直白SQL简单数据量小的时候性能也不差。如果要应对几千人同时抢课的并发场景就得考虑数据库层面加锁或者用Redis做预占位但这个体量的双技术栈课程设计项目先保证逻辑正确更实际。Django端用ORM区间查询解决同一个问题写出来的代码行数更少适合快速实现。4.3 成绩加权与绩点计算成绩模块除了增删改查还有一个藏在细节里的点总评成绩的计算规则不是固定的。有的课程是“平时30% 期末70%”有的是“平时40% 期末60%”体育课甚至是“考勤20% 项目40% 期末40%”。如果把这个比例写在Service层代码里硬编码换规则就得改代码。我建议把加权方案做成可配置最简单的做法是在tb_course表里加两个字段regular_ratio和exam_ratio录入课程时直接定好权重。计算总评时int totalScore Math.round(regularScore * regularRatio examScore * examRatio);Django版则写在模型的方法里def calculate_total(self): return round( self.regular_score * self.course.regular_ratio self.exam_score * self.course.exam_ratio, 0 )这样课程的基础数据调整后成绩自动按新规则计算不会出现规则变化导致的历史数据不一致。绩点计算我按照常见的4.0制标准来做90到100分对应4.085到89对应3.7以此类推。这个逻辑也要注意如果学校采用的是5.0制只需要改grade_to_points()一个方法的映射表其他统计代码不受影响。4.4 排课与考试安排的约束处理排课模块看起来简单仔细想想其实是个小型的约束满足问题。教室不能在同一时间段被两门课占用教师不能在同一时间段上两门课班级也不能同一时间出现在两个不同的教室。在课程设计这个体量下我不打算写一个智能排课算法而是做成“排课冲突检测”。在Java端录入排课时走一个校验流程先按教室查该时间段的排课记录再按教师查再按班级查。只要某一项查出已有记录就提示冲突不让保存。这三步校验合在一起基本能覆盖现实中“教室被占”、“老师赶场”、“学生撞课”三大问题。Django端我用了事务加select_for_update对目标教室和教师的时间段记录加锁防止两个管理员几乎同时录排课时互相覆盖。考试安排则直接复用排课的时间段模型把课程、教室、监考教师绑定到某一天的具体时间段。考虑一个实用性的限制条件同一个监考教师不能在同一时间出现在两个考场。这块我做了同排课类似的冲突检查。再补一个容易被忽略的点考试安排和平时上课时间冲突与否并不重要因为期末停课后才安排考试但同一个教室同一时段只能排一场考试是必须保证的。5. 环境配置、启动步骤与调试实录5.1 开发环境版本清单基于我实测过程中反复踩坑的经验先给你一张环境版本对照表照着这个组合来配会省掉很多莫名其妙的Error组件推荐版本JDK1.8不要直接用17老框架要么跑不起来要么要额外适配Maven3.6.xTomcat8.5.xMySQL5.7或者8.08.0要注意驱动名和时区参数Spring5.2.xMyBatis3.5.xPython3.8 或 3.10Django3.2 LTS如果需要新功能再用4.x前端Bootstrap 4.x jQuery强调一下JDK版本是最容易掉坑的。如果你的电脑上装的是JDK 17而项目用的Spring老版本还是基于Java 8编译直接跑大概率会遇到非法字符或者类版本不支持的问题。给课程设计用的环境稳妥比“用最新版”要重要得多。5.2 启动Java工程的步骤和容易踩的坑Java端项目拿到手之后第一步不是直接跑Tomcat而是先改数据库配置。找到jdbc.properties文件把jdbc.url、jdbc.username、jdbc.password改成你本地MySQL的连接参数。MySQL 8.0要特别注意URL里带上时区参数jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/edu_admin?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.password你的密码注意MySQL 8.0的驱动类换成了com.mysql.cj.jdbc.Driver是带cj的如果还在用旧的com.mysql.jdbc.Driver会直接报驱动类找不到或连接失败。另外驱动依赖也在pom.xml里检查一下8.0.x对应的连接器版本不能太老。然后执行项目里的db/edu_admin.sql脚本初始化表结构和测试数据。这里我建议直接用命令行source执行比Navicat里复制粘贴更不容易漏语句。确认表建出来并且有测试数据后在IDEA里把项目打成war包丢到Tomcat的webapps目录或者直接在IDEA里配置好Tomcat通过catalina.bat run启动。本地上跑起来后打开浏览器访问http://localhost:8080/edu_admin/login能看到登录页就说明SSM主链路通了。如果报404优先检查web.xml里配置的dispatcherServlet的url-pattern是不是/以及Maven依赖里有没有把war包插件配好。如果页面能打开但样式全丢十有八九是项目里的静态资源路径问题检查spring-mvc.xml里的mvc:resources映射或者模板里${pageContext.request.contextPath}是否漏写。5.3 启动Django工程的步骤Django端的启动比Java端省心得多。推荐先建一个虚拟环境再安装依赖python -m venv venv source venv/bin/activate # Windows下是 venv\Scripts\activate pip install -r requirements.txtrequirements.txt里的核心依赖就是Django、mysqlclient或pymysql、Pillow。如果用的MySQL建议装pymysql然后在项目__init__.py里写入pymysql.install_as_MySQLdb()能避免一堆编译问题。不要盲目装最新版Django3.2、4.x、5.x在一些API上有细微差异有时同一套代码在5.x里直接报错。然后执行迁移python manage.py makemigrations python manage.py migrate python manage.py createsuperuser python manage.py runserver 8000先访问http://127.0.0.1:8000/admin/用刚创建的超级用户登录看看能不能进管理后台。如果登录页打开了但CSS样式没加载多半是settings.py里的STATIC_URL和模板里加载静态资源的方式不匹配。解决方案是检查模板里是否写的{% load static %}以及static目录路径是否真的存在。如果manage.py migrate时报错django.db.utils.OperationalError优先检查settings.py里数据库连接的用户名密码和数据库名是否正确。5.4 造数据的小技巧系统要演示得好看光靠手工在页面上一条条加数据是不现实的。我建议在数据库层面直接造一批有规律的测试数据。学生数据的学号可以用循环生成比如一个班级30个人学号从20210001到20210030姓名随机生成。课程数据按公共课、专业课、选修课三种类型各造几门。成绩数据造的时候要注意同一门课一个班几十个人的成绩最好大致符合正态分布的随机数这样后期你写“成绩分布统计”功能时图表才会好看全是满分或全是及格线附近的分数看起来非常不真实。Django端造数据可以直接写一个management/commands/generate_data.py的自定义命令用Model.objects.create()批量生成装好后执行python manage.py generate_data一次搞定。Java端没有这种便捷通道就用SQL存储过程或一个初始化SQL脚本批量INSERT。脚本文件里最好每条INSERT语句后面带明确的取值范围注释方便答辩时临时改数据量大小。6. 常见问题与排查速查表6.1 一套Bug实录从404到500的排查路径项目调试过程中遇到的各种报错是最有价值的经验沉淀。我整理了一批高频问题覆盖两套技术栈下面这个速查表可以直接当排查手册用。现象可能原因排查方向启动Tomcat后访问8080端口直接404webapps里没有war包或部署路径和URL不匹配检查war包名称和访问路径是否一致JSP页面报The absolute uri错误JSTL标签库依赖缺失在pom.xml里补上jstl和standard依赖MyBatis报Invalid bound statementMapper接口和XML的namespace或方法名对不上逐个检查namespace和id是否匹配SpringMVC返回JSON乱码消息转换器没配UTF-8在spring-mvc.xml里配StringHttpMessageConverterMySQL连接报Public Key Retrieval错误MySQL8.0的加密规则和驱动不匹配URL加allowPublicKeyRetrievaltrueuseSSLfalseDjango显示403 ForbiddenCSRF校验失败检查表单里是否加了{% csrf_token %}Django访问/admin页面样式丢失没配置静态文件收集模板确保有{% load static %}确认static目录路径正确登录后Session失效跳回登录页Session超时或拦截器配置过滤了登录请求检查拦截器的排除路径是否包含登录接口和静态资源成绩总数对不上选课记录和成绩记录存在孤儿数据检查外键级联删除配置退课时只删选课记录、不删成绩分页页码点击后无数据当前页参数丢失或SQL里LIMIT计算错误断点检查页码参数传参确认页容量和偏移量算法6.2 数据库层面的独家避坑经验数据库是这类系统最容易出问题但最容易被忽视的层面有几个我自己实测下来很重要的经验。第一个是字符集问题建库时一定要显式指定utf8mb4字符集而不是默认的latin1不然后期插入中文姓名或专业名称会变成乱码甚至直接报“Incorrect string value”错误。初始化SQL脚本开头写清楚CREATE DATABASE edu_admin DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE edu_admin;第二个是多表联查时的排序字段选择。排名类和统计类查询我建议始终以主表ID加更新时间双字段排序不要只按业务字段排序。比如成绩排名按总分倒序时总分相同的情况下按学号升序保证每次查询结果顺序稳定否则分页场景下会出现“同一行记录出现在两页”的重复数据问题。第三个是外键的级联策略。在学生和选课记录之间、课程和成绩记录之间我建议全部使用限制删除RESTRICT而不是级联删除CASCADE。为什么因为教务系统的数据是强审计敏感的。一个学生刚退课管理员就把课程删掉了结果成绩表里留下了一条指向不存在课程的记录查询时LEFT JOIN会产生大量NULL字段。实际做法是删除课程前先检查有没有关联的选课或成绩数据有就给提示不直接删库。6.3 两套框架开发体验上的差异总结既然这个项目用两套技术栈实现了同样的业务我最后再聊几句开发中的体感差异这部分内容在答辩时用来回答“为什么做两套”这类问题很合适。SSM的开发节奏是“配置驱动”。一个业务模块的完成包括创建数据库表、写实体类、写Mapper接口和XML、写Service接口和实现类、写Controller、写JSP页面。每个环节都需要手动维护对应关系写起来确实繁琐但每一步都在加深对框架协作机制的理解。我强烈建议新手至少手动配一次完整的SSM项目再使用各种快速脚手架否则出了问题你可能完全找不到排查方向。Django的开发节奏是“约定优于配置”。模型定义好后数据库表、后台管理、表单校验甚至基础URL框架都帮你生成了。MVC中的Controller被view函数和URLconf替代ORM让大部分SQL不用手写。开发效率明显更高一周不到能把功能原型写得七七八八。但要警惕所谓的“Django魔法”框架替你做的事情越多你越要主动了解它背后的实现。比如ORM延迟加载、QuerySet惰性求值这些策略如果不了解可能在数据量大时踩很深的性能坑。这两套框架没有绝对的优劣只有适不适合。教务系统这种管理型网站Django确实开发起来更顺但SSM能帮你把所有底层细节都看清楚。两者都做过一遍之后再回头去看Spring Boot、MyBatis-Plus这些更现代的框架理解速度会快很多。最后再分享一点实际运行中的体会把两套系统真正跑起来之后你会发现学分制下最核心的痛其实是选课逻辑和成绩数据的联动。选课时间冲突检测能拦住一部分问题但真正检验系统健壮性的是“退课之后成绩表里的记录怎么办”“课程停开之后已选学生怎么处理”这样的边界场景。我把这些边界情况全部在文档里做了说明也对应实现了提示逻辑而不是简单地报个错就不管了。如果你的系统还要继续扩展我建议优先考虑加入“教学评价”“培养方案管理”这两个模块它们和现有的课程、成绩数据是天然联动的扩展成本小增量价值却很明显。
返回列表