简介:这套数据库课程设计仓库管理系统文档,面向本科阶段计算机相关专业学生,用于完成课程设计任务并掌握数据库与Java结合的开发流程。系统以小型仓库为场景,围绕基础信息管理、出入库管理、查询统计和系统管理四大模块展开,使用Java与SQL Server 2005实现,包括供应商、商品、客户等数据表设计,以及程序结构图和核心代码示例,覆盖从需求分析到编码实现的完整过程。报告中还给出了Dao.java连接数据库、JXCFrame.java主窗口、Login.java登录验证等关键类说明,并附有进度安排与调试修改记录,便于理解分层实现与权限控制思路。包内仅包含1个docx格式的课程设计报告,文件大小1.44MB,内容紧凑且结构完整,适合快速参考。目前已有3609人学习下载,适合需要完成相近课题或参考数据库课程设计规范写法的同学。
1. 先看这份数据库课程设计:仓库管理系统到底值不值得照着做一遍
如果你正在为数据库课程设计选题发愁,或者已经选了仓库管理系统但不知道代码怎么组织,这份基于 JAVA + SQL Server 2005 的“小型仓库管理系统”是一个相当典型的样本。它覆盖了基础信息管理、出入库管理、查询统计、系统管理四个模块,能把商品、客户、供应商、库存盘点、操作员权限串成一套完整的业务闭环。整套系统的代码量不大,但该有的数据库表、界面跳转、登录验证都有,很适合用来理解 Swing 界面层和数据库操作层是怎么分工的。文章会从系统骨架、数据库连接、登录权限这些角度把它拆开,最后把运行中容易翻车的几个坑挑出来说清楚。搞懂这套东西,你自己的课程设计要改要扩,都能从里面找到下手点。
2. 系统骨架拆解:JXCFrame 主窗体和四个业务模块是怎么组织起来的
2.1 从主入口看整体架构
这套系统的主入口不在主窗体,而在Login.java。main方法里先用SwingUtilities.invokeLater把登录界面丢到事件分线程里,只有登录验证通过之后,才new JXCFrame()拉起主窗体。这个顺序很重要:数据库连接其实在Dao类的静态块里就已经初始化了,登录只是校验操作员身份,真正干活的界面是登录之后才创建的。
主窗体JXCFrame用的是JFrame + JDesktopPane + JInternalFrame的组合,这是典型的 MDI(多文档界面)结构。外层一个主窗体,里面的客户管理、商品管理、入库单、出库单、库存盘点全部以JInternalFrame的形式嵌在桌面上。这样做的好处是切换功能时不用销毁重建窗体,代码结构上也更好维护。
createNavigationPanel()负责生成顶部导航,四个页签对应四个模块。注意这里每个页签下面挂的是一排JButton,而不是菜单;按钮通过createFrameButton(fName, cname)创建,点击后触发openFrameAction,再反射生成具体的内部窗体。
2.2 导航面板的创建逻辑:页签、按钮和内部窗体的关系
看一下导航面板的核心代码:
private JTabbedPane createNavigationPanel() { JTabbedPane tabbedPane = new JTabbedPane(); tabbedPane.setFocusable(false); tabbedPane.setBackground(new Color(211, 230, 192)); tabbedPane.setBorder(new BevelBorder(BevelBorder.RAISED)); JPanel baseManagePanel = new JPanel(); // 基础信息管理页签 baseManagePanel.setBackground(new Color(215, 223, 194)); baseManagePanel.setLayout(new BoxLayout(baseManagePanel, BoxLayout.X_AXIS)); baseManagePanel.add(createFrameButton("客户信息管理", "KeHuGuanLi")); baseManagePanel.add(createFrameButton("商品信息管理", "ShangPinGuanLi")); baseManagePanel.add(createFrameButton("供应商信息管理", "GysGuanLi")); JPanel searchStatisticPanel = new JPanel(); // 查询统计页签 searchStatisticPanel.setLayout(new BoxLayout(searchStatisticPanel, BoxLayout.X_AXIS)); searchStatisticPanel.add(createFrameButton("客户信息查询", "KeHuChaXun")); searchStatisticPanel.add(createFrameButton("商品信息查询", "ShangPinChaXun")); searchStatisticPanel.add(createFrameButton("供应商信息查询", "GongYingShangChaXun")); searchStatisticPanel.add(createFrameButton("出库查询", "XiaoShouChaXun")); searchStatisticPanel.add(createFrameButton("入库查询", "RuKuChaXun")); searchStatisticPanel.add(createFrameButton("库存盘点", "KuCunPanDian")); // 出入库管理和系统管理页签代码省略,结构一致 tabbedPane.addTab("基础信息管理", null, baseManagePanel, "基础信息管理"); tabbedPane.addTab("查询统计", null, searchStatisticPanel, "查询统计"); return tabbedPane; }这里每个按钮的第二个参数是字符串cname,它对应的是内部窗体的类名,比如KeHuGuanLi对应internalFrame.KeHuGuanLi这个类。按钮点击后通过openFrameAction里的反射把类实例化,塞进JDesktopPane。我一般会把页签面板做成一个独立方法而不是堆在构造函数里,这样后期要加模块,只需要在对应面板上多add一个按钮,再写一个新的JInternalFrame子类就行。
2.3 反射创建内部窗体:getIFrame 的缓存机制
JXCFrame里有一个HashMap<String, JInternalFrame>用来缓存已经打开的内部窗体,避免同一个功能窗体被重复实例化:
private JInternalFrame getIFrame(String frameName) { JInternalFrame jf = null; if (!ifs.containsKey(frameName)) { try { Class fClass = Class.forName("internalFrame." + frameName); Constructor constructor = fClass.getConstructor(null); jf = (JInternalFrame) constructor.newInstance(null); ifs.put(frameName, jf); } catch (Exception e) { e.printStackTrace(); } } else { jf = ifs.get(frameName); } return jf; }用反射的好处是新增窗体不需要改getIFrame这个方法,只要类名和按钮传入的字符串对得上就行。代价是如果类不存在或者构造函数不是无参的,运行期才报错,排错成本比直接new高。实际改这个系统时,我一般会把frameName打出来核对一遍拼写,再检查对应内部窗体类是不是public且有无参构造,这两处是最容易翻车的地方。
openFrameAction里还绑了一个InternalFrameAdapter的internalFrameClosed事件,窗体关闭时从ifs移除。这个设计很关键:界面关闭后缓存不清理的话,第二次打开会拿到一个已 disposed 的旧窗体,直接白屏。课程设计答辩时如果演示到第二次打开某个功能窗体崩了,多半就是这个缓存没清干净或清得太早。
2.4 四个模块的职责边界与数据库表的对应关系
整个系统的业务逻辑其实只用了非常少的表,但每张表都对应一个模块,这个映射关系理清楚,写出来的代码不会乱。
| 模块 | 负责的功能 | 对应数据表 |
|---|---|---|
| 基础信息管理 | 商品、客户、供应商信息的增删改查 | 商品信息表、客户信息表、供应商信息表 |
| 出入库管理 | 入库单、出库单的登记与来往记录 | 库存信息表、出入库流水表 |
| 查询统计 | 按商品、时间、类型查询出入库和库存情况 | 商品信息表、库存信息表 |
| 系统管理 | 操作员增删、密码修改、权限分配 | 操作员信息表、权限配置信息 |
数据库设计上,表结构满足 BCNF,冗余控制在非常低的水位。这其实是课程设计报告的加分点,评审老师问到“为什么表这么设计”时,答“满足 BCNF、不存在传递依赖”比答“我照着老师 PPT 抄的”要有说服力得多。这套系统把表和业务模块的对应关系做得很直观,照着画 E-R 图也方便。
3. 数据库连接与 Dao 层:SQL Server 2005 和 Java 怎么打通
3.1 静态块加载驱动并建立连接
Dao.java是整套系统的数据访问层,所有模块的增删改查都通过它来执行。连接数据库的部分写在静态块里,类一加载就初始化连接,不用每次操作都重新建立,这在课程设计级别的小系统里是够用的:
public class Dao { static String dbClassName = "com.microsoft.jdbc.sqlserver.SQLServerDriver"; static String dbUrl = "jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=db_JXC;SelectMethod=Cursor"; protected static String dbUser = "sa"; protected static String dbPwd = ""; public static Connection conn = null; static { try { if (conn == null) { Class.forName(dbClassName).newInstance(); conn = DriverManager.getConnection(dbUrl, dbUser, dbPwd); } } catch (Exception e) { e.printStackTrace(); } } }注意这里的驱动和 URL 写法是 2005 时代的老写法,com.microsoft.jdbc.sqlserver.SQLServerDriver对应的是微软旧版 JDBC 驱动。如果你用的是新版驱动mssql-jdbc,URL 应该写成jdbc:sqlserver://localhost:1433;DatabaseName=db_JXC,驱动类名也变成com.microsoft.sqlserver.jdbc.SQLServerDriver。这个差异是复现时最常踩的坑,后面避坑章里详细说。
3.2 封装查询方法:把每张表的数据拉成可用的 List
Dao里提供了一组类似getKhInfos()的方法,每个方法对应一张表的简单查询。以获取客户信息为例:
public static List getKhInfos() { List list = new ArrayList(); try { Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("select id, khname from tb_khinfo"); while (rs.next()) { Map map = new HashMap(); map.put("id", rs.getString("id")); map.put("khname", rs.getString("khname")); list.add(map); } rs.close(); stmt.close(); } catch (SQLException e) { e.printStackTrace(); } return list; }这种写法本质上是在 DAO 里拼接 SQL 字符串、返回List<Map>,外层界面拿到结果后手动取值填充表格。优点是简单直接,课程设计的代码量控制在合理范围;缺点是没有事务管理和 PreparedStatement,参数拼接容易出 SQL 注入隐患。我一般会建议复现者至少把静态语句换成参数化的PreparedStatement,改动量不大,但报告里能多写一条“考虑了安全性”。
3.3 权限控制与密码登录的落地方式
Login.java 里登录按钮的事件核心是Dao.getUser(userName.getText(), userPassword.getText()),内部执行的是按用户名和密码同时匹配的查询。密码字段用的是JPasswordField,界面上不回显明文。回车登录的小细节也值得抄:userPassword.addKeyListener里判断e.getKeyCode() == 10触发login.doClick(),这意味着用户输完密码直接按回车就能登录,体验上比非要鼠标点按钮强不少。
操作员信息表除了存用户名和密码,还承担了权限分级的职责。一般操作员只有查询、插入、更新、删除业务数据的权限,管理员额外拥有添加删除操作员、重置密码、权限分配的权限。课程设计的系统管理模块里“操作员管理”“更改密码”“权限管理”三个入口就是干这个事的。做报告时建议把权限表单独拆出来,写明角色与权限的对应关系,这部分很容易被老师追问。
4. 登录、内部窗体与按钮定制:Swing 交互层的复用套路
4.1 登录界面的绝对布局与常用控件用法
Login 类里设置面板为绝对布局panel.setLayout(null),控件的坐标、宽高全部手写。这种写法在课程设计里非常普遍,虽然缩放时界面会乱,但胜在直观、不需要学布局管理器。关键控件用法值得记录:
final JTextField userName = new JTextField(); userName.setBounds(150, 115, 200, 18); // x, y, 宽度, 高度 panel.add(userName); final JPasswordField userPassword = new JPasswordField(); userPassword.addKeyListener(new KeyAdapter() { public void keyPressed(final KeyEvent e) { if (e.getKeyCode() == 10) { login.doClick(); } } }); userPassword.setBounds(150, 150, 200, 18); panel.add(userPassword);JPasswordField和JTextField的最大差异是前者自带掩码显示。注意这里获取输入值时不能用getText(),应该用getPassword()拿到char[]再转字符串,这是安全习惯问题,不少课程设计里直接用getText()也能跑,但答辩老师问到的话答不上来会扣印象分。
4.2 createFrameButton 背后的图片按钮定制逻辑
主窗体导航里的按钮不走默认的JButton样式,而是通过createFrameButton贴了图标,同时处理了普通态、悬停态、按下态三种图片:
private JButton createFrameButton(String fName, String cname) { String imgUrl = "res/ActionIcon/" + fName + ".png"; // 普通态 String imgUrl_roll = "res/ActionIcon/" + fName + "_roll.png"; // 悬停态 String imgUrl_down = "res/ActionIcon/" + fName + "_down.png"; // 按下态 Icon icon = new ImageIcon(imgUrl); Icon icon_roll = (imgUrl_roll != null) ? new ImageIcon(imgUrl_roll) : null; Icon icon_down = (imgUrl_down != null) ? new ImageIcon(imgUrl_down) : null; Action action = new openFrameAction(fName, cname, icon); JButton button = new JButton(action); button.setMargin(new Insets(0, 0, 0, 0)); button.setHideActionText(true); button.setFocusPainted(false); button.setBorderPainted(false); button.setContentAreaFilled(false); if (icon_roll != null) button.setRolloverIcon(icon_roll); if (icon_down != null) button.setPressedIcon(icon_down); return button; }几个set方法做的事情分别是:setContentAreaFilled(false)去掉按钮的默认背景填充,setBorderPainted(false)去掉边框,setFocusPainted(false)去掉点击后的焦点框。三者配合起来按钮就是纯图片效果,鼠标移上去和按下去时切换对应的图标。这套代码可以直接抄到自己的项目里,只要图片资源路径对得上就行。资源缺失时ImageIcon不会抛异常,只会显示空白,这是排查时要先想到的。
4.3 openFrameAction:事件对象里藏了按钮到窗体的映射
openFrameAction继承AbstractAction,把按钮文案、提示文本、图标和要打开的内部窗体类名绑在一起。动作事件触发时调用getIFrame(frameName)拿到内部窗体,加入桌面面板并设为选中:
public void actionPerformed(final ActionEvent e) { JInternalFrame jf = getIFrame(frameName); jf.addInternalFrameListener(new InternalFrameAdapter() { public void internalFrameClosed(InternalFrameEvent e) { ifs.remove(frameName); } }); if (jf.getDesktopPane() == null) { desktopPane.add(jf); jf.setVisible(true); } try { jf.setSelected(true); } catch (PropertyVetoException e1) { e1.printStackTrace(); } }PropertyVetoException是JInternalFrame特有的受检异常,代表这个内部窗体不允许被选中(比如最小化状态被外部 veto 掉)。课程设计的代码里直接printStackTrace不处理问题不大,但如果你想写得严谨,可以判断一下jf.isClosed()和jf.isIcon()再决定要不要setSelected。这个细节写到实验报告里,很能体现你对 Swing 内部窗体机制的理解。
4.4 内部窗体的实际形态与主窗体的信息获取
在主窗体代码里还有两个值得记录的细节:一个是updateBackImage(),它根据窗体当前尺寸动态生成一段 HTML,设置背景欢迎图的宽高;另一个是主窗体监听ComponentAdapter的componentResized事件,窗体大小一变就重新生成背景图,这样不管怎么拉伸都不会露白边。这种“窗体自适应背景”的写法在很多管理系统里都适用,拆出来可以单独复用。
注意JXCFrame构造函数里大量代码都写在createNavigationPanel之外,包括设定窗口位置setBounds(100, 100, 753, 600)、默认关闭方式setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE),这些属于基础配置。主窗体的导航数据获取,实际靠的还是 Dao 层返回的List<Map>,在KuCunPanDian、RuKuChaXun这些内部窗体里用JTable填数据展示。整个交互链路是:窗体按钮 → Action → 反射生成内部窗体 → 内部窗体构造函数里调用 Dao 方法取数 → 填充表格或表单。
5. 避坑排查:运行这套课程设计时最容易翻车的六个地方
5.1 SQL Server 2005 连接失败:驱动类名与 URL 对不上
现象:Dao静态块执行时抛ClassNotFoundException,或者DriverManager.getConnection报连接超时。 原因:这套课程设计用的是老版驱动com.microsoft.jdbc.sqlserver.SQLServerDriver和jdbc:microsoft:sqlserver://前缀,新版 SQL Server 或新版 JDBC 驱动并不认这套写法。 解决:换成官方新驱动。Maven 依赖改成com.microsoft.sqlserver:mssql-jdbc,驱动类用com.microsoft.sqlserver.jdbc.SQLServerDriver,URL 用jdbc:sqlserver://localhost:1433;DatabaseName=db_JXC。如果 SQL Server 2005 启用了命名实例,URL 里要加上instanceName参数。我一般会用 SQL Server Management Studio 先确认端口和实例名,再写 JDBC 串,省得在这上面耗一下午。
5.2 sa 密码为空导致登录被拒
现象:代码里dbPwd = "",但数据库实例配置了强密码策略,连接报“用户 sa 登录失败”。 原因:2005 默认允许空密码,但很多机器装完后被安全策略加固过,sa 要么被禁用,要么密码非空。 解决:提前在 SSMS 里用 Windows 认证登录,给 sa 单独设置一个密码并启用它,然后把dbPwd改成对应值。我一般不会用 sa 连课程设计库,而是新建一个db_jxc_user账号只授予db_JXC库的读写权限,这样报告里还能写一句“最小权限原则”。
5.3 表名或库名不一致导致操作无数据
现象:系统能启动、能登录,但所有查询页面表格都是空的。 原因:建库脚本和程序里的库名db_JXC、表名tb_khinfo对不上;或者建表时用了带前缀的名称,比如dbo.tb_khinfo和tb_khinfo在代码里都能查到,但某些客户端工具里大小写敏感。 解决:先在 SSMS 里跑一句select * from tb_khinfo确认表和字段真实存在,再排查 Dao 方法里的列名是否和表结构一致。课程设计里最容易出问题的就是手写 SQL 的列名少了某个下划线,这种错误编译器不报错,只能靠人眼查。我一般会把 Dao 里所有select语句捞出来,逐一在 SSMS 里执行一遍再放回代码。
5.4 图片按钮不显示或样式错乱
现象:导航页签的文字能看到,图标部分是空白;或者按钮显示成默认灰色方块。 原因:res/ActionIcon/目录下没有对应的png文件,或者ImageIcon加载用的是相对路径,从命令行启动时当前工作目录不对导致找不到资源。 解决:确认图片文件名、大小写和代码里imgUrl完全一致;启动时把工作目录切到项目根目录,或者在代码里用getClass().getResource()定位资源而不是写相对路径。我一般会写一个小方法检查图标是否加载成功,ImageIcon.getImageLoadStatus()不为 8 就打印一条警告,这样资源问题一眼就能看到。
5.5 内部窗体二次打开报错或白屏
现象:第一次打开某个管理窗体正常,关掉后再点同一个入口,要么报 NullPointerException,要么窗口空白。 原因:ifs缓存里存了已经关闭的JInternalFrame,而内部窗体构造函数里依赖的某些资源(比如JTable的数据模型)没有重建。 解决:检查openFrameAction里的internalFrameClosed是否正确移除缓存。更稳妥的做法是在每次显示前判断jf.isClosed(),如果是已关闭状态就从缓存里删掉重新实例化。代码可以这样改:
if (ifs.containsKey(frameName)) { JInternalFrame old = ifs.get(frameName); if (old.isClosed()) { ifs.remove(frameName); } }5.6 中文乱码:控制台和界面显示不一致
现象:SSMS 里数据正常,Java 界面里中文显示成问号或乱码。 原因:SQL Server 数据库排序规则和 Java 项目编码不一致,多数出在数据库排序规则用了非中文默认值,或者项目文件本身是 GBK、运行环境是 UTF-8。 解决:建库时把排序规则设为Chinese_PRC_CI_AS;IDE 里把项目源码编码统一成 UTF-8;数据库连接的 URL 后面追加?characterEncoding=UTF-8(新驱动)或;charset=UTF-8(老驱动)。乱码问题如果是读取已经入库的数据,重点查排序规则;如果是界面新输入的数据入库后乱,重点查 JDBC 驱动的字符集参数。课程设计里这里最容易栽跟头,我一般会先跑一条select * from tb_khinfo where khname like N'中文'来定位问题在数据库侧还是连接侧。
6. 给课程设计加分:把库存盘点改成报表式输出的小技巧
如果只是按原有代码把系统跑起来交差,答辩时老师很容易问几句就冷场。我建议从“库存盘点”这个功能入手,做成带统计信息的报表页面,既能体现你对业务的理解,又不需要大改架构。具体思路是:保留KuCunPanDian内部窗体的表格展示,同时把盘点结果按“商品类别”做汇总,输出总件数和总金额。这样老师看到的不只是增删改查页面,还有一个分析视角的亮点。
实现上不需要额外建表。写一个方法按类别分组统计库存,SQL 类似:
SELECT spfl AS 类别, COUNT(*) AS 商品数, SUM(kcsl) AS 库存总量 FROM tb_spinfo GROUP BY spfl然后在盘点窗体里加一个JTabbedPane,一个页签放原始明细表,另一个页签放分组汇总表。汇总表的数据查询走 Dao 层新增方法,不要在界面代码里直接拼 SQL。这样做的好处有两个:一是界面不堆在一起,操作层和数据层仍然是分开的;二是新增的查询方法还可以复用到“出库查询”和“入库查询”的统计逻辑里,一套代码多处受益。
再进一步,如果想把盘点结果打印出来,可以用JTable的Printable接口直接打印:
table.print(JTable.PrintMode.FIT_WIDTH, new MessageFormat("库存盘点报表"), new MessageFormat("第 {0} 页 / 共 {1} 页"));这段代码不需要引入第三方依赖,Swing 自带。打印输出时如果出现中文乱码,检查打印服务字体设置,多数情况是走系统默认字体导致的,调成宋体或雅黑就能解决。这个打印能力放在答辩演示里很容易出效果,也说明你不只是把数据查到表格里,而是考虑到了实际业务场景的输出需求。
最后说一个我从这套系统里学到的教训。2014 年的代码到今天跑起来,环境变量、驱动版本、数据库实例全都变了,我第一次复现时卡在 sa 密码上整整一个晚上。从那以后,我每次启动这类老课程设计项目,都强制自己走一遍“先确认数据库能连 → 再确认表结构存在 → 最后登界面”的三步流程,不再上来就点运行。代码老不老不重要,排查顺序对了,翻车概率就能小很多。希望这些拆解和避坑记录帮到你,让你的课程设计少走点弯路。
本文还有配套的精品资源,点击获取