第一部分:DAO设计模式

理解数据访问对象模式的核心概念

学习目标

掌握DAO模式的原理与架构,理解分层设计的优势

DAO设计模式示意图

核心概念

DAO模式的核心思想是抽象和封装

  • 分离数据访问逻辑与业务逻辑
  • 为不同数据源提供统一接口
  • 减少业务层对数据存储的依赖
  • 提高代码可维护性

基本组件

DAO模式的三个主要组成部分

  • DAO接口:定义数据访问操作
  • DAO实现:实现具体数据访问逻辑
  • 数据对象:数据实体(POJO/实体类)

架构优势

使用DAO模式的关键优势

  • 代码复用
  • 便于单元测试
  • 灵活更换数据源
  • 事务管理集中化
  • 提高安全控制

展示层

用户界面(UI)

业务逻辑层

服务(Service)

数据访问层

DAO(Data Access Object)

数据存储层

数据库(Database)

设计原则

在设计DAO层时,应遵循以下原则:

  • 一个DAO对应一个业务对象(如UserDAO)
  • 避免在DAO中包含业务逻辑
  • 接口与实现分离(面向接口编程)
  • 为复杂的查询操作创建专门的查询对象
  • 合理使用泛型减少重复代码

第二部分:JDBC DAO实现

使用原生JDBC实现数据访问层

学习目标

掌握基于JDBC的DAO实现技巧,处理资源管理和异常

1

定义DAO接口

面向接口编程是良好设计的基础

public interface UserDao { // 创建用户 void create(User user) throws DaoException; // 根据ID查询用户 User findById(Long id) throws DaoException; // 查询所有用户 List<User> findAll() throws DaoException; // 更新用户信息 void update(User user) throws DaoException; // 删除用户 void delete(Long id) throws DaoException; // 根据用户名查询用户 User findByUsername(String username) throws DaoException; }
2

JDBC实现类

实现DAO接口的JDBC版本

public class JdbcUserDao implements UserDao { private final DataSource dataSource; public JdbcUserDao(DataSource dataSource) { this.dataSource = dataSource; } @Override public User findById(Long id) throws DaoException { String sql = "SELECT id, username, email, created_at FROM users WHERE id = ?"; try (Connection conn = dataSource.getConnection(); PreparedStatement stmt = conn.prepareStatement(sql)) { stmt.setLong(1, id); try (ResultSet rs = stmt.executeQuery()) { if (rs.next()) { return mapRowToUser(rs); } return null; } } catch (SQLException ex) { throw new DaoException("Error finding user by id: " + id, ex); } } private User mapRowToUser(ResultSet rs) throws SQLException { User user = new User(); user.setId(rs.getLong("id")); user.setUsername(rs.getString("username")); user.setEmail(rs.getString("email")); user.setCreatedAt(rs.getTimestamp("created_at").toLocalDateTime()); return user; } // 其他方法实现... }

最佳实践

使用JDBC实现DAO时的注意事项:

  • 使用try-with-resources确保资源关闭
  • 使用PreparedStatement防止SQL注入
  • 统一处理SQLException为自定义异常
  • 使用连接池管理数据库连接
  • 将SQL语句提取为常量或配置文件
  • 使用RowMapper减少重复代码

第三部分:Spring DAO实现

利用Spring框架简化DAO开发

学习目标

掌握Spring JDBC模板的使用,实现高效DAO开发

JdbcTemplate核心方法

