ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

JavaWeb医药管理系统:Servlet+JSP+JDBC分层设计与答辩要点

JavaWeb医药管理系统:Servlet+JSP+JDBC分层设计与答辩要点 简介一套面向计算机专业学生的JavaWeb医药管理系统期末大作业项目包适合正在做课程设计、毕业设计以及希望进行JavaWeb实战练习的学习者。项目采用典型分层结构Controller、Dao、JSP页面职责清晰覆盖客户管理、代理商管理、药品管理与购买管理等核心业务源码中能看到控制层与数据访问层的完整实现与调用关系有助于理解JDBC操作、DAO封装和业务层协作方式。源码已经本地编译并运行验证过附带SQL数据库脚本导入后可完整复现项目环境还配有Markdown说明与界面预览截图便于阅读和二次开发。压缩包共119个文件包含33个Java源文件、33个class编译文件、30个JSP页面以及XML配置、JAR依赖、项目配置、数据库脚本等关键文件源码和编译产物同时提供既可直接部署运行也便于对照学习整包约3.01MB目录结构清晰可按模块逐一学习。目前已有94人学习使用过适合想要获得高分项目参考、快速理解完整Web项目组织方式的学生。1. JavaWeb医药管理系统期末大作业拿高分的关键在代码结构如果你正在为JavaWeb期末大作业发愁这套医药管理系统值得你花十分钟认真看看。它不是一个花哨的框架项目而是一个把Servlet JSP JDBC这套传统JavaWeb技术栈完整落地的业务系统覆盖了客户管理、医药公司管理、药品管理、采购下单四大核心模块。项目里Controller、Dao、DaoImpl的类名是真真切切能对应到代码文件的评审分98分意味着它在功能完整度、代码规范性和演示流畅度上都经得起验收。对正在做课程设计、需要一份能跑、能讲、能改的参考源码的同学来说这个项目的难度适中——既不会像SSM整合那样一堆配置劝退新手又比单纯的CRUD练习多了业务关联和分层设计能让你在答辩时讲出东西。2. 项目结构与本地运行先把JavaWeb骨架搭起来拿到压缩包后第一件事不是急着看代码而是把文件结构和运行方式理清楚。很多同学期末翻车不是因为代码写不出来而是项目在别人机器上能跑、在自己机器上死活起不来。这个医药管理系统的源代码和数据库是配套的本地编译可运行但你需要按它的技术栈去准备环境。2.1 源码文件拆解Controller与DaoImpl的角色分工从项目文件清单来看这套系统的结构很清晰是标准的JavaWeb分层写法。Controller类负责接收HTTP请求、解析参数、调用DAO层并返回页面跳转Dao接口定义数据访问方法DaoImpl实现具体的SQL操作Seetable是一个工具类从名字推测是统一查询或分页展示的辅助组件。文件类别具体类职责说明控制层ClientController、AgencyController、MedicineController、BuyController对应客户、医药公司、药品、采购四个模块的请求入口工具类Seetable通用查询辅助负责结果集封装或分页处理DAO基类BaseDao抽取JDBC公共操作如获取连接、关闭资源、通用增删改DAO实现ClientDaoImpl、AgencyDaoImpl、MedicineDaoImpl、BuyDaoImpl各业务模块的SQL执行与结果映射这种结构的价值在于你答辩被问你的项目是怎么分层的时能很自然地讲清楚页面请求打到ControllerController不直接写SQL而是调DaoImplDaoImpl通过BaseDao拿到数据库连接再执行具体语句。Controller和DaoImpl一一对应的关系也让你加新功能时有明确的落笔位置——新增一个订单模块就建OrderController和OrderDaoImpl照着Buy模块的写法复制改造即可。我一般会建议先用五分钟把每个类打开扫一遍从Controller的方法签名就能看出系统支持哪些操作这比对着PPT背功能清单靠谱得多。2.2 IDEA里本地跑起来Tomcat配置与部署路径这个项目既然标注了本地编译过可运行说明它大概率是一个可以在IDEA里直接导入的Web工程。技术栈是Servlet JSP JDBC的情况下你需要准备JDK 8或11、Tomcat 8.5或9、MySQL 5.7或8.0、IDEA Ultimate版。打开项目的操作路径是IDEA中选择File → Open选中包含pom.xml或src目录的根文件夹。如果项目是Maven结构IDEA会自动下载依赖如果是传统Web工程没有pom.xml需要右键项目选择Add Framework Support补上Web能力。接着配置TomcatRun → Edit Configurations → 点加号选Tomcat Server → Local在Deployment选项卡里把项目的war包或exploded版本添加进去Application context建议填/medicine。# 典型部署步骤IDEA菜单路径 1. File - Project Structure - Artifacts - 点 - Web Application: Exploded 2. Run - Edit Configurations - 添加 Tomcat Server - Local 3. Deployment 页签 - 点 - Artifact - 选择刚才配置的 exploded 包 4. Server 页签 - 修改 Tomcat 端口默认8080如果被占用改成8081 5. 启动前确认项目右键 - Open Module Settings - Dependencies 里有 Tomcat 依赖这里的逻辑是Artifacts配置决定IDEA把项目编译后以什么形式发布给TomcatDeployment决定Tomcat启动后加载哪个产物。第一次跑项目报ClassNotFoundException多半是第三步没做Tomcat找不到Web应用的类文件。Application context填/medicine是为了让访问路径变成http://localhost:8080/medicine/login.jsp避免直接用根路径时和其它项目冲突。2.3 数据库初始化SQL脚本导入与连接配置数据库文件是这套资源里的核心资产没有它代码跑起来也只能看到报错页面。把压缩包里的.sql文件用Navicat或命令行导入MySQL后库里应该能看到客户表、医药公司表、药品表、采购记录表这几张业务表。表之间有关联关系比如药品表通过company_id关联医药公司表采购表通过medicine_id和client_id关联药品与客户——这是期末大作业的加分点能体现外键设计和业务逻辑而不是各表孤立存在。-- 典型的连接配置写法db.properties 或 JDBC 工具类中 jdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/medicine_db?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8 jdbc.usernameroot jdbc.password你的数据库密码连接配置有几点容易卡住新手的细节。MySQL 8.x要求驱动类名写com.mysql.cj.jdbc.Driver而5.x写com.mysql.jdbc.Driver版本写错直接报ClassNotFoundException。URL里的serverTimezoneAsia/Shanghai不加上会在连接时报时区错误characterEncodingutf8不加乱码。我习惯把数据库密码单独拎出来检查——很多项目源码里的密码和本机不一致跑不通的第一反应不该是怀疑代码而是先看这个properties文件。提示导入SQL前先确认数据库字符集是utf8mb4不然药品名称里的生僻字或特殊符号可能存不进去连带查询展示时出现问号。3. 从BaseDao到业务模块JDBC封装的三个细节用户提到这套项目的DAO层是学习重点这是大实话。JavaWeb期末项目最容易被问崩溃的地方就在数据访问层——面试或答辩老师不会满足于你能查出来数据他们会问PreparedStatement和Statement的区别、连接资源什么时候关闭、SQL注入怎么防。这套系统的BaseDao DaoImpl结构正好是回答这些问题的现成案例。3.1 BaseDao的抽取逻辑连接、增删改、资源关闭三板斧BaseDao是整套DAO层的底座。虽然源码里没有展开但从JavaWeb项目的常见写法可以还原它的骨架一个获取Connection的方法、一个通用的增删改方法、一个关闭资源的close方法。核心价值是把拿连接、执行SQL、关资源这三个动作固定下来让每个DaoImpl不用重复写七行垃圾代码。// 典型的BaseDao抽象形态参考此类项目最常见写法 public class BaseDao { // 获取连接从配置文件读驱动和URLClass.forName只需要执行一次 protected Connection getConnection() throws SQLException { return DriverManager.getConnection(url, username, password); } // 通用增删改传入SQL和可变参数内部统一走PreparedStatement protected int executeUpdate(String sql, Object... params) { try (Connection conn getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { for (int i 0; i params.length; i) { ps.setObject(i 1, params[i]); } return ps.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); return 0; } } }这套写法的关键点有三个。第一用try-with-resources自动关连接和PreparedStatement避免手动close时忘记处理Java 7以后的标配第二可变参数Object... params让子类调用时传参很方便传几个都行不用写重载方法第三setObject省去了按类型手动调setString、setInt的麻烦代码量少一半。对于期末项目来说这种封装已经足够体现你对JDBC的理解而且面试追问你的项目怎么管理数据库资源时你可以直接说所有资源统一在BaseDao的模板方法里关闭。3.2 ClientDaoImpl与MedicineDaoImpl增删改查的标准姿势DaoImpl的作用是为每个业务表提供具体的SQL操作。拿客户管理模块举例ClientDaoImpl里应该有按ID查询客户、查询全部客户列表、新增客户、修改客户信息、删除客户这五个最基础的方法。而MedicineDaoImpl除了基础CRUD通常多一个按关键字模糊搜索药品的方法因为药品列表页一般带搜索框。// MedicineDaoImpl中按名称模糊查询的典型实现 public ListMedicine findByName(String keyword) { String sql SELECT * FROM medicine WHERE name LIKE ?; ListMedicine list new ArrayList(); try (Connection conn getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, % keyword %); ResultSet rs ps.executeQuery(); while (rs.next()) { Medicine m new Medicine(); m.setId(rs.getInt(id)); m.setName(rs.getString(name)); m.setPrice(rs.getBigDecimal(price)); m.setStock(rs.getInt(stock)); m.setCompanyId(rs.getInt(company_id)); list.add(m); } return list; } catch (SQLException e) { e.printStackTrace(); return list; } }这段代码里值得细说的是LIKE模糊查询和结果集映射。LIKE ?配合% keyword %是JDBC里做搜索的标准做法注意这里的百分号是拼在参数里的而不是拼在SQL模板里——如果拼在SQL里写成WHERE name LIKE %?%就会报SQL语法错误这是新手特别喜欢踩的坑。结果集映射这段每张表的字段和JavaBean的setter一一对应看着繁琐但你可以在Seetable这个工具类里做一次封装把这种遍历ResultSet然后set值的重复劳动收敛起来。3.3 PreparedStatement参数绑定引发的三个改写症状PreparedStatement是这层代码的绝对主角。它比起直接拼接SQL字符串至少带来三个质变第一SQL结构预编译数据库能复用执行计划高频查询的性能更稳定第二参数通过setObject绑定不需要手动处理引号转义——直接拼字符串的时候用户输入一个 or 11就能把查询条件改写成恒真预编译后这个输入只被当作普通字符处理第三可读性更好SQL模板和参数值分离代码评审时一眼能看到查询条件是什么。// 正确写法模板和参数分离 SELECT * FROM client WHERE name ? // 错误写法直接拼接存在SQL注入风险 SELECT * FROM client WHERE name name 在这个资源里检查你自己的理解是否到位有个自测方法打开任意一个DaoImpl看SQL语句里面有没有出现号拼接变量。如果出现了说明这个位置有SQL注入风险也说明写代码的人对PreparedStatement的理解还停在表面。这套源码既然是98分项目大概率不会出现这种写法但你把代码改造成自己的过程需要留意——很多同学为了给查询条件加动态排序不小心就把原来安全的写法改成了拼接。4. 控制层流转Servlet怎么把页面、请求和DAO串起来如果说DAO层是项目的心脏那Controller层就是项目的神经系统。所有用户操作——点登录、查药品、下单采购——都是先经过Controller再被翻译成DAO调用。这一章重点拆四个Controller的职责边界以及一次完整请求从浏览器到数据库再返回页面的路径。4.1 四类Controller的请求分发逻辑从类名就能看出这个项目的模块划分ClientController管客户档案AgencyController管医药公司资料MedicineController管药品目录BuyController管采购业务。每个Controller内部最稳定的结构是一个doGet或doPost方法里面用action参数做分支判断——这是传统JavaWeb项目最常见的写法不依赖SpringMVC的注解路由但也因此更容易被答辩老师追着问你的请求是怎么路由过来的。// 典型请求路径设计以药品管理为例 POST /medicine/medicineController?actionadd - 新增药品 POST /medicine/medicineController?actionupdate - 修改药品 GET /medicine/medicineController?actiondeleteid5 - 删除ID为5的药品 GET /medicine/medicineController?actionlist - 查询全部药品 GET /medicine/medicineController?actionsearchkeyword感冒 - 模糊搜索每个action对应一个独立方法方法内部做三件事从request取参数、调DaoImpl执行、把结果放进request作用域然后转发到JSP页面。这种设计的优点是扩展新功能不需要改框架在Controller里加一个else if分支就能完成。缺点也有——当分支超过八个代码会变得很臃肿但这属于面试时可以聊的技术债务话题期末项目完全够用。4.2 一次完整下单流程的链路追踪BuyController是这个系统里最有业务含量的一块因为它不只是一个表的操作而是多个表的联动。假设用户在页面上提交一笔采购单BuyController大概要执行这样的流程先接收药品ID、客户ID、采购数量这些参数调用MedicineDaoImpl查一下当前库存够不够如果库存不足返回提示如果够就调BuyDaoImpl插入采购记录同时更新药品表的库存数字。// BuyController中下单逻辑的伪代码拆解 public void doBuy(HttpServletRequest request, HttpServletResponse response) { int medicineId Integer.parseInt(request.getParameter(medicineId)); int clientId Integer.parseInt(request.getParameter(clientId)); int quantity Integer.parseInt(request.getParameter(quantity)); // 第一步查当前库存 Medicine m medicineDao.findById(medicineId); if (m.getStock() quantity) { request.setAttribute(errorMsg, 库存不足当前仅剩 m.getStock()); request.getRequestDispatcher(/buy/error.jsp).forward(request, response); return; } // 第二步插入采购记录 int rows buyDao.insert(new BuyRecord(medicineId, clientId, quantity)); // 第三步扣减库存成功才继续 if (rows 0) { medicineDao.decreaseStock(medicineId, quantity); response.sendRedirect(buyController?actionlist); } }这个流程里的业务逻辑值得跟答辩老师讲清楚库存校验和扣减不是一个原子操作如果在真实生产环境会出现超卖风险需要加事务或锁。期末项目不要求你做成分布式事务但你主动说出这里是简化处理实际生产需要考虑同步问题评分老师会觉得你是真的做过思考而不是在背代码。参数转换这里也有个扎实的小细节——Integer.parseInt如果用户传了非数字字符会抛NumberFormatException你可以在catch里做容错处理这是代码健壮性加分的点。4.3 页面与后端数据的对接方式这套系统是JSP页面配合后端渲染不是前后端分离。页面里的表单submit到ControllerController处理完用request.setAttribute塞数据再用request.getRequestDispatcher().forward()跳转到指定的JSP页面。JSP里用c:forEach或% %把数据渲染成表格。!-- 药品列表页的核心渲染片段 -- table border1 tr th药品编号/th th药品名称/th th单价/th th库存/th th所属公司/th th操作/th /tr c:forEach items${requestScope.medicineList} varm tr td${m.id}/td td${m.name}/td td${m.price}/td td${m.stock}/td td${m.companyName}/td td a hrefmedicineController?actioneditid${m.id}编辑/a a hrefmedicineController?actiondeleteid${m.id} onclickreturn confirm(确认删除?)删除/a /td /tr /c:forEach /tableJSP页面的数据绑定有几个常见的坑这里提前点一下。items${requestScope.medicineList}里的属性名必须和Controller里setAttribute的名字完全一致大小写都不能差否则页面上表格是空的。药品所属公司这一列在数据库里存的是company_id页面上直接显示ID肯定不行需要在Service层或Dao层做一次关联查询把公司名称带出来——这个细节能做到位说明你的连表查询是过关的。删除操作上的onclickreturn confirm(...)是前端二次确认的常规写法能防止误删也显得你考虑了用户体验。5. 复现与排查JavaWeb项目最常见的五个坑及处理方法再好的源码拿到自己机器上也可能因环境差异翻车。这章整理的是我在复现同类JavaWeb项目时踩过的坑每一条都按现象、原因、解决的顺序来写基本都是你实际操作时会遇到的。5.1 Tomcat启动后访问403或直接404现象Tomcat启动日志没有报错浏览器访问http://localhost:8080/能看到Tomcat主页但输入项目的访问路径就404。原因大多数情况是IDEA部署时Application context和实际项目路径不一致。比如你配置了/medicine但访问时写的是/Medicine大小写不匹配或者Deployment里根本没添加ArtifactTomcat只是个空壳启动。解决打开Run → Edit Configurations进Deployment页签确认Artifact已经添加把Application context改成/medicine然后在Tomcat的Server页签点Update resources或重启项目。改完后直接访问http://localhost:8080/medicine/看能不能跳转到登录页如果还不行就打开Tomcat的本地日志看有没有加载到这个Web应用。5.2 数据库连接报错Public Key Retrieval is not allowed现象启动项目后第一个请求数据库的操作就报错错误信息里有Public Key Retrieval is not allowed。原因这是MySQL 8.x的驱动安全策略导致的。MySQL 8默认使用caching_sha2_password认证JDBC连接时如果没有明确允许获取公钥驱动会拒绝连接。解决在数据库连接URL上加参数allowPublicKeyRetrievaltrue。完整的URL类似jdbc:mysql://localhost:3306/medicine_db?useSSLfalseallowPublicKeyRetrievaltrueserverTimezoneAsia/ShanghaicharacterEncodingutf8。建议这四个参数写全能一次性把公钥、SSL、时区、编码四个隐患全部规避掉。5.3 JSP页面中文全是问号或乱码现象数据库里的中文数据显示正常但JSP页面上显示成???或者表单提交的中文到数据库里变成乱码。原因乱码问题的根源是三端编码不一致——JSP文件本身的编码、HTTP请求的编码、数据库连接的编码。JSP头部没写% page contentTypetext/html;charsetUTF-8 %或者Servlet里接收请求时没有调用request.setCharacterEncoding(UTF-8)就会导致乱码。解决在每个JSP页面顶部强制加上% page contentTypetext/html;charsetUTF-8 languagejava %在所有Controller处理POST请求的第一步加request.setCharacterEncoding(UTF-8)确认数据库连接URL里有characterEncodingutf8。三条同时满足乱码九成能解决剩下的可能是数据库表本身不是utf8字符集需要ALTER TABLE改一下。5.4 部署时疯狂报ClassNotFoundException现象Tomcat启动后页面能打开但访问某个功能时报java.lang.ClassNotFoundException: com.mysql.jdbc.Driver或找不到项目里的某个类。原因核心原因是Tomcat运行时找不到编译产物或依赖的jar包。MySQL驱动jar包没有添加到WEB-INF/lib下或者IDEA的Artifacts输出目录和实际编译目录不一致——代码改了一部分但Tomcat加载的还是旧的class文件。解决确认mysql-connector-java的jar包已经复制到WEB-INF/lib目录在IDEA里执行Build → Rebuild Project把class文件全部重新编译查看Project Structure → Artifacts的输出目录是否指向了target/classes或out/artifacts下的正确路径。我一般遇到这种问题直接换个干净路径重新部署一遍比在日志里猜原因快得多。5.5 页面能显示但点按钮就500参数空指针现象列表页打开正常但点击编辑或删除按钮时直接报500错误日志显示NullPointerException。原因Controller从request里取参数时拿到的值是null然后直接用这个null去调数据库或做类型转换。常见场景是跳转编辑页时URL写的是actioneditid${m.id}但JSP里这个${m.id}的变量名不对拼出来的URL里就没有id参数。解决先在Controller入口打印每个参数的取值用System.out.println或浏览器URL里直接看参数是否存在。如果确认URL没带参数就检查JSP里EL表达式的变量名是否和DaoImpl返回的JavaBean属性名一致。另外养成习惯request.getParameter返回的String转int之前先判空一段代码里做三步——取参、判空、转换能让这类500错误几乎绝迹。6. 把Seetable用活你的项目演示比别人的多一个亮点Seetable这个类在文件清单里存在但在很多同学手里会被忽略——因为Controller和DaoImpl已经能跑通功能了工具类看起来就是个不起眼的辅助角色。但我在拆这个项目时发现Seetable恰恰是能在答辩演示和功能扩展时打出差异化的位置。这个类从命名推测大概率是把ResultSet封装成表格数据的工具可能是按页查询也可能是把查询结果统一转成ListMapString, Object方便JSP页面用c:forEach渲染。不管具体实现是哪一种你在理解源码后可以做一件事给它增加一个动态查询能力让Seetable接收表名、条件字段、排序字段三个参数自动拼接SQL并返回结果集。// 给Seatable扩展一个通用分页查询方法改造思路示例 public ListMapString, Object queryByPage(String tableName, int pageNum, int pageSize) { int offset (pageNum - 1) * pageSize; String sql SELECT * FROM tableName LIMIT ? OFFSET ?; ListMapString, Object result new ArrayList(); try (Connection conn getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, pageSize); ps.setInt(2, offset); ResultSet rs ps.executeQuery(); ResultSetMetaData meta rs.getMetaData(); while (rs.next()) { MapString, Object row new LinkedHashMap(); for (int i 1; i meta.getColumnCount(); i) { row.put(meta.getColumnLabel(i), rs.getObject(i)); } result.add(row); } } catch (SQLException e) { e.printStackTrace(); } return result; }用了LIMIT ? OFFSET ?做分页是MySQL里最高效的翻页方式通过ResultSetMetaData动态获取列名就不需要为每张表单独写结果集映射了。这段代码你自己实现一遍后能明显感受到工具类的价值——药品列表加个分页、客户列表加个分页只需要一行调用搞定不用在每个DaoImpl里复制粘贴不同的分页SQL。答辩演示时我的习惯是先把系统完整跑一遍业务流程然后当场展示这个通工具类支持的表名列表演示给不同报表切换的灵活性。这个技巧不算难但很多同学不会往这个方向想属于成本低、收益高的加分项。答辩那天的血泪经验告诉我能快速演示新表接入要比反复演示增删改查更能让评委记住你的项目。从那以后我每次拿到一个新项目源码都会先找工具类文件改造一遍再谈其它——把公共能力抽出来、把演示效果做足这既是对原作者代码的理解也是项目能力最好的证明。希望帮到你祝你期末顺利拿下高分。本文还有配套的精品资源点击获取
返回列表