
简介基于 JSP 与 SQL Server 的登录注册示例项目面向刚接触 Java Web 开发与数据库交互的初学者用于演示如何把动态网页和关系型数据库结合起来完成最典型的用户身份验证流程。资源包含 46 个文件其中 28 个 JSP 页面承担页面展示与业务处理配合 Java 源文件、class 字节码、JDBC jar 驱动以及 XML 工程配置压缩后仅 581KB目录结构清晰便于对照项目各层代码理解组织方式。示例已覆盖登录、注册、表单提交、用户信息校验、密码安全处理、异常处理等环节并演示了用 session 维护登录状态的基本做法。已有 835 人浏览学习适合作为 JSP 课程设计、数据库课程作业或入门级个人练手参考。通过完整代码可以学习用户表设计、JDBC 连接、SQL 执行与会话管理等关键技术细节并在后续开发中借鉴防 SQL 注入等安全思路为搭建带用户体系的 Web 项目打好基础。1. 拿到“JSP sqlserver 登录注册.zip”后先想清楚这三件事JSP sqlserver 登录注册.zip 这个标题几乎是 JavaWeb 课程设计和基于 jsp 的毕设选题里最典型的一份交付物。很多人把它解压出来直接丢进 IDEA页面能开数据库连不上数据库连上了注册又报 500。这个包真正解决的问题不是三行代码怎么写而是怎么让一个老项目在新环境里重新立起来。当你决定照着它做时先接受三个事实第一JSP 确实老了但课设和旧系统里它还很常见没必要一上来就换 Spring Boot第二花在环境上的时间通常比改代码多JDK、Tomcat、SQLServer、驱动版本必须对齐第三登录状态主要靠 Session 管搞懂 Session 就等于搞懂这个项目的一半。下面按这个顺序展开。2. JSP 项目跑通前环境选型与 JDBC 连接是第一个必过的坎2.1 JSP SQLServer 为什么还是课设和旧系统的主力先别急着改架构JSP 严格说不是新技术但你在网上搜“idea新建jsp项目”仍然能翻出大量今年更新的教程说明这套东西的存量远比你想象的大。在 JavaWeb 课程设计里Servlet 负责接收表单JSP 负责渲染页面JDBC 负责访问数据库一条链路清楚得像是教科书模型。SQL Server 在这条链路里当数据库好处是图形化工具齐全Navicat 连接 sqlserver 之后建表、看报错信息都很直接安装向导一路点下去就是一个能用的实例对没有 Linux 基础的学生特别友好。坏处是它的历史包袱也重。老项目可能基于 JDK 7、Tomcat 7 开发驱动是 4.x。你现在用 JDK 17 和 Tomcat 10 去跑轻则编译失败重则 javax.servlet 全部变成 jakarta.servlet代码里 import 全红。我的态度是如果项目标题就是“JSP sqlserver 登录注册”不要急着把它升级成 Spring Boot先按它诞生时的技术栈把它跑通再谈改造。2.2 本地环境怎么配JDK 8、Tomcat 9、SQLServer 2019 和驱动的组合给一套我实际用下来最省事的组合。JDK 用 1.8很多老代码里写死的 API 在 8 上都还能编译Tomcat 用 9.0.x它对应 Servlet 4和传统的 javax.servlet 命名空间吻合SQL Server 用 2019 Developer官方文档说可以免费用于开发和测试安装教程搜“sqlserver 安装教程”就能找到对 Windows 环境也更友好。组件建议版本关键理由JDK1.8老项目编译目标多为 8避免新语法引发的兼容问题Tomcat9.0.x保持 javax.* 包名不会像 Tomcat 10 那样需要改 importSQL Server2019 Developer免费开发版默认实例适合入门JDBC 驱动mssql-jdbc 9.4.1.jre8支持 encrypt、trustServerCertificate 参数和 JDK 8 严格匹配我第一次跑这类项目时差点在“mssql-jdbc 到底用哪个版本”上翻车。如果你用的驱动是 6.x 甚至 4.x连接串里的 encrypt 参数会直接导致驱动不认属性反过来说9.4.1 也向下兼容老代码。记住一个原则驱动版本跟随 JDK而不是跟随 SQL Server 版本。2.3 在 IDEA 里新建 jsp 项目并确认 JDBC 连通三步走不要直接拿压缩包解压目录当工程这是第一个血泪教训。压缩包里的 .idea、target、out 全是别人电脑上的路径导入后 IDEA 会报“找不到模块”这类玄学错误。常见做法是在 IntelliJ IDEA Ultimate 里新建项目选择 Java EnterpriseApplication Server 选 Tomcat 9模板勾选 Web Application。这就完成了 idea新建jsp项目 这个动作。没有 Ultimate 的同学用 Community 也能建但需要手动补 web 目录和 Servlet 依赖不建议。项目建好后把驱动 jar 放进 WEB-INF/lib。如果用 Maven加这段依赖dependency groupIdcom.microsoft.sqlserver/groupId artifactIdmssql-jdbc/artifactId version9.4.1.jre8/version /dependency这里不推荐直接抄最新版因为新版驱动要求 JDK 11 以上而多数老代码还在 JDK 8 上跑版本对不上会出现“Unsupported major.minor version”之类的错误。然后写一个最简连接类验证整条链路import java.sql.*; public class DBHelper { private static final String URL jdbc:sqlserver://localhost:1433;DatabaseNameuserdb;encrypttrue;trustServerCertificatetrue; private static final String USER sa; private static final String PASSWORD 你的密码; static { try { Class.forName(com.microsoft.sqlserver.jdbc.SQLServerDriver); } catch (ClassNotFoundException e) { throw new RuntimeException(JDBC 驱动加载失败检查 jar 是否在 WEB-INF/lib, e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }注意 Class.forName 里的包名是com.microsoft.sqlserver.jdbc.SQLServerDriver这是 mssql-jdbc 从 4.0 开始一直没变的入口。URL 里encrypttrue;trustServerCertificatetrue这两个参数解决的是本地开发时 SSL 证书校验问题没有它们新版驱动默认加密时会报“证书链无法验证”。sa 密码如果含特殊字符比如分号推荐先用一个简单的临时密码测试连通性跑通后再改。最后跑一个 SELECT 1public static void main(String[] args) { try (Connection conn DBHelper.getConnection(); PreparedStatement ps conn.prepareStatement(SELECT 1); ResultSet rs ps.executeQuery()) { if (rs.next()) { System.out.println(连接成功: rs.getInt(1)); } } catch (SQLException e) { e.printStackTrace(); } }try-with-resources 的写法会在语句执行完自动关连接比老代码里手动 conn.close() 可靠。连接串、用户名、密码这三个变量是后面一切登录注册功能的基础。任何一个不对后续所有报错都从“数据库连不上”开始。3. 实现登录、注册、下线功能建表、Servlet 和 Session 控制3.1 用 SQLServer 建用户表NVARCHAR、密码摘要和唯一索引建议在 SQL Server Management Studio 或者 Navicat 连接 sqlserver 后先执行建表脚本。工具上直接操作会更快但把脚本存下来跟着项目走将来换机器重建库时不用重新录一遍字段。CREATE TABLE app_user ( id INT IDENTITY(1,1) PRIMARY KEY, username NVARCHAR(50) NOT NULL, password NVARCHAR(64) NOT NULL, nickname NVARCHAR(50) NULL, age INT NULL, register_time DATETIME DEFAULT GETDATE() ); GO CREATE UNIQUE INDEX uk_username ON app_user(username); GO这里有两个容易被视频教程带偏的点。第一username 和 nickname 必须用 NVARCHAR不要因为看到网上老资料用 VARCHAR 能跑就跟着写。SQL Server 的 VARCHAR 在不同排序规则下对中文支持不一样NVARCHAR 固定按 Unicode 存储中文用户名、emoji 都不会变问号。第二password 字段长度 64存放的是 SHA-256 十六进制摘要不管用户输入多长密码落库都是固定 64 字符。如果你之前接触过 Oracle会问 oracle number 对应 sqlserver 中的什么数据类型这个表里的 age 就是最典型答案没有小数用 INT有小数部分才考虑 DECIMAL(p,s)。唯一索引 uk_username 是防重复注册的数据库层保障。代码里先 SELECT 再 INSERT 在高并发下照样能插重但唯一索引会把第二次插入直接按主键冲突拒绝。3.2 注册接口PreparedStatement、sqlserver 字符串转数字与 JSON 返回注册接口的核心代码并不长但每一行都可能成为坑WebServlet(/register) public class RegisterServlet extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); resp.setCharacterEncoding(UTF-8); resp.setContentType(application/json;charsetUTF-8); String username req.getParameter(username); String password req.getParameter(password); String nickname req.getParameter(nickname); String ageStr req.getParameter(age); PrintWriter out resp.getWriter(); if (username null || username.trim().isEmpty() || password null || password.trim().isEmpty()) { out.write({\code\:400,\msg\:\用户名或密码不能为空\}); return; } int age 0; try { if (ageStr ! null !ageStr.trim().isEmpty()) { age Integer.parseInt(ageStr.trim()); } } catch (NumberFormatException e) { out.write({\code\:400,\msg\:\年龄必须是数字\}); return; } String passwordHash DigestUtils.sha256Hex(password); String sql INSERT INTO app_user(username,password,nickname,age) VALUES(?,?,?,?); try (Connection conn DBHelper.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, username.trim()); ps.setString(2, passwordHash); ps.setString(3, nickname); if (age 0) { ps.setInt(4, age); } else { ps.setNull(4, Types.INTEGER); } int rows ps.executeUpdate(); if (rows 0) { out.write({\code\:200,\msg\:\注册成功\}); } else { out.write({\code\:500,\msg\:\注册失败\}); } } catch (SQLException e) { if (e.getErrorCode() 2601 || e.getErrorCode() 2627) { out.write({\code\:400,\msg\:\用户名已存在\}); } else { e.printStackTrace(); out.write({\code\:500,\msg\:\服务器错误\}); } } } }这个 Servlet 的要点有三个。第一凡是 SQL 里有动态参数一律用 PreparedStatement 的 ? 占位符。用户名里出现单引号甚至 OR 11也只会被当成普通字符不会注入成一条额外条件。第二年龄字段的解析就是 sqlserver 字符串转数字的典型场景浏览器表单传过来的一定是 String转 int 前必须判断 NumberFormatException。这个异常在旧代码里通常被“前端用 number 输入框就没事”的假设掩盖但你拿 Postman 手动测试时很容易触发。第三返回统一 JSON 格式{code:200,msg:...}前端不管用 JSP 脚本还是 jQuery 都容易处理。要注意 DigestUtils 来自 Apache Commons Codec如果项目里没有这个依赖可以在注册和登录两个 Servlet 里用 JDK 自带 MessageDigest 写一个小工具类节省一次引包。密码摘要的算法前后端必须一致这是登录能否成功的底线。3.3 登录与下线Session 生命周期和 jsp 个人信息展示页面登录接口负责把用户身份放进 Session这样跨页面请求才能记得住你是谁。老项目里常见的写法是把用户输入直接拼在 SQL 里比较这里我们只比对摘要WebServlet(/login) public class LoginServlet extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); String username req.getParameter(username); String password req.getParameter(password); String sql SELECT id, username, nickname FROM app_user WHERE username? AND password?; try (Connection conn DBHelper.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, username); ps.setString(2, DigestUtils.sha256Hex(password)); ResultSet rs ps.executeQuery(); if (rs.next()) { req.getSession().setAttribute(userId, rs.getInt(id)); req.getSession().setAttribute(username, rs.getString(username)); req.getSession().setAttribute(nickname, rs.getString(nickname) null ? username : rs.getString(nickname)); resp.sendRedirect(profile.jsp); } else { req.setAttribute(error, 用户名或密码错误); req.getRequestDispatcher(login.jsp).forward(req, resp); } } catch (SQLException e) { e.printStackTrace(); resp.sendError(500); } } }这里rs.getString(nickname) null ? username : rs.getString(nickname)是防止用户在注册时没填昵称登录后个人信息页面上只能看到空白。登录成功后跳转到 profile.jsp也就是 jsp 个人信息展示页面页面上用% session.getAttribute(nickname) %输出昵称。下线功能则更简单WebServlet(/logout) public class LogoutServlet extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.getSession().invalidate(); resp.sendRedirect(login.jsp); } }invalidate() 清掉整个 Session包括里面的 userId、username、nickname。之后用户再访问 profile.jsp 就会因为取不到属性而出问题所以必须要有一个拦截器或者过滤器做登录态判断。这也就是后面 401 问题的来源。到这里实现登录、注册、下线功能在代码上已经齐全。如果你还顺手需要显示“欢迎回来xxx”可以往 profile.jsp 里塞一段判断 Session 是否为空的前端逻辑但更优雅的做法是放到过滤器里统一做。4. 把 zip 项目跑起来的五个排查方向驱动、导入、401、端口和中文排查前先看 Tomcat 的 catalina.out 和 IDEA 的 Run 窗口报错信息比表面现象诚实得多。下面按我实际遇到的频率从高到低排。4.1 IDEA 里 sqlserver JDBC 自动下载 failed先绕开 Maven 网络坑现象在 pom.xml 里加入 mssql-jdbc 依赖后IntelliJ IDEA 提示Download from Maven failed代码里 SQLServerDriver 一直标红。原因最常见的是中央仓库访问不稳定不是坐标写错。Gradle 项目也可能遇到同样问题本质是 Maven 仓库网络握手失败。热词里搜到的 IntelliJ IDEA sqlserver jdbc 自动下载 from maven failed 就是这个场景。解决手动下载 mssql-jdbc-9.4.1.jre8.jar放进 WEB-INF/lib在 IDEA 里右键该 jar选 Add as Library。如果你的项目确实要走 Maven就把 settings.xml 里的 mirror 指向阿里云镜像改完重新 Reload。但更稳的办法是本地引入 jar让工程不再依赖外网下载。对于课程设计来说少一个网络变量就少一个半夜翻车的理由。4.2 SQLServer 无法导入数据提示“数据无效”多半是编码现象把 zip 里的 .sql 文件拖进 Navicat 或 SSMS 执行弹窗显示“数据无效”或者部分中文注释在表里变成乱码。原因.sql 脚本文件保存的编码和客户端默认编码不一致。常见的是脚本是 UTF-8 带 BOM而 SQL Server Management Studio 默认按 ANSI/GBK 打开遇到 UTF-8 的多字节字符就把识别打断了。这和字段类型无关纯粹是文件编码问题。解决用 VS Code 打开脚本右下角点击编码选择“通过编码保存”另存为 UTF-8 with BOM。或者直接在 SSMS 的菜单里用“打开”而不是拖拽并在“高级保存选项”里选择 Unicode。如果你已经建好表可以先导出单表结构再导数据避免整个脚本一步到位。记住数据无效不等于 SQL 写错先怀疑文件编码。4.3 注册接口测试提示 401“未登录,请登录!”Session 拦截逻辑现象用 Postman 直接 POST 注册接口返回{code:401,message:未登录,请登录!}。原因这是项目里配置了过滤器默认拦截所有 /api/* 或所有请求在过滤器里判断 Session 没有登录用户就直接返回 401。注册接口本身不需要登录但过滤器没放行所以被挡在前面。解决找到 web.xml 里的 Filter 或 WebFilter把 /register、/login 以及 /static/* 加到放行名单。更规范的做法是在过滤器里维护一个 whiteList 路径集合命中就 chain.doFilter 放行。如果你测注册时不想改代码可以先调登录接口拿到 Cookie再带着 Cookie 请求注册但这只是临时绕过最终还是要把白名单配好。4.4 连接超时连不上 1433配置管理器、实例名和防火墙现象Tomcat 能启动页面也显示但一点登录就卡住直到超时异常打印The TCP/IP connection to the host localhost, port 1433 has failed。原因SQL Server 默认安装时 TCP/IP 协议可能是禁用状态或者命名实例导致连接串里的 instanceName 解析失败。也可能是服务器防火墙没有放行 1433 端口。解决打开“SQL Server 配置管理器”找到当前实例的“协议”把 TCP/IP 启用然后在 IP 地址页签里往下滚到 IPALL把端口填成 1433。重启 SQL Server 服务后用 telnet 或 PowerShell 的 Test-NetConnection localhost -Port 1433 验证。如果项目里连接串写的是 instanceNameMSSQLSERVER 而数据库又是默认实例建议直接删掉 instanceName 参数只用端口连接减少一层解析。另外确认连接串里的用户名是 sa 时数据库已经开了“SQL Server 和 Windows 身份验证模式”否则混合认证没开启密码登录一定失败。4.5 中文用户名显示成问号NVARCHAR 与页面编码现象注册时输入“小明”数据库里存的是 ???或者登录成功后在 JSP 页面上显示乱码。原因叠加了几层问题表字段用了 VARCHAR 而不是 NVARCHARJSP 页面没有 pageEncodingJDBC URL 没有按 Unicode 传参。SQL Server 驱动从 9.x 开始默认以 Unicode 发送字符串参数所以最常见的还是前两个。解决建表脚本里把 username 改成 NVARCHAR已经建成 VARCHAR 的表单独执行ALTER TABLE app_user ALTER COLUMN username NVARCHAR(50)。JSP 首行加% page contentTypetext/html;charsetUTF-8 pageEncodingUTF-8 %Servlet 里按照前面注册逻辑的设置编码。最后排查时不要用 Navicat 手动往表里插中文因为工具默认连接可能不是 UTF-8手动插入的数据会误导判断。5. 传统 jsp 项目打包 war 部署到 Tomcat验证和一个收尾习惯5.1 打包 warIDEA Artifact 与 Maven 命令拿到的 zip 大概率是传统 jsp 项目打包 war 的产物里面没有 pom.xml。那我一般用 IDEA 的 Artifact 打File - Project Structure - Artifacts - 加号 - Web Application: Archive然后 Build - Build Artifact。IDEA 会在 out 目录生成 war。如果项目带 Maven直接用命令行更省事mvn clean package -DskipTests生成的 war 丢进 Tomcat 的 webapps 目录启动后自动解压访问路径就是 war 包名。这一步要多等几秒Tomcat 解压和初始化 Servlet 都需要时间第一次访问 404 可以先看解压目录有没有生成。5.2 nginx 不解析 jsp转发给 Tomcat 的配置与 Session 注意点有同学会问 nginx 支持 jsp 吗。nginx 本身不支持它的职责是静态文件和反向代理。JSP 页面必须交给 Tomcat 或同类 Servlet 容器解析。本地调试时可以直接访问 Tomcat 端口如果要走 nginx加一段 proxy_passlocation / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $remote_addr; }此时注意 context path。如果 war 包名叫 login-demo那么 JSP 跳转里写的 /login.jsp 应该改成 /login-demo/login.jsp否则转发到 Tomcat 后直接 404。Session 的 Cookie 默认绑定在 host 上跳转路径错了登录成功后还是会被弹回登录页。5.3 部署后按这个顺序验证登录注册最后给一张验证清单照着走一遍比瞎点按钮高效步骤操作预期结果1打开 login.jsp页面正常显示2注册一个新用户返回成功数据库能查到3错误密码登录提示用户名或密码错误4正确密码登录跳转到个人信息页显示昵称5退出后点浏览器后退刷新后必须回到未登录状态第五步是我带新人时必查的一项很多登录系统退出了但浏览器后退还能看到上一个页面这是页面缓存造成的假象。要消除它可以在登录后页面加 Cache-Control: no-store或者退出时跳转到带随机参数的 URL。每次跑这种项目我的习惯都是先跑通注册再跑登录最后测下线顺序反了会因为登录态没建立而误判功能是坏的。希望这套路径对你有用。本文还有配套的精品资源点击获取