基于JSP+Servlet的在线药店销售与库存管理系统 - 源码深度解析

JavaJavaScriptHTMLCSSMySQLJSP+Servlet
2026-02-103 浏览

文章摘要

本系统是一款基于JSP和Servlet技术栈构建的在线药店核心业务管理平台,旨在解决传统药店在药品销售与库存管理环节中普遍存在的信息孤岛、数据更新滞后及人工操作繁琐等核心痛点。系统通过将销售前端与库存后台深度整合,实现了业务流程的数字化闭环,其核心业务价值在于显著提升了药品流转效率、降低了因信息不透...

在医药零售行业数字化转型的浪潮中,传统药店面临着库存信息不透明、销售数据滞后、人工操作易出错等核心挑战。一款高效、可靠的业务管理平台成为提升运营效率的关键。本系统采用经典的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

本文关键词
JSPServlet在线药店库存管理系统源码解析

上下篇

上一篇
没有更多文章
下一篇
没有更多文章