第一部分: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构建类型安全查询