ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

用 Java 数据访问对象(DAO)模式隔离业务逻辑与数据库操作:从接口设计到 CRUD 落地实战

用 Java 数据访问对象(DAO)模式隔离业务逻辑与数据库操作:从接口设计到 CRUD 落地实战 示例工程教程【免费下载链接】java-design-patternsDesign patterns implemented in Java项目地址https://gitcode.com/GitHub_Trending/ja/java-design-patterns点击查看免费下载数据访问对象Data Access Object简称 DAO是 Java 企业级应用中最经典的结构型模式之一其核心思想是把业务逻辑与持久化层彻底隔离让应用通过一个抽象接口读写数据而不必关心底层究竟是关系型数据库、XML 文件还是内存 Map。本文以 java-design-patterns 仓库中>Setter Getter ToString EqualsAndHashCode(onlyExplicitlyIncluded true) AllArgsConstructor public class Customer { EqualsAndHashCode.Include private int id; private String firstName; private String lastName; }第二步定义数据访问接口 CustomerDao接口是整个 DAO 模式的灵魂。它把应用需要什么数据操作与如何用具体 DBMS、数据库 schema 满足这些需求彻底分开。仓库中的接口定义非常规范每个方法都有完整的 Javadoc见 CustomerDao.javapublic interface CustomerDao { // 返回所有客户。流可能是惰性或即时求值取决于实现使用后必须关闭。 StreamCustomer getAll() throws Exception; // 按 id 查询客户返回 Optional不存在时返回空 Optional。 OptionalCustomer getById(int id) throws Exception; // 新增客户成功返回 true若客户已存在返回 false。 boolean add(Customer customer) throws Exception; // 更新客户客户存在且更新成功返回 true否则返回 false。 boolean update(Customer customer) throws Exception; // 删除客户客户存在且删除成功返回 true否则返回 false。 boolean delete(Customer customer) throws Exception; }有几个设计要点值得注意返回OptionalCustomer而非裸对象或 null明确表达可能查不到的语义强制调用方处理空值返回StreamCustomer而非List允许实现惰性求值、按需拉取配合 try-with-resources 管理资源方法签名统一抛出Exception把异常策略交给调用方后续 DbCustomerDao 实际用CustomException包装 SQLException。第三步两种实现——内存版与 RDBMS 版仓库为同一个接口提供了两个实现展示 DAO 模式换存储不改业务的核心价值InMemoryCustomerDao内存实现用HashMapInteger, Customer存数据适合临时数据库、原型验证和测试场景。数据随 JVM 退出而丢失。完整实现见 InMemoryCustomerDao.javapublic class InMemoryCustomerDao implements CustomerDao { private final MapInteger, Customer idToCustomer new HashMap(); Override public StreamCustomer getAll() { return idToCustomer.values().stream(); // 即时求值的流 } Override public OptionalCustomer getById(final int id) { return Optional.ofNullable(idToCustomer.get(id)); } Override public boolean add(final Customer customer) { if (getById(customer.getId()).isPresent()) { return false; // 主键已存在拒绝插入 } idToCustomer.put(customer.getId(), customer); return true; } Override public boolean update(final Customer customer) { return idToCustomer.replace(customer.getId(), customer) ! null; } Override public boolean delete(final Customer customer) { return idToCustomer.remove(customer.getId()) ! null; } }DbCustomerDaoJDBC 实现通过注入的DataSource与关系型数据库交互是真实生产形态的实现。构造函数由RequiredArgsConstructor生成强制要求传入DataSource见 DbCustomerDao.java。getAll()的实现尤其精彩——它把一个ResultSet包装成惰性 Stream并在流关闭时通过onClose回调释放连接、语句和结果集Override public StreamCustomer getAll() throws Exception { try { var connection getConnection(); var statement connection.prepareStatement(SELECT * FROM CUSTOMERS); var resultSet statement.executeQuery(); return StreamSupport.stream( new Spliterators.AbstractSpliteratorCustomer(Long.MAX_VALUE, Spliterator.ORDERED) { Override public boolean tryAdvance(Consumer? super Customer action) { try { if (!resultSet.next()) { return false; } action.accept(createCustomer(resultSet)); return true; } catch (SQLException e) { throw new RuntimeException(e); } } }, false) .onClose(() - mutedClose(connection, statement, resultSet)); } catch (SQLException e) { throw new CustomException(e.getMessage(), e); } }其余四个 CRUD 方法全部基于PreparedStatement的占位符参数?防 SQL 注入使用 try-with-resources 自动关闭连接并将底层SQLException统一包装为CustomException抛出。核心 SQL 语句如下操作SQL说明查询全部SELECT * FROM CUSTOMERS包装为惰性流需关闭按 id 查询SELECT * FROM CUSTOMERS WHERE ID ?用setInt(1, id)绑定参数新增INSERT INTO CUSTOMERS VALUES (?,?,?)id 已存在时返回 false更新UPDATE CUSTOMERS SET FNAME ?, LNAME ? WHERE ID ?executeUpdate() 0判断成功删除DELETE FROM CUSTOMERS WHERE ID ?executeUpdate() 0判断成功表结构定义在独立的CustomerSchemaSql常量类中CustomerSchemaSql.javapublic static final String CREATE_SCHEMA_SQL CREATE TABLE CUSTOMERS (ID NUMBER, FNAME VARCHAR(100), LNAME VARCHAR(100)); public static final String DELETE_SCHEMA_SQL DROP TABLE CUSTOMERS;第四步组装并运行 AppApp.java 演示了同一套业务操作函数如何先后作用于两个 DAO 实现——这正是 DAO 模式价值的最直观体现业务代码零改动存储实现随意切换。Slf4j public class App { private static final String DB_URL jdbc:h2:mem:dao;DB_CLOSE_DELAY-1; public static void main(final String[] args) throws Exception { final var inMemoryDao new InMemoryCustomerDao(); performOperationsUsing(inMemoryDao); final var dataSource createDataSource(); createSchema(dataSource); final var dbDao new DbCustomerDao(dataSource); performOperationsUsing(dbDao); deleteSchema(dataSource); } private static DataSource createDataSource() { var dataSource new JdbcDataSource(); dataSource.setURL(DB_URL); // H2 内存数据库 return dataSource; } private static void performOperationsUsing(final CustomerDao customerDao) throws Exception { addCustomers(customerDao); // 批量新增 3 个客户 LOGGER.info(ALL_CUSTOMERS); try (var customerStream customerDao.getAll()) { // 关闭流以释放资源 customerStream.forEach(customer - LOGGER.info(customer.toString())); } LOGGER.info(customerDao.getCustomerById(2): customerDao.getById(2)); final var customer new Customer(4, Dan, Danson); customerDao.add(customer); // 新增 customer.setFirstName(Daniel); customer.setLastName(Danielson); customerDao.update(customer); // 更新 customerDao.delete(customer); // 删除 LOGGER.info(ALL_CUSTOMERS customerDao.getAll()); } public static ListCustomer generateSampleCustomers() { final var customer1 new Customer(1, Adam, Adamson); final var customer2 new Customer(2, Bob, Bobson); final var customer3 new Customer(3, Carl, Carlson); return List.of(customer1, customer2, customer3); } }示例运行输出H2 内存数据库jdbc:h2:mem:dao;DB_CLOSE_DELAY-1表示 JVM 退出前保留内存库INFO com.iluwatar.dao.App -- customerDao.getAllCustomers(): INFO com.iluwatar.dao.App -- Customer(id1, firstNameAdam, lastNameAdamson) INFO com.iluwatar.dao.App -- Customer(id2, firstNameBob, lastNameBobson) INFO com.iluwatar.dao.App -- Customer(id3, firstNameCarl, lastNameCarlson) INFO com.iluwatar.dao.App -- customerDao.getCustomerById(2): Optional[Customer(id2, firstNameBob, lastNameBobson)] INFO com.iluwatar.dao.App -- customerDao.getAllCustomers(): java.util.stream.ReferencePipeline$Head4c3e4790 INFO com.iluwatar.dao.App -- Customer(id4, firstNameDaniel, lastNameDanielson)可以看到内存版与 DB 版输出完全一致业务调用方对底层存储毫无感知。依赖与运行方式模块依赖见 />从上图可以看出典型的 DAO 调用链业务客户端Client/App→ CustomerDao 接口 → 具体 DAO 实现 → DataSource/数据库。业务层永远不直接触碰Connection、Statement、ResultSet等 JDBC 细节这些全部封装在 DAO 实现内部。源码级验证测试用例如何保障 DAO 行为模块的测试代码与接口 Javadoc 中的契约一一对应是最佳的行为规格说明书。InMemoryCustomerDaoTest测试文件与DbCustomerDaoTest测试文件结构对称均按三层场景组织NonExistingCustomer客户不存在时新增成功add返回 true删除/更新失败返回 false且不影响已有数据按 id 查询返回空 Optional。ExistingCustomer客户已存在时重复新增失败add返回 false且不影响已有数据删除成功且客户不可再访问更新成功且再次查询返回新信息。ConnectivityIssue仅 DbCustomerDaoTest数据库不可达用 Mockito mock 一个会抛SQLException的DataSource断言增删改查全部以异常形式反馈给客户端验证了 DAO 对故障的显式暴露策略。测试前后通过DriverManager.getConnection(DB_URL)执行CustomerSchemaSql.CREATE_SCHEMA_SQL/DELETE_SCHEMA_SQL保证每个用例从干净的CUSTOMERS表开始。这套测试直接证明了 DAO 接口的行为契约与实现细节无关——换实现、换数据库测试逻辑完全复用。何时使用 DAO 模式出现以下任何一种情况都适合引入 DAO 模式需要对数据源访问进行抽象和封装所有数据访问都收敛到 DAO 层业务层不散落 SQL应用需要支持多种数据库或存储机制且不希望大改代码例如同一套业务同时跑 H2 与生产 MySQL或从内存 Map 平滑切换到 JDBC希望数据访问保持干净简单、与业务逻辑分离SQL、连接管理、事务边界都收在 DAO 内部。典型真实应用场景包括需要数据库交互的企业应用数据访问需要适配多种存储类型关系数据库、XML 文件、纯文本文件等的应用提供通用数据访问能力的框架层。收益与权衡收益解耦Decoupling数据访问逻辑与业务逻辑分离提升模块化程度与可读性复用ReusabilityDAO 可在应用的不同部分甚至不同项目间复用可测试性Testability业务逻辑可与数据访问逻辑分开测试配合内存实现或 Mock 数据源即可完成单元测试灵活性Flexibility切换底层存储机制时对应用代码影响极小。代价层数复杂度Layer Complexity多出的一层抽象会增加整体复杂度和开发时间额外开销Overhead对简单应用而言DAO 模式可能引入超出必要的间接层学习曲线Learning Curve尤其在大项目中开发者需要时间理解并正确实现该模式。与相关设计模式的关系DAO 模式在仓库中往往与以下模式搭配使用形成完整的分层架构Abstract Factory抽象工厂抽象 DAO 的创建过程尤其适合需要支持多种数据库或存储机制的场合Factory工厂动态实例化 DAO在选择实现时提供灵活性Service Layer服务层常与 DAO 联用定义应用边界及其可用操作集合——典型的三层结构是 Controller → Service → DAOStrategy策略可在运行时根据上下文切换数据访问策略。小结DAO 模式的价值不在于代码量而在于边界它用接口划清了业务要什么数据与存储怎么给数据之间的界线。java-design-patterns 仓库的>赞分享示例工程教程【免费下载链接】java-design-patternsDesign patterns implemented in Java项目地址https://gitcode.com/GitHub_Trending/ja/java-design-patterns点击查看免费下载相关推荐Yii 2 DAO 数据库访问对象实战从连接管理到读写分离的完整指南Yii 2 DAO 数据库访问对象实战从连接管理到读写分离的完整指南 Yii 2 内置的数据访问层DAODatabase Access Objects构后端Web框架Yii 2 DAO 数据库访问对象完全指南连接、查询、事务与读写分离实战Yii 2 DAO 数据库访问对象完全指南连接、查询、事务与读写分离实战 Yii 2 的 DAODatabase Access Objects数据库访问对后端Web框架告别SQL繁琐操作Exposed DAO模式让数据库访问像操作对象一样简单告别SQL繁琐操作Exposed DAO模式让数据库访问像操作对象一样简单 你还在为编写复杂SQL语句烦恼吗还在手动映射数据库表和Java对象吗ExposORM后端数据存储上一篇在 Xinference 中部署 Qwen3.6八种规格、五种引擎与量化方案全指南下一篇MSEC负载均衡与路由系统详解构建高可用服务架构创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表