25.DAO模式:企业级数据访问的“万能钥匙“

“好的数据访问层就像银行金库管理员,既要知道每把钥匙对应的保险柜,也要懂得什么时候该用密码、指纹或者虹膜验证” —— 来自某金融系统架构师的自白

一、从图书馆管理系统说起

假设我们要开发一个图书管理系统,典型的错误实现可能是这样的:

// 业务逻辑与数据访问严重耦合
public class BookService {
    public void addBook(Book book) {
        try (Connection conn = DriverManager.getConnection(url, user, pwd)) {
            String sql = "INSERT INTO books (isbn, title) VALUES (?, ?)";
            PreparedStatement pstmt = conn.prepareStatement(sql);
            pstmt.setString(1, book.getIsbn());
            pstmt.setString(2, book.getTitle());
            pstmt.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    
    // 其他业务方法中重复类似的数据库操作...
}

问题诊断:就像图书馆员既要管理书籍分类又要亲自搬运图书,业务逻辑和数据库操作纠缠不清!

二、DAO模式的四重进化论

2.1 传统JDBC DAO(手动挡模式)

public interface BookDao {
    void add(Book book);
    Book findByIsbn(String isbn);
    List<Book> findAll();
}

public class JdbcBookDao implements BookDao {
    private final DataSource dataSource;

    @Override
    public void add(Book book) {
        try (Connection conn = dataSource.getConnection()) {
            PreparedStatement pstmt = conn.prepareStatement(
                "INSERT INTO books (isbn, title, author) VALUES (?, ?, ?)");
            pstmt.setString(1, book.getIsbn());
            pstmt.setString(2, book.getTitle());
            pstmt.setString(3, book.getAuthor());
            pstmt.executeUpdate();
        } catch (SQLException e) {
            throw new DataAccessException("插入失败", e);
        }
    }
    
    // 其他方法实现...
}

2.2 Spring Data JPA(自动驾驶模式)

public interface BookRepository extends JpaRepository<Book, Long> {
    
    @Query("SELECT b FROM Book b WHERE b.publishDate BETWEEN ?1 AND ?2")
    List<Book> findBooksPublishedBetween(Date start, Date end);
    
    Optional<Book> findByIsbn(String isbn);
}

// 自动生成的实现类由Spring提供
@Service
public class BookService {
    @Autowired
    private BookRepository repository;
    
    @Transactional
    public Book registerNewBook(Book book) {
        return repository.save(book);
    }
}

2.3 MyBatis(手动与自动的完美平衡)

<!-- mapper.xml -->
<mapper namespace="com.example.dao.BookMapper">
    <insert id="insertBook" parameterType="Book">
        INSERT INTO books (isbn, title, author)
        VALUES (#{isbn}, #{title}, #{author})
    </insert>
    
    <select id="selectByIsbn" resultType="Book">
        SELECT * FROM books WHERE isbn = #{isbn}
    </select>
</mapper>
public interface BookMapper {
    void insertBook(Book book);
    Book selectByIsbn(String isbn);
}

// 使用示例
@Autowired
private BookMapper bookMapper;

public void addBook(Book book) {
    bookMapper.insertBook(book);
}

2.4 响应式DAO(WebFlux + MongoDB)

public interface ReactiveBookRepository extends ReactiveMongoRepository<Book, String> {
    
    Flux<Book> findByAuthorOrderByPublishDateDesc(String author);
    
    @Query("{ 'price': { $gt: ?0, $lt: ?1 } }")
    Flux<Book> findByPriceBetween(double low, double high);
}

// 使用示例
public Flux<Book> getExpensiveBooks() {
    return repository.findByPriceBetween(50, 100)
                     .delayElements(Duration.ofMillis(100));
}

三、实战:多数据源的分库秘籍

// 主库配置
@Configuration
@EnableJpaRepositories(
    basePackages = "com.dao.primary",
    entityManagerFactoryRef = "primaryEntityManager",
    transactionManagerRef = "primaryTransactionManager"
)
public class PrimaryDataSourceConfig {
    // 数据源、EntityManager等配置...
}

// 从库配置
@Configuration
@EnableJpaRepositories(
    basePackages = "com.dao.replica",
    entityManagerFactoryRef = "replicaEntityManager",
    transactionManagerRef = "replicaTransactionManager"
)
public class ReplicaDataSourceConfig {
    // 从库数据源配置...
}

// 动态数据源路由
public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return TransactionContext.isReadOnly() ? "replica" : "primary";
    }
}

// 使用示例
@Transactional(readOnly = true)
public List<Book> searchBooks(String keyword) {
    return replicaBookDao.search(keyword);
}

四、DAO模式七十二变

4.1 DDD仓储模式增强版

public interface BookRepository extends Repository<Book, ISBN> {
    
