
简介这份资源是面向高校计算机相关专业毕业设计场景的Java土地档案管理系统完整交付包适合正在准备毕设选题、需要参考企业级Web应用开发流程的本科生与自学者。内容涵盖项目报告、答辩PPT、源代码、数据库脚本与部署视频可帮助读者从需求分析、系统设计到编码实现、数据库管理直至生产环境部署完整走通一个Java Web项目的生命周期。压缩包整体约136.21MB以zip形式打包内含项目报告、答辩演示文稿、Java源代码、数据库脚本及部署录屏等主要文件类型分别对应文档说明、答辩准备、功能实现、数据建模与环境搭建等用途。目前已有373人学习关注。通过研读源代码可深入理解类设计、方法编写与异常处理技巧项目报告与PPT则梳理了功能模块、技术选型与系统架构部署视频进一步演示了Tomcat配置、数据库导入等关键环节对提升实际开发与答辩准备能力具有参考价值。1. 从一份 zip 说起土地档案管理系统到底在管什么土地档案管理系统说白了就是把纸质土地登记簿、权属来源证明、宗地图、审批表这些材料从档案柜搬进数据库再用一套流程把它们管起来。它要解决的核心问题有三个一是档案条目能被快速检索二是权属变更能留下可追溯的痕迹三是不同角色档案管理员、审批人、普通查询者看到的数据范围不一样。这套东西放在毕业设计里恰好覆盖了 Java Web 开发的主要知识点MVC 分层、数据库设计、权限控制、文件上传下载、报表导出。很多人拿到这个题目第一反应是不就是增删改查吗真动手才发现坑在细节里宗地编号怎么设计主键、档案附件怎么存、历史版本怎么留、并发修改怎么防。这篇笔记按能跑起来、能答辩、能讲清楚的标准把从环境搭建到部署上线的完整路径拆开讲适合正在做计算机毕业设计、需要一套可复现方案的同学也适合想拿它当 Java Web 练手项目的开发者。2. 技术选型与数据库设计别让表结构在答辩时被问穿2.1 为什么选 SSM 而不是 Spring Boot 一把梭毕业设计的技术栈选择第一原则是你能讲清楚每一层在干什么。SSMSpring Spring MVC MyBatis虽然配置繁琐但分层清晰答辩老师问请求怎么从 Controller 走到 Mapper你能一条线答完。Spring Boot 省配置但自动装配的魔法太多被追问这个 Bean 什么时候注入的容易卡壳。我一般推荐的组合是Spring 5 Spring MVC MyBatis 3 MySQL 8 JSP 或 Thymeleaf Maven。JDK 用 1.8 或 11 都行Tomcat 8.5 以上。前端不用上 VueJSP jQuery Bootstrap 足够重点是后端逻辑完整。如果你的学校明确要求前后端分离那就 Spring Boot Vue但数据库设计和业务逻辑是一样的。依赖清单里几个关键项spring-webmvc、mybatis-spring、mysql-connector-java、druid连接池、commons-fileupload附件上传、poi导出 Excel、jstl。版本号不要追新选稳定版避免和 Tomcat 冲突。2.2 土地档案的库表怎么拆六张核心表土地档案系统的数据模型核心是一宗地对应多份档案一份档案对应多个附件。拆表的时候要区分业务主表和辅助表不然后期查询全是 join性能和维护都难受。表名作用关键字段t_land_parcel宗地基本信息parcel_id, parcel_code, location, area, use_type, owner_namet_archive档案主表archive_id, parcel_id, archive_type, status, create_timet_archive_attachment档案附件attach_id, archive_id, file_name, file_path, upload_timet_archive_history变更历史history_id, archive_id, change_type, old_value, new_value, operatort_user用户user_id, username, password, real_name, role_idt_role角色权限role_id, role_name, permissionsparcel_code是宗地编号建议用行政区划码 年份 顺序号的规则生成比如32010220240001不要用自增 ID 当业务编号否则数据迁移时对不上。archive_type用字典表或枚举存比如初始登记、变更登记、抵押登记、注销登记。建表 SQL 里几个容易忽略的点area用DECIMAL(12,2)不要用FLOAT面积算错是硬伤create_time用DATETIME并设默认值CURRENT_TIMESTAMP外键约束在毕业设计里可以加但生产环境很多团队会去掉靠应用层保证一致性答辩时你可以主动提这一点显得有思考。CREATE TABLE t_land_parcel ( parcel_id BIGINT PRIMARY KEY AUTO_INCREMENT, parcel_code VARCHAR(32) NOT NULL UNIQUE COMMENT 宗地编号, location VARCHAR(255) NOT NULL COMMENT 坐落, area DECIMAL(12,2) NOT NULL COMMENT 面积(平方米), use_type VARCHAR(32) COMMENT 用途, owner_name VARCHAR(64) COMMENT 权利人, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_parcel_code (parcel_code) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这段 SQL 里utf8mb4是必须的土地档案里经常有生僻字地名utf8存不进去会报错。INDEX idx_parcel_code是因为按编号查询是最频繁的操作不加索引数据量上万后查询会明显变慢。2.3 附件存储存路径还是存 BLOB档案附件扫描件、PDF、宗地图的存储方式有两种存文件路径或存 BLOB 二进制。毕业设计推荐存路径文件放服务器本地目录或webapp/upload下数据库只存相对路径。原因是 BLOB 会让数据库体积暴涨备份和迁移都痛苦而且 MyBatis 处理大字段容易出内存问题。目录结构建议按日期分片upload/2024/06/15/uuid_filename.pdf避免单目录文件过多导致ls都卡。文件名用 UUID 重命名原始文件名存在数据库里下载时再还原。这样既避免中文文件名乱码也防止同名覆盖。3. 核心功能实现从登录到档案归档的完整链路3.1 登录与权限拦截器别把权限写在每个 Controller 里权限控制最常见的翻车写法是在每个 Controller 方法里写if (user.getRole() ! ADMIN) return error;代码重复且容易漏。正确做法是用 Spring 拦截器 自定义注解。先定义一个注解RequirePermission(archive:edit)然后在拦截器里读取当前登录用户的权限集合判断是否包含注解声明的权限。用户权限在登录时从t_role表查出存进 Session。public class AuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception { if (!(handler instanceof HandlerMethod)) return true; HandlerMethod hm (HandlerMethod) handler; RequirePermission rp hm.getMethodAnnotation(RequirePermission.class); if (rp null) return true; // 没标注的方法默认放行 User user (User) req.getSession().getAttribute(loginUser); if (user null) { resp.sendRedirect(req.getContextPath() /login); return false; } if (!user.getPermissions().contains(rp.value())) { resp.setStatus(403); resp.getWriter().write(no permission); return false; } return true; } }拦截器注册时注意排除登录页、静态资源路径否则 CSS 和 JS 也会被拦。preHandle返回false后请求就终止了所以重定向和写响应只能做一次。这个模式的好处是新增接口时只要加注解不用改拦截器代码。3.2 档案录入与附件上传一次请求搞定主表和附件档案录入的典型场景是用户填一张表单宗地信息 档案基本信息同时上传多个附件点提交后主表和附件表都要写入。这里的关键是事务控制——主表插入失败附件不能落库附件上传失败主表要回滚。实现上用MultipartHttpServletRequest接收文件先保存文件到磁盘再在同一个Transactional方法里插入主表和附件记录。注意文件保存是 IO 操作不在事务范围内所以如果数据库回滚了磁盘上的文件要手动删掉否则会留下孤儿文件。Transactional(rollbackFor Exception.class) public void saveArchive(ArchiveForm form, MultipartHttpServletRequest request) throws IOException { // 1. 插入档案主表 Archive archive new Archive(); BeanUtils.copyProperties(form, archive); archiveMapper.insert(archive); // 插入后 archive.getId() 可用 // 2. 处理附件 ListMultipartFile files request.getFiles(attachments); ListString savedPaths new ArrayList(); try { for (MultipartFile file : files) { if (file.isEmpty()) continue; String ext FilenameUtils.getExtension(file.getOriginalFilename()); String storedName UUID.randomUUID() . ext; String datePath new SimpleDateFormat(yyyy/MM/dd).format(new Date()); File dest new File(uploadRoot / datePath / storedName); dest.getParentFile().mkdirs(); file.transferTo(dest); savedPaths.add(dest.getAbsolutePath()); ArchiveAttachment att new ArchiveAttachment(); att.setArchiveId(archive.getId()); att.setFileName(file.getOriginalFilename()); att.setFilePath(datePath / storedName); attachmentMapper.insert(att); } } catch (Exception e) { // 数据库会回滚但磁盘文件要手动清理 for (String p : savedPaths) new File(p).delete(); throw e; } }参数说明uploadRoot从配置文件读取不要硬编码rollbackFor Exception.class确保受检异常也回滚transferTo在 Spring 里如果目标文件已存在会抛异常所以 UUID 命名很关键。这段代码答辩时是加分项因为它体现了事务边界和资源清理的意识。3.3 档案检索多条件组合查询怎么写才不臃肿档案检索通常支持按宗地编号、权利人、档案类型、时间范围组合查询。用 MyBatis 的动态 SQLwhereif是最简洁的方案不要用字符串拼接SQL 注入是答辩必问的安全点。select idsearchArchives resultMapArchiveMap SELECT a.*, p.parcel_code, p.owner_name FROM t_archive a LEFT JOIN t_land_parcel p ON a.parcel_id p.parcel_id where if testparcelCode ! null and parcelCode ! AND p.parcel_code LIKE CONCAT(%, #{parcelCode}, %) /if if testownerName ! null and ownerName ! AND p.owner_name LIKE CONCAT(%, #{ownerName}, %) /if if testarchiveType ! null and archiveType ! AND a.archive_type #{archiveType} /if if teststartTime ! null AND a.create_time gt; #{startTime} /if if testendTime ! null AND a.create_time lt; #{endTime} /if /where ORDER BY a.create_time DESC LIMIT #{offset}, #{pageSize} /selectLIKE CONCAT(%, #{}, %)是预编译参数安全且能走索引前缀模糊查询走不了索引但毕业设计数据量下无所谓。LIMIT分页在数据量大时深分页会慢优化方案是用上次查询的最大 ID做游标但答辩时讲清楚LIMIT的局限就够了。3.4 变更历史留痕档案修改不能覆盖原值土地档案的特殊性在于历史可追溯。一份档案被修改旧值不能直接丢掉要写进t_archive_history。实现方式有两种一是手动在 Service 层对比新旧值写历史二是用数据库触发器。推荐第一种因为触发器对调试不友好而且业务逻辑散落在数据库里不好维护。public void updateArchive(Archive newVal, Long operatorId) { Archive old archiveMapper.selectById(newVal.getId()); if (old null) throw new BizException(档案不存在); // 逐字段对比只记录变化的字段 ListArchiveHistory histories new ArrayList(); if (!Objects.equals(old.getStatus(), newVal.getStatus())) { histories.add(buildHistory(old.getId(), status, old.getStatus(), newVal.getStatus(), operatorId)); } if (!Objects.equals(old.getOwnerName(), newVal.getOwnerName())) { histories.add(buildHistory(old.getId(), owner_name, old.getOwnerName(), newVal.getOwnerName(), operatorId)); } // ... 其他字段 archiveMapper.updateById(newVal); for (ArchiveHistory h : histories) historyMapper.insert(h); }buildHistory里把change_type、old_value、new_value、operator、change_time都填上。这样查询历史时按archive_id一查就能看到完整变更链。答辩老师如果问怎么防止篡改历史你可以答历史表只插入不更新且操作人来自 Session 不可伪造。4. 避坑与排查那些答辩前一夜才发现的坑4.1 中文乱码从 Tomcat 到数据库的全链路排查现象表单提交的中文地名存进数据库变成???或者页面显示乱码。原因乱码可能出现在三个环节——请求编码、数据库连接编码、响应编码。Tomcat 8 默认 URI 编码是 UTF-8但 POST 请求体编码取决于request.setCharacterEncoding。数据库连接 URL 如果没加characterEncodingutf8MySQL 驱动会用平台默认编码。解决三处一起改。web.xml 加CharacterEncodingFilter设forceEncodingtrueJDBC URL 加?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai数据库和表的 charset 用utf8mb4。改完重启 Tomcat清浏览器缓存再测。4.2 附件上传大小超限413 错误不是前端的问题现象上传 5MB 以上的 PDF 报 413 或连接重置。原因Tomcat 的maxPostSize和 Spring 的maxUploadSize是两道独立的限制默认值都不大。很多人只改了 Spring 配置忘了 Tomcat 的server.xml里Connector节点的maxPostSize默认 2MB。解决Spring 配置里bean idmultipartResolver class...CommonsMultipartResolverproperty namemaxUploadSize value52428800//bean50MBTomcatserver.xml的 Connector 加maxPostSize52428800。如果还不行检查 Nginx 的client_max_body_size如果用了反向代理。4.3 MyBatis 返回 null字段名和属性名对不上现象查询能返回记录但某个字段总是 null。原因数据库字段是owner_name下划线Java 属性是ownerName驼峰MyBatis 默认不会自动映射。开了mapUnderscoreToCamelCase才行。解决在 MyBatis 配置文件里加setting namemapUnderscoreToCamelCase valuetrue/。或者在 SQL 里用AS别名但字段多了很啰嗦。这个坑在答辩演示时如果出现会显得很尴尬建议一开始就配好。4.4 事务不生效方法内部调用是经典翻车点现象Service 里 A 方法调 B 方法B 方法标了Transactional但 B 抛异常后 A 的数据没回滚。原因Spring 的Transactional基于 AOP 代理同一个类内部方法调用不走代理注解失效。解决把 B 方法抽到另一个 Service 类里或者通过AopContext.currentProxy()调用需要开exposeProxy。最干净的做法是拆类。这个点在答辩时如果被问到事务怎么实现的答基于动态代理比答加个注解就行高一个层次。4.5 部署后 404war 包名和访问路径不一致现象本地 Tomcat 跑得好好的部署到服务器访问 404。原因war 包名决定了 context path。landarchive.war部署后访问路径是/landarchive/xxx但代码里重定向写的是/xxx。解决重定向统一用request.getContextPath()拼接。或者把 war 包改名为ROOT.war直接部署到根路径。另外检查 Tomcat 的webapps目录下是否有旧版本残留有时候旧目录没删干净会导致类冲突。5. 答辩演示与部署视频的录制技巧答辩演示最怕的是现场跑不起来。我的习惯是提前准备两套环境一套本地 IDE 里跑一套服务器上部署好的。演示时优先用服务器那套因为环境固定不会因为教室电脑没装 JDK 而翻车。部署视频的录制核心是让看的人能跟着做。建议按这个顺序录先展示服务器环境java -version、mysql --version再导入数据库source landarchive.sql然后部署 war 包到 Tomcat最后浏览器访问验证。每一步都停留几秒让观众看清命令关键路径用鼠标高亮。数据库导入时有个细节landarchive.sql里如果有CREATE DATABASE语句要先确认数据库不存在否则报错。建议脚本开头写DROP DATABASE IF EXISTS landarchive; CREATE DATABASE landarchive DEFAULT CHARSET utf8mb4;这样每次导入都是干净环境。答辩 PPT 不要堆代码截图老师想看的是你做了什么、为什么这么做、遇到什么问题怎么解决。建议结构选题背景1页→ 技术选型与理由2页→ 数据库设计2页ER图 核心表说明→ 功能演示3页截图配简短说明→ 难点与解决2页挑事务、权限、附件存储讲→ 不足与改进1页。难点那部分是你区别于只会增删改查的关键。最后说一个我自己的习惯答辩前一天把整个流程从登录到导出报表完整走三遍每遍记录卡顿的地方。第三遍还卡的地方就是答辩时最可能出问题的地方提前准备好解释话术。希望帮到你。本文还有配套的精品资源点击获取