恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
数据库课程设计实战:通讯录管理系统从E-R图到Java联调全链路拆解
首页
资讯中心
/
数据库课程设计实战:通讯录管理系统从E-R图到Java联调全链路拆解
数据库课程设计实战:通讯录管理系统从E-R图到Java联调全链路拆解
发布时间:2026/10/12 1:18:44
简介这份《通讯录管理系统数据库课程设计报告》面向高校数据库原理与应用课程的选课学生帮助完成从需求分析到运行维护的完整课程设计任务。资源包内含1个docx文档压缩包约840KB以课程设计报告为主体涵盖摘要、绪论、需求分析、概念结构设计、逻辑设计、数据库实施、运行维护与总结等章节并附任务分配表与参考文献。报告以SQL Server为存储、JAVA构建界面完整呈现用户登录、联系人增删改查、分组管理等功能的设计思路包含数据流图、数据字典、E-R图、关系模式优化及建库建表、视图、存储过程等SQL代码还配有登录、联系人、分组与查询等界面设计说明。已有74人学习适合需要参考数据库课程设计框架、撰写规范报告或梳理设计流程的读者可据此理解数据库设计各阶段的方法与文档组织方式。1. 从一份课设报告说起通讯录管理系统到底能跑通什么很多人看到「通讯录管理系统数据库课程设计报告.docx」这个文件名第一反应是「又是一份交完就删的作业」。但我实际拆过这份报告之后发现它完整走了一遍数据库应用开发的标准链路需求分析、数据流图、数据字典、概念结构设计、E-R 图、逻辑设计、范式优化、建库建表、视图与存储过程、再到 Java 界面联调。这套流程放到今天任何一个 CRUD 类业务系统上骨架都是一样的。这份资源的核心价值不在于通讯录本身有多复杂而在于它把「数据库原理与应用」这门课里那些抽象概念——函数依赖、第三范式、外键约束、视图、存储过程——全部落到了一个能编译、能建库、能点按钮的具体系统上。适合两类人一是正在做数据库课程设计、需要一份可参照的完整实现思路的同学二是工作后要补数据库设计基本功、想找一个体量适中的练手项目的开发者。它用的是 SQL Server Java 这套经典组合技术栈不新但胜在链路完整、坑点真实。2. 需求分析与概念设计把通讯录拆成三张表和一组 E-R 图2.1 功能边界怎么划六个核心操作与三类分组这份报告在需求分析阶段把系统功能收敛得很干净一共六个操作用户登录、添加联系人、修改联系人、删除联系人、查询联系人、浏览全部联系人。分组维度上它把联系人分成朋友、同学、同事三类每类有独立的查询界面。这个划分看起来简单但它决定了后面表结构的设计方向。我一般会建议在动手画 E-R 图之前先把「谁操作什么数据」列清楚。这份报告的数据流图里顶层是「用户登录系统」0 层展开后分成联系人信息存储和分组信息存储两条线。这个拆法是对的因为登录态和业务数据是两套生命周期——用户表管认证联系人表管业务分组表管分类。三者通过外键关联而不是揉在一张宽表里。数据字典部分给出了关键字段的类型和长度约定我整理成表格方便对照数据项类型长度约束说明用户编号Char10主键非空登录唯一标识密码Char10非空登录凭证联系人编号Char50主键非空联系人唯一标识姓名Char10非空联系人姓名性别Char10可空联系人性别电话号码Char50可空联系方式出生日期Char50可空日期字段E-mailChar10可空电子邮箱分组类别Char10可空朋友/同学/同事这里有个细节值得注意出生日期用的是 Char 类型而不是 Date。这在课设里很常见好处是省去了日期格式转换的麻烦坏处是无法做日期范围查询和排序。如果你要在这个基础上做扩展比如「查询本月过生日的联系人」就得先把字段改成 Date 或者 Datetime否则字符串比较会出玄学问题。2.2 E-R 图到关系模式三个实体、两组联系概念结构设计部分定义了三个实体用户登录信息、联系人详细信息、分组信息。实体间的联系有两组用户与联系人是多对多一个用户可以有多个联系人一个联系人理论上可以被多个用户引用联系人与分组是一对多一个联系人属于一个分组一个分组包含多个联系人。E-R 图转关系模式的时候报告给出的结果是联系人信息编号姓名性别出生日期电话号码E-mail地址分组类别主键为编号朋友分组信息朋友编号姓名外键为朋友编号同学分组信息同学编号姓名外键为朋友编号这里有个明显的设计问题朋友分组表和同学分组表的结构完全一样都是「编号 姓名」完全可以用一张分组表加一个「分组类型」字段来替代。报告里把它们拆成两张独立表好处是查询时不用过滤类型字段坏处是每加一种分组就要新建一张表扩展性很差。如果你要复现这个项目我建议合并成一张ContactGroup表字段为group_id、group_name、group_type用group_type区分朋友、同学、同事。范式优化部分报告论证了三个关系模式都满足第三范式所有属性都是简单属性第一范式不存在部分函数依赖第二范式不存在传递依赖第三范式。这个论证过程是标准的课设写法逻辑上没问题。但实际开发中有时候为了查询性能会故意反范式比如在联系人表里冗余一个「分组名称」字段避免每次查询都 join 分组表。课设追求范式工程追求平衡这个边界要清楚。3. 建库建表与存储过程SQL Server 脚本逐段拆解3.1 数据库创建文件组、初始大小与增长策略报告第五章给出了完整的建库脚本我把它整理成可直接执行的版本并补上注释-- 创建通讯录管理系统数据库 CREATE DATABASE 通讯录管理系统 ON PRIMARY ( NAME 通讯录管理系统, FILENAME d:\数据库\通讯管理系统.mdf, SIZE 10MB, MAXSIZE 100MB, FILEGROWTH 20% ) LOG ON ( NAME 通讯录管理系统_log, FILENAME d:\数据库\通讯录管理系统.ldf, SIZE 20MB, MAXSIZE 100MB, FILEGROWTH 20% );这段脚本的关键参数有三个。SIZE 10MB是数据文件初始大小课设数据量小10MB 足够MAXSIZE 100MB是上限防止文件无限增长占满磁盘FILEGROWTH 20%是每次自动增长的比例按百分比增长比按固定 MB 增长更适应数据量变化。日志文件初始给 20MB比数据文件大因为插入和修改操作会先写日志。有个坑要注意FILENAME里的路径d:\数据库\必须提前手动创建SQL Server 不会自动建目录。如果路径不存在建库语句会直接报错「操作系统错误 3系统找不到指定的路径」。另外如果 SQL Server 服务账户没有 D 盘的写权限也会失败。我一般会先把路径改成 SQL Server 默认的数据目录或者提前用mkdir建好并确认权限。3.2 三张核心表的建表语句与约束设计报告里给出了用户表、联系人表、分组表的建表代码。我按它的字段设计整理成可执行版本-- 用户表存储登录凭证 CREATE TABLE 用户表 ( 用户编号 CHAR(10) NOT NULL PRIMARY KEY, 密码 CHAR(10) NOT NULL ); -- 联系人表存储联系人详细信息 CREATE TABLE 联系人表 ( 联系人编号 CHAR(50) NOT NULL PRIMARY KEY, 姓名 CHAR(10) NOT NULL, 性别 CHAR(10) NULL, 电话号码 CHAR(50) NULL, 出生日期 CHAR(50) NULL, Email CHAR(50) NULL, 地址 CHAR(50) NULL, 分组类别 CHAR(10) NULL ); -- 分组表存储分组信息 CREATE TABLE 分组表 ( 分组编号 CHAR(10) NOT NULL PRIMARY KEY, 分组名称 CHAR(20) NOT NULL, 分组类型 CHAR(10) NOT NULL );建表时有几个参数选择需要说明。CHAR是定长字符串存不足长度会用空格补齐查询时可能遇到「明明值一样但比较不相等」的情况。比如CHAR(10)存「张三」会变成「张三 」如果和VARCHAR的「张三」比较就会出问题。课设里统一用CHAR问题不大但生产环境建议姓名、地址这类变长字段用VARCHAR。主键选择上联系人编号用CHAR(50)偏长如果编号是自增数字用INT IDENTITY(1,1)更省空间且插入性能更好。报告里用CHAR(50)可能是为了兼容学号或手机号作为编号这个要看具体业务。外键约束在报告里没有显式写出但分组表和联系人表之间应该加一个ALTER TABLE 联系人表 ADD CONSTRAINT FK_联系人_分组 FOREIGN KEY (分组类别) REFERENCES 分组表(分组类型);加上外键之后删除分组时会检查是否有联系人引用该分组避免出现「孤儿联系人」。这是数据完整性的一道保险课设里经常被忽略但实际项目里必须加。3.3 视图与存储过程把查询逻辑封装进数据库报告提到了视图和存储过程的创建但正文里没有给出完整代码。按这个系统的功能我补全两个最常用的-- 视图按分组查询联系人摘要 CREATE VIEW v_联系人分组摘要 AS SELECT c.分组类别, c.联系人编号, c.姓名, c.电话号码 FROM 联系人表 c WHERE c.分组类别 IS NOT NULL; -- 存储过程按姓名模糊查询联系人 CREATE PROCEDURE sp_查询联系人 关键字 NVARCHAR(20) AS BEGIN SELECT 联系人编号, 姓名, 性别, 电话号码, Email, 分组类别 FROM 联系人表 WHERE 姓名 LIKE % 关键字 %; END;视图的作用是把常用的查询逻辑固化下来Java 端直接SELECT * FROM v_联系人分组摘要就行不用每次写 join。存储过程sp_查询联系人接收一个关键字参数用LIKE做模糊匹配。调用方式是EXEC sp_查询联系人 关键字 张。参数关键字用NVARCHAR而不是VARCHAR是为了支持中文。如果用VARCHAR中文可能变成乱码。LIKE % 关键字 %的写法要注意 SQL 注入风险不过在存储过程里参数是强类型的风险比拼接字符串低很多。如果要做分页查询可以再加OFFSET和FETCH NEXT但 SQL Server 2012 才支持这个语法2008 需要用ROW_NUMBER()绕。4. Java 界面与数据库联调登录、增删改查的落地细节4.1 JDBC 连接配置驱动、URL 与登录验证Java 端连接 SQL Server 需要 JDBC 驱动。报告里没有写具体版本常见做法是用sqljdbc4.jar或mssql-jdbc。连接代码如下// 数据库连接工具类 public class DBUtil { private static final String DRIVER com.microsoft.sqlserver.jdbc.SQLServerDriver; private static final String URL jdbc:sqlserver://localhost:1433;databaseName通讯录管理系统; private static final String USER sa; private static final String PASSWORD 你的密码; public static Connection getConnection() throws Exception { Class.forName(DRIVER); return DriverManager.getConnection(URL, USER, PASSWORD); } }URL里的databaseName必须和建库时的数据库名完全一致包括中文。localhost:1433是默认端口如果 SQL Server 配置里改了端口这里要同步改。sa是超级管理员账户课设里常用但生产环境应该用独立的应用账户并限制权限。登录验证的 SQL 是String sql SELECT * FROM 用户表 WHERE 用户编号 ? AND 密码 ?; PreparedStatement ps conn.prepareStatement(sql); ps.setString(1, userId); ps.setString(2, password); ResultSet rs ps.executeQuery(); if (rs.next()) { // 登录成功跳转主界面 }用PreparedStatement而不是Statement拼接字符串是为了防 SQL 注入。?是占位符setString按顺序填充。如果用户输入 OR 11拼接写法会直接绕过验证而PreparedStatement会把它当成普通字符串处理。4.2 增删改查的 SQL 实现与界面绑定添加联系人的核心 SQLString sql INSERT INTO 联系人表 (联系人编号, 姓名, 性别, 电话号码, 出生日期, Email, 地址, 分组类别) VALUES (?, ?, ?, ?, ?, ?, ?, ?); PreparedStatement ps conn.prepareStatement(sql); ps.setString(1, contactId); ps.setString(2, name); ps.setString(3, gender); ps.setString(4, phone); ps.setString(5, birthday); ps.setString(6, email); ps.setString(7, address); ps.setString(8, groupType); ps.executeUpdate();修改和删除类似把INSERT换成UPDATE或DELETEWHERE条件用联系人编号。查询全部联系人的 SQL 是SELECT * FROM 联系人表按分组查询是SELECT * FROM 联系人表 WHERE 分组类别 ?。界面绑定上报告里分了登录模块、联系人界面、朋友分组界面、同学分组界面、同事分组界面、查询界面、增加界面、修改界面、删除界面。这个拆分粒度偏细实际开发中可以用一个主界面加 Tab 页或者下拉筛选来替代多个独立窗口。但课设要求展示完整功能拆细一点反而方便演示每个操作。有个容易翻车的地方Java 的JTextField获取输入后如果用户没填某个可选字段getText()返回空字符串直接传给setString会插入空串而不是NULL。如果数据库字段允许NULL且业务上要区分「未填」和「填了空」需要在 Java 端做判断空串时用ps.setNull(index, Types.CHAR)。5. 避坑与排查课设里最容易翻车的五个点5.1 中文乱码数据库排序规则与 JDBC 编码不一致现象插入中文联系人姓名后查询出来显示成???或者乱码。原因SQL Server 数据库的排序规则Collation默认可能是SQL_Latin1_General_CP1_CI_AS不支持中文存储。或者 JDBC URL 没有指定编码Java 字符串传到数据库时编码转换出错。解决建库时指定排序规则COLLATE Chinese_PRC_CI_AS或者在连接 URL 后加;sendStringParametersAsUnicodetrue。字段类型用NVARCHAR而不是VARCHARJava 端用setNString代替setString。5.2 端口不通SQL Server 配置管理器里 TCP/IP 没启用现象Java 程序报Connection refused或The TCP/IP connection to the host localhost, port 1433 has failed。原因SQL Server 默认安装后TCP/IP 协议可能是禁用状态只启用了共享内存或命名管道。Java JDBC 走的是 TCP/IP。解决打开 SQL Server 配置管理器展开「SQL Server 网络配置」→「实例的协议」把 TCP/IP 状态改为「已启用」然后重启 SQL Server 服务。如果端口不是 1433在 TCP/IP 属性里查看 IP 地址页签的 TCP 端口。5.3 主键冲突联系人编号重复导致插入失败现象添加联系人时抛出Violation of PRIMARY KEY constraint。原因联系人编号是主键必须唯一。如果用户手动输入编号很容易重复。或者程序里用时间戳生成编号高并发下可能撞车。解决课设场景下可以在插入前先SELECT COUNT(*) FROM 联系人表 WHERE 联系人编号 ?检查是否存在。更稳妥的做法是用IDENTITY自增列作为主键编号由数据库生成Java 端插入时不传该字段。5.4 外键约束删除分组时被联系人表引用现象删除分组时报The DELETE statement conflicted with the REFERENCE constraint。原因联系人表的分组类别字段引用了分组表直接删除分组会导致联系人失去分类。解决先删除或更新引用该分组的所有联系人再删除分组。或者在建外键时加ON DELETE SET NULL删除分组时自动把联系人的分组类别置空。但SET NULL要求外键字段允许为空。5.5 日期格式Char 类型存储导致的查询异常现象按出生日期范围查询时结果不符合预期比如1990-01-01到1999-12-31查不到1995-6-15。原因出生日期用CHAR(50)存储格式不统一。1995-6-15和1995-06-15字符串比较时6大于0排序和范围判断都会出错。解决把出生日期字段改成DATE或DATETIME类型Java 端用setDate传入java.sql.Date。如果必须用字符串强制统一格式为yyyy-MM-dd并在插入前用SimpleDateFormat格式化。6. 从课设到可复用把通讯录系统改造成通用 CRUD 模板这份课设最大的价值不是通讯录本身而是它验证了一套「数据库设计 → SQL 实现 → Java 联调」的完整流程。如果你把这套流程抽象出来换掉实体和字段就能快速套到其他管理系统上比如图书管理、学生成绩管理、库存管理。我一般会做三个改造。第一把三张表的分组设计合并成一张Group表加一个type字段这样加新分组不用改表结构。第二把增删改查的 SQL 封装成一个泛型 DAO 基类用反射获取实体字段自动生成INSERT、UPDATE、DELETE语句子类只需要定义表名和字段映射。第三把登录验证从明文密码改成 MD5 或 BCrypt 哈希数据库里存哈希值而不是明文。验证改造是否成功的方法很简单新建一个「图书管理」实体继承 DAO 基类看能不能在不写新 SQL 的情况下完成增删改查。如果能跑通说明抽象层是有效的。如果报错检查字段名和表名的映射关系以及主键生成策略是否兼容。// 泛型 DAO 基类示意 public abstract class BaseDaoT { protected abstract String getTableName(); protected abstract MapString, Object toMap(T entity); public void insert(T entity) throws Exception { MapString, Object map toMap(entity); String columns String.join(,, map.keySet()); String placeholders map.keySet().stream().map(k - ?).collect(Collectors.joining(,)); String sql INSERT INTO getTableName() ( columns ) VALUES ( placeholders ); // 执行插入... } }这个基类的关键是toMap方法子类负责把实体字段转成「列名 → 值」的映射。getTableName返回表名。插入时动态拼 SQL占位符数量由字段数量决定。这样加新实体只需要写一个子类不用重复写 JDBC 模板代码。从那以后我每次做课设或者小工具都会先把数据库设计跑一遍范式检查再写一个最小可用的 DAO 基类最后才做界面。这样即使界面翻车底层数据操作也是稳的。希望帮到你。本文还有配套的精品资源点击获取