简介面向JSP初学者的课程设计资源包提供基于JSPJavaBeanAccess的留言板完整项目无需配置数据源部署至Tomcat即可运行适合作为Web课程作业或入门实战。项目内置UBB代码解析与UBB头像替换基于正则表达式管理登录加入验证码数据库密码采用MD5加密兼顾功能完整与基础安全。压缩包为RAR格式共194个文件主体包括157个gif头像与界面素材、13个jsp页面逻辑、5个classJavaBean编译类、5个css样式以及db/mdb数据库与xml/txt配置说明整体约621KB轻量且便于分发。已有159人学习下载。通过学习该实例可理解留言模块从数据库连接、数据增删改查到前端渲染的全过程并借助验证码、MD5等细节了解Web安全处理要点适合课程设计参考与JSP入门巩固。1. jsp课程设计开题登录功能为什么是JSPJavaBeanAccess的黄金组合期末课程设计季最常被分到的题目就是「XXX管理系统」而这类题目十有八九绕不开一个登录功能。很多同学抱着 MySQL 教程啃了两天发现装数据库、配账号、导驱动就能耗掉一个周末最后答辩时还在跟环境问题搏斗。这时候你会发现JSP JavaBean Access 这套组合反而是最稳的出路——Access 是一个文件型数据库一个 .mdb 文件就是整个库不需要单独安装数据库服务JavaBean 把数据库操作和登录校验收拢成普通 Java 类JSP 页面只负责展示和收集参数三者拼起来登录、注册、增删改查都能在几天内跑通。这篇笔记就围绕这个组合讲透怎么选型、怎么搭目录、怎么用 JavaBean 访问 Access、怎么写登录流程以及最容易翻车的几个坑。适合正在做 JSP 课程设计、想快速跑通“登录 数据库操作”的读者也适合第一次接触 Access 数据库的老手快速上手。2. 课程设计的技术骨架JSP、JavaBean 与 Access 怎么分工2.1 为什么课程设计选 JSPJavaBean而不是纯 JSP 或 JSPServletJSP 本身的定位是视图层适合写 HTML 和展示逻辑。如果把数据库连接、SQL 拼接、登录校验全塞进 JSP 页面代码会混成一团答辩时老师一眼就能看出问题。而 JavaBean 是普通的 Java 类用来封装业务逻辑和数据正好弥补 JSP 不适合写复杂逻辑的短板。这个分工就是经典的 JSP Model 1 结构JSP 负责收集请求参数、显示结果JavaBean 负责连接数据库、执行 SQL、返回结果。相比之下JSP Servlet 的 Model 2 结构更规范但课程设计里很多同学的 Servlet 只是把请求转发给 JSP并没有真正发挥控制层的作用。与其写出一个名不副实的 Servlet不如老老实实把逻辑收进 JavaBean让页面代码干净、模块职责清楚答辩时也更容易讲明白每段代码在干什么。Access 数据库在这个组合里的角色是“零安装的存储层”。它不需要安装服务不需要配置端口甚至不需要账号密码。只要 JDK 和 Tomcat 环境正常把一个 .accdb 文件放进项目驱动就能直接读写。对于课程设计这种“演示完就交”的场景数据库文件还能随项目一起打包带走比 MySQL 的导出导入省心太多。2.2 搭建课程设计项目目录放对文件比写对代码更重要很多翻车现场不是代码写错而是文件放错位置。访问 Access 数据库需要驱动 JAR 包这些包必须放在 WEB-INF/lib 下才能被 Tomcat 加载数据库文件如果放在 WebContent 根目录存在被浏览器直接下载的风险正确做法是放 WEB-INF 目录下或者项目外部的固定路径。常见的项目结构是这样的jspbook/ ├── src/ │ ├── bean/ │ │ └── UserBean.java │ └── util/ │ └── DbUtil.java ├── WebContent/ │ ├── WEB-INF/ │ │ ├── lib/ │ │ │ ├── ucanaccess-5.0.1.jar │ │ │ ├── jackcess-*.jar │ │ │ └── commons-lang3-*.jar │ │ ├── book.accdb │ │ └── web.xml │ ├── login.jsp │ ├── register.jsp │ ├── index.jsp │ └── error.jsp └── build/说明几个关键点。lib 目录下的驱动 JAR 包会被 Tomcat 自动加载不用手动配置 classpath数据库文件 book.accdb 放在 WEB-INF 下浏览器无法直接访问但代码可以通过 ServletContext 读取真实路径后面会给出动态获取路径的方法web.xml 负责配置欢迎页面和编码过滤器建议一上来就配好字符编码过滤器否则登录中文用户名时必乱码。2.3 数据表设计Access 里的用户表字段与建表语句登录功能只需要一张用户表但字段设计直接影响后续代码的复杂度。最简方案是四个字段用户 ID、用户名、密码、创建时间。用户名做唯一约束密码字段先规划好长度创建时间用默认值。在 Access 中执行以下 SQL 创建表注意 Access 的自增字段写法与 MySQL 不同CREATE TABLE users ( id AUTOINCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, realname VARCHAR(50), create_time DATETIME DEFAULT NOW() );逻辑说明AUTOINCREMENT 是 Access 的自增类型不需要也不能手动赋值插入时直接忽略该字段VARCHAR(50) 对应 Access 的文本类型DEFAULT NOW() 是 Access 支持的默认时间写法。如果你在 MySQL 里习惯写 AUTO_INCREMENT在这里会直接报语法错误这是最容易在答辩前夜发现的问题。3. 用 JavaBean 封装 Access 访问连接、SQL 执行与结果集处理3.1 数据库连接工具类固定 URL 格式与动态路径获取写 JavaBean 之前先写一个连接工具类把所有驱动加载、Connection 获取、资源关闭收拢到一个类里。这样 UserBean 里就不用重复写驱动加载代码也方便将来换成 MySQL 时只改这一个类。驱动选择上我强烈建议用 UCanAccess 而不是 JDBC-ODBC 桥。JDBC-ODBC 桥在 JDK 8 以后已经被移除64 位系统下 ODBC 数据源管理器的位数问题也经常让连接失败UCanAccess 是纯 Java 实现不依赖本机 ODBC只要把 JAR 包放进 lib 就能用。这也是近两年做 Access 课程设计的主流通行做法。package util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; public class DbUtil { private static final String DRIVER net.ucanaccess.jdbc.UcanaccessDriver; private static String DB_PATH; // 由外部传入数据库文件的真实路径避免硬编码 public static void init(String dbPath) { DB_PATH dbPath; try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { throw new RuntimeException(UCanAccess 驱动未找到请检查 WEB-INF/lib, e); } } public static Connection getConnection() throws SQLException { if (DB_PATH null) { throw new SQLException(数据库路径未初始化请先调用 init()); } String url jdbc:ucanaccess:// DB_PATH; return DriverManager.getConnection(url); } public static void close(Connection conn, Statement stmt, ResultSet rs) { if (rs ! null) { try { rs.close(); } catch (SQLException ignored) {} } if (stmt ! null) { try { stmt.close(); } catch (SQLException ignored) {} } if (conn ! null) { try { conn.close(); } catch (SQLException ignored) {} } } }逻辑说明init()接收数据库文件的绝对路径并加载驱动getConnection()每次调用建立新连接UCanAccess 对并发连接有一定开销课程设计这种低并发场景完全够用close()方法统一处理资源释放避免每段代码都写一遍 try-catch 关闭逻辑。路径建议在 Servlet 初始化时动态获取不要写死成D:/book.accdb否则换电脑就崩。参数说明连接 URL 的格式是jdbc:ucanaccess://加文件绝对路径路径中不能带中文字符否则部分环境下驱动解析会出问题。如果数据库文件带密码URL 后面可以追加;passwordxxx但课程设计一般不用。3.2 用户登录校验PreparedStatement 与防 SQL 注入登录校验是 UserBean 的核心方法。这里有一个提分点不要用 Statement 拼接 SQL 字符串而是用 PreparedStatement 的占位符。答辩时老师大概率会问“SQL 注入怎么防”用 PreparedStatement 就是标准答案之一。具体场景是用户输入admin和123456代码要查询 users 表中是否存在匹配记录。如果用户名和密码都匹配返回该用户信息否则返回 null。用 or 11这类输入测试时PreparedStatement 会把整个字符串当作普通参数而不是 SQL 代码的一部分这就是防注入的原理。package bean; import util.DbUtil; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; public class UserBean { private int id; private String username; private String realname; // 登录校验成功返回 true失败返回 false public boolean checkLogin(String username, String password) { String sql SELECT id, realname FROM users WHERE username ? AND password ?; try (Connection conn DbUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, username); ps.setString(2, password); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { this.id rs.getInt(id); this.realname rs.getString(realname); this.username username; return true; } } } catch (Exception e) { e.printStackTrace(); } return false; } public String getRealname() { return realname; } public String getUsername() { return username; } public int getId() { return id; } }逻辑说明查询只取 id 和 realname 两个字段密码不进入对象属性try-with-resources 语法自动关闭 Connection 和 PreparedStatement省去手动 close 的繁琐ps.setString(1, username)就是占位符传参方式索引从 1 开始顺序对应 SQL 中 ? 的位置。参数说明如果后续增加“记住密码”或“密码错误次数”功能需要把这个方法的返回值从 boolean 扩展为错误码。比如 0 表示成功、1 表示用户不存在、2 表示密码错误届时只需要把返回类型改成 int逻辑判断移到 Servlet 层。3.3 注册新用户Access 自增字段与插入语句的坑注册功能是课程设计的加分项有登录最好有注册否则老师会问“用户数据怎么来的”。插入用户时最容易踩的坑是给自增字段赋值。Access 的 AUTOINCREMENT 列在插入时不能出现在字段列表中否则会报“不能为自增字段插入值”的错误。public boolean register(String username, String password, String realname) { String sql INSERT INTO users (username, password, realname) VALUES (?, ?, ?); try (Connection conn DbUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, username); ps.setString(2, password); ps.setString(3, realname); int rows ps.executeUpdate(); return rows 0; } catch (Exception e) { e.printStackTrace(); return false; } }逻辑说明INSERT 语句中刻意省略了 id 和 create_time 字段id 由 Access 自动生成create_time 由字段默认值填充executeUpdate()返回受影响行数1 表示插入成功0 表示没插进去如果 username 有 UNIQUE 约束重复用户名会抛 SQLIntegrityConstraintViolationException可以在 Servlet 里捕获后提示“用户名已存在”。参数说明密码字段目前是明文存储能跑通功能但不是好习惯。课程设计如果想提分可以在注册时做一次 MD5 加盐登录时对输入密码做同样的哈希再比对。这样做唯一的改动是checkLogin方法里参数也要哈希处理SQL 本身不用改。4. JSP 登录功能落地表单、控制层、Session 会话与登录拦截4.1 登录页 login.jsp表单控件与提交方式选择登录页面的核心是form标签。两个最容易犯的错误一是 method 用了 GET密码直接出现在 URL 上二是没有做非空校验用户直接点登录就提交。课程设计阶段至少要保证 POST 提交和基本的前端非空判断。% page contentTypetext/html;charsetUTF-8 languagejava % html head title用户登录/title /head body h2图书管理系统登录/h2 % String msg (String) request.getAttribute(msg); if (msg ! null) { % p stylecolor:red;% msg %/p % } % form action${pageContext.request.contextPath}/login methodpost 用户名input typetext nameusername requiredbr 密码input typepassword namepassword requiredbr button typesubmit登录/button /form p没有账号a href${pageContext.request.contextPath}/register.jsp去注册/a/p /body /html逻辑说明${pageContext.request.contextPath}会动态拼出项目上下文路径这样部署时改了应用名链接也不会失效required属性是 HTML5 自带的前端校验空值直接拦截不给后端添麻烦% msg %是 JSP 表达式用来回显 Servlet 转发过来的错误提示。参数说明表单 action 指向/login对应的 Servlet 映射在 web.xml 里配置为/login。如果不想用 Servlet可以把 action 指向 login.jsp 本身在 JSP 顶部用request.getMethod()判断 POST 请求再调用 UserBean但那样会把逻辑写进视图代码可读性差答辩不好讲。4.2 登录控制层Servlet 接收参数并调用 UserBean控制层的职责是接收请求参数 → 调用 UserBean 校验 → 根据结果决定跳转。登录成功用重定向到首页登录失败用转发回登录页。重定向和转发的区别是转发是服务器内部跳转浏览器 URL 不变重定向是服务器告诉浏览器重新请求新地址URL 会变。登录成功必须用重定向否则刷新页面会重复提交登录请求。package controller; import bean.UserBean; import util.DbUtil; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; WebServlet(/login) public class LoginServlet extends HttpServlet { Override public void init() throws ServletException { // 数据库文件放 WEB-INF 下通过真实路径获取避免硬编码 String dbPath getServletContext().getRealPath(/WEB-INF/book.accdb); DbUtil.init(dbPath); } Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); String username request.getParameter(username); String password request.getParameter(password); if (username null || password null || username.trim().isEmpty() || password.isEmpty()) { request.setAttribute(msg, 用户名和密码不能为空); request.getRequestDispatcher(/login.jsp).forward(request, response); return; } UserBean user new UserBean(); boolean ok user.checkLogin(username.trim(), password); if (ok) { HttpSession session request.getSession(); session.setAttribute(user, user); response.sendRedirect(request.getContextPath() /index.jsp); } else { request.setAttribute(msg, 用户名或密码错误); request.getRequestDispatcher(/login.jsp).forward(request, response); } } }逻辑说明init()只在 Servlet 第一次加载时执行一次这里拿到数据库文件的真实路径并初始化 DbUtil后面所有 UserBean 都能直接使用request.setCharacterEncoding(UTF-8)必须写在读取参数之前否则用户名里的中文会乱码登录成功后把 UserBean 对象整体存进 Session比只存用户名更方便因为 UserBean 里还带了 realname 等个人信息。参数说明WebServlet(/login)是 Servlet 3.0 的注解写法省去 web.xml 中配置 servlet-mapping 的繁琐步骤。如果你用的 Tomcat 版本较老不支持注解就在 web.xml 里手动配servlet和servlet-mapping效果一样。4.3 登录状态校验Session 与会话超时处理登录成功存了 Session但每个页面都要检查是否登录否则用户直接访问 index.jsp 就能绕过登录。最省事的方案是写一个 Filter拦截除登录页和注册页之外的所有请求。这也是课程设计里能讲出亮点的模块。package filter; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; WebFilter(/*) public class LoginFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req (HttpServletRequest) request; HttpServletResponse resp (HttpServletResponse) response; HttpSession session req.getSession(false); String path req.getRequestURI().substring(req.getContextPath().length()); // 放行登录页、注册页、登录接口和静态资源 if (path.equals(/login.jsp) || path.equals(/register.jsp) || path.equals(/login) || path.startsWith(/static/)) { chain.doFilter(request, response); return; } if (session null || session.getAttribute(user) null) { resp.sendRedirect(req.getContextPath() /login.jsp); return; } chain.doFilter(request, response); } }逻辑说明req.getSession(false)不会自动创建新 Session如果当前请求没有 Session 会返回 null配合后面的 null 判断正好能捕获未登录访问放行路径列表必须包含 /login 接口本身否则登录请求会被过滤器拦截造成“死循环跳回登录页”的翻车现场path.startsWith(/static/)是为了放行 CSS、JS 等静态资源。参数说明Tomcat 默认 Session 超时时间是 30 分钟可以按分钟为单位修改在 web.xml 里加一行即可。如果项目要求“登录后长时间不操作自动退出”就保持默认如果演示场景需要更长会话再增加超时时间。5. 课程设计避坑Access 驱动、中文乱码与路径问题的排查记录5.1 ClassNotFoundException驱动 JAR 没进 lib 或数据库路径不对现象Tomcat 启动后第一次访问登录接口控制台报java.lang.ClassNotFoundException: net.ucanaccess.jdbc.UcanaccessDriver登录页面直接 500。原因UCanAccess 是第三方驱动JDK 自身不带。JAR 包没有复制到 WEB-INF/lib 目录或者复制了但 IDE 没有重新发布到 Tomcat 的部署目录。另一个常见原因是用了 JDBC-ODBC 桥在 JDK 8 的环境下根本没有这个驱动类。解决确认 lib 目录下包含 ucanaccess 主 JAR 以及它的依赖包jackcess、commons-lang3 等在 IDE 里执行 clean 后重新发布。如果还想排查类是否真的加载成功可以在 DbUtil.init() 里用Class.forName(DRIVER)主动触发加载报错时堆栈会直接点名哪个类缺失。5.2 中文乱码POST 参数和页面编码不一致现象登录表单输入中文用户名校验总是失败或者注册后页面显示乱码。数据库里手动能看到中文正常但通过 Web 页面写入的数据是问号。原因Tomcat 8 之后 GET 请求默认 UTF-8但 POST 请求默认按 ISO-8859-1 解析JSP 页面声明的是 UTF-8JVM 读取字节流时用错了字符集导致中文参数变成乱码还有一类是 Access 数据库文件本身的编码问题UCanAccess 默认按 UTF-8 读写。解决三层都统一到 UTF-8。第一层每个 Servlet 的 doPost 第一行写request.setCharacterEncoding(UTF-8)第二层web.xml 配置 CharacterEncodingFilter拦截所有请求第三层连接 URL 不要手动指定编码让 UCanAccess 走默认 UTF-8 即可。如果注册写入还是乱码检查 JSP 页面顶部的 contentType 是否还继承了旧的pageEncodingGBK。5.3 Access 数据库文件被占用程序无法写入现象注册新用户时页面报错堆栈里出现file is already in use或者数据库已锁定之类的信息。重启 Tomcat 后正常过一会儿又报错。原因用 Microsoft Access 桌面程序打开过 .accdb 文件没有彻底关闭或者 Windows 的文件预览窗格、杀毒软件后台扫描暂时占用了文件句柄。Access 是文件型数据库写入时会对文件加锁任何外部程序占用都会导致写入失败。解决开发期间不要在 Access 里同时打开项目数据库文件需要改表结构时先复制一份到桌面改完再覆盖回 WEB-INF 目录。部署到 Tomcat 后确保 mx 进程可读可写该文件否则只读文件会同时导致读取正常、写入失败。5.4 自增 ID 和日期字段的类型转换异常现象从 ResultSet 里取 id 字段用rs.getInt(id)时报异常或者拿到一个很奇怪的数字按 create_time 排序时顺序不对。原因UCanAccess 引擎对 Access 的 AUTOINCREMENT 字段在某些条件下返回的类型不是 java.lang.Integer而是 BigDecimal 或 LongDATETIME 字段在部分驱动实现里拿到的可能是 java.sql.Timestamp 而不是 java.util.Date。类型转换失败是因为代码里强行(Integer) rs.getObject(id)而不是用规范的方法取数。解决统一用rs.getInt(id)和rs.getTimestamp(create_time)这些方法内部会自动做类型转换不要用 getObject 再手动强转。如果 getInt 依然报错检查表结构确认字段确实是 AUTOINCREMENT 而不是普通的数字型 Long。5.5 部署路径写死换电脑就崩现象在开发机上好好地代码打包发给同学或者换一台电脑部署登录直接报“数据库路径未初始化”或 FileNotFound排查半天发现是代码里写死了D:/jspbook/book.accdb。原因Access 是文件型数据库连接的路径必须是绝对路径。每个人机器上的目录结构不同写死路径自然就失效。这个问题在课程设计答辩时特别常见——老师在演示机上运行你的项目环境一换就出问题。解决去掉所有硬编码路径改在 Servlet 的 init() 或 Filter 中使用getServletContext().getRealPath(/WEB-INF/book.accdb)动态获取。这样项目只要按标准目录结构部署不管在谁的机器上都能找到数据库文件。这是整个项目里性价比最高的一次改造改动只有一行但能避免最尴尬的课堂事故。6. 从能跑到跑好登录模块的三个提分技巧与自测方法登录功能跑通了只是及格想让课程设计拿高分重点不是加多少页面而是把登录模块做得完整。第一个提分点是密码存储把明文密码改成 MD5 加盐存储。注册时生成一个随机盐值存salt md5(salt password)登录时取出该用户的盐值重新计算比对。改动集中在 UserBean 的 register 和 checkLogin 两个方法页面和 Servlet 不用动。答辩时只说你用了差异化摘要存储老师就清楚你懂密码安全的基本常识。第二个提分点是登录失败次数限制。在 Session 里记录失败次数连续失败 5 次锁定 10 分钟或者显示验证码。实现思路不复杂Session 里放一个计数器checkLogin返回失败时加 1成功时清零达到阈值后禁止继续提交。这个功能能体现会话管理和账号安全两个知识点是课程设计答辩的高频亮点。第三个提分点是操作日志。登录成功后把用户名、登录时间写入一张 login_log 表表结构是id、username、login_time、ip。这是标准的审计需求也能让系统看起来更完整。实现时只需要在checkLogin返回成功后追加一条 INSERT复用已有的 DbUtil 即可。最后一定要做一轮自测。照着下面这张清单过一遍每项都要实际敲一遍验证正确用户名和密码能否登录错误密码是否提示并按预期留在登录页未登录直接访问 index.jsp 是否被拦截回登录页输入 or 11是否被当成普通字符串而不是绕过校验注册一个已存在的用户名是否提示重复中文用户名和密码是否能正常登录。这六项过关基本就达到课程设计的良好档了。我个人的习惯是答辩前把整个项目从零到一重新部署一遍用的是另一台只装了 JDK 和 Tomcat 的干净环境数据库文件、JAR 包全部按部署目录重新放一遍。很多同学在开发环境里跑得顺现场换机器就翻车问题基本都出在路径写死和漏传 JAR 包这两件事上。这套 JSP JavaBean Access 的组合虽然不算新潮但胜在结构清楚、依赖少、答辩好解释把上面这些细节做好课程设计就能稳稳落地。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?