  • execute(): 执行任意SQL语句
  • update(): 执行INSERT/UPDATE/DELETE
  • query(): 执行SELECT返回多行
  • queryForObject(): 执行SELECT返回单行
  • batchUpdate(): 批量执行操作
DataSource RowMapper ResultSetExtractor
@Repository public class SpringUserDao implements UserDao { private final JdbcTemplate jdbcTemplate; @Autowired public SpringUserDao(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } @Override public User findById(Long id) { String sql = "SELECT id, username, email, created_at FROM users WHERE id = ?"; return jdbcTemplate.queryForObject( sql, new Object[]{id}, (rs, rowNum) -> { User user = new User(); user.setId(rs.getLong("id")); user.setUsername(rs.getString("username")); user.setEmail(rs.getString("email")); user.setCreatedAt(rs.getTimestamp("created_at").toLocalDateTime()); return user; } ); } @Override public List<User> findAll() { String sql = "SELECT * FROM users"; return jdbcTemplate.query(sql, new UserRowMapper()); } private static class UserRowMapper implements RowMapper<User> { @Override public User mapRow(ResultSet rs, int rowNum) throws SQLException { // 映射代码... } } // 其他方法实现... }

Spring DAO优势

使用Spring框架实现DAO的优点:

  • 无需手动处理资源(连接、语句、结果集)
  • 统一的异常处理体系(DataAccessException)
  • 简化的事务管理(声明式事务)
  • 内置的DAO支持类(JdbcDaoSupport)
  • 与Spring生态系统无缝集成

第四部分:JPA DAO实现

使用JPA和Hibernate进行ORM数据访问

学习目标

掌握JPA Repository的开发,理解实体管理

JPA核心组件

  • EntityManager: 管理实体操作
  • EntityManagerFactory: 创建EntityManager
  • 实体类(Entity): 数据模型
  • Repository: 数据访问接口
  • JPQL: 面向对象的查询语言
@Entity @Repository @PersistenceContext
// 实体类 @Entity @Table(name = "users") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false, unique = true) private String username; @Column(nullable = false) private String email; @Column(name = "created_at", updatable = false) private LocalDateTime createdAt; // getter/setter省略 } // Repository接口 public interface UserRepository extends JpaRepository<User, Long> { // 根据用户名查询用户 User findByUsername(String username); // 查询创建时间在指定时间之后的用户 List<User> findByCreatedAtAfter(LocalDateTime date); // 自定义查询 @Query("SELECT u FROM User u WHERE u.email LIKE %?1%") List<User> findByEmailContaining(String emailPart); }

JPA开发建议

使用JPA开发DAO时的最佳实践:

  • 优先使用派生查询方法(findBy...)
  • 复杂查询使用@Query定义JPQL或SQL
  • 使用EntityGraph解决N+1查询问题
  • 对写操作添加@Transactional
  • 使用Spring Data的Repository接口代替自定义DAO
  • 合理使用乐观锁(@Version)

第五部分:MyBatis DAO实现

使用MyBatis进行灵活的SQL映射

学习目标

掌握MyBatis Mapper的开发,编写高效的SQL映射

MyBatis核心组件

  • SqlSessionFactory: 创建SqlSession
  • SqlSession: 执行SQL操作
  • Mapper接口: 定义DAO操作
  • Mapper XML: SQL映射配置
  • 类型处理器: 类型转换
@Mapper @Select @Results
// Mapper接口 @Mapper public interface UserMapper { @Insert("INSERT INTO users (username, email, created_at) " + "VALUES (#{username}, #{email}, #{createdAt})") @Options(useGeneratedKeys = true, keyProperty = "id") void insert(User user); @Select("SELECT * FROM users WHERE id = #{id}") @Results(id = "userResultMap", value = { @Result(property = "id", column = "id"), @Result(property = "username", column = "username"), @Result(property = "email", column = "email"), @Result(property = "createdAt", column = "created_at") }) User findById(Long id); @SelectProvider(type = UserSqlBuilder.class, method = "buildFindUsersByCriteria") List<User> findByCriteria(UserCriteria criteria); } // 动态SQL构建器 public class UserSqlBuilder { public static String buildFindUsersByCriteria(UserCriteria criteria) { return new SQL() { { SELECT("*"); FROM("users"); if (criteria.getUsername() != null) { WHERE("username = #{username}"); } if (criteria.getEmail() != null) { WHERE("email LIKE CONCAT('%', #{email}, '%')"); } if (criteria.getStartDate() != null) { WHERE("created_at >= #{startDate}"); } if (criteria.getEndDate() != null) { WHERE("created_at <= #{endDate}"); } ORDER_BY("created_at DESC"); } }.toString(); } }

MyBatis优势

使用MyBatis实现DAO层的优点:

