Java Web CRM系统实战:Servlet+JSP+JDBC全链路解析
2026/9/14 14:34:16 网站建设 项目流程

简介:这是一套基于Java技术栈开发的客户关系与进销存一体化管理系统,面向Java初学者及中小型企业管理软件学习者,适用于课程设计、毕业项目或企业轻量级OA系统原型开发。系统覆盖客户全生命周期管理(含VIP、签约、潜在、放弃等客户分类)、日常办公流程(OA模块)以及核心进销存功能(入库、出库、库存查询),具备完整业务闭环能力。资源包共114个文件,含36个Java源码文件构成主体逻辑,47个JAR包提供Spring 4.0、MyBatis 3.2、Jackson、EhCache、MySQL驱动等关键依赖,22个XML配置文件支撑框架整合与SQL映射,辅以JSP页面和Properties属性配置,整体压缩包大小为14.71MB。目前已有60人学习下载,读者可直接导入IDE运行调试,获得结构清晰的MVC分层代码、典型企业级框架集成实践、数据库表设计参考及完整的客户+库存双主线业务实现方案。

1. 一个能跑起来的 Java 客户关系管理系统,不是 Demo,是能填客户、查跟进、导数据的真实业务入口

你下载到一个叫java客户关系管理系统.rar的压缩包,双击解压后看到src/WebContent/lib/和一堆.class文件——这不是教学用的“Hello CRM”小练习,而是典型基于 Servlet + JSP + JDBC 构建的轻量级企业级 CRM 骨架。它不依赖 Spring Boot 自动装配,也不用 Maven 一键拉包,但正因如此,它成了 Java 初中级开发者理解「请求怎么从浏览器进到数据库、页面如何动态拼接、权限怎么靠 session 控制」最扎实的沙盒。适合刚学完 JDBC 和 JSP 的人,在本地 Tomcat 上部署、改字段、加按钮、连真实 MySQL;也适合面试前一周想快速串起「Java Web 全链路」的人:从web.xml的 servlet-mapping,到CustomerDao.java里的 PreparedStatement 防 SQL 注入,再到list.jsp里用 JSTL<c:forEach>渲染客户列表——所有环节都裸露可调试。它不炫技,但每行代码都在回答「Java Web 应用到底长什么样」。

2. 用标准 Java Web 技术栈在本地跑通 CRM 的最小命令与结构验证

2.1 确认 JDK 与 Tomcat 版本兼容性:JDK 8 是当前最稳的基线

这个系统大概率编译于 JDK 1.8(即 JDK 8),因为其web.xmlversion="2.5"3.0lib/下常见servlet-api-2.5.jarjsp-api-2.1.jar,且无module-info.java。若你本地已装 JDK 17 或 21,不要直接运行——Tomcat 9 可兼容 JDK 17,但该系统若含javax.*包(如javax.servlet.http.HttpServlet)而非jakarta.*,则必须降级或启用 Jakarta 迁移工具。最稳妥做法是单独安装 JDK 8u202(官网归档版),并配置环境变量:

# Linux/macOS 示例(~/.bashrc 或 ~/.zshrc) export JAVA_HOME=/opt/jdk1.8.0_202 export PATH=$JAVA_HOME/bin:$PATH

提示:执行java -version输出必须为1.8.0_202,且javac -version一致。若java -version显示 17 但javac -version显示 8,说明 PATH 混乱,需彻底清理旧 JDK 路径。

2.2 解压后直读web.xml,定位核心 Servlet 与 URL 映射路径

进入解压目录,打开WebContent/WEB-INF/web.xml。重点看<servlet><servlet-mapping>块。典型结构如下:

<servlet> <servlet-name>CustomerServlet</servlet-name> <servlet-class>com.crm.servlet.CustomerServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>CustomerServlet</servlet-name> <url-pattern>/customer</url-pattern> </servlet-mapping>

这意味着:访问http://localhost:8080/your-app-name/customer将触发com.crm.servlet.CustomerServlet类的doGet()doPost()方法。注意<url-pattern>值——它决定了你在浏览器输入的路径,也是后续测试接口的基础。若<url-pattern>/cust,那你就不能访问/customer,否则 404。

