1. 概述

Java 应用连接数据库有多种方案,通常涉及分层架构:从基础的 JDBC 开始,到使用 JPA(如 Hibernate 实现),再到框架级集成如 Spring Data JPA。JPA 底层仍依赖 JDBC,但通过对象-实体管理提供更透明的抽象。

本文将深入对比 Spring Data JPA 与 JPA 的核心差异,结合高层架构分析和代码示例。首先回顾 JDBC 的发展历程及 JPA 的诞生背景。

2. 从 JDBC 到 JPA 的演进

自 1997 年 JDK 1.1 以来,JDBC 一直是 Java 访问关系数据库的基础。其关键特性包括:

  • 驱动管理与查询执行:通过特定驱动(如 MySQL Java 连接器)连接 ODBC 数据源,支持事务管理。核心优势:仅更换驱动即可适配 MySQL、Oracle、PostgreSQL 等数据库
  • 数据源(Data Source):在 Java 企业级应用和 Spring 框架中,定义工作上下文中的数据库连接获取方式。
  • 连接池:缓存数据库连接对象,复用活跃/空闲连接,减少创建开销。
  • 分布式事务:在单个事务中操作多个数据库或资源。

随后,ORM 框架(如 Hibernate)兴起,将数据库资源映射为 POJOORM 层负责定义模式生成、数据库方言等核心功能

EJB 3.0 时代,持久化框架被整合到 Java Persistence API(JPA)中,Hibernate 和 EclipseLink 成为 JPA 规范的主流实现。

3. JPA 核心机制

JPA 允许用面向对象语法编写数据库操作,与具体数据库解耦。以员工表为例,通过注解将 POJO 映射为数据库表:

@Entity
@Table(name = "employee")
public class Employee implements Serializable {
    
    @Id
    @Generated
    private Long id;

    @Column(nullable = false)
    private String firstName;

    // 其他字段、getter/setter
}

JPA 支持主键策略、关联关系(如 多对多)等高级特性。关键能力:集合的延迟加载(lazy initialization),按需获取数据

通过 EntityManager 执行 CRUD 操作,事务管理可由容器(如 Spring 事务管理)或 ORM 工具(如 Hibernate)处理。持久化实体示例:

Employee employee = new Employee();
// 设置属性
entityManager.persist(employee);

3.1. Criteria 查询与 JPQL

根据 ID 查询实体:

Employee response = entityManager.find(Employee.class, id);

更强大的类型安全查询:使用 Criteria API 操作 @Entity。例如按 ID 查询:

CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Employee> cr = cb.createQuery(Employee.class);
Root<Employee> root = cr.from(Employee.class);
cr.select(root);
criteriaQuery.where(criteriaBuilder.equal(root.get(Employee_.ID), employee.getId()));
Employee employee = entityManager.createQuery(criteriaQuery).getSingleResult();

支持排序和分页:

criteriaQuery.orderBy(criteriaBuilder.asc(root.get(Employee_.FIRST_NAME)));

TypedQuery<Employee> query = entityManager.createQuery(criteriaQuery);
query.setFirstResult(0);
query.setMaxResults(3);

List<Employee> employeeList = query.getResultList();

更新操作示例(使用 CriteriaUpdate):

CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaUpdate<Employee> criteriaQuery = criteriaBuilder.createCriteriaUpdate(Employee.class);
Root<Employee> root = criteriaQuery.from(Employee.class);
criteriaQuery.set(Employee_.EMAIL, email);
criteriaQuery.where(criteriaBuilder.equal(root.get(Employee_.ID), employee));

entityManager.createQuery(criteriaQuery).executeUpdate();

JPA 还提供 JPQL(或 Hibernate 的 HQL),使用类 SQL 语法操作实体:

public Employee getEmployeeById(Long id) {
    Query jpqlQuery = getEntityManager().createQuery("SELECT e from Employee e WHERE e.id=:id");
    jpqlQuery.setParameter("id", id);
    return jpqlQuery.getSingleResult();
}

