作者:笙囧同学 🧑💻
技术栈: Java + JSP + Servlet + MySQL + Bootstrap
项目难度: ⭐⭐⭐⭐⭐
📖 前言
大家好,我是笙囧同学!👋
最近花了一个多月的时间,从零开始设计并实现了一个企业级的网上购书网站系统。这个项目不仅仅是一个简单的CRUD应用,而是一个功能完整、安全可靠、用户体验优秀的电商平台。
在这篇文章中,我将详细分享整个项目的设计思路、技术选型、核心功能实现以及踩过的坑。希望能给正在学习Java Web开发的小伙伴们一些启发和帮助!
💡 温馨提示:文章较长,建议收藏后慢慢阅读。文末会提供完整的源码下载链接!
🎯 项目概览
📊 项目基本信息
项目属性 | 详细信息 |
---|---|
项目名称 | 网上购书网站系统 |
项目类型 | B2C电子商务平台 |
开发周期 | 30天 |
代码量 | 8000+ 行 |
功能模块 | 5大核心模块 |
技术栈 | Java Web全栈 |
🏗️ 系统架构总览
🎨 项目亮点
- 🔐 企业级安全防护:多层安全验证,防SQL注入、XSS攻击
- 📱 响应式设计:完美适配PC端和移动端
- 🚀 高性能架构:连接池、缓存优化、分页查询
- 🛠️ 完善的测试体系:单元测试、集成测试、性能测试
- 📚 详细的文档:从需求分析到部署运维的完整文档
🎓 核心技术知识点深度解析
在开始介绍技术选型之前,让我先为大家梳理一下这个项目涉及的核心技术知识点,这些都是Java Web开发的精髓所在!作为一名深耕Java Web开发多年的程序员,我将从理论到实践,为大家详细讲解每个技术点的原理、应用场景和最佳实践。
🔍 Java Web技术原理深度剖析
📚 Servlet技术核心原理
Servlet是什么?
Servlet是运行在Web服务器上的Java程序,它是Java Web开发的基石。让我们深入了解Servlet的工作原理:
Servlet核心概念详解:
Servlet容器(Container)
- 负责管理Servlet的生命周期
- 提供网络服务,解析请求并构造响应
- 管理Servlet实例的创建、初始化、服务和销毁
ServletConfig对象
- 包含Servlet的配置信息
- 在init()方法中传入
- 可以获取初始化参数和ServletContext
ServletContext对象
- 代表整个Web应用程序
- 所有Servlet共享同一个ServletContext
- 可以用来存储应用级别的数据
实际代码示例:
public class BookServlet extends HttpServlet {
private BookDAO bookDAO;
@Override
public void init() throws ServletException {
// 初始化阶段,只执行一次
super.init();
bookDAO = new BookDAO();
// 获取初始化参数
String dbUrl = getInitParameter("dbUrl");
String dbUser = getInitParameter("dbUser");
// 初始化数据库连接
bookDAO.initConnection(dbUrl, dbUser);
System.out.println("BookServlet初始化完成");
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 每次请求都会调用
String method = request.getMethod();
// 根据HTTP方法分发请求
if ("GET".equals(method)) {
doGet(request, response);
} else if ("POST".equals(method)) {
doPost(request, response);
} else if ("PUT".equals(method)) {
doPut(request, response);
} else if ("DELETE".equals(method)) {
doDelete(request, response);
}
}
@Override
public void destroy() {
// 销毁阶段,释放资源
if (bookDAO != null) {
bookDAO.closeConnection();
}
System.out.println("BookServlet销毁完成");
}
}
🌐 HTTP协议深度解析
HTTP请求响应模型:
HTTP状态码深度理解:
状态码类别 | 含义 | 常用状态码 | 应用场景 |
---|---|---|---|
1xx 信息性 | 请求已接收,继续处理 | 100 Continue | 大文件上传 |
2xx 成功 | 请求已成功处理 | 200 OK, 201 Created | 正常响应 |
3xx 重定向 | 需要进一步操作 | 301 Moved, 302 Found | 页面跳转 |
4xx 客户端错误 | 请求有语法错误 | 400 Bad Request, 404 Not Found | 参数错误 |
5xx 服务器错误 | 服务器处理错误 | 500 Internal Error, 503 Unavailable | 系统异常 |
🗄️ JDBC技术深度讲解
JDBC架构原理:
JDBC核心接口详解:
DriverManager类
- 管理数据库驱动程序
- 建立数据库连接
- 选择合适的驱动程序
Connection接口
- 代表与数据库的连接
- 管理事务
- 创建Statement对象
Statement接口
- 执行SQL语句
- 三种类型:Statement、PreparedStatement、CallableStatement
ResultSet接口
- 表示查询结果集
- 提供游标操作
- 支持不同的滚动类型
JDBC最佳实践代码:
public class BookDAO {
private static final String DB_URL = "jdbc:mysql://localhost:3306/bookstore";
private static final String DB_USER = "root";
private static final String DB_PASSWORD = "password";
// 使用连接池
private DataSource dataSource;
public BookDAO() {
// 初始化连接池
HikariConfig config = new HikariConfig();
config.setJdbcUrl(DB_URL);
config.setUsername(DB_USER);
config.setPassword(DB_PASSWORD);
config.setMaximumPoolSize(20);
config.setMinimumIdle(5);
config.setConnectionTimeout(30000);
config.setIdleTimeout(600000);
config.setMaxLifetime(1800000);
this.dataSource = new HikariDataSource(config);
}
public List<Book> getBooksByCategory(String category, int page, int size) {
List<Book> books = new ArrayList<>();
// 使用PreparedStatement防止SQL注入
String sql = "SELECT book_id, title, author, price, stock " +
"FROM books WHERE category = ? " +
"ORDER BY created_at DESC " +
"LIMIT ? OFFSET ?";
try (Connection conn = dataSource.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
// 设置参数
pstmt.setString(1, category);
pstmt.setInt(2, size);
pstmt.setInt(3, page * size);
try (ResultSet rs = pstmt.executeQuery()) {
while (rs.next()) {
Book book = new Book();
book.setBookId(rs.getInt("book_id"));
book.setTitle(rs.getString("title"));
book.setAuthor(rs.getString("author"));
book.setPrice(rs.getBigDecimal("price"));
book.setStock(rs.getInt("stock"));
books.add(book);
}
}
} catch (SQLException e) {
// 记录详细的错误信息
logger.error("查询图书失败: category={}, page={}, size={}",
category, page, size, e);
throw new DAOException("查询图书失败", e);
}
return books;
}
// 事务处理示例
public boolean transferStock(int fromBookId, int toBookId, int quantity) {
Connection conn = null;
try {
conn = dataSource.getConnection();
conn.setAutoCommit(false); // 开启事务
// 减少源图书库存
String sql1 = "UPDATE books SET stock = stock - ? WHERE book_id = ? AND stock >= ?";
try (PreparedStatement pstmt1 = conn.prepareStatement(sql1)) {
pstmt1.setInt(1, quantity);
pstmt1.setInt(2, fromBookId);
pstmt1.setInt(3, quantity);
int affected1 = pstmt1.executeUpdate();
if (affected1 == 0) {
throw new BusinessException("源图书库存不足");
}
}
// 增加目标图书库存
String sql2 = "UPDATE books SET stock = stock + ? WHERE book_id = ?";
try (PreparedStatement pstmt2 = conn.prepareStatement(sql2)) {
pstmt2.setInt(1, quantity);
pstmt2.setInt(2, toBookId);
pstmt2.executeUpdate();
}
conn.commit(); // 提交事务
return true;
} catch (Exception e) {
if (conn != null) {
try {
conn.rollback(); // 回滚事务
} catch (SQLException ex) {
logger.error("事务回滚失败", ex);
}
}
logger.error("库存转移失败", e);
return false;
} finally {
if (conn != null) {
try {
conn.setAutoCommit(true); // 恢复自动提交
conn.close();
} catch (SQLException e) {
logger.error("关闭连接失败", e);
}
}
}
}
}
🎨 JSP技术深度解析
JSP(JavaServer Pages)工作原理:
JSP本质上是Servlet的一种简化形式,让我们深入了解JSP的编译和执行过程:
flowchart TD
A[JSP页面] --> B[JSP引擎]
B --> C{首次访问?}
C -->|是| D[JSP编译阶段]
C -->|否| H[直接执行Servlet]
D --> E[解析JSP语法]
E --> F[生成Java源码]
F --> G[编译成Servlet字节码]
G --> H[执行Servlet]
H --> I[生成HTML响应]
I --> J[返回给客户端]
subgraph "JSP编译详细过程"
D1[解析指令标签 <%@ %>]
D2[解析脚本片段 <% %>]
D3[解析表达式 <%= %>]
D4[解析声明 <%! %>]
D5[解析动作标签 <jsp:>]
D6[解析EL表达式 ${}]
end
E --> D1 --> D2 --> D3 --> D4 --> D5 --> D6
style A fill:#e3f2fd
style J fill:#c8e6c9
JSP核心语法详解:
- 指令标签(Directive)
<%-- 页面指令:设置页面属性 --%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" import="java.util.*"
errorPage="error.jsp" isErrorPage="false" %>
<%-- 包含指令:静态包含其他文件 --%>
<%@ include file="header.jsp" %>
<%-- 标签库指令:引入标签库 --%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
- 脚本元素详解
<%-- 声明:定义变量和方法 --%>
<%!
private int visitCount = 0;
public String formatPrice(double price) {
return String.format("¥%.2f", price);
}
%>
<%-- 脚本片段:Java代码块 --%>
<%
visitCount++;
List<Book> books = (List<Book>) request.getAttribute("books");
String currentUser = (String) session.getAttribute("username");
%>
<%-- 表达式:输出值 --%>
<p>访问次数:<%= visitCount %></p>
<p>当前用户:<%= currentUser != null ? currentUser : "游客" %></p>
- EL表达式深度应用
<%-- 基本语法 --%>
<p>用户名:${sessionScope.username}</p>
<p>图书数量:${requestScope.books.size()}</p>
<%-- 运算符使用 --%>
<p>总价:${book.price * book.quantity}</p>
<p>是否有库存:${book.stock > 0 ? '有货' : '缺货'}</p>
<%-- 集合操作 --%>
<c:forEach items="${books}" var="book" varStatus="status">
<tr class="${status.index % 2 == 0 ? 'even' : 'odd'}">
<td>${book.title}</td>
<td>${book.author}</td>
<td>${book.price}</td>
</tr>
</c:forEach>
🔧 Maven构建工具深度讲解
Maven核心概念:
Maven生命周期详解:
阶段 | 描述 | 主要任务 |
---|---|---|
validate | 验证项目 | 检查项目结构和必要信息 |
compile | 编译源码 | 编译src/main/java下的源码 |
test | 运行测试 | 执行src/test/java下的测试 |
package | 打包项目 | 创建JAR/WAR文件 |
verify | 验证包 | 运行集成测试验证包的有效性 |
install | 安装到本地 | 将包安装到本地仓库 |
deploy | 部署到远程 | 将包部署到远程仓库 |
实际项目的pom.xml配置详解:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- 项目基本信息 -->
<groupId>com.shengjiongtongxue</groupId>
<artifactId>bookstore-web</artifactId>
<version>1.0.0</version>
<packaging>war</packaging>
<name>网上购书网站</name>
<description>基于Java Web的企业级购书平台</description>
<!-- 属性配置 -->
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- 依赖版本管理 -->
<servlet.version>4.0.1</servlet.version>
<jsp.version>2.3.3</jsp.version>
<jstl.version>1.2</jstl.version>
<mysql.version>8.0.33</mysql.version>
<junit.version>5.9.2</junit.version>
<mockito.version>5.1.1</mockito.version>
</properties>
<!-- 依赖管理 -->
<dependencies>
<!-- Servlet API -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${servlet.version}</version>
<scope>provided</scope>
</dependency>
<!-- JSP API -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>${jsp.version}</version>
<scope>provided</scope>
</dependency>
<!-- JSTL -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>${jstl.version}</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!-- 连接池 -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>5.0.1</version>
</dependency>
<!-- JSON处理 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
<!-- 日志框架 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.7</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.8</version>
</dependency>
<!-- 测试依赖 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<!-- 构建配置 -->
<build>
<finalName>bookstore</finalName>
<plugins>
<!-- 编译插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>11</source>
<target>11</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<!-- WAR打包插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.3.1</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
<!-- 测试插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.1.2</version>
<configuration>
<includes>
<include>**/*Test.java</include>
<include>**/*Tests.java</include>
</includes>
</configuration>
</plugin>
<!-- Jetty插件 - 用于开发测试 -->
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>11.0.15</version>
<configuration>
<httpConnector>
<port>8080</port>
</httpConnector>
<webApp>
<contextPath>/bookstore</contextPath>
</webApp>
<reload>manual</reload>
</configuration>
</plugin>
</plugins>
</build>
<!-- 配置文件 -->
<profiles>
<!-- 开发环境 -->
<profile>
<id>dev</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<db.url>jdbc:mysql://localhost:3306/bookstore_dev</db.url>
<db.username>root</db.username>
<db.password>password</db.password>
</properties>
</profile>
<!-- 测试环境 -->
<profile>
<id>test</id>
<properties>
<db.url>jdbc:mysql://test-server:3306/bookstore_test</db.url>
<db.username>test_user</db.username>
<db.password>test_password</db.password>
</properties>
</profile>
<!-- 生产环境 -->
<profile>
<id>prod</id>
<properties>
<db.url>jdbc:mysql://prod-server:3306/bookstore_prod</db.url>
<db.username>prod_user</db.username>
<db.password>prod_password</db.password>
</properties>
</profile>
</profiles>
</project>
📚 Java Web核心技术栈
mindmap
root((Java Web技术栈))
前端技术
HTML5
语义化标签
表单验证
本地存储
响应式设计
CSS3
Flexbox布局
Grid布局
动画效果
媒体查询
JavaScript
DOM操作
事件处理
Ajax异步
ES6语法
Bootstrap
栅格系统
组件库
响应式工具
主题定制
后端技术
Java基础
面向对象
集合框架
异常处理
多线程
Servlet
生命周期
请求处理
会话管理
过滤器
JSP
指令标签
动作标签
EL表达式
JSTL标签库
JDBC
连接管理
预编译语句
事务控制
连接池
数据库技术
MySQL
存储引擎
索引优化
查询优化
事务机制
SQL语言
DDL语句
DML语句
DCL语句
存储过程
开发工具
Maven
依赖管理
生命周期
插件机制
多模块项目
Git
版本控制
分支管理
协作开发
代码回滚
🏗️ MVC架构模式深度剖析
🎯 设计模式在Java Web中的应用
在企业级Java Web开发中,设计模式的合理运用能够大大提高代码的可维护性和扩展性。让我详细讲解几个核心设计模式:
1. MVC模式(Model-View-Controller)
实际代码实现:
// Model层 - 实体类
public class Book {
private int bookId;
private String title;
private String author;
private BigDecimal price;
private int stock;
// 构造方法、getter、setter省略
// 业务方法
public boolean isAvailable() {
return stock > 0;
}
public BigDecimal calculateDiscountPrice(double discountRate) {
return price.multiply(BigDecimal.valueOf(1 - discountRate));
}
}
// Model层 - 业务逻辑
public class BookService {
private BookDAO bookDAO;
public BookService() {
this.bookDAO = new BookDAO();
}
public List<Book> searchBooks(String keyword, String category, int page, int size) {
// 参数验证
if (keyword == null || keyword.trim().isEmpty()) {
throw new IllegalArgumentException("搜索关键词不能为空");
}
// 业务逻辑处理
List<Book> books = bookDAO.searchBooks(keyword, category, page, size);
// 数据后处理
return books.stream()
.filter(Book::isAvailable)
.collect(Collectors.toList());
}
}
// Controller层 - 控制器
@WebServlet("/books")
public class BookController extends HttpServlet {
private BookService bookService;
@Override
public void init() throws ServletException {
this.bookService = new BookService();
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String action = request.getParameter("action");
try {
switch (action) {
case "search":
handleSearch(request, response);
break;
case "detail":
handleDetail(request, response);
break;
default:
handleList(request, response);
}
} catch (Exception e) {
handleError(request, response, e);
}
}
private void handleSearch(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String keyword = request.getParameter("keyword");
String category = request.getParameter("category");
int page = Integer.parseInt(request.getParameter("page"));
int size = Integer.parseInt(request.getParameter("size"));
List<Book> books = bookService.searchBooks(keyword, category, page, size);
request.setAttribute("books", books);
request.setAttribute("keyword", keyword);
request.setAttribute("category", category);
RequestDispatcher dispatcher = request.getRequestDispatcher("/books.jsp");
dispatcher.forward(request, response);
}
}
2. DAO模式(Data Access Object)
DAO模式实现:
// DAO接口定义
public interface BookDAO {
// 基本CRUD操作
void save(Book book);
void update(Book book);
void delete(int bookId);
Book findById(int bookId);
List<Book> findAll();
// 业务查询方法
List<Book> findByCategory(String category);
List<Book> searchByKeyword(String keyword);
List<Book> findPopularBooks(int limit);
// 分页查询
List<Book> findWithPagination(int page, int size);
int getTotalCount();
}
// DAO接口实现
public class BookDAOImpl implements BookDAO {
private DataSource dataSource;
public BookDAOImpl() {
this.dataSource = DataSourceFactory.getDataSource();
}
@Override
public void save(Book book) {
String sql = "INSERT INTO books (title, author, publisher, price, stock, category) " +
"VALUES (?, ?, ?, ?, ?, ?)";
try (Connection conn = dataSource.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
pstmt.setString(1, book.getTitle());
pstmt.setString(2, book.getAuthor());
pstmt.setString(3, book.getPublisher());
pstmt.setBigDecimal(4, book.getPrice());
pstmt.setInt(5, book.getStock());
pstmt.setString(6, book.getCategory());
int affectedRows = pstmt.executeUpdate();
if (affectedRows == 0) {
throw new DAOException("保存图书失败,没有行被影响");
}
// 获取生成的主键
try (ResultSet generatedKeys = pstmt.getGeneratedKeys()) {
if (generatedKeys.next()) {
book.setBookId(generatedKeys.getInt(1));
}
}
} catch (SQLException e) {
throw new DAOException("保存图书失败", e);
}
}
@Override
public List<Book> searchByKeyword(String keyword) {
String sql = "SELECT * FROM books WHERE " +
"(title LIKE ? OR author LIKE ? OR description LIKE ?) " +
"AND stock > 0 ORDER BY created_at DESC";
List<Book> books = new ArrayList<>();
String searchPattern = "%" + keyword + "%";
try (Connection conn = dataSource.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, searchPattern);
pstmt.setString(2, searchPattern);
pstmt.setString(3, searchPattern);
try (ResultSet rs = pstmt.executeQuery()) {
while (rs.next()) {
books.add(mapResultSetToBook(rs));
}
}
} catch (SQLException e) {
throw new DAOException("搜索图书失败", e);
}
return books;
}
// 结果集映射方法
private Book mapResultSetToBook(ResultSet rs) throws SQLException {
Book book = new Book();
book.setBookId(rs.getInt("book_id"));
book.setTitle(rs.getString("title"));
book.setAuthor(rs.getString("author"));
book.setPublisher(rs.getString("publisher"));
book.setPrice(rs.getBigDecimal("price"));
book.setStock(rs.getInt("stock"));
book.setCategory(rs.getString("category"));
book.setCreatedAt(rs.getTimestamp("created_at"));
return book;
}
}
3. 工厂模式(Factory Pattern)
// 数据源工厂
public class DataSourceFactory {
private static DataSource dataSource;
private static final Object lock = new Object();
public static DataSource getDataSource() {
if (dataSource == null) {
synchronized (lock) {
if (dataSource == null) {
dataSource = createDataSource();
}
}
}
return dataSource;
}
private static DataSource createDataSource() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl(ConfigManager.getProperty("db.url"));
config.setUsername(ConfigManager.getProperty("db.username"));
config.setPassword(ConfigManager.getProperty("db.password"));
config.setMaximumPoolSize(20);
config.setMinimumIdle(5);
return new HikariDataSource(config);
}
}
// DAO工厂
public class DAOFactory {
public static BookDAO getBookDAO() {
return new BookDAOImpl();
}
public static UserDAO getUserDAO() {
return new UserDAOImpl();
}
public static OrderDAO getOrderDAO() {
return new OrderDAOImpl();
}
}
🔄 HTTP请求处理生命周期
下篇的链接为:https://editor.csdn.net/md/?articleId=149720198