在电子商务蓬勃发展的时代背景下,专营数码电子产品的零售商面临着实体店模式固有的局限性:营业时间与地域的束缚、商品信息更新缓慢、库存管理效率低下以及高昂的运营成本。为了解决这些核心痛点,一个基于成熟稳定的SSH(Struts2 + Spring + Hibernate)集成框架的线上销售平台应运而生。该系统为“数码优品在线商城”提供了一个功能完备、技术可靠的解决方案,实现了从商品展示、用户交互到订单处理、后台管理的全流程数字化。
该系统采用经典的三层架构设计,每一层都由特定的框架组件负责,确保了系统的高内聚、低耦合特性。表现层由Struts2框架主导,负责处理所有用户界面交互。Struts2通过其强大的拦截器机制,实现了统一的权限验证、请求日志记录和异常处理,保证了前端请求的安全性与可追溯性。业务逻辑层则由Spring框架的IoC(控制反转)容器进行统一管理。Spring通过依赖注入(DI)将各个Service组件(如ProductService, OrderService, UserService)进行解耦,使得业务逻辑的单元测试和模块替换变得异常便捷。同时,Spring的声明式事务管理为订单创建、库存扣减等核心业务操作提供了坚实的数据一致性保障。数据持久层建立在Hibernate之上,通过对象关系映射(ORM)技术,将Java实体对象与数据库表无缝关联,极大地简化了数据持久化操作,并支持使用HQL(Hibernate Query Language)进行灵活高效的数据查询。
数据库架构设计与核心表解析
一个稳健的电子商务系统,其根基在于精心设计的数据库模型。“数码优品在线商城”的数据库由7张核心表构成,它们共同支撑了用户、商品、订单等关键业务数据的存储与流转。以下重点分析其中几个具有代表性的表结构。
1. 商品信息表(product)
商品表是系统的核心数据载体,其设计直接影响到商品管理的灵活性和查询性能。
CREATE TABLE `product` (
`pid` int(11) NOT NULL AUTO_INCREMENT,
`pname` varchar(255) DEFAULT NULL,
`market_price` double DEFAULT NULL,
`shop_price` double DEFAULT NULL,
`pimage` varchar(255) DEFAULT NULL,
`pdate` date DEFAULT NULL,
`is_hot` int(11) DEFAULT NULL,
`pdesc` varchar(255) DEFAULT NULL,
`pflag` int(11) DEFAULT NULL,
`cid` int(11) DEFAULT NULL,
`csid` int(11) DEFAULT NULL,
PRIMARY KEY (`pid`),
KEY `FKED8DCCEFB9B74E02` (`cid`),
KEY `FKED8DCCEF3DB8B0AD` (`csid`),
CONSTRAINT `FKED8DCCEF3DB8B0AD` FOREIGN KEY (`csid`) REFERENCES `categorysecond` (`csid`),
CONSTRAINT `FKED8DCCEFB9B74E02` FOREIGN KEY (`cid`) REFERENCES `category` (`cid`)
) ENGINE=InnoDB AUTO_INCREMENT=74 DEFAULT CHARSET=utf8;
该表设计的亮点在于:
- 价格策略字段:同时定义了
market_price(市场价)和shop_price(商城价),为营销活动(如显示原价与折扣价)提供了数据支持。 - 商品状态标识:通过
is_hot(是否热门)和pflag(商品状态,如上架/下架)等标志位,实现了商品的可视化与精细化运营。 - 二级分类关联:通过
cid(一级分类ID)和csid(二级分类ID)两个外键,与分类表建立了多级关联。这种设计支持了“手机 -> 智能手机”这样的层级导航,使得商品归类清晰,用户筛选便捷。外键约束确保了分类数据的完整性和一致性。
2. 订单表(orders)
订单表记录了交易的最终结果,是电商业务流程的枢纽。
CREATE TABLE `orders` (
`oid` int(11) NOT NULL AUTO_INCREMENT,
`total` double DEFAULT NULL,
`ordertime` datetime DEFAULT NULL,
`state` int(11) DEFAULT NULL,
`name` varchar(20) DEFAULT NULL,
`phone` varchar(20) DEFAULT NULL,
`addr` varchar(30) DEFAULT NULL,
`uid` int(11) DEFAULT NULL,
PRIMARY KEY (`oid`),
KEY `FKC3DF62E5AA3D9C7` (`uid`),
CONSTRAINT `FKC3DF62E5AA3D9C7` FOREIGN KEY (`uid`) REFERENCES `user` (`uid`)
) ENGINE=InnoDB AUTO_INCREMENT=10004 DEFAULT CHARSET=utf8;
该表设计的核心考量是:
- 订单状态流:
state字段用于标识订单的生命周期状态,如1(未付款)、2(已付款未发货)、3(已发货)、4(已完成)等。这是驱动后台订单处理流程和前台用户查看进度的重要依据。 - 冗余信息存储:订单中存储了收货人
name、电话phone和地址addr。这是一个关键设计。它保存了下单瞬间的快照信息,即使用户后续修改了默认收货地址,历史订单的配送信息也不会改变,保证了交易记录的不可篡改性。 - 用户关联:通过
uid与用户表关联,建立了用户与其所有订单的一对多关系。
3. 订单项表(orderitem)
订单项表是解决“订单-商品”多对多关系的典型中间表设计。
CREATE TABLE `orderitem` (
`itemid` int(11) NOT NULL AUTO_INCREMENT,
`count` int(11) DEFAULT NULL,
`subtotal` double DEFAULT NULL,
`pid` int(11) DEFAULT NULL,
`oid` int(11) DEFAULT NULL,
PRIMARY KEY (`itemid`),
KEY `FKE8B2AB6166C01961` (`oid`),
KEY `FKE8B2AB6171DB7AE4` (`pid`),
CONSTRAINT `FKE8B2AB6166C01961` FOREIGN KEY (`oid`) REFERENCES `orders` (`oid`),
CONSTRAINT `FKE8B2AB6171DB7AE4` FOREIGN KEY (`pid`) REFERENCES `product` (`pid`)
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8;
其核心价值在于:
- 解耦订单与商品:一个订单(
oid)可以包含多个商品(pid),一个商品也可以出现在多个订单中。此表完美地描述了这种复杂的多对多关系。 - 快照与汇总:该表存储了商品购买时的数量(
count)和小计金额(subtotal)。即使商品后续价格变动,订单项中的历史价格和金额依然保持不变,确保了财务数据的准确性。订单的总金额(orders.total)本质上是由其关联的所有订单项的subtotal汇总而成。
核心业务功能的技术实现
1. 商品浏览与多级分类检索
系统首页和商品列表页需要高效地展示商品,并支持按分类、价格、热度等多维度筛选。这主要通过Hibernate的动态查询来实现。
实现代码示例:商品分页查询Service方法
// ProductService.java
@Service
@Transactional
public class ProductService {
@Autowired
private ProductDao productDao;
public PageBean<Product> findByPage(Integer cid, Integer csid, String pname,
int page, int limit) {
PageBean<Product> pageBean = new PageBean<>();
// 设置当前页
pageBean.setPage(page);
// 设置每页记录数
pageBean.setLimit(limit);
// 调用Dao层获取总记录数
int totalCount = productDao.findCount(cid, csid, pname);
pageBean.setTotalCount(totalCount);
// 计算总页数
int totalPage = (totalCount % limit == 0) ? (totalCount / limit) : (totalCount / limit + 1);
pageBean.setTotalPage(totalPage);
// 计算开始索引
int begin = (page - 1) * limit;
// 调用Dao层获取分页列表数据
List<Product> list = productDao.findByPage(cid, csid, pname, begin, limit);
pageBean.setList(list);
return pageBean;
}
}
// ProductDaoImpl.java
@Repository
public class ProductDaoImpl extends HibernateDaoSupport implements ProductDao {
@Autowired
public void setSessionFactoryOverride(SessionFactory sessionFactory) {
super.setSessionFactory(sessionFactory);
}
@Override
public int findCount(Integer cid, Integer csid, String pname) {
String hql = "select count(*) from Product p where 1=1 ";
Map<String, Object> params = new HashMap<>();
if (cid != null) {
hql += " and p.category.cid = :cid ";
params.put("cid", cid);
}
if (csid != null) {
hql += " and p.categorySecond.csid = :csid ";
params.put("csid", csid);
}
if (pname != null && !pname.trim().isEmpty()) {
hql += " and p.pname like :pname ";
params.put("pname", "%" + pname + "%");
}
Query query = this.getSessionFactory().getCurrentSession().createQuery(hql);
for (String key : params.keySet()) {
query.setParameter(key, params.get(key));
}
return ((Long) query.uniqueResult()).intValue();
}
@Override
public List<Product> findByPage(Integer cid, Integer csid, String pname,
int begin, int limit) {
String hql = "from Product p where 1=1 ";
Map<String, Object> params = new HashMap<>();
if (cid != null) {
hql += " and p.category.cid = :cid ";
params.put("cid", cid);
}
if (csid != null) {
hql += " and p.categorySecond.csid = :csid ";
params.put("csid", csid);
}
if (pname != null && !pname.trim().isEmpty()) {
hql += " and p.pname like :pname ";
params.put("pname", "%" + pname + "%");
}
hql += " order by p.pdate desc"; // 按上架时间倒序排列
Query query = this.getSessionFactory().getCurrentSession().createQuery(hql);
for (String key : params.keySet()) {
query.setParameter(key, params.get(key));
}
query.setFirstResult(begin);
query.setMaxResults(limit);
return query.list();
}
}
技术解析:上述代码展示了后端分页查询的经典模式。PageBean是一个封装了分页信息(当前页、总页数、数据列表等)的通用类。ProductService的业务方法通过动态拼接HQL语句,根据传入的分类ID、二级分类ID和商品名称关键字构建查询条件,并使用setFirstResult和setMaxResults方法实现数据库层面的分页,有效避免了内存溢出和性能瓶颈。

2. 购物车管理与订单生成
购物车是连接商品浏览与订单结算的桥梁,其核心是维护一个临时的商品清单及其数量。
实现代码示例:购物车实体与添加商品操作
// Cart.java (购物车实体)
public class Cart {
// 购物项集合: key为商品ID, value为购物项
private Map<Integer, CartItem> map = new HashMap<>();
private double total; // 购物车总金额
// 添加商品到购物车
public void addCart(Product product, int count) {
Integer pid = product.getPid();
// 判断购物车中是否存在该购物项
if (map.containsKey(pid)) {
// 存在,修改数量和小计
CartItem cartItem = map.get(pid);
cartItem.setCount(cartItem.getCount() + count);
} else {
// 不存在,创建新的购物项
CartItem cartItem = new CartItem();
cartItem.setProduct(product);
cartItem.setCount(count);
map.put(pid, cartItem);
}
// 设置总金额
total += product.getShop_price() * count;
}
// 从购物车移除购物项
public void removeCart(Integer pid) {
CartItem cartItem = map.remove(pid);
total -= cartItem.getSubtotal();
}
// 清空购物车
public void clearCart() {
map.clear();
total = 0;
}
// ... getters and setters
}
// CartAction.java (Struts2 Action)
public class CartAction extends ActionSupport implements SessionAware {
private Integer pid; // 接收商品ID
private Integer count; // 接收购买数量
private Map<String, Object> session;
public String addToCart() {
// 1. 根据pid查询商品信息
Product product = productService.findByPid(pid);
// 2. 从Session中获取购物车
Cart cart = (Cart) session.get("cart");
if (cart == null) {
cart = new Cart();
session.put("cart", cart);
}
// 3. 将商品添加到购物车
cart.addCart(product, count);
return "addToCartSuccess";
}
@Override
public void setSession(Map<String, Object> session) {
this.session = session;
}
// ... getters and setters
}
技术解析:购物车通常存储在用户的Session中,以保证在同一浏览器会话期间数据的连续性。Cart类内部使用一个Map来管理购物项(CartItem),键为商品ID,值为包含商品、数量和小计的购物项对象。这种设计使得添加、删除和更新商品数量的操作非常高效(时间复杂度接近O(1))。CartAction接收前端传递的商品ID和数量,调用Service层获取完整的商品对象,然后操作Session中的购物车。

3. 订单创建与事务管理
订单生成是电商系统最核心、最复杂的业务,涉及购物车清理、订单主表记录生成、多个订单项记录生成、库存扣减等多个步骤,必须保证在一个事务内完成。
实现代码示例:订单生成Service方法
// OrderServiceImpl.java
@Service
@Transactional // 声明式事务管理:整个方法在一个事务中执行
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderDao orderDao;
@Autowired
private ProductDao productDao;
@Override
public void save(Orders order, Cart cart) {
// 1. 保存订单主表数据 (向orders表插入一条记录)
orderDao.save(order);
// 2. 遍历购物车,生成订单项 (向orderitem表插入多条记录)
for (CartItem cartItem : cart.getCartItems()) {
OrderItem orderItem = new OrderItem();
orderItem.setOrders(order); // 设置所属订单
orderItem.setProduct(cartItem.getProduct()); // 设置商品
orderItem.setCount(cartItem.getCount()); // 设置数量
orderItem.setSubtotal(cartItem.getSubtotal()); // 设置小计
orderDao.saveItem(orderItem);
// 3. 更新商品库存(此处示例,实际表结构中需有stock字段)
Product product = cartItem.getProduct();
int newStock = product.getStock() - cartItem.getCount();
if (newStock < 0) {
throw new RuntimeException("商品[" + product.getPname() + "]库存不足");
}
product.setStock(newStock);
productDao.update(product); // 更新商品库存
}
// 4. 清空购物车
cart.clearCart();
}
}
技术解析:该方法使用了Spring的@Transactional注解进行声明式事务管理。这是至关重要的。如果在保存订单项或更新库存的过程中发生任何异常(如库存不足、数据库连接中断),Spring会自动回滚整个事务,确保不会产生脏数据(例如,订单创建了却没有扣减库存)。这种“原子性”操作是保证系统数据一致性的基石。Order和OrderItem实体类之间通过Hibernate配置了一对多的关联关系。

4. 后台商品与订单管理
后台管理系统为商家提供了商品上架、信息修改、订单处理等核心功能。
实现代码示例:商品上架Action
// AdminProductAction.java
public class AdminProductAction extends ActionSupport implements ModelDriven<Product> {
private Product product = new Product();
// 上传文件相关属性
private File upload; // 上传的文件本身
private String uploadFileName; // 上传文件的原始名称
private String uploadContentType;
@Autowired
private ProductService productService;
public String save() throws IOException {
// 1. 处理图片上传
if (upload != null) {
String filePath = "路径/to/upload/";
// 生成唯一文件名,防止覆盖
String uuid = UUID.randomUUID().toString().replace("-", "");
String realName = uuid + "_" + uploadFileName;
File destFile = new File(filePath + realName);
FileUtils.copyFile(upload, destFile); // 使用commons-io工具类
product.setPimage("products/" + realName); // 设置图片路径
}
// 2. 设置其他属性(如时间)
product.setPdate(new Date());
// 3. 调用Service保存商品
productService.save(product);
return "saveSuccess";
}
@Override
public Product getModel() {
return product;
}
// ... getters and setters for upload fields
}
技术解析:此Action使用