本文还有配套的精品资源点击获取简介直接导入就能跑的班级通讯录系统用Java Servlet写后端逻辑MySQL 5.5存数据前端用LayUI搭出清爽操作界面。登录页是login.jsp管理员账号密码都是admin数据库脚本jspbanji.sql导入后生成banji库。整个结构按MVC分层Bean封装学生、用户、管理员等数据DAO类负责查增改删Servlet协调页面跳转和业务处理。项目包含三大功能区——普通用户管理yonghu、管理员后台gly、通讯录维护txl所有操作都走标准HTTP请求。源码里有完整的web目录含WEB-INF、assets、JSP、src/com下的Java类、DBO.java里配好了数据库连接参数还有IntelliJ IDEA 2019.3专属配置.idea和.iml文件。开发环境要JDK 1.8、Tomcat 9.0适合Java Web入门练习、课程设计或毕业设计参考。1. 项目概述为什么这个班级通讯录值得你花两小时跑通一遍我带过六届Java Web课程设计每年都有学生卡在“写完第一个Servlet却连数据库都连不上”的阶段。这个班级通讯录项目就是我从上百个教学案例里亲手筛出来、反复打磨三年的“新手通关钥匙”。它不炫技不堆砌Spring Boot或MyBatis就用最原始的Servlet JDBC JSP三层结构把Java Web最核心的脉络——请求怎么来、数据怎么走、页面怎么变——掰开揉碎喂到你嘴里。关键词里那个Servlet不是摆设它是整个后端的神经中枢MySQL不是配角5.5版本特意选得保守避开高版本驱动兼容性雷区LayUI也不是随便套的皮肤它的模块化加载机制和表单验证逻辑恰恰能帮你理解前端如何与Servlet做干净的数据契约而班级通讯录这个场景足够小到两天内能闭环又足够真实——学生信息增删改查、角色权限分离普通用户只能看自己班管理员能管全校、登录态校验、分页列表渲染全都在里面。你不需要懂MVC是什么概念只要照着步骤把jspbanji.sql导入MySQL把项目丢进IntelliJ IDEA点一下绿色三角形看到http://localhost:8080/login.jsp弹出那个灰蓝配色的登录框你就已经跨过了80%初学者的心理门槛。管理员账号密码都是admin这不是偷懒是刻意降低初始阻力——先让你看见结果再回头琢磨原理。项目目录里那个DBO.java文件连数据库URL、用户名、密码都给你写死了不是教条是告诉你在真实开发中“硬编码”是调试期最高效的起点等你跑通了再把它抽成db.properties那才是真正的进阶。它适合谁如果你正在写Java Web课程设计、赶毕业设计DDL、或者自学Servlet卡在request.getParameter()收不到值这个项目就是你的“最小可行产品”。它不承诺教你成为架构师但能确保你亲手把一个Web请求从浏览器地址栏完整地走完Servlet→DAO→MySQL→再回到JSP页面的全过程。2. 整体架构与分层设计MVC不是教科书里的名词是代码里的呼吸节奏2.1 为什么坚持用原生Servlet而不是Spring Boot很多同学一上来就想抄Spring Boot模板结果配置文件改到崩溃报错堆栈翻三页找不到根源。这个项目用Servlet是因为它把Web开发最本质的“请求-响应”模型赤裸裸地摊开给你看。比如登录功能login.jsp提交表单到LoginServlet后者拿到request.getParameter(username)调用UserDAO.checkLogin()查数据库再根据返回结果用response.sendRedirect()跳转到index.jsp或留在登录页。整个链条只有4个关键节点没有自动装配、没有AOP代理、没有隐藏的过滤器链。我试过让零基础学生用这个流程画一张手绘时序图90%的人能在30分钟内标出每个对象的生命周期——这就是原生Servlet的价值它不替你思考逼你直面HTTP协议本身。Spring Boot当然更高效但就像学开车先上高铁模拟器不如先踩实离合器。等你用这个项目跑通三次完整的CRUD再切到Spring Boot你会突然明白Controller背后到底封装了多少HttpServlet的重复劳动。2.2 MVC三层如何在代码里落地不是目录分层是职责切割很多人以为把Java类放src/com/dao、JSP放web/、Servlet放src/com/servlet就叫MVC。错。真正的MVC是思维分层。看txl/TxlServlet.java它只做三件事——接收request参数如request.getParameter(studentId)调用TxlDAO.updateStudent()执行业务逻辑最后用request.setAttribute()把结果塞进request域request.getRequestDispatcher(txl/list.jsp).forward(request, response)转发给JSP。它绝不直接拼SQL字符串绝不在Servlet里写out.println(div...)绝不处理任何HTML渲染逻辑。所有数据库操作被锁死在TxlDAO.java里所有页面展示被锁死在list.jsp里。这种切割带来的好处是当你发现学生姓名显示乱码你立刻知道问题在JSP的% page contentTypetext/html;charsetUTF-8%声明或Tomcat的URI编码配置当你发现删除按钮点了没反应你直接去TxlServlet查doPost()方法里有没有漏掉request.setCharacterEncoding(UTF-8)。我带学生debug时常让他们用荧光笔标出每一行代码属于M、V、C哪一层标错的地方就是理解断层的位置。2.3 LayUI如何与Servlet协同不是前端框架是数据管道工LayUI在这里的角色常被误解为“美化界面”。其实它最关键的贡献是标准化数据接口。打开txl/list.jsp你会发现所有表格数据都来自c:forEach items${studentList} varstu而studentList是Servlet通过request.setAttribute(studentList, list)传进来的ArrayListStudentBean。LayUI的table.render()只是把Java List渲染成HTML表格它不关心数据怎么来。真正重要的是StudentBean.java里的getXXX()方法——LayUI的列字段名如field: name必须和Bean的getter方法名getName()严格对应大小写都不能错。我见过太多学生把Bean属性写成private String stuName;然后JSP里写field: stuName结果表格空空如也。原因LayUI通过反射调用getStuName()但Bean里只有getStuname()驼峰规则失效。这个细节暴露了Java Bean规范的本质它不是语法糖而是前后端约定的契约。项目里所有Bean都遵循private String xxx; public String getXxx(){}的命名就是为了堵死这类低级错误。3. 核心模块实现详解从数据库脚本到页面跳转的完整链路3.1 数据库初始化jspbanji.sql里的每一个分号都是伏笔别急着双击运行SQL脚本。先打开jspbanji.sql逐行读注释。第一行CREATE DATABASE IF NOT EXISTS banji DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;指定了字符集这是中文不乱码的前提。如果跳过这步直接建库后续插入学生姓名“张三”可能变成“??”。接着看CREATE TABLE yonghu (...)注意id字段是INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY这意味着你在UserDAO.addUser()里执行INSERT INTO yonghu (username,password,role) VALUES (?,?,?)后不能再手动SELECT LAST_INSERT_ID()——因为AUTO_INCREMENT已由MySQL托管addUser()方法的返回值应该直接用PreparedStatement.getGeneratedKeys()获取新ID。再看txl表的class_id字段它关联class表但脚本里没建外键约束。这是刻意为之教学项目优先保证可运行性避免初学者被外键报错卡住。等你跑通后可以自己加上FOREIGN KEY (class_id) REFERENCES class(id)再测试删除班级时是否触发约束异常——这就是理解数据库关系的实战入口。导入脚本后在MySQL命令行执行USE banji; SHOW TABLES;确认yonghu、gly、txl、class四张表都在。然后执行SELECT * FROM gly;看到admin/admin记录说明管理员账户已就位。这里有个经验如果导入后表名显示为小写如yonghu但在Java代码里写成YONGHUWindows系统可能不报错Linux服务器必跪。所以DBO.java里所有SQL语句都用小写表名这是跨平台安全的底线。3.2 登录认证流程LoginServlet如何用Session守住大门login.jsp的表单form actionLoginServlet methodpost指向LoginServlet这是整个系统的守门员。打开LoginServlet.java核心逻辑在doPost()String username request.getParameter(username); String password request.getParameter(password); UserBean user UserDAO.checkLogin(username, password); if (user ! null) { HttpSession session request.getSession(); session.setAttribute(currentUser, user); response.sendRedirect(index.jsp); } else { request.setAttribute(errorMsg, 用户名或密码错误); request.getRequestDispatcher(login.jsp).forward(request, response); }关键点有三第一request.getParameter()必须在request.setCharacterEncoding(UTF-8)之后调用否则中文密码会乱码第二session.setAttribute(currentUser, user)把用户对象存入Session这是后续所有页面权限校验的基石第三成功时用sendRedirect()重定向到新URL失败时用forward()服务器内部跳转二者区别决定浏览器地址栏是否变化。我在教学中让学生修改sendRedirect(index.jsp)为forward()结果登录后地址栏还是LoginServlet这就是理解HTTP重定向与请求转发的黄金实验。3.3 通讯录管理TxlServlet的增删改查如何与LayUI表格联动txl/list.jsp里的LayUI表格配置table.render({ elem: #studentTable, url: TxlServlet?actionlist, // 关键请求Servlet而非JSON接口 cols: [[ {field: id, title: ID, width:80}, {field: name, title: 姓名, width:120}, {field: className, title: 班级, width:150}, {title: 操作, width:180, toolbar: #barDemo} ]] });注意url指向TxlServlet?actionlist这不是RESTful风格而是传统Servlet参数路由。TxlServlet.java里用request.getParameter(action)判断动作String action request.getParameter(action); if (list.equals(action)) { ListStudentBean list TxlDAO.getAllStudents(); request.setAttribute(studentList, list); request.getRequestDispatcher(txl/list.jsp).forward(request, response); } else if (delete.equals(action)) { int id Integer.parseInt(request.getParameter(id)); TxlDAO.deleteStudent(id); response.sendRedirect(TxlServlet?actionlist); }这里藏着两个易错点一是Integer.parseInt()可能抛NumberFormatException生产环境必须加try-catch并返回友好提示二是删除后用sendRedirect()刷新列表而不是forward()否则F5刷新会重复提交删除请求即“重复提交问题”。解决方案是PRG模式Post-Redirect-Get项目已默认采用你只需记住所有修改数据的操作增删改结尾必须是sendRedirect()。3.4 权限控制gly/index.jsp如何识别管理员身份gly/index.jsp顶部有段关键代码% UserBean user (UserBean) session.getAttribute(currentUser); if (user null || !admin.equals(user.getRole())) { response.sendRedirect(../login.jsp); return; } %这是最朴素的权限拦截。它不依赖Shiro或Spring Security就靠Session里存的UserBean对象的role属性。UserDAO.checkLogin()方法里查询语句是SELECT id,username,password,role FROM gly WHERE username? AND password?所以gly表必须有role字段脚本里已定义。这里有个陷阱如果学生表yonghu也有role字段但值是student而管理员表gly的role是admin那么checkLogin()必须明确指定查哪张表。项目里UserDAO同时操作yonghu和gly两张表通过if (role.equals(admin))分支决定查哪个表——这就是角色路由的雏形。你可以试着在gly表里新增一条roleteacher的记录然后修改checkLogin()逻辑支持多角色这就是权限系统的第一次进化。4. 开发环境搭建与工程导入IntelliJ IDEA 2019.3专属配置的深意4.1 JDK 1.8与Tomcat 9.0的版本咬合点为什么指定JDK 1.8而不是11或17因为Tomcat 9.0的官方支持矩阵明确标注JDK 8是最低兼容版本而JDK 11需要Tomcat 9.0.31以上。项目里.iml文件里orderEntry typejdk jdkName1.8 jdkTypeJavaSDK /这行就是IDEA认准JDK版本的铁证。如果你强行用JDK 17编译时会报Unsupported class file major version 61——因为JDK 17生成的字节码版本是61而Tomcat 9.0内置的Jasper JSP编译器只认识到60JDK 16。解决方案不是升级Tomcat而是降级JDK。我在实验室统一部署时要求所有学生卸载JDK 11安装jdk-8u202-windows-x64.exe官网可下载这是经过300台机器验证的黄金组合。4.2DBO.java里的数据库连接参数从硬编码到配置化的演进路径打开src/com/db/DBO.java核心代码private static final String URL jdbc:mysql://localhost:3306/banji?useUnicodetruecharacterEncodingUTF-8; private static final String USER root; private static final String PASSWORD 123456;这里的PASSWORD 123456不是漏洞是教学设计。初学者第一次连不上数据库90%原因是密码不对。把密码明文写死等于给了你一把万能钥匙——先确保能连上再讨论安全性。等你跑通后可以把它改成从db.properties读取Properties props new Properties(); props.load(new FileInputStream(src/db.properties)); String url props.getProperty(db.url); String user props.getProperty(db.user); String pwd props.getProperty(db.password);对应的db.properties内容db.urljdbc:mysql://localhost:3306/banji?useUnicodetruecharacterEncodingUTF-8 db.userroot db.password123456这样改的好处是下次换数据库只需改properties文件不用动Java代码。这就是配置与代码分离的第一课。4.3 IntelliJ IDEA 2019.3专属配置.idea目录里的秘密项目根目录下的.idea文件夹是IDEA的项目元数据仓库。其中modules.xml定义了模块依赖workspace.xml保存了你的断点和窗口布局而最关键的artifacts.xml决定了打包行为。打开artifacts.xml你会看到artifact namebanji:war exploded typewar exploded output-path$PROJECT_DIR$/out/artifacts/banji_war_exploded/output-path root idarchive namebanji.war element iddirectory nameWEB-INF element iddirectory nameclasses element idmodule-output namebanji / /element /element /root /artifact这段XML告诉IDEA“打包时把src下的编译类放到WEB-INF/classes下”。如果你删掉.idea目录重新导入项目IDEA会自动生成新的配置但可能把输出路径设错导致ClassNotFoundException。所以教学时我强调.idea目录要和源码一起提交Git不是垃圾文件而是项目运行的DNA。5. 实操避坑指南那些文档里不会写的血泪教训5.1 中文乱码的七种死法与解法乱码是Java Web新手的头号杀手。我整理了这个项目里最可能触发乱码的七个场景及对应解法场景现象定位方法解决方案数据库连接URL缺参数插入“张三”显示“??”查DBO.java的URL字符串在URL末尾添加?useUnicodetruecharacterEncodingUTF-8JSP页面未声明编码login.jsp输入框显示方块查JSP第一行% page ... %添加contentTypetext/html;charsetUTF-8Tomcat URI编码未配置地址栏中文参数乱码如?name张三查conf/server.xml的Connector标签添加URIEncodingUTF-8属性Servlet未设置请求编码request.getParameter()获取中文为空查doPost()方法第一行添加request.setCharacterEncoding(UTF-8)MySQL服务端字符集非UTF8SHOW VARIABLES LIKE character_set_%显示latin1在MySQL命令行执行SET character_set_databaseutf8;并修改my.iniIDEA文件编码非UTF8Java文件里中文注释显示乱码File → Settings → Editor → File Encodings全局编码设为UTF-8Transparent native-to-ascii conversion勾选浏览器缓存旧页面修改JSP后刷新仍是旧内容浏览器按CtrlF5强制刷新或在JSP里加meta http-equivpragma contentno-cache最狠的一招在LoginServlet.doPost()开头加一行System.out.println(username: username);如果控制台打印出username: ??说明乱码发生在请求解析环节如果打印username: 张三但数据库里存成??问题就在数据库连接或存储环节。这种二分法定位法比百度搜“中文乱码”高效十倍。5.2 Tomcat启动失败的三大高频原因学生问得最多的问题“点击RunTomcat启动几秒就退出控制台一片红”。90%集中在以下三点第一端口被占用现象控制台首行报Address already in use: bind。解法Windows下打开cmd执行netstat -ano | findstr :8080找到PID再用taskkill /f /pid XXXX干掉进程。更彻底的是在conf/server.xml里把Connector port8080改成port8081。第二web.xml配置错误现象报Error configuring application listener of class [xxx]。解法检查WEB-INF/web.xml里servlet-class的全路径是否正确。比如servlet-classcom.servlet.LoginServlet/servlet-class对应Java文件必须在src/com/servlet/LoginServlet.java且类声明是public class LoginServlet extends HttpServlet。少一个com.或大小写错如loginservlet都会跪。第三缺少MySQL驱动jar包现象报java.lang.ClassNotFoundException: com.mysql.jdbc.Driver。解法把mysql-connector-java-5.1.47.jar项目资源包里已提供复制到WEB-INF/lib/目录下。注意不是放在src下也不是放在IDEA的Libraries里——Tomcat只认WEB-INF/lib。我曾见学生把jar包扔进src/lib折腾三天没解决就因为没搞懂Tomcat的类加载机制。5.3 LayUI表格不显示数据的排查清单当list.jsp打开后表格空白不要急着重写代码。按顺序检查确认Servlet是否真的被访问在TxlServlet.doGet()第一行加System.out.println(TxlServlet accessed!);刷新页面看控制台是否有输出。没有说明URL映射错了检查web.xml里servlet-mapping的url-pattern是否匹配TxlServlet?actionlist。确认DAO是否查到数据在TxlDAO.getAllStudents()方法里while(rs.next())循环前加System.out.println(ResultSet size: rs.getFetchSize());。如果输出0说明SQL没查到数据检查SELECT * FROM txl是否真有记录。确认Bean属性名与LayUI字段名一致StudentBean.java里必须有private String name; public String getName(){return name;}LayUI列配置{field: name, ...}。如果Bean里是private String stuName;字段名必须写stuName且getter必须是getStuName()。确认JSP是否正确接收List在list.jsp顶部加% request.getAttribute(studentList) null ? NULL : NOT NULL %如果输出NULL说明Servlet没执行setAttribute()检查forward()前是否有逻辑分支跳过了这行。确认LayUI JS是否加载成功浏览器按F12看Console是否有layui is not defined。如果有检查script src../assets/layui/layui.js/script路径是否正确assets目录是否在web/下。这套清单是我帮学生远程debug时用屏幕共享一步步执行的标准流程。平均3分钟定位问题比重装环境快十倍。6. 功能扩展与二次开发从通讯录到轻量级教务系统的跃迁6.1 增加学生照片上传功能multipart/form-data的实战拆解想让学生头像显示在通讯录里别急着抄Apache Commons FileUpload。先用原生Servlet 3.0的MultipartConfig搞定。步骤如下在txl/add.jsp表单里加enctypemultipart/form-data和文件输入框form actionTxlServlet?actionadd methodpost enctypemultipart/form-data input typefile namephoto acceptimage/* /form在TxlServlet.java的addStudent()方法里Part photoPart request.getPart(photo); String fileName Paths.get(photoPart.getSubmittedFileName()).getFileName().toString(); InputStream photoStream photoPart.getInputStream(); // 将photoStream保存到web/assets/photo/目录下 Files.copy(photoStream, Paths.get(getServletContext().getRealPath(/assets/photo/), fileName)); // 将fileName存入数据库 student.setPhoto(fileName); TxlDAO.addStudent(student);关键点getPart(photo)必须与表单name属性一致getRealPath()获取服务器绝对路径确保图片能被img src/assets/photo/xxx.jpg访问acceptimage/*限制前端只能选图片文件。这个功能增加不到20行代码却覆盖了文件上传的核心知识点。6.2 实现分页查询从LIMIT 0,10到LayUI分页组件当前getAllStudents()查全表数据一多就卡死。改造为分页只需三步在TxlDAO.java里新增方法public ListStudentBean getStudentsByPage(int start, int pageSize) { String sql SELECT * FROM txl LIMIT ?,?; // 执行PreparedStatement参数start和pageSize }在TxlServlet.java里计算分页参数int page Integer.parseInt(request.getParameter(page)); // LayUI传来的当前页 int limit Integer.parseInt(request.getParameter(limit)); // 每页条数 int start (page - 1) * limit; ListStudentBean list TxlDAO.getStudentsByPage(start, limit); // 同时查总数SELECT COUNT(*) FROM txl在list.jsp的LayUI配置里启用分页table.render({ elem: #studentTable, url: TxlServlet?actionlist, page: true, // 开启分页 limit: 10, limits: [10, 20, 30] });LayUI会自动在URL后拼?page1limit10Servlet解析即可。这个改造过程让你亲手触摸到“大数据量下性能优化”的第一块砖。6.3 集成邮件通知用JavaMail发送注册成功邮件当管理员添加新学生时自动发邮件通知本人。引入javax.mail-api-1.6.2.jar和activation-1.1.1.jar到WEB-INF/lib在TxlDAO.addStudent()成功后加Properties props new Properties(); props.put(mail.smtp.host, smtp.163.com); props.put(mail.smtp.auth, true); Session session Session.getInstance(props, new Authenticator() { protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication(your_email163.com, your_auth_code); } }); Message message new MimeMessage(session); message.setFrom(new InternetAddress(your_email163.com)); message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(studentxxx.com)); message.setSubject(班级通讯录注册成功); message.setText(您好您已在班级通讯录注册成功账号 student.getUsername()); Transport.send(message);注意163邮箱需开启SMTP服务并生成授权码不能用登录密码。这个功能把Java Web和网络编程串联起来是课程设计加分项的标配。7. 项目价值再审视它为什么是Java Web学习的“锚点”我之所以把这个项目称为“锚点”是因为它在技术坐标系里牢牢钉住了三个维度深度、广度、温度。深度上它不回避request.setCharacterEncoding()这样的底层细节逼你直面HTTP协议的字节流本质广度上它覆盖了Web开发全链路——从数据库建模jspbanji.sql里的范式设计、后端逻辑Servlet的线程安全考量、前端交互LayUI的模块化加载到部署运维Tomcat的context配置温度上它用“班级通讯录”这个具象场景把抽象的技术概念转化成可触摸的成果当学生看到自己名字出现在list.jsp表格里那种“我造出来了”的兴奋感是任何理论课都无法替代的。很多学生问我“学完这个下一步该学什么”我的答案永远是先把它改造成你自己的项目。把“班级通讯录”换成“实验室设备管理系统”把txl表改成equipment把StudentBean改成EquipmentBean把TxlDAO改成EquipmentDAO——你会发现90%的代码结构可以直接复用只是替换名词。这种“抽象-具象-再抽象”的螺旋上升才是工程师思维的真正养成路径。这个项目不是终点而是你技术地图上的第一个坐标原点。当你某天用Spring Boot重写它时回看这段原生Servlet代码会突然读懂RestController背后那行response.getWriter().write(json)的重量。所以请认真跑通它哪怕只是为了解决一个乱码问题那也是你和Java Web世界签下的第一份契约。本文还有配套的精品资源点击获取简介直接导入就能跑的班级通讯录系统用Java Servlet写后端逻辑MySQL 5.5存数据前端用LayUI搭出清爽操作界面。登录页是login.jsp管理员账号密码都是admin数据库脚本jspbanji.sql导入后生成banji库。整个结构按MVC分层Bean封装学生、用户、管理员等数据DAO类负责查增改删Servlet协调页面跳转和业务处理。项目包含三大功能区——普通用户管理yonghu、管理员后台gly、通讯录维护txl所有操作都走标准HTTP请求。源码里有完整的web目录含WEB-INF、assets、JSP、src/com下的Java类、DBO.java里配好了数据库连接参数还有IntelliJ IDEA 2019.3专属配置.idea和.iml文件。开发环境要JDK 1.8、Tomcat 9.0适合Java Web入门练习、课程设计或毕业设计参考。本文还有配套的精品资源点击获取