基于javaweb+mysql的jsp+servlet嘟嘟蛋糕商城系统(java+jdbc+servlet+html+ajax+mysql+fileupload)
'# 基于javaweb+mysql的jsp+servlet嘟嘟蛋糕商城系统(java+jdbc+servlet+html+ajax+mysql+fileupload)
一、背景与问题
在传统Web开发中,JSP+Servlet+JDBC的组合曾是主流架构。本系统基于这一技术栈实现一个蛋糕商城系统,涉及核心功能包括商品展示、搜索过滤、购物车管理、文件上传等。
该技术栈面临以下挑战:
- 跨域请求处理
- 数据库连接池优化
- 文件上传安全机制
- 事务一致性保障
- 前后端数据交互安全
二、基本原理
1. 技术栈架构
前端:HTML + JavaScript + Ajax
后端:Servlet + JSP + JDBC
数据库:MySQL
文件存储:本地文件系统2. 核心组件交互
- Servlet处理业务逻辑,通过JDBC与MySQL交互
- JSP作为动态页面展示层
- Ajax实现前后端异步通信
- FileUpload处理商品图片上传
- 连接池管理数据库连接资源
三、环境准备
1. 开发环境
- JDK 1.8+
- Tomcat 9.x
- MySQL 8.x
- Maven 3.x
2. 数据库设计
创建cake_shop数据库,包含以下表:
CREATE DATABASE cake_shop;
USE cake_shop;
CREATE TABLE products (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255) NOT NULL,
price DECIMAL(10,2) NOT NULL,
description TEXT,
image VARCHAR(255)
);
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) UNIQUE NOT NULL,
password VARCHAR(100) NOT NULL,
email VARCHAR(100)
);
-- 添加索引优化查询
CREATE INDEX idx_product_name ON products(name);四、核心实现
1. 数据库连接池配置(JDBC)
关键代码:
// 数据库配置类
public class DBUtil {
private static final String URL = "jdbc:mysql://localhost:3306/cake_shop?useSSL=false&serverTimezone=UTC";
private static final String USER = "root";
private static final String PASSWORD = "your_password";
// 静态代码块初始化连接池
static {
try {
Class.forName("com.mysql.cj.jdbc.Driver");
// 使用HikariCP连接池
HikariConfig config = new HikariConfig();
config.setJdbcUrl(URL);
config.setUsername(USER);
config.setPassword(PASSWORD);
config.setMaximumPoolSize(10);
config.setConnectionTimeout(30000);
config.setIdleTimeout(60000);
config.setPoolName("CakeShopPool");
dataSource = new HikariDataSource(config);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
}关键点说明:
- 使用HikariCP连接池提升性能
- 设置连接池参数防止资源耗尽
- 静态初始化确保单例模式
2. Ajax异步通信实现
前端代码:
<!-- 搜索框 -->
<input type="text" id="searchInput" placeholder="搜索蛋糕...">
<button onclick="searchProducts()">搜索</button>
<script>
function searchProducts() {
const query = document.getElementById('searchInput').value;
fetch('/search', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ query })
})
.then(response => response.json())
.then(data => {
const container = document.getElementById('productList');
container.innerHTML = '';
data.forEach(product => {
const div = document.createElement('div');
div.innerHTML = `<h3>${product.name}</h3><p>¥${product.price}</p>`;
container.appendChild(div);
});
});
}
</script>后端Servlet:
@WebServlet("/search")
public class SearchServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String query = request.getParameter("query");
try (Connection conn = DBUtil.getConnection();
PreparedStatement stmt = conn.prepareStatement("SELECT * FROM products WHERE name LIKE ?")) {
stmt.setString(1, "%" + query + "%");
ResultSet rs = stmt.executeQuery();
List<Product> products = new ArrayList<>();
while (rs.next()) {
products.add(new Product(
rs.getInt("id"),
rs.getString("name"),
rs.getDecimal("price"),
rs.getString("description")
));
}
response.setContentType("application/json");
new ObjectMapper().writeValue(response.getWriter(), products);
} catch (Exception e) {
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
}
}
}关键点说明:
- 使用JSON格式传输数据
- 异常处理防止错误传播
- 使用ObjectMapper进行序列化
3. 文件上传处理
Servlet配置:
@WebServlet("/upload")
public class FileUploadServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Part filePart = request.getPart("image");
String fileName = Paths.get(filePart.getSubmittedFileName()).getFileName().toString();
String uploadDir = "/var/uploads/cake_shop";
try (InputStream is = filePart.getInputStream();
FileOutputStream fos = new FileOutputStream(uploadDir + "/" + fileName)) {
byte[] buffer = new byte[1024];
int length;
while ((length = is.read(buffer)) > 0) {
fos.write(buffer, 0, length);
}
// 更新数据库记录
String sql = "UPDATE products SET image = ? WHERE id = ?";
try (Connection conn = DBUtil.getConnection();
PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setString(1, fileName);
stmt.setInt(2, Integer.parseInt(request.getParameter("productId")));
stmt.executeUpdate();
}
} catch (Exception e) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "文件上传失败");
}
}
}前端表单:
<form enctype="multipart/form-data">
<input type="file" name="image" required>
<input type="hidden" name="productId" value="123">
<button type="submit">上传</button>
</form>关键点说明:
- 使用
Part接口处理文件上传 - 需要配置
multipart/form-data编码 - 上传路径需要权限控制
五、完整案例:商品展示系统
1. 项目结构
src
├── main
│ ├── java
│ │ └── com
│ │ └── cake
│ │ └── servlet
│ │ ├── DBUtil.java
│ │ ├── SearchServlet.java
│ │ ├── FileUploadServlet.java
│ │ └── ProductServlet.java
│ └── webapp
│ ├── index.jsp
│ ├── product.jsp
│ └── upload.jsp
│ └── WEB-INF
│ └── web.xml2. 核心功能实现
商品展示Servlet:
@WebServlet("/products")
public class ProductServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
try (Connection conn = DBUtil.getConnection();
PreparedStatement stmt = conn.prepareStatement("SELECT * FROM products")) {
ResultSet rs = stmt.executeQuery();
List<Product> products = new ArrayList<>();
while (rs.next()) {
products.add(new Product(
rs.getInt("id"),
rs.getString("name"),
rs.getBigDecimal("price"),
rs.getString("description"),
rs.getString("image")
));
}
request.setAttribute("products", products);
request.getRequestDispatcher("product.jsp").forward(request, response);
} catch (Exception e) {
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
}
}
}产品展示页面(product.jsp):
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>商品展示</title>
</head>
<body>
<h1>蛋糕商城</h1>
<div id="productList">
<c:forEach items="${products}" var="product">
<div style="border:1px solid #ccc; padding:10px; margin:10px;">
<h2>${product.name}</h2>
<p>价格:¥${product.price}</p>
<img src="/uploads/${product.image}" width="200">
<p>${product.description}</p>
</div>
</c:forEach>
</div>
</body>
</html>六、源码解析
1. 数据库连接池优化
HikariCP连接池配置关键参数:
maximumPoolSize:最大连接数connectionTimeout:连接超时时间idleTimeout:空闲连接最大存活时间
优化建议:
- 配置连接池时要根据服务器性能合理设置
- 使用
serverTimezone=UTC防止时区问题 - 使用
useSSL=false避免SSL握手耗时
2. 文件上传安全处理
关键安全措施:
- 限制文件类型(仅允许jpg/png)
- 限制文件大小(如最大2MB)
- 重命名文件防止路径遍历攻击
- 存储在独立目录防止Web访问
改进代码示例:
// 文件类型验证
String contentType = filePart.getContentType();
if (!contentType.equals("image/jpeg") && !contentType.equals("image/png")) {
throw new IllegalArgumentException("仅允许上传JPG/PNG格式图片");
}
// 文件大小限制
long size = filePart.getSize();
if (size > 2 * 1024 * 1024) {
throw new IllegalArgumentException("文件大小超过限制");
}七、进阶使用
1. 增强搜索功能
改进方案:
- 使用Elasticsearch实现全文检索
- 添加分页功能
- 支持按价格区间筛选
代码示例(分页):
String sql = "SELECT * FROM products WHERE name LIKE ? LIMIT ? OFFSET ?";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setString(1, "%" + query + "%");
stmt.setInt(2, 10);
stmt.setInt(3, (page - 1) * 10);2. 增加缓存机制
使用Redis缓存商品数据:
// 缓存商品数据
String key = "products:all";
String cached = jedis.get(key);
if (cached != null) {
return new ObjectMapper().readValue(cached, List.class);
}
// 查询数据库
List<Product> products = ...;
jedis.setex(key, 3600, new ObjectMapper().writeValueAsString(products));
return products;八、性能与工程实践
1. 性能优化方案
| 优化点 | 方法 | 效果 |
|---|---|---|
| 数据库 | 使用索引 | 查询速度提升10倍 |
| 缓存 | Redis缓存 | 响应时间从500ms降到50ms |
| 网络 | GZIP压缩 | 传输体积减少30% |
| 前端 | 静态资源CDN | 加载速度提升40% |
2. 异常处理策略
关键原则:
- 使用
try-with-resources自动关闭资源 - 对所有异常进行统一处理
- 记录日志到日志文件
- 前端显示用户友好的提示
3. 安全加固措施
安全防护要点:
- 防止SQL注入(使用预编译语句)
- 防止XSS攻击(转义输出)
- 防止CSRF攻击(使用Token机制)
- 防止文件上传漏洞(严格校验)
九、常见问题与踩坑
1. 常见错误及解决
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 500错误 | 未捕获异常 | 添加全局异常处理 |
| 404错误 | 路径错误 | 检查web.xml配置 |
| 文件无法上传 | 配置错误 | 检查form的enctype属性 |
| 性能瓶颈 | 未使用连接池 | 更换HikariCP连接池 |
2. 高级问题分析
问题:跨域请求失败
- 原因:浏览器同源策略限制
解决:后端添加CORS头
response.setHeader("Access-Control-Allow-Origin", "*"); response.setHeader("Access-Control-Allow-Methods", "GET, POST");
问题:文件上传被拒绝
- 原因:服务器配置限制
- 解决:调整
upload_tmp_dir和post_max_size配置
十、最佳实践
1. 推荐方案
- 使用HikariCP连接池替代传统连接池
- 所有文件上传都要进行严格校验
- 使用Jackson进行JSON序列化
- 对敏感数据进行加密存储
- 使用log4j记录关键操作日志
2. 适用场景
- 小型项目(<10万UV)
- 资源有限的开发环境
- 需要快速迭代的原型系统
- 对实时性要求不高的业务场景
3. 不推荐场景
- 高并发系统(建议使用Spring Cloud)
- 需要复杂业务逻辑的系统
- 需要微服务架构的系统
- 需要分布式事务的系统
十一、总结
本文深入探讨了基于JSP+Servlet+JDBC的蛋糕商城系统实现,重点分析了数据库连接池配置、Ajax通信、文件上传等关键技术点。通过完整案例展示了如何构建一个可运行的电商系统,并讨论了性能优化、安全防护等实际开发中的关键问题。
虽然该技术栈已逐渐被Spring Boot等现代框架取代,但在某些特定场景下(如遗留系统维护、小型项目快速开发)仍有其独特优势。开发过程中需要注意连接池配置、异常处理、安全校验等关键点,同时要根据业务需求选择合适的优化方案。
对于现代项目,建议考虑使用Spring Boot+MyBatis+Redis+Spring Security的组合,但在理解传统技术栈原理的基础上进行技术选型,能够帮助开发者更好地把握系统设计的核心思想。
评论已关闭