目录
“好的数据访问层就像银行金库管理员,既要知道每把钥匙对应的保险柜,也要懂得什么时候该用密码、指纹或者虹膜验证” —— 来自某金融系统架构师的自白
一、从图书馆管理系统说起
假设我们要开发一个图书管理系统,典型的错误实现可能是这样的:
// 业务逻辑与数据访问严重耦合
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
- 批量操作要特别优化
- 注意连接泄露问题
💥 常见陷阱:
- 在DAO中实现业务逻辑
- 忽视事务边界管理
- 过度通用导致的性能问题
- 缓存与数据库不一致
- 分页查询的内存泄露
六、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层,如何在保证接口统一性的同时实现分表逻辑?欢迎在评论区留下你的设计思路!