3.2. JDBC 集成

尽管 JPA 提供数据库无关的抽象,实际开发中仍需 JDBC 支持(如使用特定数据库语法或批量处理优化)。通过 createNativeQuery 执行原生 SQL:

Query query = entityManager
  .createNativeQuery("select * from employee where rownum < :limit", Employee.class);
query.setParameter("limit", limit);
List<Employee> employeeList = query.getResultList();

支持存储过程调用:

StoredProcedureQuery storedProcedure = em.createStoredProcedureQuery("calculate_something");
// 设置参数
storedProcedure.execute();
Double result = (Double) storedProcedure.getOutputParameterValue("output");

3.3. 注解体系

JPA 提供丰富注解集。除已见的 @Table、*@Entity@Id@Column* 外,常用注解包括:

  • @NamedQuery:在实体类上定义可复用 JPQL 查询

    @NamedQuery(name="Employee.findById", query="SELECT e FROM Employee e WHERE e.id = :id") 
    

    调用方式:

    Query query = em.createNamedQuery("Employee.findById", Employee.class);
    query.setParameter("id", id);
    Employee result = query.getResultList();
    
  • @NamedNativeQuery:定义原生 SQL 查询

    @NamedNativeQuery(name="Employee.findAllWithLimit", query="SELECT * FROM employee WHERE rownum < :limit")
    

3.4. 元模型(Metamodel)

生成 元模型类 实现类型安全的字段访问。例如 Employee_ 类:

@Generated(value = "org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor")
@StaticMetamodel(Employee.class)
public abstract class Employee_ {

    public static volatile SingularAttribute<Employee, String> firstName;
    public static volatile SingularAttribute<Employee, String> lastName;
    public static volatile SingularAttribute<Employee, Long> id;
    public static volatile SingularAttribute<Employee, String> email;

    public static final String FIRST_NAME = "firstName";
    public static final String LAST_NAME = "lastName";
    public static final String ID = "id";
    public static final String EMAIL = "email";
}

数据模型变更时自动重新生成,支持静态字段访问。

4. Spring Data JPA 核心特性

作为 Spring Data 家族成员,Spring Data JPA 在 JPA 之上构建抽象层,保留 JPA 全部能力的同时提供 Spring 式开发体验

传统 JPA DAO 需编写大量模板代码,Spring 通过预定义接口显著减少样板代码。

4.1. 仓库接口(Repositories)

创建员工表的 CRUD 仓库只需继承 JpaRepository

public interface EmployeeRepository extends JpaRepository<Employee, Long> {
}

持久化操作简化为:

employeeRepository.save(employee);

方法名查询:通过方法签名自动生成查询实现:

public interface EmployeeRepository extends JpaRepository<Employee, Long> {

    List<Employee> findByFirstName(String firstName);
}

调用示例:

List<Employee> employees = employeeRepository.findByFirstName("John");

支持分页和排序(继承 PagingAndSortingRepository):

public interface EmployeeRepository extends PagingAndSortingRepository<Employee, Long> {
}

分页查询:

Pageable pageable = PageRequest.of(5, 10, Sort.by("firstName"));
Page<Employee> employees = employeeRepositorySortAndPaging.findAll(pageable);

4.2. 查询增强

@Query 注解提供强大查询支持,可定义 JPQL 或原生 SQL:

@Query(value = "SELECT e FROM Employee e")
List<Employee> findAllEmployee(Sort sort);

调用示例:

List<Employee> employees = employeeRepository.findAllEmployee(Sort.by("firstName"));

4.3. QueryDsl 集成

类似 JPA Criteria API,提供 QueryDsl 流式查询和元模型生成。示例:

QEmployee employee = QEmployee.employee;
List<Employee> employees = queryFactory.selectFrom(employee)
  .where(
    employee.firstName.eq("John"),
    employee.lastName.eq("Doe"))
  .fetch();

5. JPA 实战测试

5.1. 依赖配置

pom.xml 核心依赖:

