上一篇
Java的DAO需先定义接口声明CRUD方法,再实现类完成具体数据库操作,利用JDBC等技术实现数据交互
Java应用中,DAO(Data Access Object,数据访问对象)作为系统架构的关键组件,负责封装所有与数据库交互的细节,实现业务逻辑和底层持久化存储之间的解耦,以下是详细的实现步骤和最佳实践:
理解DAO的核心职责
- 单一职责原则:每个DAO类仅对应一张数据库表或视图,专注于该实体的CRUD操作(增删改查),UserDao处理用户相关的所有数据请求,避免与其他模块耦合;
- 资源管理自动化:确保连接泄露问题通过try-with-resources或框架提供的自动提交机制解决;
- 异常分层转化:将SQLException转换为自定义的业务异常,使上层调用方无需关心底层错误类型。
基础实现方式(纯JDBC)
定义领域模型(Entity Class)
public class Book {
private Long id; // 主键
private String title; // 书名
private String author; // 作者
// getters/setters省略...
}
注意:属性名需与数据库列名映射一致,建议使用驼峰命名法配合结果集元数据处理工具。
创建DAO接口规范操作行为
| 方法签名 | 功能描述 | 参数示例 | 返回值类型 |
|---|---|---|---|
int add(Book entity) |
插入新记录 | Book对象 | 受影响行数 |
boolean deleteById(Long id) |
根据ID删除 | Long型主键值 | 是否成功布尔值 |
Book findById(Long id) |
主键查询 | Long型主键值 | Book实体或null |
List<Book> findAll() |
获取全表数据 | 无 | List集合 |
int update(Book entity) |
更新现有记录 | Book对象 | 受影响行数 |
手写实现类处理原始JDBC操作
public class JdbcBookDaoImpl implements BookDao {
private final DataSource dataSource; // 依赖注入的数据源
@Override
public int add(Book book) {
String sql = "INSERT INTO books(title, author) VALUES(?, ?)";
try (Connection conn = dataSource.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, book.getTitle());
pstmt.setString(2, book.getAuthor());
return pstmt.executeUpdate();
} catch (SQLException e) {
throw new DataAccessException("新增书籍失败", e);
}
}
@Override
public Book findById(Long id) {
String sql = "SELECT FROM books WHERE id=?";
try (Connection conn = dataSource.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setLong(1, id);
ResultSet rs = pstmt.executeQuery();
if (rs.next()) {
return mapResultSetToEntity(rs); // 手动映射结果集到对象
}
return null;
} catch (SQLException e) {
throw new DataAccessException("查询书籍失败", e);
}
}
private Book mapResultSetToEntity(ResultSet rs) throws SQLException {
Book book = new Book();
book.setId(rs.getLong("id"));
book.setTitle(rs.getString("title"));
book.setAuthor(rs.getString("author"));
return book;
}
}
关键点:①始终使用预编译语句防止SQL注入;②合理设置事务隔离级别;③结果集到对象的手动转换需要特别注意类型匹配。
进阶方案(整合MyBatis框架)
当项目复杂度提升时,推荐采用成熟的ORM解决方案:
XML映射文件配置示例(BookMapper.xml)
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.dao.BookMapper">
<resultMap id="BaseResultMap" type="Book">
<id column="id" property="id"/>
<result column="title" property="title"/>
<result column="author" property="author"/>
</resultMap>
<select id="selectById" resultMap="BaseResultMap">
SELECT FROM books WHERE id=#{id}
</select>
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
INSERT INTO books(title, author) VALUES(#{title}, #{author})
</insert>
</mapper>
动态代理开发优势对比表
| 特性 | 传统手写实现 | MyBatis动态代理 |
|---|---|---|
| 代码量 | 大量重复模板化代码 | 近乎零配置 |
| SQL可维护性 | 硬编码在Java文件中 | 独立XML便于DBA协作 |
| 缓存支持 | 需自行实现 | 内置一级/二级缓存机制 |
| 批量操作效率 | 循环单条执行 | 支持collection参数批量插入 |
设计模式增强扩展性
- 泛型DAO基类:针对简单CRUD场景抽取公共父类,减少代码冗余。
public abstract class BaseDao<T extends BaseEntity> { public T selectByPrimaryKey(Serializable id) {...} public int deleteByPrimaryKey(Serializable id) {...} // 其他通用方法声明 } - 装饰器模式应用:通过面向切面编程添加日志、性能监控等功能而不修改原有实现,如使用Spring AOP记录方法执行时间:
@Aspect @Component public class DaoPerformanceMonitor { @Around("execution( com.example.dao..(..))") public Object measureExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable { long start = System.currentTimeMillis(); try { return joinPoint.proceed(); } finally { System.out.println("方法执行耗时:" + (System.currentTimeMillis() start) + "ms"); } } }
工程实践建议
- 分包策略:按功能模块划分dao包结构(如
com.company.projectx.user.dao); - 版本控制:将SQL语句纳入Git管理,重要复杂查询建议单独建档;
- 单元测试覆盖率:针对每个DAO方法编写集成测试验证边界条件;
- 慢查询分析:定期使用Explain计划优化热点SQL执行路径。
FAQs:
Q1:为什么不应该让Service层直接操作数据库连接?
答:违反单一职责原则和分层架构设计理念,直接耦合会导致事务管理困难、连接池耗尽风险增加,且不利于团队协作时的代码维护,通过DAO统一管控数据访问,可以集中处理加密解密、审计日志等横切关注点。
Q2:如何处理多数据源场景下的DAO设计?
答:可采用抽象工厂模式创建不同环境的DataSource实例,结合Spring的@Qualifier注解指定特定数据源,对于跨库关联查询,建议显式分布式事务管理而非简单的多源切换,确保ACID特性不被
