Java Swing+MySQL购物系统项目实战【含源码+文档+视频】
简介:本项目是一个基于Java Swing与MySQL开发的完整购物系统,涵盖图形界面设计、数据库管理及前后端交互,提供源代码、详细文档和视频教程,是掌握Java GUI编程与数据库应用集成的优质学习资源。系统支持商品浏览、购物车管理、订单处理等核心功能,采用MVC架构模式,结合JDBC实现数据持久化,并融入异常处理、文件操作、多线程及常见设计模式等关键技术,助力开发者全面提升Java桌面应用开发能力。
购物系统开发全栈实战:从Swing界面到数据库安全与并发控制
你有没有遇到过这样的场景?一个用户刚买完最后一件商品,刷新页面却发现还能下单——库存变成负数了。😅 这不是bug,这是典型的“超卖”问题,而它背后藏着的是整个购物系统最核心的挑战: 如何在高并发下保证数据一致性、用户体验流畅且系统安全可靠 。
今天我们要一起打造的,就是一个基于 Java Swing + MySQL 的桌面端购物系统。别看它是“老派”的桌面应用,但它麻雀虽小五脏俱全:GUI交互、MVC架构、DAO模式、事务管理、SQL注入防护、多线程同步……这些可都是现代企业级开发的核心技能点!🚀
构建稳固基石:Java Swing 与 用户界面设计
说到 Java 桌面开发,很多人第一反应是:“这不是早就被淘汰了吗?”但事实恰恰相反,在很多内部管理系统、POS终端、工业控制软件中,Swing 依然扮演着不可替代的角色。为什么?
因为它轻量、跨平台、无需浏览器支持,启动快,而且完全由 Java 控制——没有外部依赖,部署简单,维护成本低。🎯
我们这个购物系统的前端就用 Swing 来实现。虽然它不像 Web 那样炫酷,但我们照样可以做出清晰、响应灵敏的界面。
窗口骨架:JFrame 是你的主舞台
一切 GUI 的起点,都是 JFrame 。你可以把它想象成网页里的 <body> 标签——所有组件都得往里面塞。
public class MainFrame extends JFrame {
public MainFrame() {
setTitle("🛒 购物系统");
setSize(800, 600);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null); // 居中显示
JLabel label = new JLabel("欢迎使用购物系统", SwingConstants.CENTER);
add(label);
setVisible(true);
}
}
注意那个 SwingUtilities.invokeLater() 吗?这可不是多余的代码。Swing 是单线程的,所有的 UI 更新必须在 事件调度线程(EDT) 中执行。如果我们在主线程直接创建窗口,可能会导致界面卡顿甚至崩溃。
所以标准写法永远是:
SwingUtilities.invokeLater(() -> new MainFrame());
✅ 小贴士:记住一句话——“不要阻塞 EDT”。任何耗时操作(比如查数据库),都不能放在 UI 线程里干!
组件拼装:JPanel + JButton = 可复用的UI模块
光有窗口不够,还得有按钮、输入框、表格……这些就是 Swing 的“积木块”。
| 组件 | 功能 | 使用建议 |
|---|---|---|
JFrame |
主窗口容器 | 设置标题、大小、关闭行为 |
JPanel |
容器面板 | 分组控件,嵌套布局 |
JButton |
按钮 | 注册 ActionListener 响应点击 |
JLabel |
文本标签 | 显示提示信息或动态内容 |
JTextField / JPasswordField |
输入框 | 获取用户输入,密码框自动掩码 |
举个例子,做个登录区域:
JPanel topPanel = new JPanel();
topPanel.add(new JButton("登录"));
topPanel.add(new JButton("注册"));
getContentPane().add(topPanel, BorderLayout.NORTH);
这里用了 BorderLayout ,把顶部工具栏固定在上方。是不是有点像 CSS 的 position: fixed ?😄
布局策略:选对“排版方式”事半功倍
Swing 提供了好几种布局管理器,每种都有它的适用场景:
✅ BorderLayout —— 主窗口结构首选
适合划分五大区域:北、南、东、西、中。中间区域会自动填充剩余空间,非常适合做主视图。
setLayout(new BorderLayout());
add(toolbar, BorderLayout.NORTH);
add(tableScrollPane, BorderLayout.CENTER);
add(statusBar, BorderLayout.SOUTH);
✅ GridLayout —— 规则排列好帮手
比如做一个数字键盘或者商品分类网格,用 GridLayout(3, 3) 就能轻松搞定 3x3 的按钮阵列。
JPanel grid = new JPanel(new GridLayout(3, 3, 5, 5)); // 行数、列数、间距
for (int i = 1; i <= 9; i++) {
grid.add(new JButton(String.valueOf(i)));
}
✅ BoxLayout —— 灵活堆叠神器
支持水平或垂直排列,特别适合需要动态增减组件的列表。
boxPanel.setLayout(new BoxLayout(boxPanel, BoxLayout.Y_AXIS));
boxPanel.add(new JLabel("商品1"));
boxPanel.add(Box.createVerticalStrut(10)); // 插入空白间隔
boxPanel.add(new JLabel("商品2"));
想知道怎么选?来看这张决策图👇:
graph TD
A[选择布局策略] --> B{是否为主窗口?}
B -->|是| C[使用BorderLayout]
B -->|否| D{组件是否规则排列?}
D -->|是| E[GridLayout]
D -->|否| F{是否线性排列?}
F -->|是| G[BoxLayout]
F -->|否| H[GridBagLayout]
看到没?大部分情况都不需要用到复杂的 GridBagLayout ,那玩意儿调试起来简直是噩梦 😵💫。
自定义面板:封装才是专业之道
如果你发现到处都在重复写“标签+输入框”的组合,说明该封装了!
public class LabeledInputPanel extends JPanel {
private JTextField textField;
public LabeledInputPanel(String labelText) {
setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
JLabel label = new JLabel(labelText, SwingConstants.RIGHT);
label.setPreferredSize(new Dimension(80, 25));
textField = new JTextField(15);
add(label);
add(Box.createHorizontalStrut(10));
add(textField);
}
public String getText() {
return textField.getText();
}
public void addActionListener(ActionListener listener) {
textField.addActionListener(listener);
}
}
这样一来,创建用户名输入框只需要一行:
LabeledInputPanel usernameField = new LabeledInputPanel("用户名:");
干净利落,代码复用率飙升 💪!
数据大脑:MySQL 设计与 JDBC 持久化层构建
没有数据支撑的应用就像没有地基的房子,风一吹就倒。我们的购物系统,核心围绕三个实体展开: 用户 → 商品 → 订单 。
ER模型设计:让关系一目了然
先画张ER图理清思路:
erDiagram
USER ||--o{ ORDER : places
ORDER ||--o{ ORDER_ITEM : contains
PRODUCT ||--o{ ORDER_ITEM : included_in
USER {
bigint id PK
varchar(50) username
varchar(255) password
varchar(100) email
enum role
datetime created_at
}
PRODUCT {
bigint id PK
varchar(100) name
decimal(10,2) price
int stock
tinyint status
datetime created_at
}
ORDER {
bigint id PK
bigint user_id FK
decimal(10,2) total_amount
varchar(20) order_status
datetime created_at
}
ORDER_ITEM {
bigint id PK
bigint order_id FK
bigint product_id FK
int quantity
decimal(10,2) unit_price
}
四个表,形成典型的星型结构:
- user 和 order 是一对多
- order 和 order_item 是一对多
- product 被多个订单引用
重点来了:为什么要在 order_item 里保存 unit_price ?难道不能直接关联 product.price 吗?
当然能,但不推荐 ❌。因为价格会变!今天卖 99.9 元的商品,明天可能涨价到 199.9。如果不快照当时的价格,历史订单金额就不准确了。
这就是所谓的“反范式优化”——为了业务准确性,适当牺牲规范化原则。🧠
索引优化:查询提速的秘密武器
你有没有经历过等了十几秒才加载出商品列表?很可能是因为缺少索引。
给高频查询字段加索引,效果立竿见影:
| 查询场景 | 推荐索引 | 类型 |
|---|---|---|
| 按用户名登录 | user(username) |
唯一索引 |
| 查某人所有订单 | order(user_id) |
普通索引 |
| 最近订单分页 | order(created_at DESC) |
普通索引 |
| 商品模糊搜索 | product(name) |
全文索引(FULLTEXT) |
不过切记:索引不是越多越好!每次插入/更新都要维护B+树,反而拖慢写入速度。所以要权衡读写比例和字段选择性。
📊 经验法则:选择性 > 10% 的字段才考虑建索引。
连接池:别再用 DriverManager 了!
还在这样写连接代码?
Connection conn = DriverManager.getConnection(url, user, pass);
醒醒吧朋友!生产环境这么干迟早出事 ⚠️。
每次调用都会新建 TCP 连接,经历握手、认证等一系列开销,性能极差。更可怕的是,没人回收的话,数据库连接很快就会被耗尽。
正解是使用 连接池 ,比如 HikariCP(目前最快的 JDBC 连接池):
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/shopdb");
config.setUsername("root");
config.setPassword("123456");
config.setMaximumPoolSize(20);
config.setConnectionTimeout(30_000);
HikariDataSource dataSource = new HikariDataSource(config);
然后通过单例封装:
public class DBConnectionPool {
private static volatile HikariDataSource dataSource;
public static HikariDataSource getInstance() {
if (dataSource == null) {
synchronized (DBConnectionPool.class) {
if (dataSource == null) {
dataSource = new HikariDataSource(createConfig());
}
}
}
return dataSource;
}
}
以后获取连接只需一行:
try (Connection conn = DBConnectionPool.getInstance().getConnection()) {
// 执行SQL
}
简洁又高效 ✅。
资源释放:try-with-resources 救我狗命
以前写 JDBC 经常忘记关 ResultSet 、 Statement ,导致连接泄漏,程序跑几天就挂了……
现在有了 try-with-resources ,一切都变得优雅了:
public List<Product> searchByName(String keyword) throws SQLException {
String sql = "SELECT * FROM product WHERE name LIKE ?";
try (Connection conn = dataSource.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, "%" + keyword + "%");
try (ResultSet rs = pstmt.executeQuery()) {
List<Product> list = new ArrayList<>();
while (rs.next()) {
Product p = new Product();
p.setId(rs.getLong("id"));
p.setName(rs.getString("name"));
p.setPrice(rs.getBigDecimal("price"));
list.add(p);
}
return list;
}
}
}
只要实现了 AutoCloseable 接口,都会在 try 块结束时自动关闭,哪怕抛异常也不怕!
架构灵魂:MVC 分层与设计模式实战
如果说数据是大脑,那架构就是神经系统。我们采用经典的 MVC 模式 来组织代码:
- Model :数据实体 + DAO
- View :Swing 界面
- Controller :事件协调者
实体类:POJO 规范不能少
每个表对应一个 Java Bean,遵循 JavaBean 规范:
public class Product {
private Long id;
private String name;
private BigDecimal price;
private Integer stock;
// 无参构造函数
public Product() {}
// getter/setter...
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
@Override
public String toString() {
return "Product{" +
"id=" + id +
", name='" + name + '\'' +
", price=" + price +
", stock=" + stock +
'}';
}
}
IDEA 可以一键生成 getter/setter,记得加上 toString() 方便调试输出。
DAO 模式:接口抽象隔离变化
我们不直接操作 JDBC,而是通过 DAO 接口屏蔽细节:
public interface ProductDao {
List<Product> findAll() throws SQLException;
Product findById(Long id) throws SQLException;
void insert(Product product) throws SQLException;
void update(Product product) throws SQLException;
void delete(Long id) throws SQLException;
}
实现类负责具体 SQL:
public class ProductDaoImpl implements ProductDao {
private final Connection conn;
public ProductDaoImpl(Connection conn) {
this.conn = conn;
}
@Override
public List<Product> findAll() throws SQLException {
String sql = "SELECT id, name, price, stock FROM product";
try (PreparedStatement stmt = conn.prepareStatement(sql);
ResultSet rs = stmt.executeQuery()) {
List<Product> products = new ArrayList<>();
while (rs.next()) {
Product p = new Product();
p.setId(rs.getLong("id"));
p.setName(rs.getString("name"));
p.setPrice(rs.getBigDecimal("price"));
p.setStock(rs.getInt("stock"));
products.add(p);
}
return products;
}
}
}
将来想换数据库?只要换个实现类就行,上层逻辑完全不用动!
工厂模式:统一创建入口
随着 DAO 越来越多,总不能到处 new 实例吧?来个工厂统一分发:
public class DaoFactory {
private static DaoFactory instance;
private final Connection conn;
private DaoFactory(Connection conn) {
this.conn = conn;
}
public static DaoFactory getInstance(Connection conn) {
if (instance == null) {
synchronized (DaoFactory.class) {
if (instance == null) {
instance = new DaoFactory(conn);
}
}
}
return instance;
}
public ProductDao getProductDao() {
return new ProductDaoImpl(conn);
}
public UserDao getUserDao() {
return new UserDaoImpl(conn);
}
}
调用方只需要:
ProductDao dao = DaoFactory.getInstance(conn).getProductDao();
List<Product> products = dao.findAll();
干净清爽,耦合度降到最低 💧。
观察者模式:实时刷新库存显示
当多个用户同时抢购同一件商品时,你怎么确保别人看到的是最新库存?
答案是: 观察者模式 !
// 主题
public interface Subject {
void registerObserver(Observer o);
void removeObserver(Observer o);
void notifyObservers();
}
// 观察者
public interface Observer {
void update();
}
// 库存服务(单例)
public class InventoryService implements Subject {
private static InventoryService instance;
private final List<Observer> observers = new ArrayList<>();
public static synchronized InventoryService getInstance() {
if (instance == null) {
instance = new InventoryService();
}
return instance;
}
public void reduceStock(Long productId, int quantity) {
// 扣减库存...
notifyObservers(); // 通知所有人刷新
}
@Override
public void notifyObservers() {
for (Observer o : observers) {
o.update();
}
}
}
商品列表面板注册监听:
public class ProductPanel extends JPanel implements Observer {
@Override
public void update() {
SwingUtilities.invokeLater(() -> loadProducts());
}
private void loadProducts() {
try {
ProductDao dao = DaoFactory.getInstance(DBUtil.getConnection()).getProductDao();
setProducts(dao.findAll());
} catch (SQLException e) {
JOptionPane.showMessageDialog(this, "加载失败:" + e.getMessage());
}
}
}
这样一来,只要有人下单,所有打开的商品页都会自动刷新,真正做到“一处变更,处处响应”✨。
安全防线:防注入、防超卖、防崩溃
功能做得再漂亮,安全性不过关也是白搭。下面我们聊聊几个关键防御手段。
SQL注入:PreparedStatement 是唯一出路
看看这个危险的操作:
String sql = "SELECT * FROM user WHERE username = '" + username +
"' AND password = '" + password + "'";
攻击者只要输入 ' OR '1'='1 ,就能绕过登录验证!
解决方案只有一个: 预编译语句 + 参数占位符
String sql = "SELECT * FROM user WHERE username = ? AND password = ?";
pstmt.setString(1, username);
pstmt.setString(2, hashedPassword);
数据库会对参数做转义处理,彻底杜绝注入风险。🔒
登录保护:防止暴力破解
连续输错5次就锁账户,怎么做?
private static final Map<String, Integer> attempts = new ConcurrentHashMap<>();
private static final int MAX_ATTEMPTS = 5;
public boolean isLocked(String username) {
return attempts.getOrDefault(username, 0) >= MAX_ATTEMPTS;
}
public void incrementAttempt(String username) {
attempts.merge(username, 1, Integer::sum);
}
内存级计数简单有效。生产环境建议持久化到数据库并加入定时解锁机制。
并发超卖:synchronized + 数据库约束双保险
两个用户同时下单,库存只有一件,怎么办?
public synchronized boolean placeOrder(Order order) {
Product product = productDao.findById(order.getProductId());
if (product.getStock() < order.getQuantity()) {
return false;
}
// 扣库存 + 写订单
productDao.reduceStock(product.getId(), order.getQuantity());
orderDao.insert(order);
return true;
}
synchronized 保证同一时间只有一个线程进入方法。再加上数据库层面的 CHECK(stock >= 0) 约束,双重保险更安心。
流程闭环:完整业务链路集成测试
最后我们走一遍完整的购物流程:
flowchart TD
A[用户点击“搜索”按钮] --> B{ActionListener 触发}
B --> C[Controller: SearchProductAction]
C --> D[调用 ProductDao.searchByName()]
D --> E[访问 MySQL 数据库]
E --> F[返回 ResultSet]
F --> G[映射为 List<Product>]
G --> H[调用 view.setProducts()]
H --> I[JTable 更新显示]
I --> J[用户看到结果]
每一环都可以打日志、设断点,逐步验证逻辑是否正确。
遇到界面卡顿?很可能是数据库查询在 EDT 线程执行了!赶紧换成 SwingWorker 异步加载:
new SwingWorker<List<Product>, Void>() {
@Override
protected List<Product> doInBackground() throws Exception {
return productDao.searchByName(keyword);
}
@Override
protected void done() {
try {
List<Product> results = get();
panel.setProducts(results);
} catch (Exception e) {
JOptionPane.showMessageDialog(panel, "搜索失败:" + e.getMessage());
}
}
}.execute();
既不影响界面流畅,又能拿到结果后自动刷新。
结语:技术的价值在于解决问题
这个看似简单的购物系统,其实浓缩了大量工程实践智慧:
- 用 MVC 解耦关注点
- 用 DAO + 工厂 提升可维护性
- 用 连接池 + try-with-resources 保障资源安全
- 用 PreparedStatement 防止注入
- 用 观察者模式 实现状态同步
- 用 synchronized + 事务 应对并发
它们不是孤立存在的技巧,而是构成健壮系统的有机整体。
当你下次接到一个新项目时,不妨问问自己:
- 我的数据结构合理吗?
- 我的异常处理完善吗?
- 我的代码容易测试和扩展吗?
记住: 真正的高手,不在炫技,而在稳扎稳打地把每一个细节做到位 。🛠️
现在,轮到你动手了——试着把这个系统加上图片展示、支付模拟、订单导出等功能,让它真正“活”起来吧!💻🔥
简介:本项目是一个基于Java Swing与MySQL开发的完整购物系统,涵盖图形界面设计、数据库管理及前后端交互,提供源代码、详细文档和视频教程,是掌握Java GUI编程与数据库应用集成的优质学习资源。系统支持商品浏览、购物车管理、订单处理等核心功能,采用MVC架构模式,结合JDBC实现数据持久化,并融入异常处理、文件操作、多线程及常见设计模式等关键技术,助力开发者全面提升Java桌面应用开发能力。
更多推荐



所有评论(0)