ARTICLE DETAIL

资讯详情

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

SSM学生考勤管理系统实战:Maven整合、POI导出与EasyUI报表

SSM学生考勤管理系统实战:Maven整合、POI导出与EasyUI报表 1. 项目全景与核心痛点拆解1.1 为什么需要一套学生考勤管理系统做这个项目的起因其实很朴素。学校里老师的日常考勤要么靠课堂点名要么靠班长手工登记一个班四五十号人点名两分钟起步登记一张表还要手动统计出勤率、迟到了几次、旷课了几节一个学期下来这些纸质表格基本就压箱底了。如果课程多、班级多负责教学管理的老师核对考勤数据更是费时费力效率低还容易出错。说白了考勤这个环节并不是高深的业务但它的本质是高频、重复、数据分散这种场景就特别适合用一套信息化系统去替代手工操作。这个系统的定位很清晰面向教师和管理员解决课堂考勤数据的录入、维护、统计和导出问题。学生信息、课程信息、班级信息统一在后台维护考勤记录按课程按周次存储出勤、请假、迟到、缺勤这些状态一目了然按学期按班级可以自动汇总出勤率最后用图表展示趋势用Excel导出报表归档。整套流程下来原本一个学期末才统计一次的工作变成了随时打开系统就能实时查看的即时数据。技术栈选型也对应了这套需求SSM框架负责业务逻辑和前后端交互MySQL存储所有业务数据Maven管理项目依赖和构建流程POI负责导出Excel报表JFreeChart生成统计图表EasyUI提供后台管理界面的表格、树、表单组件JSP承载页面展示。这套组合是JavaWeb方向非常经典的“课程设计级”配置既能覆盖主流的技术知识点又能实际跑通完整业务闭环。这套系统适合谁看两类人。一类是做JavaWeb课程设计或毕业设计的在校学生另一类是刚入行想搞明白SSM整合、POI导出、图表渲染这些实操技术的初级开发。如果你已经在公司做Java开发这套项目的业务价值可能不算高但技术路径和细节处理仍然有参考意义尤其是Maven依赖管理和POI大数据量导出时踩过的那些坑放到真实项目里一样会遇到。1.2 系统核心模块与功能边界先给整个系统画一个轮廓不涉及具体代码先明确业务边界这样后面聊技术实现的时候你才知道为什么某些设计是这样做的。系统的角色分三种管理员、教师、学生。管理员维护基础数据——班级、学生、教师、课程、学期并且拥有全部模块的访问权限。教师负责具体的考勤操作可以按课程选择班级、按日期记录每个学生的出勤状态、补充请假和迟到备注还可以查看自己授课班级的统计结果。学生端功能最轻基本就是查看自己的出勤记录和考勤统计不涉及修改操作。核心业务模块可以拆成四块基础信息管理模块。班级、学生、教师、课程这四张基础表是整个系统的地基。课程要关联教师和班级学生要关联班级教务排课的数据结构其实就是一对多和多对一的关系映射。这块看起来简单但字段设计不好后面做统计查询时会非常痛苦。考勤记录模块。这是业务核心按“课程日期”维度创建一次考勤批次然后对班级每个学生逐条登记状态。状态字段用数字字典来存比如0代表正常出勤、1代表迟到、2代表请假、3代表旷课而不是直接存中文字符串。为什么数字占空间小、查询快、可扩展性强后面接统计逻辑也很干净前端再用字典映射回汉字显示就行。统计报表模块。按班级、课程、时间范围三个维度聚合考勤数据生成每个学生的出勤次数、缺勤次数、出勤率再汇总成图表。JFreeChart这里承担的是柱状图和饼图两类核心图表——班级出勤率对比、考勤状态分布。数据导出模块。用Apache POI把考勤记录和统计报表导出成Excel文件支持单表导出和多表打包导出。这一步实际上是很多学生项目容易忽略但实际最出彩的部分面试时也会被追问“导出大数据量是怎么处理的”后面专门讲。这四个模块都不复杂但它们像齿轮一样咬合在一起恰好覆盖了CRUD、关联查询、聚合统计、文件IO、图表渲染这几类JavaWeb开发中最常见的技术场景。把这个项目吃透等于把JavaWeb日常开发的主干路径走了一遍。2. 技术选型背后的权衡逻辑2.1 SSM框架组合的优势与适用场景SSM是指Spring、SpringMVC、MyBatis这三个框架的组合在几年前它就是JavaWeb开发的事实标准现在虽然Spring Boot已经是主流但SSM的理解依然是Spring家族的基础。为什么选SSM而不是SSHSpringStruts2Hibernate很简单Struts2的拦截器和配置繁琐Action类的写法绕来绕去开发体验远不如SpringMVC的注解式Controller干净。Hibernate的ORM虽然高度自动化但遇到复杂查询时HQL和SQL之间的来回妥协非常折磨人。MyBatis这种半自动ORMSQL自己写、参数自己映射对开发者来说反而是最大程度的掌控力。考勤系统的查询有大量多表关联和按条件筛选的场景用MyBatis的XML编写动态SQL来应对非常灵活。Spring在这套组合里扮演容器和事务管理的角色。Service层的业务方法加一个Transactional注解事务的开启、提交、回滚就全部交给Spring容器处理不需要手写Connection管理。SpringMVC负责请求分发Controller标注控制器RequestMapping配置URL映射JSON数据交互靠ResponseBody自动序列化。MyBatis负责数据持久化Mapper接口加XML文件一个SQL对应一个方法清晰到看一眼就知道这条数据是怎么查出来的。这套组合学习和排错路径非常清晰请求进来找ControllerController调ServiceService调MapperMapper执行SQL返回结果数据逐层向上传递再渲染到页面。任何一个环节出了问题唯一顺着调用链找就行。这种“链路式”架构对新手极其友好也是它至今依然是Java课程设计主流方案的原因。2.2 Maven在项目中的核心价值很多初学者不理解Maven到底在项目里扮演什么角色。打个比方如果你的电脑需要装各种软件每个软件又依赖特定的运行库版本还对不上手动一个个找、一个个装装完这个把那个搞坏了——你很快就会疯掉。Maven就是这个“软件管家”它通过pom.xml统一声明项目依赖自动从中央仓库下载对应版本的jar包再传递性地把间接依赖也一并搞定。在实际构建考勤系统的过程中Maven的价值体现在三个维度。第一依赖版本管理。SSM框架涉及的jar包数量极其庞大Spring有十多个模块jar再加上MyBatis、MyBatis-Spring整合包、Jackson、POI、JFreeChart、EasyUI的静态资源等等加起来三四十个依赖项。如果没有Maven你需要手动去下载每个jar并放到lib目录版本号稍有冲突就启动报错。有了Maven只需要在pom.xml里写下核心依赖坐标版本统一管理一处声明全局生效。第二项目标准化。Maven强制规定了项目目录结构src/main/java放Java源码src/main/resources放配置文件src/main/webapp放Web资源src/test/java放测试代码。所有使用Maven的JavaWeb项目长一个样你拿到任何一个人的Maven项目都不需要问“这段代码放哪”直接就能定位。第三生命周期管理。mvn clean清理编译产物mvn compile编译mvn test跑测试mvn package打包最后mvn tomcat7:run之类的插件命令还能直接启动Web应用。一条命令替代了手动设置Classpath、手动维护编译顺序这些杂事。不过这里必须提醒一个新手最容易踩的坑Maven依赖能导出到lib目录才能被Tomcat识别。JavaWeb项目部署到外部Tomcat时如果没有配置maven-war-plugin的copy操作或者没有用mvn package打出war包再部署而是直接用IDEA的Tomcat运行某些依赖可能不会被加载启动时就会抛ClassNotFoundException比如POI相关的类找不到。后面排查章节会再细讲。2.3 POI、EasyUI、JFreeChart的场景匹配这三个组件各管一段业务选型的逻辑都盯准了特定场景的需求。POI是Apache的开源库用来读写Microsoft Office格式文件在考勤系统里的角色是导出Excel报表。为什么不用CSVCSV导出简单但格式控制能力太弱合并单元格、设置列宽、加边框、设置数据格式这些操作CSV完全做不到。考勤统计表在实际使用场景里是需要按班级分sheet、按行展示每个学生各类出勤次数的这种排布用POI操作Workbook对象来控制非常顺手。EasyUI是一套基于jQuery的UI框架它提供的datagrid表格组件在考勤系统里撑起了最核心的数据展示页面。为什么选它而不是VueElementUI因为EasyUI是后端渲染友好的纯前端方案不需要Node.js环境直接把JS和CSS引入JSP页面就能工作数据交互靠JSON接口对接即可。对课程设计这种以“快速跑通”为目标的项目来说它的学习成本和使用成本都极低。当然如果你后续想转向前后端分离把它替换成Vue也不难毕竟后端接口已经做好了。JFreeChart是Java领域的经典图表库专门生成PNG/JPG图片格式的统计图表。在JSP页面中通过img标签引用后端动态生成的图表图片实现逻辑非常简单。为什么不直接用前端图表库比如ECharts因为选题要求的技术栈锁定的是Java体系而且JFreeChart是纯后端方案图片生成后作为静态资源输出即可不需要页面端再引入大量JS依赖在答辩演示时也更稳不必担心浏览器兼容问题。3. 数据库设计与核心模块细节3.1 表结构设计与关联关系数据库设计是这类项目最容易被忽略但又最影响后续开发的部分。建表之前先想清楚查询需求再反推表结构这个顺序不能反。考勤系统我规划了六张核心表按依赖层级排列学生表t_student字段包括主键id、学号student_no、姓名student_name、性别gender、班级id外键class_id、手机号phone、入学年份enroll_year。学号要加唯一索引因为学号是业务上的自然主键平时代码里到处要用它查询。这里有一个设计细节用自增id做表主键用学号做唯一索引的“逻辑主键”两者并存。为什么自增id作为外键引用时存储和索引效率更高而学号天然具备业务唯一性适合人工记忆和查询。班级表t_class字段包括班级id、班级名称class_name、所属专业major、入学年份grade_year、辅导员advisor。班级表相对简单但给后续统计“按班级维度汇总”提供分组依据。教师表t_teacher字段包括教师id、工号teacher_no、姓名teacher_name、职称title、手机号phone。课程表t_course字段包括课程id、课程名称course_name、课程编号course_code、授课教师id外键teacher_id、上课班级id外键class_id、上课周次weeks、上课节次period。授课教师和班级与课程的绑定关系做了冗余存储实际业务中一个课程可能对应多个班级为了简化模型这里采用一个课程记录对应一个教师和一个班级的方式对考勤系统来说足够用。考勤记录表t_attendance这是全系统的核心表。字段包括主键id、学生id外键student_id、课程id外键course_id、考勤日期att_date、节次period、状态status0出勤 1迟到 2请假 3旷课、备注remark、创建时间create_time。这张表需要加一个联合索引(course_id, att_date, student_id)因为最频繁的查询就是“某课程某天某学生是否已考勤”联合索引能直接命中。用户表t_user存登录账号、密码、角色类型。密码用MD5或者BCrypt加密存储绝对不能明文存。这个属于安全红线答辩时被老师问到一定要能答上来。这一套表设计完整个系统的数据流已经通了登录用户选择某门课程系统根据课程id查出绑定的班级id再查出该班级所有学生列表每个学生在考勤记录表里生成一条待登记记录教师逐条修改状态后保存。3.2 考勤状态的字典设计与统计逻辑刚才提到考勤状态用数字字典存储。这里解释一下为什么这么做。考勤状态本质是枚举语义。如果数据库里直接存“迟到”“请假”这种中文看起来直观但在代码里做判断时要拼字符串该写成“迟到.equals(status)”的地方一不小心就拼错了而且想增加一种状态“早退”时必须改表数据。用数字字典之后代码里全是对int常量的比较比如直接定义一个状态枚举类AttendanceStatus里面写NORMAL0, LATE1, LEAVE2, ABSENT3Java代码里引用这个常量即可。页面显示时通过前端映射或后端DTO转换把0123转成中文标签。统计逻辑上出勤率的计算方式是出勤率实际出勤次数/应出勤总次数。实际出勤次数的口径要斟酌一下迟到算不算出勤不同学校定义不同。我这边设了个简单口径迟到和请假都不算出勤旷课更不算。也就是说出勤次数只统计status为0的记录应出勤总次数是课程计划周次乘以每周课时数。这样统计公式就非常干净出勤率 该学生状态为0的记录数 / 该课程应考勤总次数 * 100%查询时可以用一条SQL聚合搞定。比如统计某课程下所有学生的出勤概况SELECT s.student_no, s.student_name, COUNT(*) AS total_days, SUM(CASE WHEN a.status 0 THEN 1 ELSE 0 END) AS normal_days, SUM(CASE WHEN a.status 1 THEN 1 ELSE 0 END) AS late_days, SUM(CASE WHEN a.status 2 THEN 1 ELSE 0 END) AS leave_days, SUM(CASE WHEN a.status 3 THEN 1 ELSE 0 END) AS absent_days FROM t_attendance a LEFT JOIN t_student s ON a.student_id s.id WHERE a.course_id #{courseId} GROUP BY s.id, s.student_no, s.student_nameSUM(CASE WHEN ... THEN 1 ELSE 0 END)这种写法是SQL里做条件计数的经典方式比查出来数据再到Java里循环统计要高效得多也优雅得多。很多人写统计代码喜欢先把明细捞出来遍历一遍再累加数据量小还好数据量一大性能立刻露馅。3.3 事务边界与数据一致性控制考勤记录操作有一个典型的数据一致性场景批量登记。一次考勤批次会同时插入或更新一个班级三四十名学生的考勤状态如果逐条操作、没有事务包裹插入到一半数据库挂了或者接口报错就会出现部分学生有考勤记录、部分学生没有记录的“半截数据”这是绝对不能接受的。解决办法就是在Service层方法上加Transactional注解。比如批量保存考勤的方法Transactional(rollbackFor Exception.class) public void batchSaveAttendance(ListAttendance attendanceList) { for (Attendance attendance : attendanceList) { if (attendance.getId() ! null) { attendanceMapper.updateByPrimaryKey(attendance); } else { attendanceMapper.insert(attendance); } } }rollbackFor Exception.class这个属性很关键。Spring默认只在遇到RuntimeException时回滚如果方法体里抛的是普通Exception或者自定义业务异常事务是不会回滚的。所以务必要显式指定rollbackFor Exception.class确保任何异常都触发数据库回滚。另外要注意的一点是批量操作不要用循环里逐条select再逐条insert的方式性能太差。更优做法是MyBatis的批量插入在Mapper接口里定义一个传入List的方法XML里用foreach标签拼接批量INSERT语句一次数据库交互搞定所有记录。40条记录逐条插入需要40次数据库交互批量插入只需要一次性能差距在真实场景大数据量时会非常明显。4. 核心模块的实操过程与实现要点4.1 项目搭建与SSM整合步骤从零搭建一个SSMMaven项目很多人第一次搞会踩一堆配置坑。这里把完整步骤走一遍每一步都说清楚为什么。**第一步创建Maven Web项目。**在IDEA里选择Maven Archetype的maven-archetype-webapp模板创建项目这个模板会直接生成标准的Web目录结构。如果不用模板就需要手动创建src/main/webapp/WEB-INF/web.xml目录结构比较麻烦。创建后记得在project structure里把webapp目录标记为Web资源目录把src/main/resources标记为资源目录。**第二步配置pom.xml核心依赖。**这个文件是整个项目的基石在项目开发过程中依赖会不断追加但起始阶段至少要包含以下几组Spring核心spring-context、spring-webmvc、spring-jdbc、spring-txMyBatis及其整合包mybatis、mybatis-spring数据库驱动mysql-connector-java连接池druid或c3p0用DruidServlet和JSP相关javax.servlet-api、jstlJSON序列化jackson-databindPOI和JFreeChart在开发到导出和图表模块时再加一开始就全加上问题也不大。Maven坐标中版本号要选稳定的比如Spring就用4.3.20.RELEASE这也是很多教程的老版本搭配稳定可靠。选太新的版本可能导致MyBatis-Spring版本不兼容反正课程设计求稳不求新。**第三步配置web.xml。**SSM项目里web.xml需要配置三样东西Spring容器监听器ContextLoaderListener、SpringMVC的前端控制器DispatcherServlet、字符编码过滤器CharacterEncodingFilter。字符编码过滤器必须放在最前面并且映射/*否则请求参数中文乱码会一路传染到数据库。为什么编码问题这么突出因为浏览器默认用UTF-8提交表单Tomcat 8之前默认用ISO-8859-1解析POST请求参数两者不一致就乱码了。虽然Tomcat 8之后支持UTF-8但显式配置过滤器是稳妥做法属于JavaWeb项目的必备安全网。**第四步配置spring.xml和spring-mvc.xml。**严格来说Spring和SpringMVC可以共用配置文件但为了职责清晰通常拆成两个文件。spring.xml里配置组件扫描扫描Service和Dao、数据源、SqlSessionFactoryBean、Mapper扫描器、事务管理器。spring-mvc.xml里配置组件扫描扫描Controller、注解驱动、视图解析器、静态资源放行。注意两个配置文件扫描包的范围不要重叠否则会出现Controller被Spring容器管理两次的问题。干净的做法spring.xml只扫描com.xxx.servicespring-mvc.xml只扫描com.xxx.controller。**第五步配置Druid数据库连接池和jdbc.properties。**数据库连接信息建议放在properties文件里用context:property-placeholder标签加载这样切换数据库时只改配置文件不动代码。Druid的连接池参数里初始连接数、最大活跃连接数这些按默认即可验证SQL可以配select 1防止数据库连接被防火墙断开后应用不知道。**最后一步写一个测试接口验证整合成功。**先创建一个简单的Controller返回JSON启动Tomcat访问对应的URL看到数据就说明SpringMVC通了再写一个Mapper查询学生表用JUnit测试跑一下能查到数据说明MyBatis和数据库通了。每整合一层验证一层不要等所有配置写完再启动排查那样报错范围太大新手会崩溃。4.2 登录认证与权限控制实现考勤系统涉及三种角色权限控制必须分级别。简单做法是在自定义拦截器里校验登录状态和角色类型。SSM项目里实现登录认证的经典组合是HandlerInterceptor Session。自定义一个LoginInterceptor重写preHandle方法在方法里取Session中的登录用户对象如果为空就重定向到登录页不为空就继续放行。角色控制上可以再写一个AuthInterceptor或者直接在拦截器中配置不同的URL匹配规则/admin/**开头必须管理员权限才能访问教师和学生访问则拒绝。/teacher/**教师和管理员都可以访问。/student/**登录用户都能访问。Spring MVC中配置拦截器的地方是在spring-mvc.xml里mvc:interceptors mvc:interceptor mvc:mapping path/admin/**/ bean classcom.xxx.interceptor.AdminInterceptor/ /mvc:interceptor /mvc:interceptors有一个容易漏的点/login、/css/**、/js/**、/images/**这些路径要放行否则登录页面都打不开静态资源也会被拦截。静态资源还要配合mvc:resources mapping/js/** location/static/js//手动映射目录。这些细节排查起来很费时所以配置时就要想到。看了很多学生项目最典型的权限漏洞就是“只在前端隐藏按钮后端接口没有任何校验”。比如普通学生直接访问/admin/user/list的URL结果就能看到所有用户信息这就是严重的安全缺陷。后端校验必须做在拦截器层而不是前端页面上这条是我反复强调的安全底线。4.3 POI导出Excel的完整实现POI是这套系统里最有说头的一块也是面试时很容易被追问的技术点。考勤报表导出的完整流程分三步创建Workbook、填充数据与样式、输出到响应流。先说核心的依赖导入pom.xml里加dependency groupIdorg.apache.poi/groupId artifactIdpoi/artifactId version3.17/version /dependency dependency groupIdorg.apache.poi/groupId artifactIdpoi-ooxml/artifactId version3.17/version /dependency有的教程会建议用poi-ooxml和poi-ooxml-schemas其实只要poi和poi-ooxml两个就够用了。3.17版本是稳定版兼容JDK8。再看导出的核心代码逻辑。比如要导出一个班级某课程的考勤汇总表大概这样组织public void exportAttendanceSummary(HttpServletResponse response, Integer courseId) throws Exception { ListMapString, Object dataList attendanceMapper.selectAttendanceSummary(courseId); String fileName 考勤统计表_ System.currentTimeMillis() .xls; // 设置响应头告诉浏览器以下载方式处理 response.setContentType(application/vnd.ms-excel;charsetutf-8); response.setHeader(Content-Disposition, attachment;filename URLEncoder.encode(fileName, UTF-8)); // 创建工作簿、工作表 Workbook workbook new HSSFWorkbook(); Sheet sheet workbook.createSheet(考勤统计); // 创建表头行 Row header sheet.createRow(0); String[] headers {学号, 姓名, 应出勤次数, 正常出勤, 迟到, 请假, 旷课, 出勤率}; for (int i 0; i headers.length; i) { header.createCell(i).setCellValue(headers[i]); } // 填充数据行 for (int i 0; i dataList.size(); i) { Row row sheet.createRow(i 1); MapString, Object item dataList.get(i); row.createCell(0).setCellValue(item.get(student_no).toString()); row.createCell(1).setCellValue(item.get(student_name).toString()); // ...其余单元格 } // 列宽自适应 for (int i 0; i headers.length; i) { sheet.autoSizeColumn(i); } workbook.write(response.getOutputStream()); workbook.close(); }这里每个细节都有门道。response.setContentType(application/vnd.ms-excel)是告诉浏览器这是一个Excel文件如果漏掉浏览器可能直接用文本格式打开导出结果全是一串乱码。Content-Disposition里的attachment代表以附件形式下载不设置的话浏览器可能会尝试用插件打开。文件名用URLEncoder.encode处理是因为中文文件名在HTTP头里传输必须URL编码不处理的话Chrome可能显示乱码文件名。还有一个常见问题导出时列太多或者单元格内容太长autoSizeColumn会抛异常或导致性能极低。列数多时建议不调autoSizeColumn而是手动调setColumnWidth。另外sheet.autoSizeColumn(i)在数据量超过几百行时会明显卡顿因为POI需要逐个计算字符宽度。报表导出量大时直接固定列宽更实际。这种模式下HSSFWorkbook就能满足需求了存储上限是65536行。如果数据量巨大超过这个上限就要用XSSFWorkbook支持百万行但更耗内存或者用SXSSFWorkbook流式写入这是另一个进阶话题后面扩展讲。4.4 JFreeChart统计图表生成与页面嵌合JFreeChart把Java后端生成图表的流程展示得淋漓尽致原理流程是后端根据统计结果构造数据集然后调用ChartFactory创建图表对象再把图表对象编码成图片字节流输出到响应最后页面的img标签直接引用这个URL。关键代码如下RequestMapping(/chart/classAttendanceRate) public void classAttendanceRate(Integer courseId, HttpServletResponse response) throws Exception { // 1. 查询统计数据每个班级的出勤率 ListMapString, Object rateList attendanceMapper.selectClassAttendanceRate(courseId); // 2. 构造JFreeChart数据集 DefaultCategoryDataset dataset new DefaultCategoryDataset(); for (MapString, Object item : rateList) { dataset.addValue( Double.valueOf(item.get(rate).toString()), 出勤率, item.get(class_name).toString() ); } // 3. 生成柱状图对象 JFreeChart chart ChartFactory.createBarChart( 班级出勤率对比图, 班级, 出勤率(%), dataset, PlotOrientation.VERTICAL, true, true, false ); // 4. 设置图片质量与尺寸 chart.setAntiAlias(true); ChartUtils.writeChartAsPNG(response.getOutputStream(), chart, 800, 400); }页面那块就极度简单了JSP里放一个img标签img src${pageContext.request.contextPath}/chart/classAttendanceRate?courseId${courseId} alt出勤率柱状图/这里有个实际操作要点每次页面刷新都会重新请求后端生成一次图表。在高并发场景下这会成为性能瓶颈所以课程设计层面无所谓真正生产环境应该把图表生成的图片缓存起来或者写成定时任务提前生成静态图片。另外ChartUtils.writeChartAsPNG这个方法的输出流用完要记得关否则多次刷新页面后会出现文件句柄耗尽的问题Windows系统上表现尤其明显。饼图的实现逻辑和柱状图几乎一致只是换一个createPieChart方法、换一个DefaultPieDataset数据集。JFreeChart的API设计非常统一学一个类型其余都能举一反三。4.5 EasyUI数据表格对接后端JSON接口EasyUI的DataGrid组件是这套后台管理界面的门面担当几乎所有列表页面都由它来承载。它的工作方式很简单给datagrid指定一个url属性框架就会自动向这个地址发Ajax请求然后从返回值里取数据渲染表格。EasyUI的DataGrid需要特定格式的JSON数据{total: 100, rows: [{...}, {...}]}。total是总记录数rows是当前页数据列表。所以后端的列表查询接口必须返回这种结构。SSM里结合PageHelper分页插件来实现非常方便先在pom.xml里加入PageHelper依赖注意版本要和MyBatis版本兼容然后配置一个PageHelper拦截器插件查询前调用PageHelper.startPage(pageNum, pageSize)后面的第一条SQL查询就会自动带上LIMIT并且返回的Page对象里包含getTotal()方法。Controller返回值的写法RequestMapping(/student/list) ResponseBody public MapString, Object studentList(Integer page, Integer rows, String keyword) { PageHelper.startPage(page, rows); ListStudent studentList studentMapper.selectByCondition(keyword); PageInfoStudent pageInfo new PageInfo(studentList); MapString, Object result new HashMap(); result.put(total, pageInfo.getTotal()); result.put(rows, pageInfo.getList()); return result; }这个分页模式是EasyUI后端接口对接的标准姿势。前端页面的datagrid初始化代码$(#dg).datagrid({ url: student/list, method: get, fitColumns: true, striped: true, rownumbers: true, pagination: true, pageSize: 10, pageList: [10, 20, 50], columns: [[ { field: studentNo, title: 学号, width: 100 }, { field: studentName, title: 姓名, width: 100 }, { field: className, title: 班级, width: 120 }, { field: enrollYear, title: 入学年份, width: 80 } ]], toolbar: #toolbar });核心的注意点前端字段名必须和后端返回值里的key一一对应。比如后端返回的是studentNo驼峰命名前端field写studentNo就能对上。如果用_命名如student_no前端field也要写student_no。建议统一风格Java里用驼峰JSON序列化后保持驼峰前端field也是驼峰对应关系最不易出错。还有一个实际改动得很频繁的细节如果类属性是驼峰数据库字段是下划线MyBatis的mapUnderscoreToCamelCase设置为true之后查询能自动把student_no映射为studentNo。这个配置在SqlSessionFactoryBean的configuration里设置property nameconfiguration refmybatisConfig/对新手来说光这一个配置就能省掉一大片resultMap手写映射代码简洁很多。4.6 响应式前端布局与JSP页面结构JSP在这套系统里是页面模板主载体。后台管理页面的整体布局用EasyUI的layout组件左右上下分区左侧菜单树、顶部标题栏、右侧Tab选项卡承载各个功能页面。tabs组件在点击菜单时动态开启一个新的tabURL指向对应模块的JSP页面再配合iframe嵌入页面的方式实现“菜单切换页面不变”的经典后台布局。具体操作比如在左侧菜单树中加一条“考勤管理”定义一个onSelect事件在事件里tabs组件的add方法打开新tab。代码类似$(#menuTree).tree({ onClick: function(node) { if (node.attributes node.attributes.url) { addTab(node.text, node.attributes.url); } } }); function addTab(title, url) { if ($(#tabs).tabs(exists, title)) { $(#tabs).tabs(select, title); } else { $(#tabs).tabs(add, { title: title, href: url, closable: true, bodyCls: tab-body }); } }这里要小心一个常见问题href加载的是一个独立JSP页面如果这个页面中也引用了EasyUI的脚本文件会出现重复加载的情况极端状况下会报JS错误。建议通用脚本放到主布局页面里子页面只写HTML和业务JS不重复引框架资源。样式上EasyUI的主题色默认是蓝色可以在EasyUI的themes目录下换其它主题比如gray或metro。表格条纹、行号这些通过属性控制不用写额外CSS比其他UI框架省事在这一点上体现明显。5. 常见问题与排查技巧实录5.1 启动类问题排查速查整个项目在开发过程中总会遇到各类启动或运行异常这里把最常见的几个问题和排查路径整理成速查表。这些坑我全部自己踩过列出来供你排查时对照。启动Tomcat报ClassNotFoundException或NoClassDefFoundError。多半是Maven依赖没有部署到Tomcat的WEB-INF/lib目录。右键项目选择“Open Module Settings”看Artifacts标签页的输出目录里有没有完整的jar包。没有的话在Artifacts上选择“Output Layout”右键“Put Into Output Root”把webapp作为归档根。IDEA的Tomcat运行时经常因为Artifacts配置缺失导致依赖没随项目部署。启动报Error creating bean with name xxxMapper。大概率是MyBatis的Mapper XML文件没有扫描到。检查spring.xml里MapperScannerConfigurer的basePackage是否指向了正确的包路径还要检查SqlSessionFactoryBean的mapperLocations属性是否配置了classpath:mapper/*.xml。Mapper接口和XML文件分处不同目录时这个配置最容易出问题。请求接口报HTTP 406错误。Controller返回对象期望SpringMVC转成JSON但406代表请求的Accept头与返回的ContentType不匹配。大概率是缺少Jackson依赖或者mvc:annotation-driven没配置。配置了这个标签之后SpringMVC自动配置Jackson消息转换器JSON序列化才能生效。JSP页面EL表达式显示不出值或者直接输出${xxx}文本。原因是web.xml的Servlet版本声明太低比如声明为2.3版本默认不支持EL表达式。把web.xml根节点的版本声明改到3.1或4.0同时确认使用了对应的Servlet API版本。数据库中文乱码。整条链路都有可能出现乱码排查顺序是浏览器页面的encoding设置、Tomcat的URIEncoding、JDBC连接串的characterEncodingUTF-8、MySQL表本身的DEFAULT CHARSETutf8。任何一环断了都会看到乱码。使用全表统一的UTF-8能解决问题绝大多数场景。5.2 数据与导出模块的常见异常数据层面的问题比启动层面的更隐蔽我这里记录几个高频事故。批量插入考勤记录时SQL超长。foreach批量INSERT在数据量只有几十条时完全没问题但如果一个批次的学生数量超过几百拼接的SQL可能会超过MySQL的max_allowed_packet限制报Packet for query is too large。解决办法是分片批量插入——每500条提交一次。POI导出后Excel里中文是乱码。Excel文件里的中文乱码通常不是编码过滤器的问题而是JVM默认字符集和Excel预期不一致。写单元格值时确保代码源文件是UTF-8读取数据时字符串本身不出现乱码。如果是CSV导出中文乱码则要在输出流前加BOM头\uFEFF。POI直接写Word/Excel文件一般不存在这个问题。JFreeChart生成的图表中文显示为方块。这是Java图形环境缺少中文字体导致的JFreeChart默认使用的字体不支持中文。解决办法是设置图表标题和坐标轴的中文字体Font cnFont new Font(宋体, Font.PLAIN, 12); chart.getTitle().setFont(new Font(宋体, Font.BOLD, 16)); CategoryPlot plot (CategoryPlot) chart.getPlot(); plot.getDomainAxis().setLabelFont(cnFont); plot.getRangeAxis().setLabelFont(cnFont);Linux服务器上如果没装中文字体包Java里画中文全都变豆腐块设置字体名称后还需要确认系统确实安装了对应字体。导出的Excel文件无法打开提示格式错误。先用文本编辑器打开文件看首部字节正常XLS文件应当是D0 CF 11 E0开头XLSX文件是50 4B开头就是一个ZIP包。如果输出的是HTML或JSON文本说明后端接口没有按二进制方式输出。还有一种情况代码在workbook.write(outputStream)之前就把response的ContentType设成了text/html自然打不开。5.3 排查思路与日志分析方法最后一个部分分享一套普适性的排查方法论。做JavaWeb项目最忌瞎猜乱试。我的排查习惯永远是从外到内、从网络到代码、从日志到数据。先看请求到底有没有到达后端。打开浏览器的开发者工具看Network面板里的请求状态码——404说明URL路径或Controller映射错了405说明请求方式不对500说明后端代码抛异常。再去看Tomcat的控制台日志找到异常堆栈的第一行Caused by那个才是问题的根因。初学时常犯的错是只看异常信息最后一段但其实很多信息就在第一行要定位到直接原因。拿到异常之后优先检查三个点依赖缺失、配置错误、空指针。这三个一起排查能解决90%的问题。至于更复杂的场景比如Spring事务不生效就要检查三个条件事务管理器是否配置、Transactional是否加在了public方法上、是否通过代理调用同类内部调用this.xxx()是无效的。这几乎是所有SSM项目的“通病三问”。我自己带过很多新人看到报错就慌、就去百度搜片段代码的情况太常见了。其实一旦养成“先看日志、再理链路、最后动手改”的习惯项目的开发速度会快很多排查问题的时间甚至可以缩短一半。6. 从经验角度聊聊这套系统的价值做这套学生考勤管理系统表面上是完成一个课程设计实际上是把JavaWeb方向的核心技能串成了一条线。CRUD谁都会写但能把数据设计、权限控制、批量事务、报表导出、图表渲染这些点有机组织在一个系统里才是真正的能力体现。如果要用一句话总结做这个项目最有价值的收获我认为是学会了**“为一个真实的业务场景设计数据结构和代码结构”**。很多人在学习框架时是零散的今天学Spring明天学MyBatis每个框架的Demo都能跑通但一遇到综合项目就不知道怎么组织代码。考勤系统逼迫你从表结构开始思考然后设计接口、实现业务、对接前端走完一个完整的软件生产链路。这条链路走一遍比你单独看十篇框架教程都管用。最后再分享一个小技巧。做完这个项目之后可以试着把它往Spring Boot方向迁移一下。SSM的手动配置是学习过程中必要的痛苦让你真正理解每个配置项的意义而Spring Boot的自动化配置则让你体会到约定优于配置的便利。两个版本都做过一遍你对JavaWeb项目的理解就会从“会写”升级到“懂原理”。这个项目作为起点后续扩展方向还有Shiro权限框架、Redis缓存、Vue前后端分离改造、WebSocket实时考勤推送等等每一步都是能力升级的阶梯。
返回列表