使用DAO模式改造学生信息管理系统

学生信息管理系统的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模式通过“分层设计+接口抽象”,让学生信息管理系统的核心价值落地:

  1. 功能易用性:业务层提供统一交互入口(如addStudent/searchStudentByName),上层无需关心数据存在哪里、如何存储;
  2. 扩展灵活性:新增“MySQL存储”仅需写DBStudentDAO实现接口,现有添加、查询逻辑完全复用;
  3. 维护低成本:修改Excel格式(如新增“性别”列),仅需改ExcelStudentDAO的解析逻辑,业务规则和其他存储不受影响。

这种设计特别适合学生系统这类“存储需求可能变化、功能需持续扩展”的场景,能有效降低后期迭代的复杂度。

posted @ 2025-10-19 17:42  Jbuckle  阅读(4)  评论(0)    收藏  举报