<dependency>
    <groupId>javax.persistence</groupId>
    <artifactId>javax.persistence-api</artifactId>
    <version>3.1.0</version>
</dependency>
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>6.4.2.Final</version>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>2.1.214</version>
</dependency>

元模型生成插件:

<plugin>
  <groupId>org.bsc.maven</groupId>
  <artifactId>maven-processor-plugin</artifactId>
  <version>3.3.3</version>
  <executions>
      <execution>
          <id>process</id>
          <goals>
              <goal>process</goal>
          </goals>
          <phase>generate-sources</phase>
          <configuration>
              <outputDirectory>${project.build.directory}/generated-sources</outputDirectory>
              <processors>
                  <processor>org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor</processor>
              </processors>
          </configuration>
      </execution>
  </executions>
  <dependencies>
      <dependency>
          <groupId>org.hibernate</groupId>
          <artifactId>hibernate-jpamodelgen</artifactId>
          <version>6.4.2.Final</version>
      </dependency>
  </dependencies>
</plugin>

5.2. 配置文件

使用 persistence.xml 配置:

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
             http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"
             version="2.1">
    <persistence-unit name="pu-test">
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
        <class>com.baeldung.spring.data.persistence.springdata_jpa_difference.model.Employee</class>
        <properties>
            <property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
            <property name="javax.persistence.jdbc.url" value="jdbc:h2:mem:test;DB_CLOSE_DELAY=-1"/>
            <property name="javax.persistence.jdbc.user" value="sa"/>
            <property name="javax.persistence.jdbc.password" value=""/>
            <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
            <property name="hibernate.hbm2ddl.auto" value="create-drop"/>
        </properties>
    </persistence-unit>
</persistence>

5.3. 测试类结构

public class JpaDaoIntegrationTest {

    private final EntityManagerFactory emf = Persistence.createEntityManagerFactory("pu-test");
    private final EntityManager entityManager = emf.createEntityManager();

    @Before
    public void setup() {
        deleteAllEmployees();
    }

    // 测试方法

    private void deleteAllEmployees() {
        entityManager.getTransaction()
          .begin();
        entityManager.createNativeQuery("DELETE from Employee")
          .executeUpdate();
        entityManager.getTransaction()
          .commit();
    }

    public void save(Employee entity) {
        entityManager.getTransaction()
          .begin();
        entityManager.persist(entity);
        entityManager.getTransaction()
          .commit();
    }

    // 其他 CRUD 方法
}

5.4. 关键测试用例

ID 查询测试

@Test
public void givenPersistedEmployee_whenFindById_thenEmployeeIsFound() {
    // 保存 employee
    assertEquals(employee, entityManager.find(Employee.class, employee.getId()));
}

Criteria 查询测试

@Test
public void givenPersistedEmployee_whenFindByIdCriteriaQuery_thenEmployeeIsFound() {
    // 保存 employee
    CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
    CriteriaQuery<Employee> criteriaQuery = criteriaBuilder.createQuery(Employee.class);
    Root<Employee> root = criteriaQuery.from(Employee.class);
    criteriaQuery.select(root);

    criteriaQuery.where(criteriaBuilder.equal(root.get(Employee_.ID), employee.getId()));

    assertEquals(employee, entityManager.createQuery(criteriaQuery)
      .getSingleResult());
}

JPQL 查询测试

@Test
public void givenPersistedEmployee_whenFindByIdJpql_thenEmployeeIsFound() {
    // 保存 employee
    Query jpqlQuery = entityManager.createQuery("SELECT e from Employee e WHERE e.id=:id");
    jpqlQuery.setParameter("id", employee.getId());

    assertEquals(employee, jpqlQuery.getSingleResult());
}

分页排序测试