2.3 部署到 Tomcat 并验证基础服务是否启动成功

将整个解压后的文件夹(如crm-system/直接复制到 Tomcat 的webapps/目录下(不要嵌套子文件夹)。启动 Tomcat:

# Linux/macOS $CATALINA_HOME/bin/startup.sh # Windows %CATALINA_HOME%\bin\startup.bat

等待日志输出Server startup in [xxx] ms后,访问http://localhost:8080/crm-system/crm-system为文件夹名)。若看到登录页(常见login.jsp)或 404,说明部署成功;若报HTTP Status 500,则进入下一步排查。

注意:Tomcat 默认端口为 8080。若被占用,修改conf/server.xml<Connector port="8080" .../>8081,重启后访问http://localhost:8081/crm-system/

2.4 快速验证数据库连接:用CustomerDao.java中的 JDBC URL 手动连库

打开src/com/crm/dao/CustomerDao.java(路径可能略有差异,如dao.impl.CustomerDaoImpl),查找类似以下代码:

private static final String URL = "jdbc:mysql://localhost:3306/crm_db?useUnicode=true&characterEncoding=UTF-8"; private static final String USER = "root"; private static final String PASSWORD = "123456";

用命令行 MySQL 客户端验证:

mysql -h localhost -P 3306 -u root -p123456 -e "USE crm_db; SHOW TABLES;"

若返回客户表名(如t_customer),说明库存在且账号密码正确;若报Unknown database 'crm_db',需手动创建库并导入 SQL 文件(通常在sql/db/目录下,文件名如crm_init.sql):

mysql -u root -p -e "CREATE DATABASE IF NOT EXISTS crm_db DEFAULT CHARSET utf8mb4;" mysql -u root -p crm_db < sql/crm_init.sql

提示:SQL 文件中若含DROP TABLE IF EXISTS,请确保目标库无重要数据;若执行报错Incorrect string value,说明 SQL 文件编码非 UTF-8,用 VS Code 以 UTF-8 无 BOM 重新保存后再导入。

3. 修改登录逻辑与客户列表展示:从硬编码校验到真实数据库查询

3.1 登录功能改造:把LoginServlet中的用户名密码比对改为 JDBC 查询

原始LoginServlet.java常见写法是硬编码校验:

// ❌ 危险!不可用于生产 if ("admin".equals(username) && "123".equals(password)) { request.getSession().setAttribute("user", username); response.sendRedirect("main.jsp"); }

应替换为调用UserDao查询数据库:

// ✅ 安全做法:查库验证 UserDao userDao = new UserDao(); // 假设已实现 User user = userDao.findByUsernameAndPassword(username, password); if (user != null) { request.getSession().setAttribute("user", user); // 存完整 User 对象,含 id、role 等 response.sendRedirect("main.jsp"); } else { request.setAttribute("error", "用户名或密码错误"); request.getRequestDispatcher("login.jsp").forward(request, response); }

对应UserDao.java需实现:

public User findByUsernameAndPassword(String username, String password) { String sql = "SELECT id, username, password, role FROM t_user WHERE username = ? AND password = ?"; // 注意:实际项目应加密存储密码,此处为演示用明文(仅限学习环境) return query(sql, User.class, username, password); // query() 为通用 JDBC 查询方法 }

3.2 客户列表页list.jsp的 JSTL 动态渲染与分页参数传递

打开WebContent/list.jsp,确认是否使用 JSTL 标签库。顶部应有:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>

客户表格主体应为:

<table border="1"> <tr> <th>客户ID</th><th>姓名</th><th>电话</th><th>邮箱</th><th>操作</th> </tr> <c:forEach items="${customerList}" var="cust"> <tr> <td>${cust.id}</td> <td>${cust.name}</td> <td>${cust.phone}</td> <td>${cust.email}</td> <td> <a href="customer?action=edit&id=${cust.id}">编辑</a> | <a href="customer?action=delete&id=${cust.id}" onclick="return confirm('确定删除?')">删除</a> </td> </tr> </c:forEach> </table>

关键点:${customerList}必须由CustomerServletlistaction 放入 request:

// CustomerServlet.java 中 list 方法 List<Customer> list = customerDao.findAll(); request.setAttribute("customerList", list); request.getRequestDispatcher("list.jsp").forward(request, response);

3.3 添加新客户:add.jsp表单提交到CustomerServletaddaction

add.jsp应包含标准 HTML 表单:

<form action="customer" method="post"> <input type="hidden" name="action" value="add"/> 姓名:<input type="text" name="name" required/><br/> 电话:<input type="text" name="phone"/><br/> 邮箱:<input type="email" name="email"/><br/> <input type="submit" value="添加客户"/> </form>

CustomerServlet.java中需新增add分支:

} else if ("add".equals(action)) { String name = request.getParameter("name"); String phone = request.getParameter("phone"); String email = request.getParameter("email"); Customer cust = new Customer(); cust.setName(name); cust.setPhone(phone); cust.setEmail(email); int result = customerDao.add(cust); // 返回影响行数 if (result > 0) { request.setAttribute("msg", "添加成功!"); } else { request.setAttribute("msg", "添加失败,请重试"); } request.getRequestDispatcher("add.jsp").forward(request, response); }

注意:add.jsp<form action="customer">action值必须与web.xml<url-pattern>一致(如/customer),否则请求无法路由到该 Servlet。

4. 数据库字段扩展与中文乱码修复:让客户信息支持地址、备注等业务字段

4.1 在t_customer表中安全添加addressremark字段

先备份原表:

CREATE TABLE t_customer_backup AS SELECT * FROM t_customer;

再执行添加字段语句(MySQL 5.6+):

ALTER TABLE t_customer ADD COLUMN address VARCHAR(255) DEFAULT '' COMMENT '客户地址', ADD COLUMN remark TEXT COMMENT '备注信息';

验证字段是否添加成功:

DESCRIBE t_customer; -- 应看到 address 和 remark 列

4.2 同步更新 Java 实体类Customer.java与 DAO 层映射

打开src/com/crm/entity/Customer.java,添加属性及 getter/setter:

private String address; private String remark; // getter/setter 省略,IDE 可自动生成 public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public String getRemark() { return remark; } public void setRemark(String remark) { this.remark = remark; }

CustomerDao.javaadd()方法中,更新 SQL 与参数绑定:

// 原 add() 方法中 String sql = "INSERT INTO t_customer (name, phone, email, address, remark) VALUES (?, ?, ?, ?, ?)"; return update(sql, customer.getName(), customer.getPhone(), customer.getEmail(), customer.getAddress(), customer.getRemark());

同理,update()findById()方法也需同步增加addressremark字段的读写逻辑。

4.3 彻底解决中文乱码:从数据库、连接 URL 到 JSP 页面三重设置

乱码根源常在三层脱节。按顺序检查:

  1. MySQL 库与表字符集

    SHOW CREATE DATABASE crm_db; -- 应为 DEFAULT CHARACTER SET = utf8mb4 SHOW CREATE TABLE t_customer; -- 应为 ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

    若非utf8mb4,执行:

    ALTER DATABASE crm_db CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; ALTER TABLE t_customer CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
  2. JDBC 连接 URL 强制指定编码
    CustomerDao.java中 URL 必须含characterEncoding=UTF-8

    private static final String URL = "jdbc:mysql://localhost:3306/crm_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai";
  3. JSP 页面声明 UTF-8 编码
    所有.jsp文件顶部第一行必须为:

    <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

    <head>中含:

    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

提示:若 Tomcat 日志中出现?替代中文,优先检查第 2 步 JDBC URL;若浏览器页面显示方块,优先检查第 3 步 JSP 编码声明。

5. 面试高频考点落地:用这个 CRM 理解 Java Web 核心机制与八股文答案

5.1 Servlet 生命周期与web.xml配置的对应关系:从load-on-startupdestroy()

这个 CRM 的CustomerServlet未配置load-on-startup,因此首次访问/customer时才实例化(init()被调用)。若你想让 Servlet 启动时就加载(如初始化缓存),可在web.xml中添加:

<servlet> <servlet-name>CustomerServlet</servlet-name> <servlet-class>com.crm.servlet.CustomerServlet</servlet-class> <load-on-startup>1</load-on-startup> <!-- 数字越小,优先级越高 --> </servlet>

此时 Tomcat 启动时即调用init()。而destroy()在 Tomcat 关闭或应用卸载时调用,常用于释放数据库连接池资源。在CustomerServlet.java中可重写:

@Override public void destroy() { System.out.println("CustomerServlet 已销毁,释放资源..."); // 如关闭静态连接池(若使用了) if (connectionPool != null) { connectionPool.close(); } }

这直接对应 Java 面试题:“Servlet 的生命周期是怎样的?”——答案必须包含init()(一次)、service()(多次)、destroy()(一次),且明确init()destroy()由容器调用,非程序员主动触发。

5.2 Session 与 Cookie 在 CRM 登录中的协作:为什么关浏览器就退出?

CRM 登录后,request.getSession().setAttribute("user", user)将用户对象存入 Session。Session ID 默认通过 Cookie 传递(名为JSESSIONID)。当浏览器关闭,该 Cookie 被清除,下次请求无有效 Session ID,服务器生成新 Session,原user属性丢失,导致“退出”。

验证方式:登录后,浏览器开发者工具 → Application → Cookies,查看JSESSIONID值;关闭浏览器再打开,该值已变。

若需“记住我”功能,需额外实现:

  • 前端勾选“记住我”,后端生成长期 Token 存入数据库;
  • 设置 CookiemaxAge为 7 天(非会话 Cookie);
  • 每次请求优先校验 Token,再 fallback 到 Session。

这正是 Java 面试中“Session 和 Cookie 区别”的实操注脚:Cookie 是客户端存储,Session 是服务端存储,二者通过 Cookie 中的 Session ID 关联。

5.3 JDBC 中PreparedStatement防 SQL 注入的代码级证明

打开UserDao.javafindByUsernameAndPassword方法,对比两种写法:

// ❌ Statement 拼接字符串 —— 危险! String sql = "SELECT * FROM t_user WHERE username = '" + username + "' AND password = '" + password + "'"; Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(sql); // 若 username='admin' --',则绕过密码校验! // ✅ PreparedStatement 占位符 —— 安全! String sql = "SELECT * FROM t_user WHERE username = ? AND password = ?"; PreparedStatement pstmt = conn.prepareStatement(sql); pstmt.setString(1, username); // 参数 1 绑定 username pstmt.setString(2, password); // 参数 2 绑定 password ResultSet rs = pstmt.executeQuery(); // username 中的 ' 不会被解析为 SQL 语法

在 CRM 中搜索所有executeQuery(executeUpdate(,确认均使用PreparedStatement而非Statement。这是 Java 基础和 Java 面试八股文中“如何防止 SQL 注入”的标准答案——必须写出?占位符和setXxx()方法调用。

5.4 JSP 四大作用域与 EL 表达式${}的实际作用范围对照表

CRM 中不同位置使用的 EL 表达式,对应不同作用域:

EL 表达式示例作用域设置位置(Java 侧)生效范围面试考点
${user.name}sessionrequest.getSession().setAttribute("user", user)当前会话所有请求Session 作用域最大,跨请求
${msg}requestrequest.setAttribute("msg", "添加成功")仅本次请求转发(如forwardRequest 作用域最小,仅一次跳转
${param.id}paramURL 参数?id=123任何 JSP 可读取Param 是只读作用域,对应 GET/POST 参数
${initParam.appName}applicationweb.xml<context-param>整个 Web 应用Application 作用域最大,全局共享

list.jsp${customerList}来自request.setAttribute(),故属于 request 作用域;而login.jsp${error}同理。理解此表,就能清晰回答“JSP 四大作用域的区别与使用场景”。

注意:若 EL 表达式不解析(显示为原文${user.name}),检查web.xml是否为version="2.5"以上,且 JSP 顶部有<%@ page isELIgnored="false" %>(默认为 false,通常无需显式写)。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询