数据访问

Spring Boot 支持多种数据访问方案,从轻量的 JDBC 到全自动 ORM,按复杂度和灵活性分层选择。

方案全景

SQL 控制程度
  高 ◄────────────────────────────────────────► 低
  │                                              │
JdbcTemplate   MyBatis    Spring Data JPA   Spring Data JDBC
(手写全部 SQL)(XML/注解)(自动生成 SQL)  (轻量 ORM)
方案SQL 控制学习曲线复杂查询推荐场景
JdbcTemplate完全手写灵活简单 CRUD、遗留系统
MyBatis手写 SQL + 映射优秀复杂查询、DBA 协作
MyBatis-Plus单表自动、复杂手写优秀业务开发首选
Spring Data JPA自动生成 + JPQL一般领域驱动设计、标准化 CRUD
Spring Data JDBC手写 SQL + 简单映射一般轻量 ORM 替代

JdbcTemplate

Spring 对 JDBC 的薄封装,自动处理连接获取/释放、异常转换。

@Repository
@RequiredArgsConstructor
public class UserJdbcRepository {
 
    private final JdbcTemplate jdbc;
 
    // 查询列表
    public List<User> findByStatus(int status) {
        return jdbc.query(
            "SELECT id, name, email FROM users WHERE status = ?",
            (rs, rowNum) -> new User(
                rs.getLong("id"),
                rs.getString("name"),
                rs.getString("email")
            ),
            status
        );
    }
 
    // 查询单个(结果为空时抛 EmptyResultDataAccessException)
    public Optional<User> findById(Long id) {
        try {
            User user = jdbc.queryForObject(
                "SELECT * FROM users WHERE id = ?",
                BeanPropertyRowMapper.newInstance(User.class),
                id
            );
            return Optional.ofNullable(user);
        } catch (EmptyResultDataAccessException e) {
            return Optional.empty();
        }
    }
 
    // 插入并返回自增主键
    public Long insert(User user) {
        KeyHolder keyHolder = new GeneratedKeyHolder();
        jdbc.update(conn -> {
            PreparedStatement ps = conn.prepareStatement(
                "INSERT INTO users (name, email) VALUES (?, ?)",
                Statement.RETURN_GENERATED_KEYS
            );
            ps.setString(1, user.getName());
            ps.setString(2, user.getEmail());
            return ps;
        }, keyHolder);
        return keyHolder.getKey().longValue();
    }
 
    // 批量插入
    public void batchInsert(List<User> users) {
        jdbc.batchUpdate(
            "INSERT INTO users (name, email) VALUES (?, ?)",
            users,
            500,
            (ps, user) -> {
                ps.setString(1, user.getName());
                ps.setString(2, user.getEmail());
            }
        );
    }
 
    // NamedParameterJdbcTemplate:命名参数(可读性更好)
    public List<User> findByIds(List<Long> ids) {
        String sql = "SELECT * FROM users WHERE id IN (:ids)";
        MapSqlParameterSource params = new MapSqlParameterSource("ids", ids);
        return namedJdbc.query(sql, params,
            BeanPropertyRowMapper.newInstance(User.class));
    }
}

Spring Data JPA

基于 Hibernate,通过接口声明自动生成 SQL,适合标准 CRUD 和领域建模。

// 实体
@Entity
@Table(name = "orders")
@Data
public class Order {
 
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
 
    @Column(nullable = false)
    private String orderNo;
 
    @Enumerated(EnumType.STRING)
    private OrderStatus status;
 
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;
 
    @OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<OrderItem> items = new ArrayList<>();
 
    @CreatedDate
    private LocalDateTime createdAt;
 
    @LastModifiedDate
    private LocalDateTime updatedAt;
}
 
// Repository 接口:Spring Data 自动实现
public interface OrderRepository extends JpaRepository<Long, Order> {
 
    // 方法名推导 SQL
    List<Order> findByUserIdAndStatus(Long userId, OrderStatus status);
 
    // @Query 写 JPQL(面向实体,非表名)
    @Query("SELECT o FROM Order o WHERE o.createdAt > :since AND o.status = :status")
    List<Order> findRecentByStatus(
        @Param("since") LocalDateTime since,
        @Param("status") OrderStatus status
    );
 
    // 原生 SQL
    @Query(
        value = "SELECT * FROM orders WHERE amount > :amount LIMIT :limit",
        nativeQuery = true
    )
    List<Order> findHighValueOrders(@Param("amount") BigDecimal amount,
                                   @Param("limit") int limit);
 