@Test
public void givenPersistedEmployee_whenFindWithPaginationAndSort_thenEmployeesAreFound() {
    // 保存 John, Frank, Bob, James
    CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
    CriteriaQuery<Employee> criteriaQuery = criteriaBuilder.createQuery(Employee.class);
    Root<Employee> root = criteriaQuery.from(Employee.class);
    criteriaQuery.select(root);
    criteriaQuery.orderBy(criteriaBuilder.asc(root.get(Employee_.FIRST_NAME)));

    TypedQuery<Employee> query = entityManager.createQuery(criteriaQuery);

    query.setFirstResult(0);
    query.setMaxResults(3);

    List<Employee> employeeList = query.getResultList();

    assertEquals(Arrays.asList(bob, frank, james), employeeList);
}

⚠️ 更新测试(邮箱 mock 为 updated@example.com):

@Test
public void givenPersistedEmployee_whenUpdateEmployeeEmailWithCriteria_thenEmployeeHasUpdatedEmail() {
    // 保存 employee
    String updatedEmail = "updated@example.com";

    CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
    CriteriaUpdate<Employee> criteriaUpdate = criteriaBuilder.createCriteriaUpdate(Employee.class);
    Root<Employee> root = criteriaUpdate.from(Employee.class);

    criteriaUpdate.set(Employee_.EMAIL, updatedEmail);
    criteriaUpdate.where(criteriaBuilder.equal(root.get(Employee_.ID), employee.getId()));

    assertEquals(1, update(criteriaUpdate));
    assertEquals(updatedEmail, entityManager.find(Employee.class, employee.getId())
      .getEmail());
}

6. Spring Data JPA 实战测试

6.1. 依赖升级

新增 Spring Data 和 QueryDsl 依赖:

<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-jpa</artifactId>
    <version>3.1.5</version>
</dependency>
<dependency>
    <groupId>com.querydsl</groupId>
    <artifactId>querydsl-apt</artifactId>
    <version>5.0.0</version>
    <classifier>jakarta</classifier>
</dependency>
<dependency>
    <groupId>com.querydsl</groupId>
    <artifactId>querydsl-jpa</artifactId>
    <version>5.0.0</version>
    <classifier>jakarta</classifier>
</dependency>

6.2. Java 配置类

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(basePackageClasses = EmployeeRepository.class)
public class SpringDataJpaConfig {

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) {
        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(dataSource);
        em.setPackagesToScan(Employee.class.getPackage().getName());

        JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        em.setJpaVendorAdapter(vendorAdapter);

        Properties properties = new Properties();
        properties.setProperty("hibernate.hbm2ddl.auto", "create-drop");
        properties.setProperty("hibernate.dialect", "org.hibernate.dialect.H2Dialect");

        em.setJpaProperties(properties);
        return em;
    }

    @Bean
    public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(emf);
        return transactionManager;
    }

    @Bean
    public DataSource dataSource() {
        return DataSourceBuilder.create()
          .url("jdbc:h2:mem:db;DB_CLOSE_DELAY=-1")
          .driverClassName("org.h2.Driver")
          .username("sa")
          .password("sa")
          .build();
    }

    @Bean
    public JPAQueryFactory jpaQueryFactory(EntityManager entityManager) {
        return new JPAQueryFactory(entityManager);
    }
}

仓库接口定义:

@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {

    List<Employee> findByFirstName(String firstName);

    @Query(value = "SELECT e FROM Employee e")
    List<Employee> findAllEmployee(Sort sort);
}

@Repository
public interface EmployeeRepositoryPagingAndSort extends PagingAndSortingRepository<Employee, Long> {
}

6.3. 测试类结构

