451 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

本次实验的主要任务是完成期末案例的**数据访问层**Data Access Object, DAO开发和其单元测试并且再次熟悉单元测试的使用方法。
## 准备工作
{{< admonition info 环境准备>}}
在开始今天的任务之前,请确保你已经完成了前两天的任务,包括项目框架搭建和实体类的开发等。
{{< /admonition >}}
### 注册账号并建立代码仓库
[Gitea](https://git.seahi.me)
注册一个账号,成功后,点击占上角的+创建仓库:
![](https://static.seahi.me/2024/12/202412191440855.png)
按下列信息填写:
- 仓库名stu00把00换成自己学号的后两位
- 仓库描述:填写姓名
- 可见性:私有
![](https://static.seahi.me/2024/12/202412200844243.png)
### 安装Git工具
[下载](https://static.seahi.me/2024/12/Git-2.47.1-64-bit.exe)
### 解压项目
将上节课保存好的项目解压到桌面上,最终效果如下:
![](https://static.seahi.me/2024/12/202412191438488.png)
在代码目录点击右键,选择 Open Git Bash here意思是“在这里打开Git Bash”
![](https://static.seahi.me/2024/12/202412191439007.png)
### 提交项目
**在刚刚打开的 Git Bash 中**执行下列命令:
```bash
git init
git checkout -b main
git add .
git commit -m "完成Day2"
git remote add origin https://git.seahi.me/用户名/stu00.git # 00换成学号
git push -u origin main
```
执行完成后,刷新 Gitea 网页,可以看到自己的代码
## 任务一DAO层接口开发
在本任务中我们将开发数据访问层DAO的接口。**DAO层是连接业务逻辑和数据库的桥梁通过定义统一的接口规范我们可以确保数据访问的一致性和可维护性。**
### 需要开发的接口
{{< admonition info 提示>}}
通过代码中的包名来判断该文件应该处于哪个目录。
{{< /admonition >}}
1. **BaseDAO接口**
基础DAO接口该接口规定通用的CRUD操作应该包含哪些操作。其他具体的DAO接口应该继承自该接口。
{{< admonition quote >}}
CRUD的含义是
- **C** 创建 Create
- **R** 读取 Read
- **U** 更新 Update
- **D** 删除 Delete
CRUD即平时说的增、删、改、查。
{{< /admonition >}}
```java
package dao;
import java.util.List;
/**
* 基础DAO接口定义通用的CRUD操作
* @param <T> 实体类型
*/
public interface BaseDAO<T> {
/**
* 插入一条记录
* @param entity 实体对象
* @return 影响的行数
*/
int insert(T entity);
/**
* 根据ID删除记录
* @param id 主键ID
* @return 影响的行数
*/
int deleteById(int id);
/**
* 更新记录
* @param entity 实体对象
* @return 影响的行数
*/
int update(T entity);
/**
* 根据ID查询记录
* @param id 主键ID
* @return 实体对象
*/
T findById(int id);
/**
* 查询所有记录
* @return 实体对象列表
*/
List<T> findAll();
}
```
2. **StudentDAO接口**
StudentDAO 接口应该继承 BaseDAO 接口,同时扩展学生相关的数据库操作。
```java
package dao;
import model.Student;
import java.util.List;
/**
* 学生DAO接口
*/
public interface StudentDAO extends BaseDAO<Student> {
/**
* 根据学号查询学生
* @param studentId 学号
* @return 学生对象
*/
Student findByStudentId(String studentId);
/**
* 根据姓名模糊查询学生
* @param name 学生姓名
* @return 学生列表
*/
List<Student> findByNameLike(String name);
}
```
3. **TeacherDAO接口**
教师的 DAO 接口应该继承 BaseDAO 接口,同时扩展教师相关的数据库操作。
```java
package dao;
import model.Teacher;
import java.util.List;
/**
* 教师DAO接口
*/
public interface TeacherDAO extends BaseDAO<Teacher> {
/**
* 根据工号查询教师
* @param teacherId 教师工号
* @return 教师对象
*/
Teacher findByTeacherId(String teacherId);
}
```
4. **LeaveRequestDAO接口**
```java
package dao;
import model.LeaveRequest;
import model.ApprovalStatus;
import java.util.Date;
import java.util.List;
/**
* 请假申请DAO接口
*/
public interface LeaveRequestDAO extends BaseDAO<LeaveRequest> {
/**
* 根据学生ID查询请假记录
* @param studentId 学生ID
* @return 请假记录列表
*/
List<LeaveRequest> findByStudentId(int studentId);
/**
* 根据审批状态查询请假记录
* @param status 审批状态
* @return 请假记录列表
*/
List<LeaveRequest> findByStatus(ApprovalStatus status);
/**
* 更新请假申请状态
* @param id 请假申请ID
* @param status 新状态
* @param approverComment 审批意见
* @return 影响的行数
*/
int updateStatus(int id, ApprovalStatus status, String approverComment);
}
```
## 任务二StudentDAOImpl类开发
在完成了DAO层接口的定义后我们需要**实现这些接口**。本任务将以`StudentDAOImpl`为例详细讲解如何实现一个完整的DAO实现类。`StudentDAOImpl`类需要实现`StudentDAO`接口,提供对学生数据的具体数据库操作实现。
### 1. 创建类基本结构
```java
package dao.impl;
/**
* StudentDAO接口的实现类提供对学生数据的访问操作
* 实现了StudentDAO接口定义的所有方法包括基础的CRUD操作和特定的查询方法
* 使用JDBC与数据库进行交互所有数据库操作都包含适当的异常处理
*/
public class StudentDAOImpl implements StudentDAO {
// TODO 实现StudentDAO接口的所有方法
}
```
### 2. 实现insert方法
```java
/**
* 插入一条学生记录
* 将Student对象的信息保存到数据库中并获取生成的主键ID
*
* @param student 要插入的学生对象,包含学生的详细信息
* @return 影响的行数插入成功返回1失败返回0
*/
@Override
public int insert(Student student) {
String sql = "INSERT INTO students (student_id, name, class_name, contact, college, major, password) VALUES (?, ?, ?, ?, ?, ?, ?)";
try (Connection conn = DatabaseUtil.getConnection();
PreparedStatement stmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
// 设置预处理语句的参数
stmt.setString(1, student.getStudentId());
stmt.setString(2, student.getName());
stmt.setString(3, student.getClassName());
stmt.setString(4, student.getContact());
stmt.setString(5, student.getCollege());
stmt.setString(6, student.getMajor());
stmt.setString(7, student.getPassword());
int affectedRows = stmt.executeUpdate();
if (affectedRows == 0) {
return 0;
}
// 获取自动生成的主键
try (ResultSet generatedKeys = stmt.getGeneratedKeys()) {
if (generatedKeys.next()) {
student.setId(generatedKeys.getInt(1));
}
}
return affectedRows;
} catch (SQLException e) {
e.printStackTrace();
return 0;
}
}
```
{{< admonition info 关于注解>}}
@Override是一个注解,作用是**表示当前方法重写了父类的方法**。
它的主要作用是:
1. 防止写错方法名:如果方法名写错了,编译器会报错提醒你
2. 提高代码可读性:让其他程序员一眼就能看出这是一个重写的方法
{{< /admonition >}}
### 3. 实现查询方法
```java
/**
* 根据ID查询学生信息
*
* @param id 要查询的学生ID
* @return 如果找到则返回学生对象否则返回null
*/
@Override
public Student findById(int id) {
String sql = "SELECT * FROM students WHERE id = ?";
try (Connection conn = DatabaseUtil.getConnection();
PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setInt(1, id);
ResultSet rs = stmt.executeQuery();
if (rs.next()) {
return mapResultSetToStudent(rs);
}
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
```
仿照以上示例,完成下列两个方法:
```java
/**
* 查询所有学生记录
*
* @return 包含所有学生对象的列表,如果没有记录则返回空列表
*/
@Override
public List<Student> findAll() {
List<Student> students = new ArrayList<>();
String sql = "SELECT * FROM students";
try (Connection conn = DatabaseUtil.getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql)) {
while (rs.next()) {
students.add(mapResultSetToStudent(rs));
}
} catch (SQLException e) {
e.printStackTrace();
}
return students;
}
/**
* 根据姓名模糊查询学生信息
*
* @param name 要查询的学生姓名(支持模糊查询)
* @return 符合条件的学生对象列表,如果没有匹配则返回空列表
*/
@Override
public List<Student> findByNameLike(String name) {
List<Student> students = new ArrayList<>();
String sql = "SELECT * FROM students WHERE name LIKE ?";
try (Connection conn = DatabaseUtil.getConnection();
PreparedStatement stmt = conn.prepareStatement(sql)) {
stmt.setString(1, "%" + name + "%");
ResultSet rs = stmt.executeQuery();
while (rs.next()) {
students.add(mapResultSetToStudent(rs));
}
} catch (SQLException e) {
e.printStackTrace();
}
return students;
}
/**
* 根据学号查询学生信息
*
* @param studentId 要查询的学号
* @return 如果找到则返回学生对象否则返回null
*/
@Override
public Student findByStudentId(String studentId) {
// TODO 根据数据库中表的定义,完成该方法
}
```
### 4. 实现ResultSet映射方法
```java
/**
* 将ResultSet映射为Student对象
* 从结果集中提取数据并创建Student实例
*
* @param rs 包含学生数据的ResultSet对象
* @return 映射后的Student对象
* @throws SQLException 如果访问ResultSet时发生错误
*/
private Student mapResultSetToStudent(ResultSet rs) throws SQLException {
Student student = new Student();
student.setId(rs.getInt("id"));
student.setStudentId(rs.getString("student_id"));
student.setName(rs.getString("name"));
student.setClassName(rs.getString("class_name"));
student.setContact(rs.getString("contact"));
student.setCollege(rs.getString("college"));
student.setMajor(rs.getString("major"));
student.setPassword(rs.getString("password"));
return student;
}
```
## 任务三:单元测试
将下列文件放置在 `src/test/dao/impl` 目录中,检查测试是否通过
[StudentDAOImplTest.java](https://static.seahi.me/2024/12/StudentDAOImplTest.java)
## 附
### 如何获取课上代码
1. 确保电脑已安装Git
2. 选择一个文件夹存放代码
3. 在该文件夹内部右键,选择"open Git Bash Here"
4. 输入命令:
```bash
# 第一次获取
git clone https://git.seahi.me/用户名/stu学号.git
# 后期再次获取时,直接进入到项目目录,执行下列命令即可
git pull
```
> 注意:把"学号"替换成你的实际学号后两位
### 如何提交修改的代码
当你在自己电脑上修改了代码后,需要提交到仓库:
1. 打开Git Bash
2. 输入以下命令:
```
git add .
git commit -m "修改说明"
git push
```
{{< admonition tip "提示">}}
- 第一次使用时需要设置用户名和密码
- 建议经常提交代码,避免代码丢失
{{< /admonition >}}