    // 分页
    Page<Order> findByUserId(Long userId, Pageable pageable);
 
    // 更新(需 @Modifying + @Transactional)
    @Modifying
    @Transactional
    @Query("UPDATE Order o SET o.status = :status WHERE o.id = :id")
    int updateStatus(@Param("id") Long id, @Param("status") OrderStatus status);
 
    // Projection:只查需要的字段
    List<OrderSummary> findByStatus(OrderStatus status);
}
 
// Projection 接口
public interface OrderSummary {
    Long getId();
    String getOrderNo();
    OrderStatus getStatus();
}

分页使用:

Pageable pageable = PageRequest.of(0, 20,
    Sort.by(Sort.Direction.DESC, "createdAt"));
Page<Order> page = orderRepo.findByUserId(userId, pageable);
 
page.getContent();      // 当前页数据
page.getTotalElements(); // 总记录数
page.getTotalPages();    // 总页数

JPA 详细配置见 JPA与Hibernate


MyBatis

mybatis:
  mapper-locations: classpath:mapper/**/*.xml
  type-aliases-package: com.example.entity
  configuration:
    map-underscore-to-camel-case: true   # user_name → userName
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  # 开发打印 SQL
// Mapper 接口
@Mapper
public interface UserMapper {
 
    @Select("SELECT * FROM users WHERE id = #{id}")
    User findById(Long id);
 
    // 复杂查询用 XML
    List<User> findByCondition(UserQueryParam param);
 
    @Insert("INSERT INTO users(name, email) VALUES(#{name}, #{email})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    int insert(User user);
}
<!-- mapper/UserMapper.xml -->
<mapper namespace="com.example.mapper.UserMapper">
    <resultMap id="UserResultMap" type="User">
        <id column="id" property="id"/>
        <result column="user_name" property="name"/>
        <collection property="roles" ofType="Role"
                    select="selectRolesByUserId" column="id"/>
    </resultMap>
 
    <select id="findByCondition" resultMap="UserResultMap">
        SELECT * FROM users
        <where>
            <if test="name != null and name != ''">
                AND name LIKE CONCAT('%', #{name}, '%')
            </if>
            <if test="status != null">
                AND status = #{status}
            </if>
            <if test="ids != null and ids.size() > 0">
                AND id IN
                <foreach collection="ids" item="id"
                         open="(" separator="," close=")">#{id}
                </foreach>
            </if>
        </where>
        ORDER BY created_at DESC
        LIMIT #{offset}, #{pageSize}
    </select>
</mapper>

MyBatis-Plus 的代码生成与单表 CRUD 详见 MyBatis-Plus,MyBatis 完整配置见 MyBatis


Spring Data 通用能力

无论 JPA 还是 MongoDB 等,Spring Data 都提供统一的抽象:

// 通用审计(自动填充创建人/更新时间)
@Configuration
@EnableJpaAuditing
public class JpaConfig {
    @Bean
    public AuditorAware<String> auditorProvider() {
        return () -> Optional.ofNullable(SecurityContextHolder.getContext()
            .getAuthentication())
            .map(Authentication::getName);
    }
}
 
@Entity
public class BaseEntity {
    @CreatedDate
    private LocalDateTime createdAt;
 
    @LastModifiedDate
    private LocalDateTime updatedAt;
 
    @CreatedBy
    private String createdBy;
 
    @LastModifiedBy
    private String updatedBy;
}
 
// Specification(动态条件,JPA)
public class OrderSpec {
    public static Specification<Order> hasStatus(OrderStatus status) {
        return (root, query, cb) ->
            status == null ? null : cb.equal(root.get("status"), status);
    }
 
    public static Specification<Order> createdAfter(LocalDateTime time) {
        return (root, query, cb) ->
            time == null ? null : cb.greaterThan(root.get("createdAt"), time);
    }
}
 
// 使用
List<Order> orders = orderRepo.findAll(
    Specification.where(OrderSpec.hasStatus(PAID))
        .and(OrderSpec.createdAfter(LocalDate.now().atStartOfDay()))
);

数据访问层选型建议

  • 新项目 + 标准 CRUD 多:MyBatis-Plus(单表自动,复杂 SQL 手写)
  • 领域驱动设计 + 复杂关联:Spring Data JPA + Hibernate
  • 报表 / 数仓 / 复杂 SQL 多:MyBatis(XML 管理 SQL,DBA 可维护)
  • 微服务 + 极简依赖:Spring Data JDBC 或 JdbcTemplate

相关链接