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)兴起,将数据库资源映射为 POJO。ORM 层负责定义模式生成、数据库方言等核心功能。
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 实现 |
关键结论:
- JPA 是 Java ORM 的基础规范,提供数据库无关的持久化抽象
- Spring Data JPA 在 JPA 基础上提供更简洁的开发体验
- 没有 JPA 就没有 Spring Data JPA,学习 JPA 是理解 Java 持久化的基础
- 实际项目中 Spring Data JPA 能显著提升开发效率
8. 结语
本文从 JDBC 演进出发,深入分析了 JPA 与 Spring Data JPA 的核心差异。通过实际代码示例展示了:
- JPA 的基础 CRUD、Criteria 查询、元模型等能力
- Spring Data JPA 的仓库接口、方法名查询、分页等简化特性
- 两者的测试对比与集成方案
代码示例见 GitHub 仓库。选择技术方案时,基础项目从 JPA 入手,复杂业务推荐 Spring Data JPA,能兼顾灵活性与开发效率。