1. Repository模式的核心价值与实现路径
在Java企业级应用开发中,数据访问层(DAO)的设计直接影响着系统的可维护性和扩展性。Repository模式作为领域驱动设计(DDD)的核心组件,其本质是建立领域对象与数据存储之间的抽象层。Spring生态提供了三种主流的Repository实现方式,各自适用于不同的技术栈和业务场景。
以电商平台的商品管理为例,当我们需要查询某个分类下的热销商品时,理想的Repository应该:
- 隐藏底层是使用MySQL还是MongoDB
- 屏蔽分页查询的复杂SQL拼接
- 提供类似
findHotProductsByCategory(categoryId, pageable)的语义化接口
2. 基于Spring Data JPA的声明式Repository
2.1 基础接口继承方式
这是Spring开发者最熟悉的写法,只需继承JpaRepository接口:
java复制public interface ProductRepository extends JpaRepository<Product, Long> {
// 自动实现分页查询
Page<Product> findByCategoryId(Long categoryId, Pageable pageable);
// 方法名自动推导查询
List<Product> findTop10ByOrderBySalesDesc();
}
实现原理:Spring Data在启动时会为这些接口生成动态代理:
- 解析方法名中的关键词(findBy/OrderBy等)
- 根据实体类注解生成JPQL
- 最终转换为原生SQL执行
实际开发中发现:方法名超过4个条件时建议改用@Query,否则可读性会显著下降
2.2 自定义查询进阶
复杂场景下可以使用@Query注解:
java复制@Query("SELECT p FROM Product p WHERE p.stock < :threshold AND p.status = 'ON_SHELF'")
List<Product> findLowStockProducts(@Param("threshold") int stockThreshold);
性能优化技巧:
- 添加
@EntityGraph解决N+1查询问题 - 使用
@Modifying+@Transactional进行批量更新 - 投影(Projection)接口减少字段查询
3. 基于JDBC的轻量级Repository实现
3.1 原生JDBC模板方案
适合需要精细控制SQL的场景:
java复制@Repository
public class JdbcProductRepository {
private final JdbcTemplate jdbcTemplate;
public List<Product> findProductsByCategory(Long categoryId) {
String sql = """
SELECT id, name, price
FROM products
WHERE category_id = ?
AND deleted = 0
ORDER BY create_time DESC
LIMIT 100""";
return jdbcTemplate.query(sql, (rs, rowNum) ->
new Product(
rs.getLong("id"),
rs.getString("name"),
rs.getBigDecimal("price")
), categoryId);
}
}
连接池配置要点:
yaml复制spring:
datasource:
hikari:
maximum-pool-size: 20
connection-timeout: 30000
idle-timeout: 600000
3.2 扩展JdbcRepository
Spring Data JDBC提供的中间方案:
java复制public interface ProductRepository extends CrudRepository<Product, Long>,
JdbcRepositoryCustom {
@Query("SELECT * FROM products WHERE price BETWEEN :min AND :max")
List<Product> findByPriceRange(@Param("min") BigDecimal min,
@Param("max") BigDecimal max);
}
与JPA的关键差异:
- 无一级缓存和脏检查机制
- 每个聚合根对应单独的表
- 需要手动处理关联关系
4. MyBatis混合式Repository设计
4.1 注解与XML混合模式
java复制@Mapper
public interface ProductMapper {
@Select("SELECT * FROM products WHERE id = #{id}")
Product selectById(Long id);
@Results({
@Result(property = "details", column = "id",
many = @Many(select = "selectDetails"))
})
@Select("SELECT * FROM products WHERE category_id = #{categoryId}")
List<Product> selectByCategory(Long categoryId);
@Select("SELECT * FROM product_details WHERE product_id = #{productId}")
List<ProductDetail> selectDetails(Long productId);
}
动态SQL示例:
xml复制<select id="searchProducts" resultType="Product">
SELECT * FROM products
<where>
<if test="name != null">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="minPrice != null">
AND price >= #{minPrice}
</if>
</where>
ORDER BY ${sortField} ${sortDirection}
</select>
4.2 与Spring Data的集成
通过MyBatisRepository接口实现两者融合:
java复制public interface ProductRepository extends MyBatisRepository<Product, Long> {
// 可以同时使用MyBatis方法和Spring Data方法
List<Product> findByNameContaining(String keyword);
@Select("SELECT * FROM products WHERE stock < #{stock}")
List<Product> selectLowStockProducts(int stock);
}
5. 技术选型对比与实战建议
5.1 三种方案的特性矩阵
| 特性 | Spring Data JPA | JDBC Template | MyBatis |
|---|---|---|---|
| 学习曲线 | 低 | 中 | 中高 |
| SQL控制度 | 低 | 高 | 高 |
| 对象映射 | 自动 | 手动 | 半自动 |
| 动态SQL支持 | 有限 | 手动拼接 | 强大 |
| 适合场景 | CRUD为主 | 复杂查询 | 遗留系统改造 |
5.2 性能优化实战经验
- JPA的N+1问题:通过
@EntityGraph或FetchMode.JOIN解决 - JDBC批量操作:使用
JdbcTemplate.batchUpdate() - MyBatis二级缓存:针对读多写少的数据配置
@CacheNamespace - 连接池监控:通过Micrometer暴露HikariCP指标
5.3 异常处理模式对比
java复制// JPA风格
try {
return repository.findById(id).orElseThrow(
() -> new ProductNotFoundException(id));
} catch (DataAccessException e) {
throw new ServiceException("Database error", e);
}
// JDBC风格
try {
return jdbcTemplate.queryForObject(sql, rowMapper, id);
} catch (EmptyResultDataAccessException e) {
throw new ProductNotFoundException(id);
}
// MyBatis风格
Product product = mapper.selectById(id);
if (product == null) {
throw new ProductNotFoundException(id);
}
在微服务架构实践中,我倾向于根据模块特性混合使用这些方案:核心领域用JPA保证开发效率,报表模块用JDBC或MyBatis实现复杂查询,而数据迁移任务则直接使用JdbcTemplate进行批量处理。这种分层策略既保持了代码的整洁性,又能满足不同场景下的性能需求。
