项目案例作业3:使用DAO模式改造学生信息管理系统
项目案例作业3:使用DAO模式改造学生信息管理系统
| 项目名称 | 内容 |
|---|---|
| 课程名称 | java |
| 班级 | 网安2413 |
| 学生姓名 | 王璐 |
| 学号 | 202421336068 |
一、项目修改简述
将原有的基于 List 存储的学生信息管理系统,通过 DAO(Data Access Object)模式进行重构,实现多种数据存取方式的灵活切换。
改造后系统支持两种数据存取模式:基于 List 的内存存储、和基于 Excel 文件的持久化存储。同时,通过合理的包结构设计,提升代码的可读性、可维护性和扩展性。
DAO 模式的核心思想是分离数据访问逻辑与业务逻辑,通过定义统一的数据访问接口,为不同的数据存储方式提供具体实现,从而使业务层无需关注数据的具体存储细节,只需通过接口操作数据。
二、包结构设计
设计分层包结构如下:
code/
├── model/ // 数据模型层:封装实体信息
│ └── Student.java // 学生实体类(存储学生姓名、年龄等核心属性)
├── dao/ // 数据访问层:定义接口与实现
│ ├── StudentDAO.java // 学生数据访问统一接口(规范核心操作)
│ └── impl/ // 具体存储实现类(接口的落地实现)
│ ├── ListStudentDAO.java // 基于List的内存存储实现
│ └── ExcelStudentDAO.java // 基于Excel的持久化存储实现
├── service/ // 业务逻辑层:处理业务规则
│ └── StudentManagementService.java // 学生管理服务(衔接DAO与用户交互)
├── util/ // 工具类层:提供通用辅助功能
│ └── ExcelUtil.java // Excel文件操作工具(封装POI库操作,简化DAO实现)
└── Main.java // 主程序入口:用户交互与模式切换
三、各模块详细实现
(一)model 层:实体类定义(Student.java)
package code.model;
public class Student {
private String name;
private int age;
private String gender;
private String id;
private String major;
private double gpa;
public Student(String name, int age, String gender, String id, String major, double gpa) {
this.name = name;
this.age = age;
this.gender = gender;
this.id = id;
this.major = major;
this.gpa = gpa;
}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public String getGender() { return gender; }
public void setGender(String gender) { this.gender = gender; }
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getMajor() { return major; }
public void setMajor(String major) { this.major = major; }
public double getGpa() { return gpa; }
public void setGpa(double gpa) { this.gpa = gpa; }
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", gender='" + gender + '\'' +
", id='" + id + '\'' +
", major='" + major + '\'' +
", gpa=" + gpa +
'}';
}
}
(二)DAO层:数据访问接口与实现
1. 统一接口:StudentDAO.java
定义学生数据操作的核心接口,所有存储方式(List/Excel)均需实现此接口,确保业务层操作的一致性,屏蔽不同存储的差异。
package code.dao;
import code.model.Student;
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);
List<Student> searchByGpa(double gpa);
List<Student> getAllStudents();
}
2. 基于 List 的实现:ListStudentDAO.java
使用 ArrayList 作为内存存储容器,所有操作均在内存中完成,适合临时数据管理(程序退出后数据丢失),操作效率高。
package code.dao.impl;
import code.dao.StudentDAO;
import code.model.Student;
import java.util.ArrayList;
import java.util.List;
public class ListStudentDAO implements StudentDAO {
// 内存存储容器:存储所有学生对象
private final List<Student> students;
// 构造方法:初始化内存容器
public ListStudentDAO() {
this.students = new ArrayList<>();
}
@Override
public void addStudent(Student student) {
students.add(student); // 直接添加到内存列表
}
@Override
public boolean removeStudent(String id) {
// 遍历列表,按学号匹配并删除
for (Student student : students) {
if (student.getId().equals(id)) {
students.remove(student);
return true; // 删除成功
}
}
return false; // 未找到匹配学生
}
@Override
public List<Student> searchByName(String name) {
List<Student> result = new ArrayList<>();
for (Student student : students) {
if (student.getName().equals(name)) {
result.add(student);
}
}
return result;
}
@Override
public List<Student> searchByMajor(String major) {
List<Student> result = new ArrayList<>();
for (Student student : students) {
if (student.getMajor().equals(major)) {
result.add(student);
}
}
return result;
}
@Override
public List<Student> searchByGpa(double gpa) {
List<Student> result = new ArrayList<>();
for (Student student : students) {
if (student.getGpa() == gpa) {
result.add(student);
}
}
return result;
}
@Override
public List<Student> getAllStudents() {
return new ArrayList<>(students); // 返回列表副本,避免外部修改内部数据
}
}
3. 基于 Excel 的实现:ExcelStudentDAO.java
使用 Excel 文件作为持久化存储介质,借助 Apache POI 工具库实现 Excel 的读写操作,数据在程序退出后仍可保留。约定 Excel 文件规则:
- 文件路径:
students.xlsx(项目根目录) - 工作表名:
学生信息 - 表头:第一行固定为 “姓名、年龄、性别、学号、专业、GPA”
- 数据行:从第二行开始存储学生信息
java
运行
package code.dao.impl;
import code.dao.StudentDAO;
import code.model.Student;
import code.util.ExcelUtil;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import java.util.ArrayList;
import java.util.List;
public class ExcelStudentDAO implements StudentDAO {
// Excel文件路径与工作表名(固定配置)
private static final String EXCEL_PATH = "students.xlsx";
private static final String SHEET_NAME = "学生信息";
@Override
public void addStudent(Student student) {
// 1. 获取Excel工作簿(不存在则自动创建)
Workbook workbook = ExcelUtil.getWorkbook(EXCEL_PATH);
// 2. 获取工作表(不存在则自动创建并添加表头)
Sheet sheet = ExcelUtil.getOrCreateSheet(workbook, SHEET_NAME);
// 3. 在最后一行插入新学生数据
int lastRowNum = sheet.getLastRowNum(); // 获取当前最后一行索引
Row newRow = sheet.createRow(lastRowNum + 1); // 创建新行
// 4. 填充学生数据到单元格
newRow.createCell(0).setCellValue(student.getName());
newRow.createCell(1).setCellValue(student.getAge());
newRow.createCell(2).setCellValue(student.getGender());
newRow.createCell(3).setCellValue(student.getId());
newRow.createCell(4).setCellValue(student.getMajor());
newRow.createCell(5).setCellValue(student.getGpa());
// 5. 写入数据到Excel文件并关闭资源
ExcelUtil.writeAndCloseWorkbook(workbook, EXCEL_PATH);
}
@Override
public boolean removeStudent(String id) {
Workbook workbook = ExcelUtil.getWorkbook(EXCEL_PATH);
Sheet sheet = ExcelUtil.getOrCreateSheet(workbook, SHEET_NAME);
int lastRowNum = sheet.getLastRowNum();
boolean isRemoved = false;
// 遍历数据行(从第二行开始,跳过表头)
for (int i = 1; i <= lastRowNum; i++) {
Row row = sheet.getRow(i);
if (row == null) continue; // 跳过空行
// 匹配学号(第三列:索引3)
String studentId = row.getCell(3).getStringCellValue();
if (studentId.equals(id)) {
// 删除行:将后续行上移一行,覆盖当前行
sheet.shiftRows(i + 1, lastRowNum, -1);
isRemoved = true;
break;
}
}
// 若删除成功,保存文件
if (isRemoved) {
ExcelUtil.writeAndCloseWorkbook(workbook, EXCEL_PATH);
} else {
ExcelUtil.closeWorkbook(workbook); // 未删除则直接关闭资源
}
return isRemoved;
}
@Override
public List<Student> searchByName(String name) {
List<Student> result = new ArrayList<>();
Workbook workbook = ExcelUtil.getWorkbook(EXCEL_PATH);
Sheet sheet = ExcelUtil.getOrCreateSheet(workbook, SHEET_NAME);
int lastRowNum = sheet.getLastRowNum();
// 遍历数据行,匹配姓名(第一列:索引0)
for (int i = 1; i <= lastRowNum; i++) {
Row row = sheet.getRow(i);
if (row == null) continue;
String studentName = row.getCell(0).getStringCellValue();
if (studentName.equals(name)) {
// 解析行数据为Student对象
Student student = parseRowToStudent(row);
result.add(student);
}
}
ExcelUtil.closeWorkbook(workbook);
return result;
}
@Override
public List<Student> searchByMajor(String major) {
List<Student> result = new ArrayList<>();
Workbook workbook = ExcelUtil.getWorkbook(EXCEL_PATH);
Sheet sheet = ExcelUtil.getOrCreateSheet(workbook, SHEET_NAME);
int lastRowNum = sheet.getLastRowNum();
// 遍历数据行,匹配专业(第四列:索引4)
for (int i = 1; i <= lastRowNum; i++) {
Row row = sheet.getRow(i);
if (row == null) continue;
String studentMajor = row.getCell(4).getStringCellValue();
if (studentMajor.equals(major)) {
Student student = parseRowToStudent(row);
result.add(student);
}
}
ExcelUtil.closeWorkbook(workbook);
return result;
}
@Override
public List<Student> searchByGpa(double gpa) {
List<Student> result = new ArrayList<>();
Workbook workbook = ExcelUtil.getWorkbook(EXCEL_PATH);
Sheet sheet = ExcelUtil.getOrCreateSheet(workbook, SHEET_NAME);
int lastRowNum = sheet.getLastRowNum();
// 遍历数据行,匹配GPA(第五列:索引5)
for (int i = 1; i <= lastRowNum; i++) {
Row row = sheet.getRow(i);
if (row == null) continue;
double studentGpa = row.getCell(5).getNumericCellValue();
if (Math.abs(studentGpa - gpa) < 0.001) { // 浮点数比较,避免精度问题
Student student = parseRowToStudent(row);
result.add(student);
}
}
ExcelUtil.closeWorkbook(workbook);
return result;
}
@Override
public List<Student> getAllStudents() {
List<Student> allStudents = new ArrayList<>();
Workbook workbook = ExcelUtil.getWorkbook(EXCEL_PATH);
Sheet sheet = ExcelUtil.getOrCreateSheet(workbook, SHEET_NAME);
int lastRowNum = sheet.getLastRowNum();
// 遍历所有数据行,解析为Student列表
for (int i = 1; i <= lastRowNum; i++) {
Row row = sheet.getRow(i);
if (row == null) continue;
Student student = parseRowToStudent(row);
allStudents.add(student);
}
ExcelUtil.closeWorkbook(workbook);
return allStudents;
}
// 辅助方法:将Excel行数据解析为Student对象
private Student parseRowToStudent(Row row) {
String name = row.getCell(0).getStringCellValue();
int age = (int) row.getCell(1).getNumericCellValue();
String gender = row.getCell(2).getStringCellValue();
String id = row.getCell(3).getStringCellValue();
String major = row.getCell(4).getStringCellValue();
double gpa = row.getCell(5).getNumericCellValue();
return new Student(name, age, gender, id, major, gpa);
}
}
(三)util 层:Excel 工具类(ExcelUtil.java)
封装 Apache POI 库的复杂操作,提供 “获取工作簿、创建工作表、写入文件、关闭资源” 等通用方法,简化 ExcelStudentDAO 的代码逻辑,避免重复代码。
package code.util;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.*;
public class ExcelUtil {
// 1. 获取Excel工作簿:不存在则创建新工作簿
public static Workbook getWorkbook(String filePath) {
File excelFile = new File(filePath);
// 若文件不存在,返回新的空白工作簿
if (!excelFile.exists()) {
return new XSSFWorkbook();
}
// 若文件存在,读取并返回工作簿
try (InputStream inputStream = new FileInputStream(excelFile)) {
return WorkbookFactory.create(inputStream);
} catch (Exception e) {
e.printStackTrace();
return new XSSFWorkbook(); // 异常时返回空白工作簿
}
}
// 2. 获取或创建工作表:不存在则创建,并添加默认表头
public static Sheet getOrCreateSheet(Workbook workbook, String sheetName) {
Sheet sheet = workbook.getSheet(sheetName);
if (sheet == null) {
sheet = workbook.createSheet(sheetName);
addDefaultHeader(sheet); // 为新工作表添加表头
}
return sheet;
}
// 3. 添加默认表头:姓名、年龄、性别、学号、专业、GPA
private static void addDefaultHeader(Sheet sheet) {
Row headerRow = sheet.createRow(0); // 第一行作为表头
String[] headers = {"姓名", "年龄", "性别", "学号", "专业", "GPA"};
for (int i = 0; i < headers.length; i++) {
headerRow.createCell(i).setCellValue(headers[i]);
}
// 自动调整列宽(优化显示效果)
for (int i = 0; i < headers.length; i++) {
sheet.autoSizeColumn(i);
}
}
// 4. 写入工作簿到文件并关闭资源
public static void writeAndCloseWorkbook(Workbook workbook, String filePath) {
try (OutputStream outputStream = new FileOutputStream(filePath)) {
workbook.write(outputStream); // 写入数据到文件
} catch (IOException e) {
e.printStackTrace();
} finally {
closeWorkbook(workbook); // 确保资源关闭
}
}
// 5. 关闭工作簿资源
public static void closeWorkbook(Workbook workbook) {
try {
if (workbook != null) {
workbook.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
(四)service 层:业务逻辑服务(StudentManagementService.java)
作为业务逻辑的核心层,依赖 StudentDAO 接口完成数据操作,隔离 “业务逻辑” 与 “数据访问逻辑”。提供 DAO 切换接口,支持动态改变存储模式,同时可扩展数据校验、权限控制等业务规则。
package code.service;
import code.dao.StudentDAO;
import code.model.Student;
import java.util.List;
public class StudentManagementService {
private StudentDAO studentDAO; // 依赖DAO接口(面向接口编程)
// 构造方法:初始化时注入DAO实现
public StudentManagementService(StudentDAO studentDAO) {
this.studentDAO = studentDAO;
}
// 切换存储模式:动态修改DAO实现(核心功能)
public void switchStorageMode(StudentDAO newStudentDAO) {
this.studentDAO = newStudentDAO;
}
// 添加学生:调用DAO接口,无额外业务规则(可扩展数据校验)
public void addStudent(Student student) {
// 可扩展:添加数据合法性校验(如年龄>0、GPA在0-4之间)
studentDAO.addStudent(student);
}
// 按学号删除学生:返回删除结果
public boolean removeStudent(String id) {
return studentDAO.removeStudent(id);
}
// 按姓名查询学生
public List<Student> searchByName(String name) {
return studentDAO.searchByName(name);
}
// 按专业查询学生
public List<Student> searchByMajor(String major) {
return studentDAO.searchByMajor(major);
}
// 按GPA查询学生
public List<Student> searchByGpa(double gpa) {
return studentDAO.searchByGpa(gpa);
}
// 获取所有学生
public List<Student> getAllStudents() {
return studentDAO.getAllStudents();
}
}
(五)主程序入口:Main.java
负责用户交互,提供 “存储模式切换” 和 “学生管理功能” 的入口,通过控制台菜单引导用户操作,动态切换 List/Excel 存储模式,调用业务服务完成具体功能。
package code;
import code.dao.StudentDAO;
import code.dao.impl.ExcelStudentDAO;
import code.dao.impl.ListStudentDAO;
import code.model.Student;
import code.service.StudentManagementService;
import java.util.List;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 1. 初始化:默认使用List内存存储模式
StudentDAO defaultDAO = new ListStudentDAO();
StudentManagementService service = new StudentManagementService(defaultDAO);
boolean isRunning = true;
System.out.println("===== 学生信息管理系统(DAO模式)=====");
System.out.println("当前默认存储模式:内存List模式(数据临时存储,退出后丢失)\n");
// 2. 主循环:处理用户操作
while (isRunning) {
// 打印菜单
System.out.println("请选择操作:");
System.out.println("1. 切换存储模式(List/Excel)");
System.out.println("2. 添加学生");
System.out.println("3. 删除学生(按学号)");
System.out.println("4. 按姓名查询学生");
System.out.println("5. 按专业查询学生");
System.out.println("6. 按GPA查询学生");
System.out.println("7. 显示所有学生");
System.out.println("8. 退出系统");
System.out.print("输入操作编号:");
// 读取用户输入
int choice = scanner.nextInt();
scanner.nextLine(); // 吸收换行符,避免后续读取字符串异常
// 处理用户选择
switch (choice) {
case 1:
// 切换存储模式
System.out.println("\n请选择目标存储模式:");
System.out.println("1. 内存List模式(临时存储)");
System.out.println("2. Excel文件模式(持久化存储)");
System.out.print("输入模式编号:");
int modeChoice = scanner.nextInt();
switch (modeChoice) {
case 1:
service.switchStorageMode(new ListStudentDAO());
System.out.println("存储模式切换成功:内存List模式\n");
break;
case 2:
service.switchStorageMode(new ExcelStudentDAO());
System.out.println("存储模式切换成功:Excel文件模式(文件路径:students.xlsx)\n");
break;
default:
System.out.println("无效的模式编号,切换失败\n");
break;
}
break;
case 2:
// 添加学生:读取用户输入的学生信息
System.out.println("\n===== 添加学生 =====");
System.out.print("姓名:");
String name = scanner.nextLine();
System.out.print("年龄:");
int age = scanner.nextInt();
scanner.nextLine(); // 吸收换行符
System.out.print("性别:");
String gender = scanner.nextLine();
System.out.print("学号:");
String id = scanner.nextLine();
System.out.print("专业:");
String major = scanner.nextLine();
System.out.print("GPA:");
double gpa = scanner.nextDouble();
// 调用服务添加学生
Student newStudent = new Student(name, age, gender, id, major, gpa);
service.addStudent(newStudent);
System.out.println("学生添加成功!\n");
break;
case 3:
// 删除学生:按学号删除
System.out.println("\n===== 删除学生 =====");
System.out.print("输入要删除的学生学号:");
String removeId = scanner.nextLine();
boolean isRemoved = service.removeStudent(removeId);
if (isRemoved) {
System.out.println("学生删除成功!\n");
} else {
System.out.println("未找到该学号的学生,删除失败!\n");
}
break;
case 4:
// 按姓名查询
System.out.println("\n===== 按姓名查询 =====");
System.out.print("输入查询姓名:");
String searchName = scanner.nextLine();
List<Student> nameResults = service.searchByName(searchName);
printStudentResults(nameResults);
break;
case 5:
// 按专业查询
System.out.println("\n===== 按专业查询 =====");
System.out.print("输入查询专业:");
String searchMajor = scanner.nextLine();
List<Student> majorResults = service.searchByMajor(searchMajor);
printStudentResults(majorResults);
break;
case 6:
// 按GPA查询
System.out.println("\n===== 按GPA查询 =====");
System.out.print("输入查询GPA:");
double searchGpa = scanner.nextDouble();
List<Student> gpaResults = service.searchByGpa(searchGpa);
printStudentResults(gpaResults);
break;
case 7:
// 显示所有学生
System.out.println("\n===== 所有学生信息 =====");
List<Student> allStudents = service.getAllStudents();
printStudentResults(allStudents);
break;
case 8:
// 退出系统
isRunning = false;
System.out.println("\n系统退出成功,感谢使用!");
break;
default:
System.out.println("\n无效的操作编号,请重新输入!\n");
break;
}
}
scanner.close(); // 关闭输入流
}
// 辅助方法:打印学生查询结果(统一格式)
private static void printStudentResults(List<Student> students) {
if (students.isEmpty()) {
System.out.println("未查询到学生信息!\n");
return;
}
// 遍历打印每个学生信息
for (Student student : students) {
System.out.println(student);
}
System.out.println("(共查询到 " + students.size() + " 名学生)\n");
}
}
四、依赖说明
Excel 存储模式依赖 Apache POI 库(处理 Excel 文件),需在项目中引入以下依赖(以 Maven 项目为例):
<dependencies>
<!-- Apache POI:处理Excel 2003及以下(.xls) -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.1.2</version>
</dependency>
<!-- Apache POI-OOXML:处理Excel 2007及以上(.xlsx) -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.2</version>
</dependency>
</dependencies>
若使用非 Maven 项目,需手动下载以下 JAR 包并添加到项目类路径:
poi-4.1.2.jarpoi-ooxml-4.1.2.jar- 依赖的辅助 JAR 包
五、系统功能测试
(一)模式切换测试
- 默认模式(List):启动系统后,添加 2 名学生(如 “张三,20,男,001,计算机,3.8”),查询可正常显示;退出系统后重新启动,数据丢失(符合临时存储特性)。
- 切换到 Excel 模式:选择 “1. 切换存储模式”→“2. Excel 文件模式”,添加学生后,在项目根目录生成
students.xlsx文件,打开文件可看到学生数据;退出系统后重新启动,切换回 Excel 模式,查询可恢复之前添加的学生(符合持久化特性)。
(二)核心功能测试
| 功能 | 测试步骤 | 预期结果 |
|---|---|---|
| 添加学生 | 选择 “2. 添加学生”,输入合法信息(如 “李四,21,女,002,数学,3.5”) | 提示 “添加成功”,Excel 文件中新增该行数据 |
| 删除学生 | 选择 “3. 删除学生”,输入存在的学号(如 001) | 提示 “删除成功”,Excel 文件中该行数据消失 |
| 按姓名查询 | 选择 “4. 按姓名查询”,输入已添加的姓名(如 “张三”) | 显示该姓名对应的所有学生(支持同名) |
| 按专业查询 | 选择 “5. 按专业查询”,输入已添加的专业(如 “计算机”) | 显示该专业的所有学生 |
| 按 GPA 查询 | 选择 “6. 按 GPA 查询”,输入已添加的 GPA(如 3.8) | 显示 GPA 匹配的所有学生(浮点数精度正常) |
| 显示所有学生 | 选择 “7. 显示所有学生” | 按统一格式显示所有学生,包含数量统计 |
六、改造总结
(一)核心优势
- 解耦与灵活性:DAO 模式分离业务逻辑与数据访问,业务层无需关注存储细节,可通过
switchStorageMode方法动态切换 List/Excel 模式,适应不同场景(临时操作 / 持久化存储)。 - 可扩展性强:若后续需新增存储方式(如数据库存储),只需实现
StudentDAO接口,无需修改业务层与主程序代码,符合 “开闭原则”。 - 代码可维护性高:分层包结构使职责清晰(model 存实体、dao 管访问、service 处理业务),便于定位问题与迭代升级。
- 用户体验优化:主程序提供清晰的菜单引导,统一的结果打印格式,支持存储模式切换提示,降低用户操作成本。
(二)不足与改进方向
- 数据校验缺失:当前未对用户输入的学生信息(如年龄为负数、GPA 超出 0-4 范围、学号重复)进行合法性校验,可在
StudentManagementService的addStudent方法中添加校验逻辑。 - Excel 性能优化:当学生数据量较大(如千级以上)时,Excel 文件读写效率较低,可引入 “内存缓存 + 批量写入” 机制,减少文件 IO 次数。
- 异常处理增强:当前异常处理仅打印堆栈信息,可优化为用户友好提示(如 “Excel 文件被占用,请关闭后重试”),提升系统健壮性。
浙公网安备 33010602011771号