十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

JavaWeb登录注册系统实战:JDBC、Servlet与Tomcat踩坑全解析

JavaWeb登录注册系统实战:JDBC、Servlet与Tomcat踩坑全解析 简介基于Eclipse与SQL数据库实现用户登录注册的Java Web入门项目适合初学JSP、JDBC及数据库交互的开发者参考。系统涵盖注册、登录、会话校验等基础功能可作为课程设计或毕业设计的起步模板。压缩包共45个文件约3.72MB包含JSP页面、Java源码、编译后的class文件、数据库脚本mdf/ldf、war包、配置文件及项目文档等其中15个JSP页面负责界面展示与请求处理6个Java源文件对应Servlet及数据访问逻辑。已有4523人浏览学习是不少Java Web初学者的参考范例。内容预览中还包括完整Eclipse工程目录、数据库备份文件与部署用war包附有入门项目实训指南文档能帮助理解JDBC连接、SQL防注入与密码哈希等关键点同时集成数据库驱动便于直接导入运行并做二次开发。1. 项目概述与整体设计思路刚学完Java基础那会儿我和大多数人一样满脑子都是“我该做个什么项目练手”。做游戏太复杂做电商又够不着最后选来选去落到了一个看着不起眼、但绝对值得认真做一遍的题目上——用户登录注册系统。这玩意儿的地位在JavaWeb学习路径里基本等同于编程界的“Hello World”升级版。你别看它功能简单无非就是注册、登录、校验、存数据库但麻雀虽小五脏俱全。只要你把这么一个系统从零到一完整写出来前端页面、后端逻辑、数据库交互、Session管理、异常处理这些Web开发的核心环节你全都能亲手摸一遍。更重要的是它能帮你把一串散落的知识点串成一条线JSP怎么写、Servlet怎么配、JDBC怎么连、SQL怎么写、Tomcat怎么跑这些问题不再是课本上孤立的概念而是一个个你亲手解决的、实实在在的问题。我用的技术组合是Eclipse SQL Server 2008 R2 JDBC Servlet JSP。选这套组合没什么高深的理由完全是因为我当时手头的机器装上这套环境最顺手而且网上资料最全。如果你用的是MySQL、PostgreSQL甚至达梦数据库这个项目的核心逻辑完全通用只需要替换数据库驱动和连接串就行。这个系统具体能做什么简单来说就是用户在前端页面填写用户名和密码点注册信息就存进数据库下次再打开登录页输入同样的账号密码系统去数据库比对匹配上了就跳转到欢迎页没匹配上就提示错误。听起来简单但里面埋着不少值得琢磨的细节后面我会一个个拆开讲。2. 数据库设计与SQL实现细节2.1 用户表结构设计整个系统里最底层也是最核心的就是数据库表设计。表设计得合理后面写代码会非常舒服设计得有坑后期排查问题能让你怀疑人生。我的用户表结构比较简洁但包含了几个关键考量CREATE TABLE t_user ( id INT IDENTITY(1,1) PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, created_time DATETIME DEFAULT GETDATE() );你可以看到我在username字段上加了UNIQUE约束。这个约束是防止重复注册的第一道防线也是最快的一道防线。在真实项目中你完全可以依赖数据库层面的唯一索引来兜底比在代码里先查一遍再插入要靠谱得多——因为并发情况下两个请求同时查到“用户名不存在”然后同时插入代码层面的检查是拦不住第二个插入的但数据库的唯一约束可以。这就是为什么我强烈建议即使你在代码里做了用户名重复检测数据库层面也一定不要省这个约束。再说password字段我用了VARCHAR(64)这个长度是给密文预留的。如果你直接明文存储在本地练习项目里倒也无所谓但只要你打算把这个项目放到网上、或者给别人演示密码存储就必须认真对待。我当时用MD5加盐处理虽然以现在的眼光看MD5已经不够安全了至少也应该用SHA-256或BCrypt但原理是一样的不能让密码以明文形式躺在数据库里。加盐是什么意思就是用户在注册时系统生成一个随机字符串盐跟用户输入的密码拼在一起再去做哈希最后把哈希结果和盐一起存起来。校验时取出盐拼上用户输入的密码再哈希一次比对两个哈希值是否一致。id字段用了自增主键这个不用多说。created_time记录的注册时间方便后续做用户统计分析比如“这个月新增了多少注册用户”这类需求没有这个字段就得抓瞎。2.2 密码处理和防SQL注入的关键写法接下来说说注册和登录涉及的SQL语句。注册逻辑比较简单INSERT INTO t_user (username, password) VALUES (?, ?);登录逻辑看起来也很简单SELECT id, username FROM t_user WHERE username ? AND password ?;注意这里用了问号占位符配合PreparedStatement传参。我见过很多新手喜欢用字符串拼接的方式来写SQL比如String sql SELECT * FROM t_user WHERE username name AND password pwd ;这么写如果用户名输入的是 OR 11那整条SQL就变成了SELECT * FROM t_user WHERE username OR 11 AND password ;11永远成立直接就把所有用户记录查出来了这就叫SQL注入。而用PreparedStatement的占位符机制参数会被当作纯数据处理数据库不会把用户输入的内容当成SQL语句执行从根上杜绝了注入问题。所以不管项目简单还是复杂SQL语句一律用预编译方式这是原则问题没有商量余地。2.3 日期去重的SQL写法说到SQL的技巧顺带提一个我后来在工作中经常用的场景按天去重统计。比如系统要统计每天的注册用户数你可能会用GROUP BY但如果你对created_time字段直接分组会因为时间精确到时分秒导致同一天的数据被拆成多个组。正确的做法是SELECT CONVERT(VARCHAR(10), created_time, 120) AS reg_date, COUNT(*) FROM t_user GROUP BY CONVERT(VARCHAR(10), created_time, 120);这段SQL在SQL Server里能把时间截断到天然后按天统计。在MySQL里写法略有不同是用DATE()函数。这种细节在工作里太常遇到了建议多留意一下。3. Eclipse环境下的实现细节与核心代码3.1 几个绕不开的Eclipse坑工欲善其事必先利其器。Eclipse作为这个项目的主力开发工具有几个经典问题必须先解决尤其是搜索热词里频繁出现的那条“eclipse 找不到或无法加载主类 org.apache.catalina.startup.bootstrap”。这个问题我当年第一次跑Tomcat的时候就碰到了说下原因和解决思路。这个错误本质上是Eclipse配置的Tomcat运行时环境出了问题导致它无法正确启动Tomcat内核。常见原因有三个一是Tomcat版本和Eclipse的Server插件不兼容比如在太老的Eclipse版本里配Tomcat 9二是Tomcat的安装路径中含有空格或中文导致启动脚本无法正确解析三是没有在Eclipse里指定正确的JRE和Server Runtime Environment。我当时遇到的是第二个原因Tomcat放在了一个带空格的目录下。解决办法也很简单把Tomcat解压到纯英文路径下重新配置Server Runtime Environment。需要提醒的是在Eclipse里添加Tomcat服务器时建议把“Server Locations”设置为“Use Tomcat installation”这样Eclipse会直接使用Tomcat的源码部署Web应用不会产生临时副本也避免了一些奇怪的ClassNotFoundException。另外一个容易踩的坑是代码提示不全。在Eclipse里写JDBC代码时明明导入了包但输入connection.prepareStatement()时提示死活不出来。这个大概率是Java版本和Eclipse内容辅助设置的问题——在Window - Preferences - Java - Editor - Content Assist里把自动触发延迟调低然后确认项目JDK版本正确。你可以按下Alt /手动触发提示看是否有效。3.2 项目结构和核心代码实现这个系统我分了三个部分实体层User、数据库访问层UserDao、以及Servlet控制层。JSP只负责页面展示和表单提交。先看实体类非常简单就是对应表结构的Java对象public class User { private int id; private String username; private String password; // getter和setter省略 }再看核心的UserDao这个类负责跟数据库打交道。我建议在写Dao层时把获取连接和关闭资源的逻辑统一封装避免每个方法里重复写一长串try-catch-finallypublic class UserDao { private String url jdbc:sqlserver://localhost:1433;DatabaseNamelogin_db; private String user sa; private String password your_password; private Connection getConnection() throws Exception { Class.forName(com.microsoft.sqlserver.jdbc.SQLServerDriver); return DriverManager.getConnection(url, user, password); } public boolean register(User u) { String sql INSERT INTO t_user (username, password) VALUES (?, ?); try (Connection conn getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, u.getUsername()); ps.setString(2, u.getPassword()); return ps.executeUpdate() 0; } catch (Exception e) { e.printStackTrace(); return false; } } public User login(String username, String password) { String sql SELECT id, username FROM t_user WHERE username ? AND password ?; try (Connection conn getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, username); ps.setString(2, password); ResultSet rs ps.executeQuery(); if (rs.next()) { User u new User(); u.setId(rs.getInt(id)); u.setUsername(rs.getString(username)); return u; } return null; } catch (Exception e) { e.printStackTrace(); return null; } } }Java 7之后支持try-with-resources写法连接、Statement、ResultSet都能自动关闭极大减少了资源泄漏的概率。这是我一直推荐的习惯——数据库连接是稀缺资源如果你手动写finally去close万一中间的代码抛出异常很容易漏掉关闭逻辑。用try-with-resources代码简洁还安全。这里有一个细节就是login查询只SELECT了id和username没有把password查出来。为什么因为用户登录成功后Session里只需要保存用户身份信息密码这种敏感信息压根不应该被拿出来。这也算一个随手就做的安全意识吧。3.3 Servlet与JSP的协作方式Servlet的主要职责是接收请求、调用Dao层、跳转页面。注册和登录这两个动作我都写在了同一个Servlet里通过action参数区分这样做项目结构比较清爽WebServlet(/user) public class UserServlet extends HttpServlet { private UserDao userDao new UserDao(); protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); String action request.getParameter(action); if (register.equals(action)) { String username request.getParameter(username); String password request.getParameter(password); User u new User(); u.setUsername(username); u.setPassword(MD5Util.md5(password)); // 加密存储 boolean ok userDao.register(u); if (ok) { response.sendRedirect(login.jsp); } else { request.setAttribute(msg, 注册失败用户名可能已存在); request.getRequestDispatcher(register.jsp).forward(request, response); } } else if (login.equals(action)) { // 登录逻辑校验成功后把用户信息塞进Session } } }这里的核心逻辑是注册成功用重定向失败用请求转发。为什么要区别对待因为重定向会发起一次全新的请求用户在浏览器刷新时不会重复提交上一次的表单。如果注册成功你用转发用户按F5刷新表单数据可能会被再次提交造成重复注册。这个细节虽然小但很多新手栽在上面。为了处理这种情况还需要在数据库表上保留UNIQUE约束作为兜底——这就是我前面讲的那条防线真正发挥作用的地方。login.jsp页面的表单action指向/user?actionloginmethod是POST。有人问为什么不用GET因为密码会出现在浏览器的地址栏里这是最低级的安全失误。登录成功后我做了两个操作一是把用户对象放进Session二是重定向到欢迎页。后续如果要做“自动登录”“记住我”之类的功能也是围绕Session和Cookie来展开的。4. 常见问题与排查技巧实录4.1 Eclipse启动报错与Tomcat加载失败第一类高频问题就是开篇说的Tomcat启动失败。除了路径有空格之外还有一个常见原因是端口被占用。Tomcat默认跑在8080端口如果之前有进程没关干净或者别的程序占用了几口Eclipse里的服务器状态就会一直是“Starting...”然后报错。排查方法打开命令行输入netstat -ano | findstr 8080看看哪条进程占用了8080记下PID用taskkill /PID 端口进程号 /F强制结束或到任务管理器里结束进程如果你手边没有命令行也可以直接把Eclipse里旧的Tomcat实例删掉重新添加一个很多时候也能解决。还有一个情况是部署时出现ClassNotFound或者404。这种情况大概率是Web应用的部署结构不对。一个标准的JavaWeb项目在部署时classes目录应该位于WEB-INF下lib目录也在WEB-INF下。Eclipse默认会处理好这些事但如果你手动改过构建路径或者项目是从别的地方拷来的部署结构就乱了。验证方法很简单在Eclipse的Servers视图里右键Tomcat选择Show In - File Manager打开实际的部署目录看WEB-INF下有没有classes和lib。4.2 数据库连接失败的三板斧排查连接数据库报错是这类项目里最让人头疼的一类问题。常见的错误信息有com.microsoft.sqlserver.jdbc.SQLServerException: 通过端口 1433 连接到主机 localhost 失败或者TCP/IP 连接失败。我排查的思路很简单三步走第一确认SQL Server服务真的在跑。打开SQL Server配置管理器看SQL Server服务的状态是不是“正在运行”。这一步能排查掉一半的问题因为很多人的电脑上SQL Server只是装了根本没启动。第二确认TCP/IP协议已启用。默认情况下SQL Server Express版本可能没有开启TCP/IP需要在SQL Server配置管理器的“SQL Server网络配置”里把MSSQLSERVER的协议里TCP/IP设为已启用然后重启SQL Server服务。这一步很重要因为JDBC走的是TCP/IP通道如果这个协议没开本地连接都连不上。第三确认防火墙允许1433端口。如果你只是在本地练习可以把防火墙对SQL Server的拦截临时关掉或者添加一条入站规则放行1433端口的TCP协议。排查完这三步基本上能解决90%的数据库连接问题。4.3 中文乱码问题的根治中文乱码在Web项目里太常见了。用户名注册成“张三”存进数据库的是乱码页面上显示出来也是一堆问号。这个问题的根源在于请求编码、服务端编码、数据库编码不一致。先说请求编码。如果表单用的是POST提交你需要在Servlet里调用request.setCharacterEncoding(UTF-8)而且必须在读取任何request参数之前调用。如果是GET请求还需要去Tomcat的server.xml里给Connector配置URIEncodingUTF-8。我的建议是表单全部用POST统一在Servlet里设置UTF-8。再说数据库编码。你要确认表字段能存中文SQL Server里推荐用NVARCHAR类型因为SQL Server的VARCHAR不支持真正的Unicode。当然这是SQL Server的细节用MySQL的话记得把表和列的字符集设为utf8mb4就行。响应编码也要注意JSP页面顶部那行pageEncodingUTF-8和contentTypetext/html; charsetUTF-8缺一不可否则页面输出的中文也可能乱掉。说句题外话这个乱码问题别嫌烦它在工作中出现的频率比你想的要高得多把这个排查流程记在脑子里以后能省很多时间。5. 项目优化与进阶方向登录注册系统做完能跑通只代表这个项目完成了60分。如果想让这个项目更有含金量我强烈建议你在基础版本之上再迭代一版。以下是我认为性价比最高的几个扩展点第一增加验证码功能。用Java的Graphics2D绘图API把4个随机字母画成图片同时把答案存到Session里。用户在输入用户名密码之前还要填上验证码。这东西防的是暴力破解和撞库而且写一遍你就能理解Session的另一个典型应用场景——存储临时状态。第二把密码存储方式从MD5升级成BCrypt。BCrypt的算法会强制加入随机盐而且它的哈希计算本身就是设计成慢速的就算数据库泄露了攻击者想通过彩虹表破解也难得多。用Java实现的话可以引入 jbcrypt 库一行代码就能完成哈希和校验非常方便。第三增加用户状态管理。比如登录成功后欢迎页显示“欢迎你xxx”退出登录时把Session失效。更进一步可以做“记住我”Cookie存一个token、可以做出“连续登录天数”之类的统计——这时候你建的那个created_time字段就能派上用场了SQL语句可以用上我前面提到的按天去重统计写法。第四如果你愿意甚至可以再往后走一步把项目改成Maven结构。Maven帮你管理依赖不用再手动下载jar包往WEB-INF/lib里塞。像SQL Server的JDBC驱动Maven里一个依赖坐标就搞定了。Maven的依赖管理是JavaWeb项目的基本功以后找工作、做项目都会用到它。说实话我后来接过不少面试跟面试官聊项目时登录注册系统几乎是必聊的。面试官不一定在意你用了多牛的技术但很在意你是否清楚为什么这么设计。为什么密码要加密为什么用预编译SQL为什么注册成功用重定向这些问题能一口气说清楚这个项目你就真正吃透了。这次我踩过的坑写出来看着都不大但每一个都实打实地消耗过我的时间。希望这篇记录能帮你把启动报错、连不上数据库、中文乱码这类问题从“折腾一晚上”变成“三分钟解决”。如果你也在做这个项目建议不要只是把代码抄跑而是亲手把每一个报错都解决一遍踩过的坑才是真正学到的东西。本文还有配套的精品资源点击获取
返回列表