@ContextConfiguration(classes = SpringDataJpaConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
@Transactional
@Rollback
public class SpringDataJpaIntegrationTest {

    @Autowired
    private EmployeeRepository employeeRepository;

    @Autowired
    private EmployeeRepositoryPagingAndSort employeeRepositoryPagingAndSort;

    @Autowired
    private JPAQueryFactory jpaQueryFactory;

    // 测试方法
}

6.4. 关键测试用例

ID 查询测试

@Test
public void givenPersistedEmployee_whenFindById_thenEmployeeIsFound() {
    Employee employee = employee("John", "Doe");
    employeeRepository.save(employee);
    assertEquals(Optional.of(employee), employeeRepository.findById(employee.getId()));
}

方法名查询测试

@Test
public void givenPersistedEmployee_whenFindByFirstName_thenEmployeeIsFound() {
    Employee employee = employee("John", "Doe");
    employeeRepository.save(employee);
    assertEquals(employee, employeeRepository.findByFirstName(employee.getFirstName()).get(0));
}

排序查询测试

@Test
public void givenPersistedEmployees_whenFindSortedByFirstName_thenEmployeeAreFoundInOrder() {
    Employee john = employee("John", "Doe");
    Employee bob = employee("Bob", "Smith");
    Employee frank = employee("Frank", "Brown");

    employeeRepository.saveAll(Arrays.asList(john, bob, frank));

    List<Employee> employees = employeeRepository.findAllEmployee(Sort.by("firstName"));

    assertEquals(3, employees.size());
    assertEquals(bob, employees.get(0));
    assertEquals(frank, employees.get(1));
    assertEquals(john, employees.get(2));
}

QueryDsl 查询测试

@Test
public void givenPersistedEmployee_whenFindByQueryDsl_thenEmployeeIsFound() {
    Employee john = employee("John", "Doe");
    Employee frank = employee("Frank", "Doe");

    employeeRepository.saveAll(Arrays.asList(john, frank));

    QEmployee employeePath = QEmployee.employee;

    List<Employee> employees = jpaQueryFactory.selectFrom(employeePath)
      .where(employeePath.firstName.eq("John"), employeePath.lastName.eq("Doe"))
      .fetch();

    assertEquals(1, employees.size());
    assertEquals(john, employees.get(0));
}

分页测试

@Test
public void givenPersistedEmployee_whenFindBySortAndPagingRepository_thenEmployeeAreFound() {
    Employee john = employee("John", "Doe");
    Employee bob = employee("Bob", "Smith");
    Employee frank = employee("Frank", "Brown");
    Employee jimmy = employee("Jimmy", "Armstrong");

    employeeRepositoryPagingAndSort.saveAll(Arrays.asList(john, bob, frank, jimmy));

    Pageable pageable = PageRequest.of(0, 2, Sort.by("firstName"));

    Page<Employee> employees = employeeRepositoryPagingAndSort.findAll(pageable);

    assertEquals(Arrays.asList(bob, frank), employees.get().collect(Collectors.toList()));
}

7. 核心差异总结

维度 JPA Spring Data JPA
定位 ORM 规范标准 JPA 的抽象层
抽象层级 提供数据库无关的 ORM 抽象 在 JPA 之上添加 Spring 特有抽象
事务管理 需手动管理或集成容器 无缝集成 Spring 事务管理
代码量 需编写大量模板代码(DAO/CRUD) 极简接口,消灭样板代码
查询方式 EntityManager + JPQL/Criteria 仓库接口 + 方法名查询/@Query
扩展能力 支持原生 SQL 和存储过程 兼容 JPA 所有能力,增加 QueryDsl 支持
依赖关系 底层规范 依赖 JPA 实现

关键结论

  1. JPA 是 Java ORM 的基础规范,提供数据库无关的持久化抽象
  2. Spring Data JPA 在 JPA 基础上提供更简洁的开发体验
  3. 没有 JPA 就没有 Spring Data JPA,学习 JPA 是理解 Java 持久化的基础
  4. 实际项目中 Spring Data JPA 能显著提升开发效率

8. 结语

本文从 JDBC 演进出发,深入分析了 JPA 与 Spring Data JPA 的核心差异。通过实际代码示例展示了:

  • JPA 的基础 CRUD、Criteria 查询、元模型等能力
  • Spring Data JPA 的仓库接口、方法名查询、分页等简化特性
  • 两者的测试对比与集成方案

代码示例见 GitHub 仓库。选择技术方案时,基础项目从 JPA 入手,复杂业务推荐 Spring Data JPA,能兼顾灵活性与开发效率。


原始标题:Difference Between JPA and Spring Data JPA