学生信息管理系统的DAO模式改造分析与实现
目录
一、DAO模式核心要点
| 核心要素 |
说明 |
| 数据访问接口(DAO Interface) |
定义增删改查标准方法,统一数据访问入口,约束所有实现类行为。 |
| 数据访问实现(DAO Impl) |
针对不同存储(List/文本/Excel)的具体实现,封装数据操作细节。 |
| 模型(Model) |
Student类封装学生数据(学号、姓名、专业、绩点),作为数据传递载体。 |
| 业务逻辑层(Service) |
调用DAO层接口,隔离数据访问与上层应用,提供统一业务入口。 |
| 依赖注入 |
通过构造/set方法注入DAO实现,支持动态切换存储方式。 |
二、DAO模式相比原代码的优势
| 对比维度 |
原代码实现 |
DAO模式实现 |
| 存储扩展性 |
仅支持List存储,新增需修改核心业务代码 |
新增DAO实现类即可扩展(如Excel/数据库),无改动业务层 |
| 代码耦合度 |
数据访问(如list.add(student))与业务逻辑混写 |
业务层仅调用DAO接口,数据细节完全隔离 |
| 维护性 |
修改存储方式需重构所有关联代码 |
仅修改对应DAO实现类,影响范围最小 |
| 功能切换 |
无法动态切换,需重新编译部署 |
运行时通过setStudentDAO()实时切换存储方式 |
三、DAO模式的具体分析与实现
1. 架构设计
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ 表现层 │ │ 业务层 │ │ 数据访问层 │
│ (Main类) │────▶│(StudentService)│────▶│ (DAO接口) │
└───────────────┘ └───────────────┘ └───────────────┘
▲
│
┌───────────────┬───────────────┴───────────────┐
│ │ │
┌───────▼───────┐ ┌─────▼───────┐ ┌───────▼───────┐
│ ListDAO实现 │ │TextFileDAO │ │ ExcelDAO实现 │
└───────────────┘ └───────────────┘ └───────────────┘
2. 核心组件职责
- Model层:
Student类封装数据,提供Getter/Setter,支持数据格式转换。
- DAO接口层:
StudentDAO定义标准方法,约束所有存储实现的行为。
- DAO实现层:各存储方式的具体逻辑,如Excel通过Apache POI操作文件。
- Service层:
StudentService封装业务规则(如参数校验),转发数据操作请求。
四、核心代码实现(精简版+功能展示)
1. 模型层:Student.java
public class Student {
private String id; // 学号(唯一标识)
private String name; // 姓名
private String major; // 专业
private double gpa; // 绩点
// 全参构造
public Student(String id, String name, String major, double gpa) {
this.id = id;
this.name = name;
this.major = major;
this.gpa = gpa;
}
// 重写toString,用于控制台展示学生信息
@Override
public String toString() {
return "学号:" + id + " | 姓名:" + name + " | 专业:" + major + " | 绩点:" + gpa;
}
// Getter(省略Setter,仅展示核心)
public String getId() { return id; }
public String getName() { return name; }
public String getMajor() { return major; }
public double getGpa() { return gpa; }
}
2. DAO接口:StudentDAO.java
import java.util.List;
public interface StudentDAO {
void addStudent(Student student); // 添加学生
boolean removeStudent(String id); // 删除学生(返回是否成功)
List<Student> searchByName(String name); // 按姓名模糊查询
List<Student> searchByMajor(String major); // 按专业精确查询
Student getStudentById(String id); // 按学号查询
}
3. DAO实现:ExcelStudentDAO.java(核心片段)
import org.apache.poi.ss.usermodel.*;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.List;
public class ExcelStudentDAO implements StudentDAO {
private final String filePath; // Excel文件路径(如"students.xlsx")
public ExcelStudentDAO(String filePath) {
this.filePath = filePath;
initExcel(); // 初始化Excel(创建文件和表头)
}
// 核心功能:添加学生到Excel
@Override
public void addStudent(Student student) {
try (Workbook workbook = new XSSFWorkbook(new FileInputStream(filePath))) {
Sheet sheet = workbook.getSheet("学生信息表");
// 在最后一行后新增数据行
Row row = sheet.createRow(sheet.getLastRowNum() + 1);
// 填充学生数据
row.createCell(0).setCellValue(student.getId());
row.createCell(1).setCellValue(student.getName());
row.createCell(2).setCellValue(student.getMajor());
row.createCell(3).setCellValue(student.getGpa());
// 保存到文件
workbook.write(new FileOutputStream(filePath));
} catch (Exception e) {
throw new RuntimeException("Excel添加学生失败:" + e.getMessage());
}
}
// 核心功能:按姓名查询学生
@Override
public List<Student> searchByName(String name) {
List<Student> allStudents = getAllStudents();
List<Student> result = new ArrayList<>();
// 模糊匹配(忽略大小写)
for (Student s : allStudents) {
if (s.getName().toLowerCase().contains(name.toLowerCase())) {
result.add(s);
}
}
return result;
}
// 其他方法(removeStudent/searchByMajor)核心逻辑类似,此处省略
@Override
public boolean removeStudent(String id) { /* 读取Excel→过滤→重新写入 */ return false; }
@Override
public List<Student> searchByMajor(String major) { /* 按专业筛选 */ return null; }
@Override
public Student getStudentById(String id) { /* 匹配学号查询 */ return null; }
// 工具方法:读取Excel中所有学生
private List<Student> getAllStudents() {
List<Student> students = new ArrayList<>();
try (Workbook workbook = new XSSFWorkbook(new FileInputStream(filePath))) {
Sheet sheet = workbook.getSheet("学生信息表");
// 跳过表头(从第1行开始,行号0为表头)
for (int i = 1; i <= sheet.getLastRowNum(); i++) {
Row row = sheet.getRow(i);
// 解析单元格数据为Student对象
String id = row.getCell(0).getStringCellValue();
String name = row.getCell(1).getStringCellValue();
String major = row.getCell(2).getStringCellValue();
double gpa = row.getCell(3).getNumericCellValue();
students.add(new Student(id, name, major, gpa));
}
} catch (Exception e) {
throw new RuntimeException("读取Excel失败:" + e.getMessage());
}
return students;
}
// 初始化Excel:创建表头
private void initExcel() { /* 若文件不存在,创建并添加“学号、姓名、专业、绩点”表头 */ }
}
4. Service层:StudentService.java
import java.util.List;
public class StudentService {
private StudentDAO studentDAO;
// 构造注入DAO实现
public StudentService(StudentDAO studentDAO) {
this.studentDAO = studentDAO;
}
// 动态切换DAO(核心:支持运行时换存储)
public void setStudentDAO(StudentDAO studentDAO) {
this.studentDAO = studentDAO;
System.out.println("存储方式已切换为:" + studentDAO.getClass().getSimpleName());
}
// 业务功能1:添加学生(含参数校验)
public void addStudent(Student student) {
// 业务规则校验
if (student.getId().trim().isEmpty()) {
throw new RuntimeException("错误:学号不能为空!");
}
if (student.getGpa() < 0 || student.getGpa() > 4.0) {
throw new RuntimeException("错误:绩点需在0-4.0之间!");
}
if (studentDAO.getStudentById(student.getId()) != null) {
throw new RuntimeException("错误:学号" + student.getId() + "已存在!");
}
// 调用DAO执行添加
studentDAO.addStudent(student);
System.out.println("添加成功!学生信息:" + student);
}
// 业务功能2:按姓名查询学生
public void searchStudentByName(String name) {
List<Student> result = studentDAO.searchByName(name);
if (result.isEmpty()) {
System.out.println("未找到姓名包含【" + name + "】的学生");
return;
}
System.out.println("找到" + result.size() + "名匹配学生:");
for (Student s : result) {
System.out.println("- " + s);
}
}
// 其他业务功能(删除、按专业查询)类似,此处省略
public void removeStudent(String id) { /* 调用DAO删除并返回结果 */ }
public void searchStudentByMajor(String major) { /* 调用DAO按专业查询 */ }
}
五、原代码与改进后代码的差异点
| 代码模块 |
原代码实现(无DAO模式) |
改进后代码(DAO模式) |
差异说明 |
| 数据操作逻辑 |
业务方法中直接写存储逻辑,如: |
数据逻辑封装在DAO,业务层仅调用接口,如: |
原代码混写,改进后分层清晰,职责单一 |
|
```java |
```java |
|
|
// 添加学生(直接操作List) |
// Service层添加学生(仅管业务规则) |
|
|
public void addStudent(Student s) { |
public void addStudent(Student s) { |
|
|
List list = new ArrayList<>(); |
if (s.getId().isEmpty()) |
|
|
// 无参数校验,直接添加 |
studentDAO.addStudent(s); // 调用DAO |
|
|
list.add(s); |
System.out.println("添加成功"); |
|
|
System.out.println("添加成功"); |
} |
|
| 存储切换方式 |
需修改业务代码中的存储对象,如: |
调用set方法即可切换,无需改业务代码,如: |
原代码需重新编译,改进后支持运行时动态切换 |
|
```java |
```java |
|
|
// 切换为文本存储需改此处代码 |
// 初始化时用Excel存储 |
|
|
List list = new ArrayList<>(); |
StudentService service = new StudentService( |
|
|
// 改为:BufferedWriter writer = new ... |
new ExcelStudentDAO("students.xlsx") |
|
|
``` |
); |
|
|
|
// 运行时切换为文本存储 |
|
|
|
service.setStudentDAO(new TextFileStudentDAO("students.txt")); |
|
| 功能复用性 |
不同存储的查询逻辑需重写,如: |
所有存储共用同一查询入口,如: |
原代码重复代码多,改进后接口统一,复用性高 |
|
```java |
```java |
|
|
// List查询姓名 |
// 查姓名:不管是Excel/List/文本,调用方式一致 |
|
|
public List searchName(String name) { |
service.searchStudentByName("张"); |
|
|
// List遍历逻辑 |
// 查专业:仅改参数,无需改逻辑 |
|
|
} |
service.searchStudentByMajor("计算机"); |
|
|
// Excel查询姓名需重写POI遍历逻辑 |
``` |
|
六、改进后核心功能运行示例
通过控制台交互展示改进后系统的核心功能,直观体现DAO模式的优势。
1. 示例1:初始化存储+添加学生
// 1. 初始化Service(默认用Excel存储)
StudentService service = new StudentService(new ExcelStudentDAO("students.xlsx"));
// 2. 添加学生(触发业务校验和DAO存储)
service.addStudent(new Student("2024001", "张三", "计算机科学", 3.8));
service.addStudent(new Student("2024002", "李四", "软件工程", 3.2));
// 控制台输出:
存储方式已切换为:ExcelStudentDAO
添加成功!学生信息:学号:2024001 | 姓名:张三 | 专业:计算机科学 | 绩点:3.8
添加成功!学生信息:学号:2024002 | 姓名:李四 | 专业:软件工程 | 绩点:3.2
2. 示例2:按姓名查询学生
// 调用查询功能(模糊匹配“张”)
service.searchStudentByName("张");
// 控制台输出:
找到1名匹配学生:
- 学号:2024001 | 姓名:张三 | 专业:计算机科学 | 绩点:3.8
// 调用查询功能(无匹配结果)
service.searchStudentByName("王");
// 控制台输出:
未找到姓名包含【王】的学生
3. 示例3:动态切换存储方式
// 从Excel切换为文本存储
service.setStudentDAO(new TextFileStudentDAO("students.txt"));
// 切换后添加学生(数据存入文本文件)
service.addStudent(new Student("2024003", "王五", "数据科学", 3.5));
// 控制台输出:
存储方式已切换为:TextFileStudentDAO
添加成功!学生信息:学号:2024003 | 姓名:王五 | 专业:数据科学 | 绩点:3.5
// 切换后查询(从文本文件读取数据)
service.searchStudentByName("王");
// 控制台输出:
找到1名匹配学生:
- 学号:2024003 | 姓名:王五 | 专业:数据科学 | 绩点:3.5
4. 示例4:业务校验拦截错误数据
// 尝试添加学号为空的学生(触发业务校验)
service.addStudent(new Student("", "赵六", "人工智能", 3.6));
// 控制台输出:
Exception in thread "main" java.lang.RuntimeException: 错误:学号不能为空!
// 尝试添加绩点超出范围的学生
service.addStudent(new Student("2024004", "赵六", "人工智能", 4.5));
// 控制台输出:
Exception in thread "main" java.lang.RuntimeException: 错误:绩点需在0-4.0之间!
七、DAO模式实现的重点与难点
| 类别 |
要点 |
解决方案 |
| 重点 |
接口设计完整性 |
确保DAO接口覆盖所有必要操作(如批量查询、单条删除),避免后续频繁修改接口。 |
|
业务与数据层严格分离 |
Service层不出现任何数据访问相关类(如POI的Workbook、FileInputStream),仅调用DAO接口。 |
| 难点 |
Excel操作复杂性 |
使用Apache POI库简化读写,封装getAllStudents()等工具方法,统一数据解析逻辑。 |
|
各DAO实现行为一致性 |
所有DAO实现类遵循同一规则,如“删除不存在学号返回false”“查询空结果返回空列表”,避免业务层处理差异。 |
八、总结
DAO模式通过“分层设计+接口抽象”,让学生信息管理系统的核心价值落地:
- 功能易用性:业务层提供统一交互入口(如
addStudent/searchStudentByName),上层无需关心数据存在哪里、如何存储;
- 扩展灵活性:新增“MySQL存储”仅需写
DBStudentDAO实现接口,现有添加、查询逻辑完全复用;
- 维护低成本:修改Excel格式(如新增“性别”列),仅需改
ExcelStudentDAO的解析逻辑,业务规则和其他存储不受影响。
这种设计特别适合学生系统这类“存储需求可能变化、功能需持续扩展”的场景,能有效降低后期迭代的复杂度。