在医药零售行业数字化转型的浪潮中,传统药店面临着库存信息不透明、销售数据滞后、人工操作易出错等核心挑战。一款高效、可靠的业务管理平台成为提升运营效率的关键。本系统采用经典的JSP+Servlet技术栈,构建了一个集药品销售、库存管理、采购供应链于一体的综合解决方案,我们将其命名为“药易管”——一个旨在实现药店业务全流程数字化闭环的智能管理引擎。
系统严格遵循MVC设计模式,Servlet作为控制器层负责请求调度和业务逻辑处理,JSP页面专注于数据展示,JavaBean实体类封装业务数据,JDBC实现与MySQL数据库的持久化交互。这种分层架构确保了代码的高内聚低耦合,为系统的可维护性和扩展性奠定了坚实基础。
系统架构与技术栈深度解析
“药易管”采用三层架构设计,每一层都承担着明确的职责。表现层使用JSP技术结合JSTL标签库和EL表达式,实现了数据与视图的分离,避免了在页面中嵌入过多的Java代码脚本。业务逻辑层由Servlet构成,每个Servlet对应一个具体的业务模块,如药品管理、订单处理、库存更新等,负责接收前端请求、验证参数、调用服务层方法。数据访问层通过封装JDBC操作,提供了对数据库的安全、高效访问。
在技术选型上,系统选择了成熟稳定的技术组合。Java EE的Servlet和JSP技术经过多年发展,拥有完善的生态系统和丰富的第三方库支持。MySQL作为关系型数据库,在事务处理和数据一致性方面表现出色,完全满足药店业务对数据准确性的高要求。前端采用经典的HTML+CSS+JavaScript组合,确保了界面的兼容性和用户体验。
数据库设计亮点与优化策略
药品表(drug)的核心设计
药品表作为系统的核心数据表,其设计体现了对业务需求的深刻理解。did(药品ID)字段采用varchar(22)类型作为主键,而非传统的自增整数,这种设计便于嵌入药品分类、批次等业务信息,提升ID的可读性。表结构中包含了药品的基本信息(名称、类别、售价)、关键日期信息(生产日期、过期日期)以及库存数量。
CREATE TABLE `drug` (
`did` varchar(22) NOT NULL COMMENT '药品ID',
`dname` varchar(22) DEFAULT NULL COMMENT '药品名称',
`dclass` varchar(22) DEFAULT NULL COMMENT '药品类别',
`dprice` varchar(22) DEFAULT NULL COMMENT '售价',
`prodate` date DEFAULT NULL COMMENT '生产日期',
`exdate` date DEFAULT NULL COMMENT '过期日期',
`requantity` int(11) DEFAULT NULL COMMENT '库存数量',
`sname` varchar(33) NOT NULL COMMENT '生产商',
`function` varchar(33) DEFAULT NULL COMMENT '功效',
PRIMARY KEY (`did`),
KEY `supplier` (`sname`),
CONSTRAINT `drug_ibfk_1` FOREIGN KEY (`sname`) REFERENCES `supplier` (`sname`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='药品表'
外键约束drug_ibfk_1确保了药品与供应商之间的引用完整性,防止了 orphan record 的产生。在requantity字段上可以进一步考虑添加检查约束,确保库存数量不为负数,这在业务逻辑层已经得到实现。
订单表(ordered)的事务完整性设计
订单表的设计重点关注了销售业务的事务完整性。该表记录了每一笔销售交易的详细信息,包括药品信息、购买数量、总价、客户信息以及销售日期。
CREATE TABLE `ordered` (
`oid` int(11) NOT NULL AUTO_INCREMENT COMMENT '订单ID',
`did` varchar(22) DEFAULT NULL COMMENT '药品ID',
`dname` varchar(22) DEFAULT NULL COMMENT '药品名称',
`oquantity` int(11) DEFAULT NULL COMMENT '购买数量',
`oprice` varchar(22) DEFAULT NULL COMMENT '总价',
`cid` int(11) DEFAULT NULL COMMENT '购买人',
`cname` varchar(22) DEFAULT NULL COMMENT '客户姓名',
`odate` datetime DEFAULT NULL COMMENT '销售日期',
PRIMARY KEY (`oid`),
KEY `did` (`did`),
KEY `cid` (`cid`),
CONSTRAINT `ordered_ibfk_1` FOREIGN KEY (`did`) REFERENCES `drug` (`did`),
CONSTRAINT `ordered_ibfk_2` FOREIGN KEY (`cid`) REFERENCES `customer` (`cid`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8 COMMENT='订单表'
值得注意的是,表中同时存储了药品ID(did)和药品名称(dname),这种反范式设计虽然增加了数据冗余,但显著提升了订单查询性能,避免每次查询都需要联表获取药品名称。两个外键约束确保了订单数据的引用完整性,AUTO_INCREMENT自增主键为订单提供了唯一的标识。
采购表(purchase)的供应链追踪
采购表的设计体现了对药品供应链的完整追踪。每个采购记录包含采购ID、药品信息、采购数量、采购人、采购价格和采购日期等关键信息。
CREATE TABLE `purchase` (
`pid` varchar(22) NOT NULL COMMENT '采购ID',
`did` varchar(22) DEFAULT NULL COMMENT '药品ID',
`pquantity` int(11) DEFAULT NULL COMMENT '采购数量',
`purchaser` varchar(22) DEFAULT NULL COMMENT '采购人',
`pprice` varchar(22) DEFAULT NULL COMMENT '采购价格',
`pdate` date DEFAULT NULL COMMENT '采购日期',
PRIMARY KEY (`pid`),
KEY `did` (`did`),
CONSTRAINT `purchase_ibfk_1` FOREIGN KEY (`did`) REFERENCES `drug` (`did`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='采购表'
采购ID(pid)采用自定义字符串格式,便于嵌入采购批次、供应商代码等业务信息。与药品表的外键关联确保了采购记录的准确性,为成本核算和供应商绩效评估提供了数据基础。
核心功能实现详解
药品信息管理模块
药品管理是系统的核心功能之一,实现了药品信息的增删改查、库存监控和过期预警。管理员可以通过直观的界面查看所有药品的详细信息,包括当前库存状态。

药品查询功能的Servlet实现展示了如何高效处理前端请求并返回查询结果:
@WebServlet("/drugQuery")
public class DrugQueryServlet extends HttpServlet {
private DrugService drugService = new DrugService();
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String drugId = request.getParameter("drugId");
String drugName = request.getParameter("drugName");
String category = request.getParameter("category");
try {
List<Drug> drugList = drugService.queryDrugs(drugId, drugName, category);
request.setAttribute("drugList", drugList);
request.getRequestDispatcher("/drugManagement.jsp").forward(request, response);
} catch (SQLException e) {
e.printStackTrace();
request.setAttribute("errorMsg", "查询失败:" + e.getMessage());
request.getRequestDispatcher("/error.jsp").forward(request, response);
}
}
}
对应的JSP页面使用JSTL标签库动态渲染查询结果:
<table class="table table-striped">
<thead>
<tr>
<th>药品ID</th>
<th>药品名称</th>
<th>类别</th>
<th>售价</th>
<th>库存数量</th>
<th>生产日期</th>
<th>过期日期</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<c:forEach var="drug" items="${drugList}">
<tr>
<td>${drug.did}</td>
<td>${drug.dname}</td>
<td>${drug.dclass}</td>
<td>¥${drug.dprice}</td>
<td>
<c:if test="${drug.requantity < 10}">
<span class="label label-warning">${drug.requantity}</span>
</c:if>
<c:if test="${drug.requantity >= 10}">
${drug.requantity}
</c:if>
</td>
<td><fmt:formatDate value="${drug.prodate}" pattern="yyyy-MM-dd"/></td>
<td>
<c:if test="${drug.exdate < now}">
<span class="label label-danger">
<fmt:formatDate value="${drug.exdate}" pattern="yyyy-MM-dd"/>
</span>
</c:if>
<c:if test="${drug.exdate >= now}">
<fmt:formatDate value="${drug.exdate}" pattern="yyyy-MM-dd"/>
</c:if>
</td>
<td>
<button class="btn btn-primary btn-sm" onclick="editDrug('${drug.did}')">编辑</button>
<button class="btn btn-danger btn-sm" onclick="deleteDrug('${drug.did}')">删除</button>
</td>
</tr>
</c:forEach>
</tbody>
</table>
销售订单处理与库存同步
销售模块实现了药品销售的全流程管理,包括订单创建、库存自动扣减和销售记录生成。系统确保销售操作的原子性,避免超卖情况的发生。

订单创建的Servlet实现展示了事务处理的关键逻辑:
@WebServlet("/createOrder")
public class CreateOrderServlet extends HttpServlet {
private OrderService orderService = new OrderService();
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String drugId = request.getParameter("drugId");
int quantity = Integer.parseInt(request.getParameter("quantity"));
int customerId = Integer.parseInt(request.getParameter("customerId"));
Connection conn = null;
try {
conn = DatabaseUtil.getConnection();
conn.setAutoCommit(false); // 开启事务
// 检查库存
Drug drug = drugService.getDrugById(drugId, conn);
if (drug.getRequantity() < quantity) {
throw new RuntimeException("库存不足,当前库存:" + drug.getRequantity());
}
// 计算总价
BigDecimal unitPrice = new BigDecimal(drug.getDprice());
BigDecimal totalPrice = unitPrice.multiply(new BigDecimal(quantity));
// 创建订单
Order order = new Order();
order.setDid(drugId);
order.setDname(drug.getDname());
order.setOquantity(quantity);
order.setOprice(totalPrice.toString());
order.setCid(customerId);
order.setOdate(new Date());
orderService.createOrder(order, conn);
// 更新库存
drugService.updateStock(drugId, drug.getRequantity() - quantity, conn);
conn.commit(); // 提交事务
request.setAttribute("successMsg", "订单创建成功");
} catch (Exception e) {
if (conn != null) {
try {
conn.rollback(); // 回滚事务
} catch (SQLException ex) {
ex.printStackTrace();
}
}
request.setAttribute("errorMsg", "订单创建失败:" + e.getMessage());
} finally {
DatabaseUtil.closeConnection(conn);
}
request.getRequestDispatcher("/orderResult.jsp").forward(request, response);
}
}
供应商信息管理
供应商管理模块维护了药品供应商的基本信息,为采购管理和供应链优化提供支持。

供应商数据访问层的实现展示了JDBC操作的最佳实践:
public class SupplierDAO {
public boolean addSupplier(Supplier supplier) throws SQLException {
String sql = "INSERT INTO supplier (sname, sphone, saddress) VALUES (?, ?, ?)";
try (Connection conn = DatabaseUtil.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, supplier.getSname());
pstmt.setString(2, supplier.getSphone());
pstmt.setString(3, supplier.getSaddress());
return pstmt.executeUpdate() > 0;
}
}
public List<Supplier> getAllSuppliers() throws SQLException {
List<Supplier> suppliers = new ArrayList<>();
String sql = "SELECT sname, sphone, saddress FROM supplier ORDER BY sname";
try (Connection conn = DatabaseUtil.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql);
ResultSet rs = pstmt.executeQuery()) {
while (rs.next()) {
Supplier supplier = new Supplier();
supplier.setSname(rs.getString("sname"));
supplier.setSphone(rs.getString("sphone"));
supplier.setSaddress(rs.getString("saddress"));
suppliers.add(supplier);
}
}
return suppliers;
}
}
客户信息管理
客户管理模块记录了购买药品的客户信息,支持客户档案的维护和销售分析。

客户实体类的设计体现了JavaBean的规范:
package entity;
import java.io.Serializable;
public class Customer implements Serializable {
private int cid;
private String cname;
private String cphone;
private String caddress;
private String callergy; // 过敏史
// Getter和Setter方法
public int getCid() {
return cid;
}
public void setCid(int cid) {
this.cid = cid;
}
public String getCname() {
return cname;
}
public void setCname(String cname) {
this.cname = cname;
}
// 其他getter和setter方法...
@Override
public String toString() {
return "Customer [cid=" + cid + ", cname=" + cname + ", cphone=" + cphone
+ ", caddress=" + caddress + ", callergy=" + callergy + "]";
}
}
实体模型设计与业务逻辑封装
系统的实体类设计严格遵循JavaBean规范,每个实体类都实现了Serializable接口,支持序列化操作。以管理员实体为例:
package entity;
import java.io.Serializable;
public class Admin implements Serializable {
private int aid;
private String aname;
private String apassword;
private String aphone;
private String aaddress;
public int getAid() {
return aid;
}
public void setAid(int aid) {
this.aid = aid;
}
public String getAname() {
return aname;
}
public void setAname(String aname) {
this.aname = aname;
}
public String getApassword() {
return apassword;
}
public void setApassword(String apassword) {
this.apassword = apassword;
}
public String getAphone() {
return aphone;
}
public void setAphone(String aphone) {
this.aphone = aphone;
}
public String getAaddress() {
return aaddress;
}
public void setAaddress(String aaddress) {
this.aaddress = aaddress;
}
@Override
public String toString() {
return "Admin [aid=" + aid + ", aname=" + aname + ", apassword=" + apassword
+ ", aphone=" + aphone + ", aaddress=" + aaddress + "]";
}
}
业务逻辑层通过Service类封装复杂的业务规则,如库存检查、价格计算、数据验证等:
public class DrugService {
private DrugDAO drugDAO = new DrugDAO();
public boolean addDrug(Drug drug) throws SQLException {
// 数据验证
if (drug.getDid() == null || drug.getDid().trim().isEmpty()) {
throw new IllegalArgumentException("药品ID不能为空");
}
if (drug.getRequantity() < 0) {
throw new IllegalArgumentException("库存数量不能为负数");
}
// 检查药品ID是否已存在
if (drugDAO.getDrugById(drug.getDid()) != null) {
throw new IllegalArgumentException("药品ID已存在");
}
return drugDAO.addDrug(drug);
}
public List<Drug> getExpiringDrugs(int days) throws SQLException {
return drugDAO.getDrugsExpiringInDays(days);
}
}
功能展望与系统优化方向
基于当前系统架构,未来可以从以下几个方向进行优化和功能扩展:
1. 引入Redis缓存提升性能
对于频繁访问的药品信息、客户数据等,可以引入Redis作为缓存层,显著减少数据库访问压力。实现思路:在Service层添加缓存逻辑,先查询缓存,缓存未命中时再查询数据库。
public class DrugServiceWithCache {
private Jedis redisClient = RedisUtil.getJedis();
private DrugDAO drugDAO = new DrugDAO();
public Drug getDrugById(String drugId) throws SQLException {
String cacheKey = "drug:" + drugId;
String cachedData = redisClient.get(cacheKey);
if (cachedData != null) {
return JSON.parseObject(cachedData, Drug.class);
}
Drug drug = drugDAO.getDrugById(drugId);
if (drug != null) {
redisClient.setex(cacheKey, 3600, JSON.toJSONString(drug)); // 缓存1小时
}
return drug;
}
}
2. 增加消息队列实现异步处理
对于库存同步、销售报表生成等耗时操作,可以引入消息队列实现异步处理,提升系统响应速度。建议使用RabbitMQ或ActiveMQ。
3. 微服务架构改造
将单体应用拆分为药品服务、订单服务、库存服务等微服务,提升系统的可维护性和扩展性。使用Spring