  • 完全控制SQL语句
  • 强大的动态SQL能力
  • 灵活的结果集映射
  • 支持存储过程
  • 二级缓存支持
  • 与Spring无缝集成

第六部分:DAO单元测试

确保数据访问层的正确性

学习目标

掌握DAO层的测试策略与工具

JDBC DAO测试

  • 使用内存数据库(H2、HSQLDB)
  • DBUnit管理测试数据
  • AbstractTransactionalTest基类
  • @Sql初始化测试数据
H2 DBUnit

JPA Repository测试

  • @DataJpaTest自动化测试配置
  • 自动配置内存数据库
  • 测试实体管理器操作
  • @TestEntityManager管理实体
@DataJpaTest H2

MyBatis Mapper测试

  • @MybatisTest专用测试注解
  • 自动注入Mapper实例
  • @AutoConfigureTestDatabase控制数据库
  • @Rollback控制事务回滚
@MybatisTest H2
// JPA Repository测试示例 @DataJpaTest @AutoConfigureTestDatabase(replace = Replace.NONE) class UserRepositoryTest { @Autowired private UserRepository userRepository; @Autowired private TestEntityManager entityManager; @Test void whenFindByUsername_thenReturnUser() { // 初始化测试数据 User testUser = new User(); testUser.setUsername("testuser"); testUser.setEmail("test@example.com"); entityManager.persist(testUser); entityManager.flush(); // 执行测试 User found = userRepository.findByUsername("testuser"); // 验证结果 assertThat(found.getUsername()).isEqualTo(testUser.getUsername()); } @Test @Sql("/test-data.sql") // 使用SQL初始化数据 void whenFindAll_thenReturnAllUsers() { List<User> users = userRepository.findAll(); assertThat(users).hasSize(3); // 根据test-data.sql中的数据 } }

测试建议

进行DAO测试时的注意事项:

  • 测试数据隔离(每个测试独立数据集)
  • 测试CRUD操作的所有路径
  • 测试边界条件和异常情况
  • 测试性能和并发情况
  • 使用真实数据库的集成测试

第七部分:常见错误与解决方案

识别并解决DAO层开发中的问题

学习目标

识别常见DAO开发错误,掌握解决方案

错误类型 现象 解决方案 预防措施
资源泄露 连接耗尽,性能下降 使用try-with-resources 代码审查,使用连接池监控
N+1查询 查询效率低下 使用JOIN FETCH或批量加载 ORM配置优化
事务传播错误 数据不一致 正确配置@Transactional 理解事务传播机制
缺少异常处理 应用崩溃 统一异常处理 检查异常捕获完整性
SQL注入 安全漏洞 使用PreparedStatement 代码扫描工具

典型错误示例:事务配置错误

@Service public class UserService { private final UserRepository userRepository; public UserService(UserRepository userRepository) { this.userRepository = userRepository; } // 错误:在内部调用的方法上使用@Transactional不会生效 public void updateUserEmail(Long userId, String email) { updateEmail(userId, email); sendEmailNotification(userId); } @Transactional public void updateEmail(Long userId, String email) { User user = userRepository.findById(userId).orElseThrow(); user.setEmail(email); userRepository.save(user); } // 解决方案1:将事务注解移到外部方法 // 解决方案2:使用AspectJ的编译时织入 }

问题分析:Spring的事务管理基于代理对象实现,当从同一类的其他方法内部调用@Transactional方法时,不会触发事务代理

开发最佳实践

设计高质量DAO层的建议:

  • 为DAO方法定义清晰的接口契约
  • 避免在DAO层包含业务逻辑
  • 使用分页处理大数据集查询
  • 合理使用二级缓存
  • 为复杂查询创建DTO对象
  • 使用QueryDSL或Specification构建类型安全查询