451 lines
11 KiB
Markdown
451 lines
11 KiB
Markdown
本次实验的主要任务是完成期末案例的**数据访问层**(Data Access Object, DAO)开发和其单元测试,并且再次熟悉单元测试的使用方法。
|
||
|
||
|
||
|
||
## 准备工作
|
||
|
||
{{< admonition info 环境准备>}}
|
||
在开始今天的任务之前,请确保你已经完成了前两天的任务,包括项目框架搭建和实体类的开发等。
|
||
{{< /admonition >}}
|
||
|
||
### 注册账号并建立代码仓库
|
||
|
||
[Gitea](https://git.seahi.me)
|
||
|
||
注册一个账号,成功后,点击占上角的+创建仓库:
|
||
|
||

|
||
|
||
按下列信息填写:
|
||
|
||
- 仓库名:stu00,把00换成自己学号的后两位
|
||
- 仓库描述:填写姓名
|
||
- 可见性:私有
|
||
|
||

|
||
|
||
|
||
### 安装Git工具
|
||
|
||
[下载](https://static.seahi.me/2024/12/Git-2.47.1-64-bit.exe)
|
||
|
||
### 解压项目
|
||
|
||
将上节课保存好的项目解压到桌面上,最终效果如下:
|
||
|
||

|
||
|
||
在代码目录点击右键,选择 Open Git Bash here(意思是“在这里打开Git Bash”)
|
||
|
||

|
||
|
||
### 提交项目
|
||
|
||
**在刚刚打开的 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 >}} |