简介:本资源是一套完整的Java土地档案管理系统毕业设计实践包,面向计算机专业本科生及Java初学者,聚焦企业级Web应用开发全流程训练。压缩包共136.21MB,包含项目报告、答辩PPT、可运行源代码、MySQL数据库脚本及系统部署实操视频,覆盖需求分析、Spring MVC+MyBatis技术栈实现、前后端交互(HTML/CSS/JS+Ajax)、数据库建模与Tomcat部署等核心环节。已有372人学习下载,是少有的集文档、代码、数据与视频于一体的闭环式毕设素材。学习者可直接复现系统功能,深入理解MVC分层架构设计逻辑,掌握从编码、调试到上线部署的完整工程化能力,尤其适合课程设计、毕业实训及Java Web进阶实践。
1. 这不是又一个“增删改查”Demo:土地档案系统如何用Java真实承载业务复杂度
很多同学拿到毕业设计选题时,第一反应是“做个图书管理系统就行”,结果答辩被问一句“你这个系统怎么处理权属变更的时效性校验?”就卡壳。而这个土地档案管理系统恰恰跳出了教学Demo的舒适区——它把不动产登记中常见的“一地多档”“权属继承链追溯”“档案借阅时效锁”等真实业务约束,全部落到了Java Web技术栈的每一层。系统里没有虚构的User表,而是真实建模了宗地编码(DLBM)、权利人类型(自然人/法人/集体)、档案状态机(归档/借阅中/已注销/影像化待审核);数据库脚本里包含复合主键、外键级联删除策略、以及针对高频查询字段(如行政区划代码+年度)的联合索引。它适合两类人:一是刚学完Servlet/JDBC想验证自己能否把知识点串成闭环的初学者;二是已经写过Spring Boot但没碰过“业务规则强耦合数据库设计”的进阶者——因为这里的MyBatis XML映射文件里,藏着大量<choose>动态SQL来应对不同审批状态下的查询条件组合,这比单纯调用@Select注解难得多。
2. 技术栈选型逻辑与核心模块实现路径
2.1 为什么用Spring MVC而非Spring Boot?——毕业设计场景下的工程可控性考量
当前主流教程几乎全推Spring Boot,但这个项目坚持使用传统Spring MVC + MyBatis + JSP的技术组合,背后有明确的教学意图:强制暴露配置细节。比如web.xml中必须显式声明DispatcherServlet的<load-on-startup>1</load-on-startup>,否则Tomcat启动时不会预加载Spring容器;applicationContext.xml里手动配置DataSource时,需要精确设置maxActive=20和minIdle=5,否则高并发测试时连接池耗尽会直接抛出SQLException: Cannot get a connection, pool error Timeout waiting for idle object。这些在Spring Boot自动装配下被隐藏的参数,在毕业设计答辩中恰恰是评委重点考察的“是否理解底层机制”的证据点。
提示:不要直接复制
pom.xml里的依赖版本。该项目使用spring-framework-4.3.28.RELEASE而非5.x,因为JDK 1.8环境下Spring 5.x对javax.servlet-api的兼容性存在隐式冲突,会导致JSP页面编译失败,错误日志显示org.apache.jasper.JasperException: Unable to compile class for JSP。
2.1.1 Servlet层路由控制:从URL到业务方法的精准映射
系统采用@Controller注解配合@RequestMapping实现MVC分发,但关键在于路径设计遵循国土业务规范。例如档案借阅申请接口不叫/borrow,而是/archive/borrow/apply?landId=DL20230001,其中landId必须匹配数据库land_info表中的land_code字段格式(前两位为省级行政区划码)。后端控制器代码如下:
@Controller @RequestMapping("/archive/borrow") public class BorrowController { @Autowired private BorrowService borrowService; @RequestMapping(value = "/apply", method = RequestMethod.POST) public String applyBorrow(@RequestParam("landId") String landId, @RequestParam("borrowerName") String borrowerName, @RequestParam("purpose") String purpose, Model model) { // 1. 校验宗地编码合法性(正则:^[A-Z]{2}\d{8}$) if (!landId.matches("^[A-Z]{2}\\d{8}$")) { model.addAttribute("error", "宗地编码格式错误,应为2位字母+8位数字"); return "borrow/apply_fail"; } // 2. 检查该宗地是否存在且状态为"可借阅" LandInfo land = borrowService.getLandByCode(landId); if (land == null || !"AVAILABLE".equals(land.getStatus())) { model.addAttribute("error", "该宗地不存在或当前不可借阅"); return "borrow/apply_fail"; } // 3. 执行借阅申请(含事务控制) try { borrowService.createBorrowApplication(landId, borrowerName, purpose); model.addAttribute("success", "借阅申请已提交,审批编号:" + borrowService.getLastAppNo()); return "borrow/apply_success"; } catch (Exception e) { model.addAttribute("error", "申请失败:" + e.getMessage()); return "borrow/apply_fail"; } } }这段代码的关键不在语法,而在业务逻辑嵌入点:landId校验放在最前端避免无效数据库查询;getLandByCode()调用前先做空值判断;事务边界由BorrowService的@Transactional注解控制,确保借阅记录与审批流程状态同步更新。若漏掉@Transactional,当审批表插入成功但借阅表因唯一约束失败时,会出现数据不一致。
2.2 数据库设计:用ER图约束业务语义,而非仅满足CRUD
土地档案系统的数据库不是简单的三张表(用户、档案、操作日志),其核心实体关系体现国土管理特殊性。land_info(宗地信息)与archive_record(档案记录)是一对多,但archive_record又通过archive_type字段区分“权属档案”“测绘档案”“规划档案”三类,每类对应不同扩展属性表(ownership_ext、survey_ext、plan_ext)。这种设计避免了单表字段爆炸,但要求MyBatis动态SQL精准识别类型:
<!-- ArchiveMapper.xml --> <select id="selectArchiveWithExt" resultType="ArchiveWithExt"> SELECT a.*, CASE a.archive_type WHEN 'OWNERSHIP' THEN o.owner_name WHEN 'SURVEY' THEN s.survey_date WHEN 'PLAN' THEN p.plan_year END as ext_value FROM archive_record a <choose> <when test="archiveType == 'OWNERSHIP'"> LEFT JOIN ownership_ext o ON a.id = o.archive_id </when> <when test="archiveType == 'SURVEY'"> LEFT JOIN survey_ext s ON a.id = s.archive_id </when> <otherwise> LEFT JOIN plan_ext p ON a.id = p.archive_id </otherwise> </choose> WHERE a.land_code = #{landCode} </select>该SQL利用MyBatis的<choose>标签实现“按档案类型动态关联扩展表”,比在Java层做三次独立查询更高效。但需注意:ext_value字段在Java实体类中必须声明为String类型,因为三类扩展表的字段类型不同(owner_name是VARCHAR,survey_date是DATE,plan_year是INT),强行用Object会导致类型转换异常。
2.2.1 索引优化实战:解决“按行政区划+年度”高频查询慢问题
系统报表模块常执行“查询某市2023年所有新增宗地档案”,原始SQL为:
SELECT * FROM archive_record WHERE city_code = '310000' AND create_year = 2023;即使city_code和create_year都有单独索引,MySQL仍可能只用其中一个。解决方案是创建联合索引并调整字段顺序:
-- 正确做法:将选择性高的字段放前面 CREATE INDEX idx_city_year ON archive_record (city_code, create_year); -- 验证是否生效 EXPLAIN SELECT * FROM archive_record WHERE city_code = '310000' AND create_year = 2023;执行EXPLAIN后key列应显示idx_city_year,rows值显著降低。若city_code选择性低(如全国统一用'000000'),则需将create_year前置,再加land_code构成三字段索引。
3. 部署全流程实操:从本地IDEA到Linux服务器的完整链路
3.1 开发环境配置陷阱排查清单
很多同学在IDEA中能运行,但导出WAR包后Tomcat报404,根源常在web.xml的<servlet-mapping>配置。该项目要求url-pattern必须为/而非/*,否则静态资源(CSS/JS)会被Spring DispatcherServlet拦截:
<!-- web.xml --> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/</url-pattern> <!-- 关键:不是 /* --> </servlet-mapping>同时需在spring-mvc.xml中启用默认servlet处理器:
<mvc:default-servlet-handler />否则/static/css/main.css请求会返回404而非实际文件。
3.1.1 JDK与Tomcat版本兼容性验证步骤
项目基于JDK 1.8编译,但部分同学安装了JDK 17,导致javac编译通过而Tomcat启动失败。验证方法:
# 1. 查看项目编译版本 javap -verbose target/classes/com/example/controller/BorrowController.class | grep "major" # 输出应为"major version: 52"(对应JDK 1.8) # 2. 检查Tomcat使用的JDK echo $JAVA_HOME ls -la $JAVA_HOME/jre/lib/rt.jar | grep "1.8" # 3. 强制Tomcat使用指定JDK(修改bin/setenv.sh) export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64 export JRE_HOME=$JAVA_HOME/jre3.2 Linux服务器部署:三步完成生产环境上线
3.2.1 数据库初始化:规避字符集导致的中文乱码
MySQL默认字符集latin1会导致插入中文时存为??。必须在创建数据库时指定utf8mb4:
-- 创建数据库(非CREATE DATABASE db_name;) CREATE DATABASE land_archive CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 导入SQL脚本前,先设置客户端连接字符集 mysql -u root -p --default-character-set=utf8mb4 land_archive < land_archive.sql验证是否生效:
SHOW CREATE DATABASE land_archive; -- 应显示 DEFAULT CHARACTER SET = utf8mb43.2.2 Tomcat配置调优:解决高并发下连接超时
默认Tomcat最大连接数为200,但毕业设计演示时若多人同时访问报表页,易触发Connection reset。需修改conf/server.xml:
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" maxThreads="500" <!-- 提升至500 --> minSpareThreads="50" <!-- 最小空闲线程 --> acceptCount="1000" <!-- 等待队列长度 --> URIEncoding="UTF-8" /> <!-- 强制URI编码 -->重启Tomcat后,用jconsole连接进程,观察ThreadPool的currentThreadCount是否在压力下稳定在400-500区间。
4. 毕业答辩高频问题应答策略与代码级验证技巧
4.1 面对“你怎么保证档案借阅的原子性?”——用代码证明事务边界
评委常质疑“借阅申请成功但审批流没启动怎么办”。答案不能只说“用了@Transactional”,必须定位到具体代码行。打开BorrowService.java,找到createBorrowApplication方法:
@Transactional(rollbackFor = Exception.class) public void createBorrowApplication(String landId, String borrowerName, String purpose) { // 步骤1:插入借阅主表 BorrowApplication app = new BorrowApplication(); app.setLandId(landId); app.setBorrowerName(borrowerName); app.setPurpose(purpose); app.setStatus("PENDING"); // 初始状态为待审批 borrowApplicationMapper.insert(app); // <-- 这里插入主表 // 步骤2:生成审批流程实例(调用Activiti或自研引擎) approvalEngine.startProcess(app.getId(), "BORROW_APPROVAL"); // <-- 若此行抛异常,上一步自动回滚 }关键点在于approvalEngine.startProcess()若抛出RuntimeException,整个方法因@Transactional回滚,borrow_application表无残留脏数据。验证方法:在approvalEngine实现类中手动抛异常,观察数据库borrow_application表是否为空。
4.1.1 快速验证数据库约束是否生效的SQL命令
答辩时被问“外键约束真的起作用吗?”,现场执行以下命令:
-- 1. 查看land_info表的外键定义 SELECT CONSTRAINT_NAME, COLUMN_NAME, REFERENCED_TABLE_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE TABLE_NAME = 'archive_record' AND REFERENCED_TABLE_NAME = 'land_info'; -- 2. 尝试插入非法land_code(假设land_info中无'ZZ99999999') INSERT INTO archive_record (id, land_code, title) VALUES (999, 'ZZ99999999', '测试档案'); -- 应返回ERROR 1452 (HY000): Cannot add or update a child row: a foreign key constraint fails4.2 PPT制作避坑指南:用架构图代替功能列表
多数答辩PPT罗列“登录模块、查询模块、统计模块”,评委一眼看出是拼凑。本项目PPT应突出三层架构中的业务规则穿透:在Spring MVC Controller层画出BorrowController接收参数后,如何调用BorrowService的validateLandStatus()方法,该方法又如何通过LandInfoMapper查询数据库并触发LandStatusValidator的规则引擎(如“已抵押宗地禁止借阅”)。图中用红色虚线框标出@Transactional注解位置,并标注“此处为事务起点”。这样一页图胜过十页文字描述。
4.2.1 部署视频未覆盖但必须掌握的应急操作
视频教你怎么部署,但答辩当天Tomcat突然报java.lang.OutOfMemoryError: Metaspace,此时需立即执行:
# 查看当前Metaspace使用量 jstat -gcmetacapacity <pid> # 临时增大Metaspace(无需重启) jinfo -flag +UseConcMarkSweepGC <pid> jinfo -flag MetaspaceSize=512m <pid> jinfo -flag MaxMetaspaceSize=1024m <pid>然后在bin/catalina.sh中永久添加:
JAVA_OPTS="$JAVA_OPTS -XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=1024m"这个操作能避免答辩中断,体现工程素养。
5. 源代码深度改造技巧:从毕业设计到真实项目的能力跃迁
5.1 将JSP页面升级为Vue组件的渐进式迁移方案
系统当前用JSP渲染页面,但企业级项目普遍要求前后端分离。改造不必重写,可采用“双模共存”策略:保留原有JSP作为管理后台,新增/api/前缀的REST接口供Vue调用。关键改造点在于BorrowController新增@ResponseBody方法:
@RestController @RequestMapping("/api/archive/borrow") public class ApiBorrowController { @Autowired private BorrowService borrowService; @PostMapping("/apply") public ResponseEntity<Map<String, Object>> applyBorrowApi( @RequestBody BorrowApplyRequest request) { Map<String, Object> result = new HashMap<>(); try { borrowService.createBorrowApplication( request.getLandId(), request.getBorrowerName(), request.getPurpose() ); result.put("code", 200); result.put("msg", "申请成功"); result.put("appNo", borrowService.getLastAppNo()); return ResponseEntity.ok(result); } catch (IllegalArgumentException e) { result.put("code", 400); result.put("msg", e.getMessage()); return ResponseEntity.badRequest().body(result); } } }前端Vue组件只需调用axios.post('/api/archive/borrow/apply', {landId: 'DL20230001', ...}),响应体结构与JSP页面的JSON输出保持一致,降低联调成本。
5.1.1 数据库脚本增强:添加审计字段自动化填充
原SQL脚本缺少create_time、update_time、create_by等审计字段。可在archive_record表中添加:
ALTER TABLE archive_record ADD COLUMN create_time DATETIME DEFAULT CURRENT_TIMESTAMP, ADD COLUMN update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, ADD COLUMN create_by VARCHAR(50);然后在MyBatis的<insert>语句中使用<selectKey>自动生成:
<insert id="insertArchive" parameterType="ArchiveRecord"> <selectKey keyProperty="createTime" resultType="java.util.Date" order="BEFORE"> SELECT NOW() </selectKey> INSERT INTO archive_record (id, land_code, title, create_time, create_by) VALUES (#{id}, #{landCode}, #{title}, #{createTime}, #{createBy}) </insert>这样既满足毕设基础要求,又为后续接入Shiro权限框架预留create_by字段。
5.2 性能压测基线建立:用JMeter验证系统承载能力
不要只说“系统性能良好”,要给出量化指标。用JMeter模拟100用户并发查询档案:
- 线程组:100个线程,Ramp-Up Period 10秒
- HTTP请求:GET
http://localhost:8080/land_archive/archive/list?cityCode=310000&year=2023 - 聚合报告查看
90% Line响应时间(应≤800ms)、Error %(应为0)
若Error %超过5%,检查Tomcat日志是否有java.lang.OutOfMemoryError: Java heap space,此时需调整JAVA_OPTS:
# 在bin/catalina.sh中添加 JAVA_OPTS="$JAVA_OPTS -Xms1024m -Xmx2048m -XX:+UseG1GC"重新压测后对比90% Line是否下降30%以上——这才是答辩时能脱口而出的硬指标。
注意:压测必须在关闭IDEA调试模式下进行,否则JVM参数被IDE接管,实际生效的是IDEA的
-Xmx512m限制。
部署视频里演示的是单机环境,但真实系统需考虑Nginx负载均衡。在nginx.conf中添加:
upstream land_backend { server 127.0.0.1:8080 weight=3; server 127.0.0.1:8081 weight=1; # 第二台Tomcat实例 } server { location /land_archive/ { proxy_pass http://land_backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }这样当一台Tomcat宕机时,流量自动切到另一台,系统可用性从99.0%提升至99.9%——这个数字比任何功能描述都更有说服力。
本文还有配套的精品资源,点击获取