本次实验的主要任务是完成期末案例的**数据访问层**(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 实体类型 */ public interface BaseDAO { /** * 插入一条记录 * @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 findAll(); } ``` 2. **StudentDAO接口** StudentDAO 接口应该继承 BaseDAO 接口,同时扩展学生相关的数据库操作。 ```java package dao; import model.Student; import java.util.List; /** * 学生DAO接口 */ public interface StudentDAO extends BaseDAO { /** * 根据学号查询学生 * @param studentId 学号 * @return 学生对象 */ Student findByStudentId(String studentId); /** * 根据姓名模糊查询学生 * @param name 学生姓名 * @return 学生列表 */ List findByNameLike(String name); } ``` 3. **TeacherDAO接口** 教师的 DAO 接口应该继承 BaseDAO 接口,同时扩展教师相关的数据库操作。 ```java package dao; import model.Teacher; import java.util.List; /** * 教师DAO接口 */ public interface TeacherDAO extends BaseDAO { /** * 根据工号查询教师 * @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 { /** * 根据学生ID查询请假记录 * @param studentId 学生ID * @return 请假记录列表 */ List findByStudentId(int studentId); /** * 根据审批状态查询请假记录 * @param status 审批状态 * @return 请假记录列表 */ List 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 findAll() { List 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 findByNameLike(String name) { List 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 >}}