1. 概述
我们有多种使用Java应用程序连接到数据库的选项。通常,我们引用不同的层,从JDBC开始。然后,我们转向JPA,使用Hibernate等实现。JPA最终将使用JDBC,但使用对象-实体管理方法使其对用户更加透明。
最后,我们可以有一个类似框架的集成,例如Spring Data JPA,它具有用于访问实体的预定义接口,但仍然在底层使用JPA和实体管理器。
在本教程中,我们将讨论Spring Data JPA和JPA之间的区别。我们还将通过一些高级概述和代码片段来解释它们如何工作。让我们首先解释一下JDBC的一些历史以及JPA是如何产生的。
2. 从JDBC到JPA
自1997年JDK(Java开发工具包) 1.1版本以来,我们就可以使用JDBC访问关系型数据库。
关于JDBC的要点对于理解JPA也是必不可少的,包括:
- DriverManager和用于连接和执行查询的接口:这使得通常使用特定驱动程序(例如MySQL Java连接器)可以连接到任何ODBC可访问的数据源。我们可以连接到数据库并在其上打开/关闭事务。最重要的是,我们只需更改数据库驱动程序就可以使用任何数据库,如MySQL、Oracle或PostgreSQL。
- 数据源:对于Java Enterprise和像Spring这样的框架,了解我们如何在工作上下文中定义和获取数据库连接很重要。
- 连接池,其作用类似于数据库连接对象的缓存:我们可以重用处于主动/被动状态的打开的连接,并减少它们的创建次数。
- 分布式事务:它们由一个或多个语句组成,这些语句在同一事务中更新多个数据库或资源上的数据。
在JDBC创建之后,像Hibernate这样的持久性框架(或ORM工具)开始出现,它将数据库资源映射为普通的旧Java对象。我们将ORM称为定义模式生成或数据库方言等的层。
此外,EntityJavaBean(EJB)创建标准来管理封装应用程序业务逻辑的服务器端组件。事务处理、JNDI和持久性服务等功能现在都是Javabeans。此外,注解和依赖注入现在简化了不同系统的配置和集成。
随着EJB3.0的发布,持久性框架被合并到JavaPersistenceAPI(JPA)中,Hibernate或EclipseLink等项目已成为JPA规范的实现。
3.联合行动计划
使用JPA,我们可以独立于所使用的数据库,以面向对象的语法编写构建块。
为了进行演示,让我们看一个员工表定义的示例。我们最终可以使用@Entity注解将表定义为POJO:
@Entity
@Table(name = "employee")
public class Employee implements Serializable {
@Id
@Generated
private Long id;
@Column(nullable = false)
private String firstName;
// other fields, setter and getters
}
JPA类可以管理数据库表功能,例如主键策略和多对多关系。例如,在使用外键时,这是相关的。JPA可以延迟初始化集合并仅在我们需要时访问数据。
我们可以使用EntityManager对实体执行所有CRUD操作(创建、检索、更新、删除)。JPA正在隐式处理事务。这可以通过像Spring事务管理这样的容器来完成,或者简单地通过使用EntityManager的Hibernate这样的ORM工具来完成。
一旦我们访问了EntityManager,我们就可以,例如,持久化一个Employee:
Employee employee = new Employee();
// set properties
entityManager.persist(employee);
3.1.条件查询和JPQL
例如,我们可以通过id找到一个Employee:
Employee response = entityManger.find(Employee.class, id);
更有趣的是,我们可以以类型安全的方式使用CriteriaQueries与@Entity进行交互。比如还是通过id查找,我们可以使用CriteriaQuery接口:
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的语法创建查询,但仍然引用@Entitybean:
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支持。这是为了使用特定的数据库查询语法或出于性能原因,例如,在批处理中。
即使我们使用JPA,我们仍然可以使用createNativeQuery方法以数据库的本机语言编写。例如,我们可能想使用rownumOracle关键字:
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");
// set parameters
storedProcedure.execute();
Double result = (Double) storedProcedure.getOutputParameterValue("output");
3.3.注解
JPA带有一组注解。我们已经看到了@Table、@Entity、@Id和@Column。
如果我们经常重用一个查询,我们可以在类级别使用@Entity将其注解为@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();
与@NamedQuery类似,我们可以使用@NamedNativeQuery进行数据库原生查询:
@NamedNativeQuery(name="Employee.findAllWithLimit", query="SELECT * FROM employee WHERE rownum < :limit")
3.4.元模型
我们可能想要生成一个元模型,允许我们以类型安全的方式静态访问表字段。例如,让我们看看从Employee生成的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.春季数据JPA
作为大型Spring Data系列的一部分,Spring Data JPA是作为JPA之上的抽象层构建的。因此,我们拥有JPA的所有功能以及易于开发的Spring。
多年来,开发人员编写了样板代码来为基本功能创建JPADAO。Spring通过提供最少的接口和实际实现来帮助显着减少代码量。
4.1.资料库
例如,假设我们要为Employee表创建一个CRUD存储库。我们可以使用JpaRepository:
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
}
这就是我们开始所需要的。所以,如果我们想要持久化或更新,我们可以获取存储库的一个实例并保存一个员工:
employeeRepository.save(employee);
我们也非常支持编写查询。有趣的是,我们可以通过简单地声明它们的方法签名来定义查询方法:
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
List<Employee> findByFirstName(String firstName);
}
Spring将在运行时从存储库接口自动创建存储库实现。
因此,我们可以使用这些方法而无需实现它们:
List<Employee> employees = employeeRepository.findByFirstName("John");
我们还支持对存储库进行排序和分页:
public interface EmployeeRepository extends PagingAndSortingRepository<Employee, Long> {
}
然后我们可以创建一个具有页面大小、数量和排序标准的Pageable对象:
Pageable pageable = PageRequest.of(5, 10, Sort.by("firstName"));
Page<Employee> employees = employeeRepositorySortAndPaging.findAll(pageable);
4.2.查询
另一个很棒的特性是对@Query注解的广泛支持。与JPA类似,这有助于定义类似JPQL或本机查询。让我们看一个示例,说明如何在存储库界面中使用它通过应用排序来获取员工列表:
@Query(value = "SELECT e FROM Employee e")
List<Employee> findAllEmployee(Sort sort);
同样,我们将使用存储库并获取列表:
List<Employee> employees = employeeRepository.findAllEmployee(Sort.by("firstName"));
4.3.查询Dsl
与JPA类似,我们有类似条件的支持,称为QueryDsl,它也有一个元模型生成。例如,假设我们想要一个员工列表,过滤名称:
QEmployee employee = QEmployee.employee;
List<Employee> employees = queryFactory.selectFrom(employee)
.where(
employee.firstName.eq("John"),
employee.lastName.eq("Doe"))
.fetch();
5.JPA测试
让我们创建并测试一个简单的JPA应用程序。我们可以让Hibernate管理事务性。
5.1.依赖关系
让我们看一下依赖关系。我们需要在pom.xml中导入JPA、Hibernate核心和H2数据库。
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>javax.persistence-api</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.6.11-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>5.6.11.Final</version>
</dependency>
</dependencies>
</plugin>
5.2.配置
为了保持简单的JPA,我们使用一个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>
我们不需要任何基于Bean的配置。
5.3.测试类定义
为了演示,让我们创建一个测试类。我们将使用createEntityManagerFactory方法获取EntityManager并手动管理事务:
public class JpaDaoIntegrationTest {
private final EntityManagerFactory emf = Persistence.createEntityManagerFactory("pu-test");
private final EntityManager entityManager = emf.createEntityManager();
@Before
public void setup() {
deleteAllEmployees();
}
// tests
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();
}
public void update(Employee entity) {
entityManager.getTransaction()
.begin();
entityManager.merge(entity);
entityManager.getTransaction()
.commit();
}
public void delete(Long employee) {
entityManager.getTransaction()
.begin();
entityManager.remove(entityManager.find(Employee.class, employee));
entityManager.getTransaction()
.commit();
}
public int update(CriteriaUpdate<Employee> criteriaUpdate) {
entityManager.getTransaction()
.begin();
int result = entityManager.createQuery(criteriaUpdate)
.executeUpdate();
entityManager.getTransaction()
.commit();
entityManager.clear();
return result;
}
}
5.4.测试JPA
首先,我们要测试是否可以通过id找到员工:
@Test
public void givenPersistedEmployee_whenFindById_thenEmployeeIsFound() {
// save employee
assertEquals(employee, entityManager.find(Employee.class, employee.getId()));
}
让我们看看我们可以找到Employee的其他方法。例如,我们可以使用CriteriaQuey:
@Test
public void givenPersistedEmployee_whenFindByIdCriteriaQuery_thenEmployeeIsFound() {
// save 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() {
// save employee
Query jpqlQuery = entityManager.createQuery("SELECT e from Employee e WHERE e.id=:id");
jpqlQuery.setParameter("id", employee.getId());
assertEquals(employee, jpqlQuery.getSingleResult());
}
让我们看看如何从@NamedQuery创建一个查询:
@Test
public void givenPersistedEmployee_whenFindByIdNamedQuery_thenEmployeeIsFound() {
// save employee
Query query = entityManager.createNamedQuery("Employee.findById");
query.setParameter(Employee_.ID, employee.getId());
assertEquals(employee, query.getSingleResult());
}
我们还可以看一个如何应用排序和分页的示例:
@Test
public void givenPersistedEmployee_whenFindWithPaginationAndSort_thenEmployeesAreFound() {
// save 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);
}
最后,让我们看看如何使用CriteriaUpdate更新员工电子邮件:
@Test
public void givenPersistedEmployee_whenUpdateEmployeeEmailWithCriteria_thenEmployeeHasUpdatedEmail() {
// save employee
String updatedEmail = "email@gmail.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测试
让我们看看如何通过添加Spring存储库和内置查询支持来改进。
6.1.依赖关系
在这种情况下,我们需要添加Spring Data依赖项。我们还需要Fluent查询API的QueryDsl依赖项。
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>2.7.5</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.2.214</version>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<version>5.0.0</version>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
<version>5.0.0</version>
</dependency>
6.2.配置
首先,让我们创建我们的配置:
@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(LocalContainerEntityManagerFactoryBean entityManagerFactoryBean) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactoryBean.getObject());
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));
}
}
最后,让我们看看我们的JpaRepository:
@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);
}
另外,我们想使用PagingAndSortingRepository:
@Repository
public interface EmployeeRepositoryPagingAndSort extends PagingAndSortingRepository<Employee, Long> {
}
6.3.测试类定义
让我们看看我们用于Spring Data测试的测试类。我们将回滚以保持每个测试的原子性:
@ContextConfiguration(classes = SpringDataJpaConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
@Transactional
@Rollback
public class SpringDataJpaIntegrationTest {
@Autowired
private EmployeeRepository employeeRepository;
@Autowired
private EmployeeRepositoryPagingAndSort employeeRepositoryPagingAndSort;
@Autowired
private JPAQueryFactory jpaQueryFactory;
// tests
}
6.4.测试Spring Data JPA
让我们从通过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));
}
最后,我们可以检查如何使用PagingAndSortingRepository:
@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有何不同
JPA定义了对象关系映射(ORM)的标准方法。
它提供了一个抽象层,使其独立于我们正在使用的数据库。JPA还可以处理事务性并且构建在JDBC之上,因此我们仍然可以使用本机数据库语言。
Spring Data JPA是JPA之上的另一层抽象。但是,它比JPA更灵活,并为所有CRUD操作提供简单的存储库和语法。我们可以从JPA应用程序中删除所有样板代码,并使用更简单的接口和注解。此外,我们将拥有Spring的开发便利性,例如,透明地处理事务性。
此外,没有JPA就没有Spring Data JPA,所以无论如何,如果我们想了解更多有关Java数据库访问层的知识,JPA是一个很好的起点。
八、结论
在本教程中,我们简要了解了JDBC的历史以及JPA为何成为关系数据库API的标准。我们还看到了用于持久化实体和创建动态查询的JPA和Spring Data JPA示例。最后,我们提供了一些测试用例来展示普通JPA应用程序和Spring Data JPA应用程序之间的区别。
与往常一样,本教程的完整源代码可在GitHub上获得。