
简介这份《通讯录管理系统数据库课程设计报告》面向高校数据库原理与应用课程的学习者尤其适合正在完成课程设计或需要参考完整设计流程的学生。报告以SQL Server为存储、JAVA构建界面完整覆盖需求分析、概念结构设计、逻辑设计、数据库实施及运行维护等环节并给出用户表、联系人表、分组表及视图、存储过程的SQL代码配合登录、联系人、分组、查询等界面设计形成一套可落地的课程设计范例。资源包共1个docx文件约840KB内容为完整的课程设计报告文档含摘要、目录、各章节正文与参考文献结构清晰便于按模块查阅。目前已有74人学习下载读者可借此掌握E-R图绘制、关系模式优化、函数依赖分析及数据库编程等关键技能并理解如何将理论应用于实际系统开发为后续数据库相关项目打下基础。1. 通讯录管理系统数据库课程设计从建表到答辩一份能跑通的完整路径很多人做数据库课程设计选题第一反应就是通讯录管理系统。原因很实在需求直观、表结构不复杂、增删改查都能覆盖答辩时老师也容易看懂。但真正动手才发现坑不在功能本身而在数据库设计合不合理、Java 代码和 SQL Server 怎么接、报告里那些关系模式和数据字典怎么写才像样。这篇内容就是围绕「通讯录管理系统数据库课程设计报告」这个标题把从需求分析、E-R 图、建表、写 Java 程序到最终报告成文的完整路径拆开讲。适合正在做数据库课程设计的本科生也适合想拿一个完整案例练手 SQL Server 和 Java 的初学者。读完你能拿到一套可复现的方案而不是一份只能看的文档。2. 需求与表结构通讯录管理系统到底要建几张表2.1 先想清楚联系人、分组、电话三者关系通讯录管理系统听起来简单但如果你只建一张联系人表把所有信息塞进去后面查询和扩展就会很难受。常见做法是拆成三张核心表联系人表、分组表、联系方式表。联系人表存姓名、邮箱、备注这些一对一的属性分组表存分组名称和描述联系方式表存电话号码因为一个人可能有手机、座机、工作电话多个号码这是一对多关系。这样拆的好处是符合第三范式避免冗余。比如你如果把电话号码直接放在联系人表里一个人有两个号码就得存两行姓名和邮箱重复更新时容易不一致。拆表之后联系人表和联系方式表通过联系人 ID 关联分组表和联系人表通过分组 ID 关联。还有一张容易被忽略的表是用户表。如果系统需要登录就得有用户表存账号密码。课程设计里如果老师要求有登录功能这张表不能少。用户表和联系人表可以关联表示每个用户管理自己的通讯录也可以不关联做成单用户系统。我一般建议加上用户表因为答辩时老师常问「多用户怎么处理」有这张表你就能答上来。2.2 用 SQL Server 建库建表的完整脚本下面这套脚本可以直接在 SQL Server Management Studio 里执行。先建数据库再建四张表最后插入测试数据。-- 创建数据库 CREATE DATABASE ContactDB; GO USE ContactDB; GO -- 用户表 CREATE TABLE Users ( UserID INT IDENTITY(1,1) PRIMARY KEY, Username NVARCHAR(50) NOT NULL UNIQUE, Password NVARCHAR(100) NOT NULL, CreateTime DATETIME DEFAULT GETDATE() ); -- 分组表 CREATE TABLE Groups ( GroupID INT IDENTITY(1,1) PRIMARY KEY, GroupName NVARCHAR(50) NOT NULL, Description NVARCHAR(200), UserID INT NOT NULL, FOREIGN KEY (UserID) REFERENCES Users(UserID) ); -- 联系人表 CREATE TABLE Contacts ( ContactID INT IDENTITY(1,1) PRIMARY KEY, Name NVARCHAR(50) NOT NULL, Email NVARCHAR(100), Remark NVARCHAR(500), GroupID INT, UserID INT NOT NULL, CreateTime DATETIME DEFAULT GETDATE(), FOREIGN KEY (GroupID) REFERENCES Groups(GroupID), FOREIGN KEY (UserID) REFERENCES Users(UserID) ); -- 联系方式表 CREATE TABLE PhoneNumbers ( PhoneID INT IDENTITY(1,1) PRIMARY KEY, ContactID INT NOT NULL, PhoneNumber NVARCHAR(20) NOT NULL, PhoneType NVARCHAR(20) DEFAULT 手机, FOREIGN KEY (ContactID) REFERENCES Contacts(ContactID) );这段脚本里几个关键点值得说明。IDENTITY(1,1)表示自增主键从 1 开始每次加 1这是 SQL Server 里最常用的主键生成方式。NVARCHAR而不是VARCHAR因为姓名和备注可能包含中文NVARCHAR用 Unicode 存储不会乱码。外键约束保证了数据一致性比如你不能往 Contacts 表里插一个不存在的 GroupID。参数方面NVARCHAR(50)里的 50 是字符数不是字节数中文和英文都算一个字符。DATETIME DEFAULT GETDATE()让创建时间自动填充插入数据时不用手动写。如果你用的是 SQL Server 2012 或更早版本这些语法都支持不用担心兼容性。2.3 插入测试数据与验证关联查询建完表先插几条数据验证外键和查询是否正常。-- 插入用户 INSERT INTO Users (Username, Password) VALUES (admin, 123456); -- 插入分组 INSERT INTO Groups (GroupName, Description, UserID) VALUES (家人, 亲属联系方式, 1), (同事, 工作相关联系人, 1); -- 插入联系人 INSERT INTO Contacts (Name, Email, Remark, GroupID, UserID) VALUES (张三, zhangsantest.com, 大学同学, 1, 1), (李四, lisitest.com, 项目搭档, 2, 1); -- 插入电话号码 INSERT INTO PhoneNumbers (ContactID, PhoneNumber, PhoneType) VALUES (1, 13800001111, 手机), (1, 010-12345678, 座机), (2, 13900002222, 手机); -- 关联查询查每个联系人及其所有电话 SELECT c.Name, c.Email, g.GroupName, p.PhoneNumber, p.PhoneType FROM Contacts c LEFT JOIN Groups g ON c.GroupID g.GroupID LEFT JOIN PhoneNumbers p ON c.ContactID p.ContactID WHERE c.UserID 1;这里用LEFT JOIN而不是INNER JOIN因为如果某个联系人没有分组或者没有电话号码用INNER JOIN会直接丢掉这条记录。课程设计报告里常要求写「查询所有联系人信息」用左连接更稳妥。执行完这条查询张三会出现两行因为有两个电话号码这是正常的一对多展开。提示如果你的 SQL Server 安装后提示密码过期用 Windows 身份验证登录后在安全性里把登录模式的密码策略取消勾选即可这是课程设计里最常见的环境问题。3. Java 连接 SQL ServerJDBC 增删改查的完整实现3.1 JDBC 驱动配置与连接字符串写法Java 连 SQL Server 需要微软提供的 JDBC 驱动。常见做法是下载mssql-jdbc的 jar 包导入到项目里。如果你用 Maven在pom.xml里加依赖更方便。注意驱动版本要和你的 SQL Server 版本匹配SQL Server 2012 用较新的驱动也能连但如果你用的是 SQL Server 2008 R2驱动版本别选太新的否则可能报 TLS 协议错误。连接字符串的写法有讲究// 连接字符串模板 String url jdbc:sqlserver://localhost:1433; databaseNameContactDB; encryptfalse; trustServerCertificatetrue;; String user sa; String password 你的密码;encryptfalse和trustServerCertificatetrue这两个参数在本地开发时很关键。新版本驱动默认开启加密但本地 SQL Server 往往没有配置证书不加这两个参数会报「无法建立到服务器的连接」或者 SSL 相关错误。这是血泪经验很多人卡在这里一下午。3.2 联系人新增与查询的 Java 代码下面是一个完整的工具类封装了获取连接和关闭资源的方法然后是一个新增联系人和查询联系人的示例。import java.sql.*; public class ContactDao { private static final String URL jdbc:sqlserver://localhost:1433;databaseNameContactDB; encryptfalse;trustServerCertificatetrue;; private static final String USER sa; private static final String PASSWORD 你的密码; // 获取连接 public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } // 新增联系人 public void addContact(String name, String email, int groupId) { String sql INSERT INTO Contacts (Name, Email, GroupID, UserID) VALUES (?, ?, ?, ?); try (Connection conn getConnection(); PreparedStatement pstmt conn.prepareStatement(sql)) { pstmt.setString(1, name); pstmt.setString(2, email); pstmt.setInt(3, groupId); pstmt.setInt(4, 1); int rows pstmt.executeUpdate(); System.out.println(影响行数 rows); } catch (SQLException e) { e.printStackTrace(); } } // 查询所有联系人 public void listContacts() { String sql SELECT c.ContactID, c.Name, c.Email, g.GroupName FROM Contacts c LEFT JOIN Groups g ON c.GroupID g.GroupID WHERE c.UserID ?; try (Connection conn getConnection(); PreparedStatement pstmt conn.prepareStatement(sql)) { pstmt.setInt(1, 1); ResultSet rs pstmt.executeQuery(); while (rs.next()) { System.out.println( rs.getInt(ContactID) | rs.getString(Name) | rs.getString(Email) | rs.getString(GroupName) ); } } catch (SQLException e) { e.printStackTrace(); } } }这段代码里用PreparedStatement而不是Statement原因是防止 SQL 注入而且参数设置更清晰。try-with-resources语法会自动关闭连接和语句对象不用手动写finally块。查询时用rs.getString(GroupName)按列名取值比按索引取值可读性更好。参数说明pstmt.setString(1, name)里的 1 对应 SQL 里第一个问号索引从 1 开始不是从 0 开始这是 JDBC 的规矩。executeUpdate()用于增删改返回影响行数executeQuery()用于查询返回ResultSet。3.3 修改和删除联系人的实现与事务处理修改和删除的逻辑类似但删除联系人时要注意外键约束。如果你直接删 Contacts 表里的记录而 PhoneNumbers 表里还有关联的电话号码数据库会报外键冲突。常见做法是先删子表的电话号码再删主表的联系人或者在建表时设置级联删除。// 修改联系人邮箱 public void updateEmail(int contactId, String newEmail) { String sql UPDATE Contacts SET Email ? WHERE ContactID ?; try (Connection conn getConnection(); PreparedStatement pstmt conn.prepareStatement(sql)) { pstmt.setString(1, newEmail); pstmt.setInt(2, contactId); pstmt.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); } } // 删除联系人及其电话号码事务 public void deleteContact(int contactId) { String deletePhones DELETE FROM PhoneNumbers WHERE ContactID ?; String deleteContact DELETE FROM Contacts WHERE ContactID ?; Connection conn null; try { conn getConnection(); conn.setAutoCommit(false); // 开启事务 try (PreparedStatement pstmt1 conn.prepareStatement(deletePhones)) { pstmt1.setInt(1, contactId); pstmt1.executeUpdate(); } try (PreparedStatement pstmt2 conn.prepareStatement(deleteContact)) { pstmt2.setInt(1, contactId); pstmt2.executeUpdate(); } conn.commit(); // 提交事务 } catch (SQLException e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } e.printStackTrace(); } finally { if (conn ! null) { try { conn.setAutoCommit(true); conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }事务处理是课程设计的加分项。setAutoCommit(false)之后两条删除语句要么都成功要么都回滚不会出现电话号码删了但联系人还在的脏数据。答辩时老师问「怎么保证数据一致性」这就是答案。注意如果你在 SQL Server 里建表时加了ON DELETE CASCADE那删除联系人时数据库会自动删电话号码Java 代码里就不用写两条删除语句。但课程设计报告里建议显式写出来体现你对事务的理解。4. 课程设计报告怎么写从 E-R 图到数据字典的成文套路4.1 需求分析和概念结构设计的写法报告的第一部分通常是需求分析。不要写空话直接列功能点联系人管理、分组管理、电话号码管理、用户登录。每个功能点写清楚输入什么、输出什么、涉及哪些表。比如「联系人管理」的输入是姓名、邮箱、分组输出是联系人列表涉及 Contacts 表和 Groups 表。概念结构设计就是画 E-R 图。实体有用户、分组、联系人、电话号码。用户和分组是一对多分组和联系人是一对多联系人和电话号码是一对多。画图工具用 Visio 或者在线画图工具都行关键是实体、属性、联系要标清楚。主键用下划线标出外键用虚线连到对应实体。4.2 逻辑结构设计与数据字典表格逻辑结构设计就是把 E-R 图转成关系模式。写法是联系人联系人 ID姓名邮箱备注分组 ID用户 ID。括号里主键加下划线外键标注引用哪个表。数据字典用表格呈现最清晰。下面是一个示例字段名数据类型长度约束说明ContactIDINT-主键自增联系人编号NameNVARCHAR50非空联系人姓名EmailNVARCHAR100可空电子邮箱GroupIDINT-外键所属分组UserIDINT-外键非空所属用户这张表放在报告里老师一眼就能看出你的字段设计是否合理。注意NVARCHAR的长度写字符数不要写字节数。约束列把主键、外键、非空、默认值都标出来。4.3 物理设计与测试截图的呈现技巧物理设计部分写你用的数据库版本、存储引擎、索引设计。SQL Server 默认用聚集索引在主键上你可以额外在Name字段上建非聚集索引加快按姓名查询的速度。报告里写一句「在 Contacts 表的 Name 字段上创建非聚集索引提升模糊查询效率」就够了。测试部分要放截图。截图包括建表成功的截图、插入数据后的查询结果截图、Java 程序运行的控制台输出截图。截图不要糊用 Windows 的Win Shift S截取清晰区域。每张截图下面写一句说明比如「图 5-1 联系人列表查询结果显示张三和李四的信息」。提示报告里的 SQL 脚本和 Java 代码不要直接贴大段挑核心的贴。老师看的是你的设计思路不是代码量。把建表脚本和关键查询语句贴出来即可Java 代码贴 Dao 类的核心方法。5. 避坑与排查通讯录课程设计里最容易翻车的 5 个点5.1 中文乱码数据库、JDBC、控制台三处都要查现象插入中文姓名后查询出来是问号或者乱码。原因通常有三个数据库排序规则不是中文的、JDBC 连接字符串没指定字符集、控制台编码不对。解决方法是建库时用Chinese_PRC_CI_AS排序规则JDBC 连接字符串加characterEncodingUTF-8Java 编译时加-encoding UTF-8。三处都对了才不会乱码。5.2 外键冲突删除联系人时报错现象执行删除联系人的 SQL 时数据库报「DELETE 语句与 REFERENCE 约束冲突」。原因是 PhoneNumbers 表里还有该联系人的电话号码记录。解决方法是先删子表记录再删主表记录或者建表时加ON DELETE CASCADE。如果你在 Java 里用事务处理记得两条删除语句放在同一个事务里。5.3 驱动报错SQL Server 版本与 JDBC 驱动不匹配现象Java 程序启动时报「The driver could not establish a secure connection to SQL Server」。原因是新版 JDBC 驱动默认要求加密连接而本地 SQL Server 没配证书。解决方法是在连接字符串里加encryptfalse;trustServerCertificatetrue;。如果还不行换一个旧版本的驱动试试。5.4 自增主键回填插入后拿不到新生成的 ID现象插入联系人后想立刻拿到自增的 ContactID但executeUpdate()只返回影响行数。解决方法是在prepareStatement时加Statement.RETURN_GENERATED_KEYS然后通过getGeneratedKeys()获取。代码示例PreparedStatement pstmt conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); pstmt.executeUpdate(); ResultSet rs pstmt.getGeneratedKeys(); if (rs.next()) { int newId rs.getInt(1); }5.5 报告查重E-R 图和表结构别照抄网上模板现象报告提交后查重率过高。原因是 E-R 图和表结构直接用了网上的模板字段名和关系一模一样。解决方法是根据自己的需求调整字段比如加一个「备注」字段或者「创建时间」字段E-R 图的布局也自己重新画。数据字典的说明列用自己的话写不要复制粘贴。6. 进阶技巧用存储过程和视图把课程设计做出差异化6.1 用视图简化多表关联查询如果你在 Java 里频繁写三表关联查询可以建一个视图把关联逻辑封装起来。视图不存数据但能让 SQL 更简洁。CREATE VIEW v_ContactDetail AS SELECT c.ContactID, c.Name, c.Email, c.Remark, g.GroupName, p.PhoneNumber, p.PhoneType FROM Contacts c LEFT JOIN Groups g ON c.GroupID g.GroupID LEFT JOIN PhoneNumbers p ON c.ContactID p.ContactID;建完视图后Java 里直接SELECT * FROM v_ContactDetail WHERE ContactID ?就行不用每次写三表连接。答辩时老师问「视图和表的区别」你可以答视图是虚拟表不占存储空间简化查询但更新受限。6.2 用存储过程封装新增联系人的逻辑存储过程把 SQL 逻辑放在数据库端Java 只负责调用。这样做的好处是减少网络传输而且逻辑集中管理。CREATE PROCEDURE sp_AddContact Name NVARCHAR(50), Email NVARCHAR(100), GroupID INT, UserID INT AS BEGIN INSERT INTO Contacts (Name, Email, GroupID, UserID) VALUES (Name, Email, GroupID, UserID); SELECT SCOPE_IDENTITY() AS NewContactID; END;Java 调用存储过程CallableStatement cstmt conn.prepareCall({call sp_AddContact(?, ?, ?, ?)}); cstmt.setString(1, 王五); cstmt.setString(2, wangwutest.com); cstmt.setInt(3, 1); cstmt.setInt(4, 1); ResultSet rs cstmt.executeQuery(); if (rs.next()) { System.out.println(新联系人ID rs.getInt(NewContactID)); }SCOPE_IDENTITY()返回当前作用域内最后插入的自增 ID比IDENTITY更安全因为不会受触发器影响。6.3 验证方法用 SQL Server Profiler 看实际执行的 SQL如果你不确定 Java 程序到底发了什么 SQL 给数据库打开 SQL Server Profiler新建跟踪勾选SQL:BatchCompleted和RPC:Completed事件然后运行 Java 程序。Profiler 会显示实际执行的 SQL 语句和参数。这个方法在排查「为什么查询结果不对」时特别有用你能看到预编译语句展开后的真实 SQL。我一般会在答辩前用 Profiler 跑一遍所有功能确认没有多余的查询或者错误的连接。这个习惯帮我省了很多后悔药因为有些问题在代码里看不出来只有数据库端才能看到真相。希望帮到你。本文还有配套的精品资源点击获取