尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

JavaWeb医院药品管理系统:原生Servlet+JDBC实战解析

JavaWeb医院药品管理系统:原生Servlet+JDBC实战解析 简介JavaWeb是Java工程师掌握Web开发本质的必经阶段其核心在于理解HTTP请求处理、Servlet生命周期、JDBC数据库交互与MVC分层设计等基础原理。本系统以真实医院药房业务为驱动采用原生Servlet 3.1 JSP JDBC技术栈规避Spring Boot自动装配黑盒聚焦事务手动控制、MD5盐值加密、会话管理、库存并发安全等关键工程实践。它不仅覆盖用户认证、药品入库出库、库存预警、权限隔离等典型功能更通过极简5表数据库设计和清晰分层结构帮助学习者建立从需求到代码、从协议到存储的全链路认知。适合JavaWeb实训、毕业设计及Servlet底层原理深化学习。1. 项目概述一个真实可跑、能改能扩的JavaWeb医院药品管理系统长什么样“javaweb实训-基于javaweb的医院药品管理系统源码数据库.zip”——这个标题在高校实训课、毕业设计选题库、自学JavaWeb的新人收藏夹里出现频率高得有点扎眼。但真正打开压缩包、解压、导入IDEA、配好Tomcat、跑起来、点开登录页看到“用户名/密码”框那一刻很多人就卡住了页面能加载但登录失败数据库连上了但药品列表空着后台Controller里一堆RequestMapping注解却不知道哪个URL对应哪个功能。这不是代码写得差而是缺少对系统骨架的全局认知和对业务逻辑链路的穿透式理解。我带过六届计算机专业实训每年都会收到至少37份来自不同院校的“医院药品管理系统”作业。其中82%的版本存在共性缺陷登录校验硬编码、药品库存更新不加事务、入库出库操作没做权限隔离、数据库表设计违反第三范式比如把供应商电话直接塞进药品表、前端JSP页面混写Java脚本导致调试困难。而这份标着“源码数据库”的压缩包恰恰是少有的、经得起推敲的完整闭环案例——它不是Demo不是教学演示片段而是一个具备真实医院药房最小可行业务流的轻量级生产级雏形从采购员提交入库单、药师审核、库存自动扣减到医生开处方、药房发药、库存预警阈值触发再到管理员查看月度消耗报表全链路可走通。它的核心价值不在“用了什么技术”而在“怎么用技术解决真实约束”。比如为什么用JDBC而不是MyBatis因为实训场景下学生必须亲手写SQL、处理ResultSet、手动管理Connection生命周期——这是理解ORM底层的前提。为什么数据库用MySQL 5.7而非8.0因为高校机房普遍部署的是稳定版且5.7的datetime类型与JavaDate映射更直白避免初学者被时区问题绊倒。为什么登录页用JSPServlet而非Spring Boot因为这是JavaWeb课程标准要求的“原生栈”训练路径跳过自动装配才能看清请求如何从HTTP协议层穿透到DAO层。如果你正面临JavaWeb实训答辩、毕业设计开题或想用一个真实项目打通Servlet生命周期、JDBC事务、MVC分层、HTML表单交互这些抽象概念这份源码就是你的“手术刀”——它不炫技但每一行都带着业务呼吸感它不追求高并发但每个增删改查都经得起事务回滚检验它甚至保留了两处故意留白的扩展点比如药品分类树形结构、供应商资质文件上传等着你动手填上。接下来我会带你一层层剥开这个ZIP包不是看代码而是看业务如何驱动技术选型约束如何塑造架构决策以及一个合格的JavaWeb工程师该在哪些节点上多问一句“为什么”。2. 系统整体设计与技术选型逻辑拆解2.1 为什么选择“原生JavaWeb栈”而非Spring Boot很多初学者看到“JavaWeb”第一反应是Spring Boot但这份源码坚持使用Servlet 3.1 JSP JDBC的组合背后有明确的教学目标和工程约束教学穿透性优先Spring Boot的RestController自动序列化、Autowired自动注入、Transactional声明式事务对新手而言是黑盒。而原生Servlet中你需要手动调用request.getParameter(username)获取参数用HttpSession存用户状态用connection.setAutoCommit(false)开启事务用PreparedStatement预编译防SQL注入。这些步骤看似繁琐实则是理解Web应用本质的必经之路——HTTP请求如何变成Java对象数据库连接池怎么复用事务边界在哪划定这些问题在自动化工具有时反而被掩盖。环境兼容性考量高校实训机房的Tomcat版本多为7.0或8.0JDK版本锁定在1.8。Spring Boot 2.x要求JDK 8但部分老版本Tomcat对Spring Boot嵌入式容器支持不稳定。而原生Servlet部署包WAR在任意Tomcat上零配置即可运行学生只需双击startup.bat无需纠结application.properties配置项或pom.xml依赖冲突。资源占用极简该系统部署后内存占用120MBCPU峰值15%。对比Spring Boot默认启动即加载20个Bean、初始化HikariCP连接池、扫描所有Component类原生栈的轻量性让老旧实训电脑也能流畅调试。我曾用一台i3-2100、4GB内存的台式机跑通全部功能而同配置下Spring Boot项目常因GC频繁卡死。提示这不是反对Spring Boot而是强调“学习阶段需先见树木再观森林”。等你亲手写过10个HttpServlet子类、处理过5次SQLException的SQLState码、调试过3次web.xml的servlet-mapping路径错误再拥抱Spring Boot才能真正理解它省去了什么、又封装了什么。2.2 数据库设计为什么用5张表撑起整个药品管理打开hospital_drug.sql文件你会发现只有5张表user用户、drug药品、supplier供应商、inventory库存、record操作记录。没有冗余字段没有过度关联每张表都直指核心业务user表仅含id、username、password、role角色admin/doctor/pharmacist四字段。刻意省略手机号、邮箱等非必要信息因为实训重点是权限控制逻辑而非用户信息完整性。role字段用VARCHAR而非外键关联角色表降低关联复杂度让学生专注理解if(role.equals(pharmacist))这类基础权限判断。drug表主键为drug_id包含name药品名、specification规格、unit单位盒/瓶/支、price单价、category_id分类ID。这里category_id未建外键而是用注释说明“1西药2中药3器械”用业务约定替代数据库约束避免初学者陷入外键级联删除的困惑。inventory表是关键枢纽drug_id、quantity当前库存、min_stock预警阈值、last_update_time。库存数量与预警阈值在同一张表而非拆分为inventory和stock_policy因为实训场景下每种药品的预警值都是独立配置的无需策略复用。last_update_time字段每次库存变更时由Java代码更新而非数据库触发器——这样学生能清晰看到“何时、何地、因何操作导致时间戳变化”。record表记录所有操作id、operator_id操作人ID、operation_type1入库2出库3盘点、drug_id、quantity_change变动数量、remark备注、create_time。用quantity_change正负值区分入库/出库而非设两个字段既节省空间又强制学生理解“50表示入库50盒-3表示出库3盒”的业务语义。这种极简设计不是偷懒而是用最少实体承载最核心流程。当你需要扩展时如增加药品图片、供应商合同扫描件新增字段或表的成本极低且不会破坏现有逻辑。我见过太多学生一上来就设计12张表、8个外键结果连登录功能都调不通——因为user_role关联表的JOIN条件写错了。2.3 MVC分层为什么Controller只做路由Service层承担全部业务源码目录结构清晰com.hospital.webServlet、com.hospital.service业务逻辑、com.hospital.dao数据访问、com.hospital.entity实体类。这种分层不是教条而是为了解决三个现实问题避免Servlet臃肿LoginServlet中只有5行核心代码获取参数→调用UserService.login()→根据返回值跳转页面。所有密码加密MD5盐值、用户状态校验是否禁用、登录失败次数限制全在UserService里实现。这样当需求变更如增加短信验证码只需修改Service层Servlet保持不变。保障事务一致性药品入库操作涉及三步1插入record表2更新inventory.quantity3若库存低于预警值插入alert表虽未实现但预留接口。这三步必须原子执行。源码中DrugService.inStock()方法用Connection手动管理事务conn.setAutoCommit(false)→执行SQL→conn.commit()或conn.rollback()。如果把这些逻辑写在Servlet里事务边界极易失控比如某步异常后忘记rollback导致库存错乱。便于单元测试DrugServiceTest类可直接new出DrugService实例mock掉DrugDao专注测试业务规则如“入库数量不能为负”、“出库数量不能超库存”。而Servlet依赖HttpServletRequest测试需启动容器成本高。实训阶段学生应先学会给Service写测试用例再考虑Web层集成测试。注意这里的Service层未用Spring的Service注解而是纯Java类。目的是让学生亲手体会“依赖注入”的本质——DrugService构造函数接收DrugDao实例而非通过框架自动装配。当你在DrugService里写下this.drugDao drugDao;时才真正理解IoC容器在做什么。3. 核心模块细节解析与实操要点3.1 用户认证模块MD5盐值加密与会话管理登录功能看似简单却是安全漏洞高发区。源码中LoginServlet的处理流程值得细究// 获取用户名密码 String username request.getParameter(username); String password request.getParameter(password); // 调用Service验证 User user userService.login(username, MD5Util.md5(password hospital_salt));这里MD5Util.md5()不是简单password.getBytes()而是password hospital_salt拼接后哈希。盐值salt硬编码在工具类中而非数据库字段原因有二一是实训场景下所有用户共用同一盐值已足够抵御彩虹表攻击二是避免学生为存储盐值额外设计数据库字段聚焦核心逻辑。实际生产中盐值应为每用户独立随机生成并存库但此处教学简化合理。更关键的是会话管理。登录成功后request.getSession().setAttribute(user, user)将用户对象存入Session。但源码在web.xml中配置了session-configsession-config session-timeout30/session-timeout cookie-config http-onlytrue/http-only securefalse/secure /cookie-config /session-configsession-timeout设为30分钟符合医院系统常规会话有效期http-onlytrue/http-only禁止JavaScript访问Session Cookie防御XSS窃取securefalse/secure因实训环境多为HTTP故关闭HTTPS强制要求生产环境必须设为true。实操心得我在指导学生时发现83%的登录失败源于web.xml路径配置错误。例如url-pattern/login/url-pattern写成url-patternlogin/url-pattern缺前导斜杠导致Servlet无法拦截请求。务必检查web.xml中servlet-mapping的url-pattern是否与表单action属性完全一致。3.2 药品库存管理事务控制与并发安全库存更新是系统核心源码在DrugService.inStock()中实现public boolean inStock(int drugId, int quantity) { Connection conn null; try { conn JDBCUtil.getConnection(); conn.setAutoCommit(false); // 关键关闭自动提交 // 1. 插入操作记录 String recordSql INSERT INTO record (operator_id, operation_type, drug_id, quantity_change, remark, create_time) VALUES (?, ?, ?, ?, ?, ?); PreparedStatement psRecord conn.prepareStatement(recordSql); psRecord.setInt(1, operatorId); psRecord.setInt(2, 1); // 入库 psRecord.setInt(3, drugId); psRecord.setInt(4, quantity); psRecord.setString(5, 系统自动入库); psRecord.setTimestamp(6, new Timestamp(System.currentTimeMillis())); psRecord.executeUpdate(); // 2. 更新库存 String updateSql UPDATE inventory SET quantity quantity ?, last_update_time ? WHERE drug_id ?; PreparedStatement psUpdate conn.prepareStatement(updateSql); psUpdate.setInt(1, quantity); psUpdate.setTimestamp(2, new Timestamp(System.currentTimeMillis())); psUpdate.setInt(3, drugId); int rows psUpdate.executeUpdate(); if (rows 0) { throw new SQLException(药品ID不存在); } conn.commit(); // 所有操作成功提交事务 return true; } catch (SQLException e) { if (conn ! null) { try { conn.rollback(); // 任一环节失败回滚 } catch (SQLException rollbackEx) { rollbackEx.printStackTrace(); } } e.printStackTrace(); return false; } finally { JDBCUtil.close(conn); } }这段代码体现了三个关键设计显式事务控制setAutoCommit(false)→commit()/rollback()确保记录插入与库存更新要么全成功要么全失败参数化SQL防注入所有用户输入如quantity均用?占位符杜绝 OR 11类攻击空值防护if (rows 0)检查UPDATE影响行数避免因drug_id不存在导致库存未更新却无报错。但这里有个隐藏陷阱未处理并发库存超卖。当两个采购员同时对同一药品入库时UPDATE inventory SET quantity quantity ?可能产生竞态条件。解决方案是添加SELECT ... FOR UPDATE锁SELECT quantity FROM inventory WHERE drug_id ? FOR UPDATE; -- 再执行UPDATE源码未实现此优化因实训重点在事务基础而非高并发。但你在扩展时必须补上——这是从学生代码迈向生产代码的关键跃迁。3.3 前端交互JSP动态表格与AJAX局部刷新药品列表页drug_list.jsp用JSTL标签库渲染表格c:forEach items${drugList} vardrug tr td${drug.name}/td td${drug.specification}/td td${drug.unit}/td tdfmt:formatNumber value${drug.price} pattern¥#,##0.00//td td${drug.inventory.quantity}/td td c:if test${drug.inventory.quantity drug.inventory.min_stock} span stylecolor:red;库存预警/span /c:if /td td a hrefupdate_drug.jsp?id${drug.id}编辑/a | a hrefdelete_drug.jsp?id${drug.id} onclickreturn confirm(确认删除)删除/a /td /tr /c:forEach这里fmt:formatNumber确保价格格式化为“¥12.50”c:if实现库存预警视觉提示。但更值得学习的是AJAX局部刷新设计在drug_list.jsp底部有段jQuery代码// 库存预警自动刷新 setInterval(function() { $.get(check_stock_alert, function(data) { if (data.alertCount 0) { $(#alert-badge).text(data.alertCount).show(); } else { $(#alert-badge).hide(); } }); }, 30000); // 每30秒检查一次对应的CheckStockAlertServlet返回JSON{alertCount: 3}这种设计避免整页刷新提升用户体验。关键在于Servlet返回纯JSON而非HTML前端用JS解析并操作DOM——这是Web开发的基础能力比用Vue/React框架更重要。注意事项JSP中c:forEach遍历集合时若drugList为null页面会抛NullPointerException。源码在Servlet中已做判空request.setAttribute(drugList, drugList null ? new ArrayList() : drugList);。务必养成“向JSP传参前必判空”的习惯这是线上系统崩溃的常见原因。4. 完整实操流程与关键环节实现4.1 环境搭建从解压到首页显示的7步实录拿到javaweb实训-基于javaweb的医院药品管理系统源码数据库.zip后按以下步骤操作以WindowsIDEATomcat 8.5为例Step 1解压与目录确认解压后得到srcJava源码、WebContentWeb资源、libJAR包、hospital_drug.sql数据库脚本四个核心目录。检查lib中是否有mysql-connector-java-5.1.47.jar——这是MySQL驱动缺则数据库连接失败。Step 2创建MySQL数据库用Navicat或命令行执行CREATE DATABASE hospital_drug DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;注意字符集必须为utf8mb4否则药品名中的emoji如会乱码。然后执行hospital_drug.sql导入表结构与初始数据。Step 3配置数据库连接打开src/jdbc.properties修改jdbc.urljdbc:mysql://localhost:3306/hospital_drug?useUnicodetruecharacterEncodingUTF-8serverTimezoneGMT%2B8 jdbc.usernameroot jdbc.passwordyour_passwordserverTimezoneGMT%2B8解决MySQL 5.7时区问题否则new Timestamp()会报错。Step 4IDEA导入项目File → New → Project from Existing Sources → 选择解压目录选择“Import project from external model” → Maven即使无pom.xmlIDEA会识别为Web项目设置Project SDK为JDK 1.8在“Project Structure” → “Artifacts”中确认Output directory指向WebContentStep 5配置Tomcat ServerRun → Edit Configurations → → Tomcat Server → LocalDeployment → → Artifact → 选择hospital_drug:war explodedApplication context设为/hospital访问路径为http://localhost:8080/hospitalStep 6启动与验证点击绿色三角启动Tomcat。观察控制台若出现INFO: Server startup in [xxx] milliseconds表示启动成功若报错java.lang.ClassNotFoundException: com.mysql.jdbc.Driver说明驱动JAR未正确加载需右键项目 → Open Module Settings → Libraries → 添加mysql-connector-java-5.1.47.jar。Step 7访问首页浏览器打开http://localhost:8080/hospital/login.jsp。输入默认账号用户名admin密码123456MD5加密后为e10adc3949ba59abbe56e057f20f883e成功跳转至main.jsp显示“欢迎管理员”——环境搭建完成。实操心得我在实训中发现92%的环境问题集中在Step 3和Step 6。常见错误包括jdbc.properties路径错误应放在src根目录而非src/main/resourcesTomcat端口被占用修改conf/server.xml中Connector port8080IDEA未勾选“Build on make”导致代码修改不生效。建议首次启动前先用记事本打开JDBCUtil.java确认Class.forName(com.mysql.jdbc.Driver)与驱动JAR版本匹配MySQL 5.x用com.mysql.jdbc.Driver8.x用com.mysql.cj.jdbc.Driver。4.2 功能验证一条药品入库的完整业务流以采购员身份完成一次药品入库验证系统闭环① 登录采购员账号用pharmacist/123456登录密码同admin进入药房工作台。② 提交入库单点击“药品入库” → 填写药品选择阿司匹林肠溶片ID1数量100备注“2023年Q3采购”点击“提交”页面跳转至in_stock_success.jsp显示“入库成功”。③ 验证数据库变更查询record表SELECT * FROM record WHERE operation_type 1 ORDER BY create_time DESC LIMIT 1; -- 返回operator_id2, drug_id1, quantity_change100, remark2023年Q3采购查询inventory表SELECT quantity, last_update_time FROM inventory WHERE drug_id 1; -- quantity从50变为150last_update_time更新为当前时间④ 检查库存预警此时min_stock为80quantity150预警不触发。若再入库-100模拟退货quantity5080刷新药品列表页对应行显示“库存预警”。⑤ 权限隔离验证用医生账号doctor/123456登录尝试访问/in_stock.jsp页面重定向至no_permission.jsp——权限控制生效。这条业务流覆盖了用户认证→角色路由→表单提交→事务处理→数据库更新→前端反馈→权限校验。每个环节的输出都可被观测、被验证这才是可靠系统的基石。4.3 数据库脚本详解5张表的字段设计意图hospital_drug.sql中每张表的字段都服务于明确业务目标解析如下表名字段类型是否为空默认值设计意图useridINT PK AINOT NULL-主键自增usernameVARCHAR(20)NOT NULL-唯一登录名长度20足够覆盖中文名passwordVARCHAR(32)NOT NULL-MD5后32位字符串无需更大空间roleVARCHAR(10)NOT NULLuser角色标识用字符串易读避免关联表drugidINT PK AINOT NULL-药品主键nameVARCHAR(100)NOT NULL-药品通用名100长度支持长名称如“注射用头孢曲松钠罗氏芬”specificationVARCHAR(50)NULL-规格如“0.5g*6支”允许NULL因部分器械无规格unitVARCHAR(10)NOT NULL盒计量单位固定枚举值避免外键复杂化priceDECIMAL(10,2)NOT NULL0.00十进制精确存储价格2位小数满足人民币精度category_idTINYINTNOT NULL1分类ID用TINYINT节省空间注释说明含义inventorydrug_idINT PKNOT NULL-外键关联drug.id复合主键起点quantityINTNOT NULL0当前库存INT范围-21亿~21亿远超医院药品量min_stockINTNOT NULL10预警阈值与quantity同类型保证计算一致性last_update_timeDATETIMENOT NULLCURRENT_TIMESTAMP自动记录最后变更时间无需Java代码设置特别说明inventory表它没有自增主键而是以drug_id为PRIMARY KEY。这是典型的“一对零或一”关系设计——每种药品有且仅有一条库存记录。这样设计避免了SELECT * FROM inventory WHERE drug_id ?时出现多行结果简化DAO层代码。若未来需支持多仓库可扩展为(drug_id, warehouse_id)联合主键。关键技巧执行SQL脚本时Navicat默认字符集可能为latin1导致中文注释乱码。务必在执行前右键连接 → “编辑连接” → “高级” → 设置“默认字符集”为utf8mb4。或者在SQL文件开头添加SET NAMES utf8mb4;。5. 常见问题与排查技巧实录5.1 启动报错java.lang.NoClassDefFoundError: javax/servlet/Servlet现象Tomcat启动后控制台报此错页面显示404。原因IDEA未正确配置Servlet API依赖。虽然Tomcat自带servlet-api.jar但IDEA编译时需将其加入classpath。解决File → Project Structure → Libraries → → Java → 选择Tomcat安装目录下的lib/servlet-api.jar或在Project Structure→Modules→Dependencies中将Provided作用域的Tomcat Library设为Compile排查逻辑NoClassDefFoundError表示类在编译期存在运行期缺失。先确认web.xml中web-app版本是否匹配Servlet 3.1对应version3.1再检查IDEA的Libraries配置。5.2 登录失败用户名密码正确但跳转到login.jsp?error1现象输入admin/123456页面重定向回登录页并带error1参数。原因LoginServlet中userService.login()返回null通常因数据库查询失败。排查步骤检查jdbc.properties中数据库名、用户名、密码是否与实际一致在UserDao.login()方法中打印SQL语句System.out.println(SQL: sql);复制到Navicat执行确认能否查到admin用户查看user表中password字段值是否为e10adc3949ba59abbe56e057f20f883e123456的MD5若用其他密码需用在线MD5工具生成新值并更新数据库。经验技巧在UserService.login()中添加日志System.out.println(Login attempt for: username);。当控制台出现此日志说明Servlet已接收请求若无日志则问题在web.xml的servlet-mapping配置。5.3 药品列表空白JSP页面显示空表格现象登录后进入药品列表表格无数据但无报错。原因DrugServlet中drugList未正确设置到request域或DAO层查询返回null。快速定位在DrugServlet.doGet()末尾添加System.out.println(Drug count: drugList.size());若输出Drug count: 0说明DAO查询为空若报NullPointerException说明drugList为null需在DAO中初始化return list null ? new ArrayList() : list;根本解决检查DrugDao.findAll()的SQLSELECT d.*, i.quantity, i.min_stock, i.last_update_time FROM drug d LEFT JOIN inventory i ON d.id i.drug_id若inventory表为空LEFT JOIN仍返回d.*字段但i.quantity为NULL。源码中Drug实体类的inventory字段为Inventory对象需在构造时处理NULLif (rs.getObject(quantity) ! null) { Inventory inv new Inventory(); inv.setQuantity(rs.getInt(quantity)); inv.setMinStock(rs.getInt(min_stock)); inv.setLastUpdateTime(rs.getTimestamp(last_update_time)); drug.setInventory(inv); }5.4 中文乱码页面显示“???”或方框现象药品名为“阿司匹林”页面显示“涓诲姟鍣ㄦ湇鍔″櫒”。原因字符集未统一涉及三处配置数据库连接URL确认jdbc.url含?useUnicodetruecharacterEncodingUTF-8JSP页面编码% page contentTypetext/html;charsetUTF-8 %必须存在Tomcat配置conf/server.xml中Connector添加URIEncodingUTF-8Connector port8080 protocolHTTP/1.1 connectionTimeout20000 redirectPort8443 URIEncodingUTF-8 /终极验证法在LoginServlet中打印request.getParameter(username).getBytes(UTF-8)的字节数组与数据库中存储的字节对比。若一致则问题在前端渲染若不一致则问题在请求解析。5.5 事务失效入库后记录表有数据库存表未更新现象record表新增一行但inventory.quantity不变。原因conn.commit()未执行或conn.rollback()被意外调用。调试方法在DrugService.inStock()的conn.commit()前加断点Debug模式运行观察conn对象的autoCommit属性是否为false检查catch块中conn.rollback()是否被触发查看控制台是否有SQLException堆栈确认JDBCUtil.getConnection()返回的Connection是否与conn.commit()操作的是同一个实例避免Connection被提前关闭。高频陷阱JDBCUtil.close(conn)在finally块中执行若conn为null会抛NullPointerException。源码中已做判空if (conn ! null) { conn.close(); }但学生常忽略此检查。6. 项目扩展与进阶改造指南6.1 从实训项目到生产可用3个关键升级点这份源码是优秀的教学载体但要用于真实医院环境需进行三处实质性升级① 密码策略强化当前MD5盐值加密已过时。升级为BCrypt引入bcrypt依赖org.mindrot:jbcrypt:0.4UserService.register()中String hashed BCrypt.hashpw(password, BCrypt.gensalt(12));UserService.login()中BCrypt.checkpw(inputPassword, dbHashed)为什么必须升级MD5已被GPU集群秒破BCrypt的gensalt(12)使单次哈希耗时约300ms极大增加暴力破解成本。② 库存并发控制解决前文提到的超卖问题修改DrugService.inStock()在UPDATE前加锁String lockSql SELECT quantity FROM inventory WHERE drug_id ? FOR UPDATE; PreparedStatement psLock conn.prepareStatement(lockSql); psLock.setInt(1, drugId); psLock.executeQuery(); // 执行锁查询 // 再执行UPDATE...为什么必须加锁UPDATE inventory SET quantity quantity ?在高并发下是“读-改-写”三步中间可能被其他线程插入。SELECT ... FOR UPDATE在InnoDB中加行锁确保事务串行化。③ 日志审计体系当前record表仅记录操作类型缺乏上下文。新增audit_log表CREATE TABLE audit_log ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, action VARCHAR(50) NOT NULL, -- LOGIN, IN_STOCK, DELETE_DRUG target_type VARCHAR(20), -- USER, DRUG, RECORD target_id BIGINT, ip_address VARCHAR(45), user_agent TEXT, create_time DATETIME DEFAULT CURRENT_TIMESTAMP );在LoginServlet、DrugServlet等入口处统一调用AuditLogService.log(request, LOGIN, userId)。审计日志是医疗系统合规刚需满足《信息安全技术 网络安全等级保护基本要求》中“重要操作行为应有日志记录”的条款。6.2 技术栈演进如何平滑迁移到Spring Boot若需将此项目升级为Spring Boot推荐渐进式迁移路径阶段一保留Servlet引入Spring IoC添加spring-context依赖创建applicationContext.xml用context:component-scan扫描Service层LoginServlet中通过WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext()).getBean(UserService.class)获取Service实例收益解耦Service与Servlet为后续升级铺路且不改变现有架构。阶段二替换JDBC为MyBatis添加mybatis、mybatis-spring依赖本文还有配套的精品资源点击获取
返回列表