    Book findByIsbn(ISBN isbn);
    
    default Book findOrCreate(ISBN isbn) {
        return findByIsbn(isbn).orElseGet(() -> createNewBook(isbn));
    }
    
    private Book createNewBook(ISBN isbn) {
        Book book = new Book(isbn);
        save(book);
        return book;
    }
}

4.2 智能缓存代理

public class CachedBookDao implements BookDao {
    private final BookDao delegate;
    private final Cache<String, Book> cache = new LRUCache<>(1000);
    
    @Override
    public Book findByIsbn(String isbn) {
        return cache.computeIfAbsent(isbn, delegate::findByIsbn);
    }
    
    @Override
    public void add(Book book) {
        delegate.add(book);
        cache.put(book.getIsbn(), book);
    }
}

五、最佳实践与防坑指南

黄金法则

  • 保持DAO接口的纯净(只做数据访问)
  • 使用异常转换模式(屏蔽底层异常)
  • 为每个聚合根建立独立的DAO
  • 批量操作要特别优化
  • 注意连接泄露问题

💥 常见陷阱

  1. 在DAO中实现业务逻辑
  2. 忽视事务边界管理
  3. 过度通用导致的性能问题
  4. 缓存与数据库不一致
  5. 分页查询的内存泄露
BusinessService
+executeBusinessLogic()
«interface»
DAOInterface
+create()
+read()
+update()
+delete()
JdbcDAO
+create()
+read()
+update()
+delete()
JpaDAO
+create()
+read()
+update()
+delete()

六、DAO的现代化身

6.1 异步DAO模式

public interface AsyncBookDao {
    CompletableFuture<Void> addAsync(Book book);
    CompletableFuture<Optional<Book>> findByIsbnAsync(String isbn);
}

public class AsyncJdbcBookDao implements AsyncBookDao {
    private final Executor executor = Executors.newWorkStealingPool();
    
    @Override
    public CompletableFuture<Optional<Book>> findByIsbnAsync(String isbn) {
        return CompletableFuture.supplyAsync(() -> {
            try (Connection conn = dataSource.getConnection()) {
                // JDBC查询操作...
                return Optional.ofNullable(book);
            } catch (SQLException e) {
                throw new CompletionException(e);
            }
        }, executor);
    }
}

6.3 单元测试妙招

@DataJpaTest
@AutoConfigureTestDatabase(replace = Replace.NONE)
class BookRepositoryTest {
    
    @Autowired
    private BookRepository repository;
    
    @Test
    void shouldFindByIsbn() {
        Book saved = repository.save(new Book("9787121316030", "Java编程思想"));
        Optional<Book> found = repository.findByIsbn(saved.getIsbn());
        assertThat(found).isPresent();
    }
    
    @TestConfiguration
    static class TestConfig {
        @Bean
        public DataSource dataSource() {
            // 使用H2内存数据库
            return new EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.H2)
                .build();
        }
    }
}

终极挑战:假设要设计一个支持动态分表(例如按年份分表)的DAO层,如何在保证接口统一性的同时实现分表逻辑?欢迎在评论区留下你的设计思路!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhysunny

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值