5. Spring Data 入门 JPA
在本章中,我们将研究以下架构:
![]() |
我们在 [DAO] 层与 SGBD 的驱动程序 JDBC 之间插入了一个 [JPA] 层(Java Persistence API)。 现在,由 JPA 层发出发往 SGBD 的 SQL 命令。 [DAO]层不再处理SQL命令,而仅处理称为JPA实体的对象,这些实体是所用数据库中各张表的映射。 通过 Java 注解,这些实体的字段与表的列建立了唯一关联。 正是这一点使得 JPA 层能够将 [DAO] 层对 JPA 实体执行的操作转换为 SQL。
Spring Data 是 Spring 框架的一个分支,专注于数据访问,无论数据存储在关系型数据库 SGBDR、NOSQL 数据库,还是其他类型的存储库中。 本文仅关注 SGBDR 及其通过 JPA 进行的访问。后续内容中,我们将使用 [Spring JPA] 来实际指代 [Spring Data JPA]。 在上述架构中,[Spring Data]层为[DAO]层提供了管理JPA实体的功能。
JPA 实际上是一项规范。我们将测试其三个实现:
- Hibernate(http://hibernate.org/);
- EclipseLink (http://www.eclipse.org/eclipselink/);
- OpenJpa (http://openjpa.apache.org/);
5.1. 示例-01
Spring 官网上有许多入门教程([http://spring.io/guides])。我们将利用其中一个教程来介绍 Spring Data。为此,我们将使用 Spring Tool Suite(STS)。
![]() |
- 在 [1] 中,我们导入 [spring.io/guides] 中的一个教程;
![]() |
- 在 [2] 中,选择教程 [Accessing Data Jpa],该教程演示了如何使用 Spring Data 访问数据库;
- 在 [3] 中,选择一个由 Maven 配置的项目;
- 在 [4] 中,教程提供两种形式:[initial] 是一个空模板,需根据教程逐步填充;[complete] 则是教程的最终版本。我们选择后者;
- 在 [5] 中,可以选择在浏览器中查看教程;
- [6] 则是最终项目。
5.1.1. 项目的Maven配置
该项目的 Maven 依赖项配置在文件 [pom.xml] 中:
<groupId>org.springframework</groupId>
<artifactId>gs-accessing-data-jpa</artifactId>
<version>0.1.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.3.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
</dependencies>
<properties>
<!-- 所有操作均使用 UTF-8 -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<start-class>hello.Application</start-class>
</properties>
- 第 5-9 行:定义了一个父级 Maven 项目。该项目定义了项目的大部分依赖项。如果这些依赖项已经足够,则无需添加;否则,需补充缺失的依赖项;
- 第 12-15 行:定义了对 [spring-boot-starter-data-jpa] 的依赖。该工件包含 Spring Data 的类;
- 第 16-19 行:定义了对 SGBD 和 H2 的依赖,这些依赖用于创建和管理内存数据库。
让我们看看这些依赖引入的类:
![]() | ![]() | ![]() |
这些类数量众多:
- 部分属于 Spring 生态系统(以 spring 开头的);
- 另一些属于 Hibernate 生态系统(如 hibernate、jboss),其中我们在此使用的是 JPA 这一实现;
- 还有的是测试库(junit、hamcrest);
- 还有日志库(log4j、logback、slf4j);
我们将保留所有这些库。对于生产环境中的应用程序,应仅保留必要的库。
在文件 [pom.xml] 的第 26 行,可以看到以下代码:
<start-class>hello.Application</start-class>
该行与以下几行相关:
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
第6-9行,[spring-boot-maven-plugin]插件用于生成应用程序的可执行jar文件。因此,[pom.xml]文件的第26行指定了该jar文件的可执行类。
5.1.2. [JPA] 层
数据库访问通过 [JPA] 层实现,Java Persistence API:
![]() |
![]() |
该应用程序较为基础,用于管理客户 [Customer]。类 [Customer] 属于 [JPA] 层,具体如下:
package hello;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String firstName;
private String lastName;
protected Customer() {
}
public Customer(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
@Override
public String toString() {
return String.format("Customer[id=%d, firstName='%s', lastName='%s']", id, firstName, lastName);
}
}
一位客户拥有ID [id]、名字 [firstName] 和姓氏 [lastName]。每个 [Customer] 实例代表数据库表中的一行。
- 第8行:注解JPA,该注解使得实例[Customer](创建、读取、更新、删除)的持久化操作将由实现类JPA管理。 根据 Maven 依赖关系,可以看到实际使用的是 JPA / Hibernate 实现;
- 第 11-12 行:JPA 注解将字段 [id] 关联到 [Customer] 表的主键。 第 12 行表明,JPA 实现将使用所用 SGBD 特有的主键生成方法,此处为 H2;
没有其他关于 JPA 的注释。此时将使用默认值:
- [Customer] 表将采用类名,即 [Customer];
- 该表的列名将采用类字段的名称:[id, firstName, lastName](需注意表列名不区分大小写);
需要注意的是,在任何情况下,所使用的 JPA 实现都未被命名。
5.1.3. [Spring Data] 层
类 [CustomerRepository] 实现了表访问层 [Customer]。其代码如下:
![]() |
![]() |
package hello;
import java.util.List;
import org.springframework.data.repository.CrudRepository;
public interface CustomerRepository extends CrudRepository<Customer, Long> {
List<Customer> findByLastName(String lastName);
}
因此,这是一个接口而非类(第 7 行)。它继承了 Spring Data 接口 [CrudRepository](第 5 行)。 该接口由两个类型参数定义:第一个是所管理元素的类型,此处为 [Customer] 类型;第二个是所管理元素的主键类型,此处为 [Long] 类型。[CrudRepository] 接口如下:
package org.springframework.data.repository;
import java.io.Serializable;
@NoRepositoryBean
public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID> {
<S extends T> S save(S entity);
<S extends T> Iterable<S> save(Iterable<S> entities);
T findOne(ID id);
boolean exists(ID id);
Iterable<T> findAll();
Iterable<T> findAll(Iterable<ID> ids);
long count();
void delete(ID id);
void delete(T entity);
void delete(Iterable<? extends T> entities);
void deleteAll();
}
该接口定义了可在类型 JPA T 上执行的操作 CRUD(创建 – 读取 – 更新 – 删除):
- 第 8 行:save 方法用于将实体 T 持久化到数据库中。它使用 SGBD 赋予的主键将实体持久化。该方法还允许更新由主键 id 标识的实体 T。 具体执行哪种操作取决于主键 id 的值:若该值为 null,则执行持久化操作;否则执行更新操作;
- 第 10 行:同上,但针对实体列表;
- 第12行:方法findOne用于根据主键id检索实体T;
- 第22行:delete方法用于删除通过主键id标识的实体T;
- 第24-28行:方法[delete]的变体;
- 第 16 行:方法 [findAll] 用于检索所有持久化实体 T;
- 第18行:同上,但仅限于已传入标识符列表的实体;
让我们回到 [CustomerRepository] 接口:
package hello;
import java.util.List;
import org.springframework.data.repository.CrudRepository;
public interface CustomerRepository extends CrudRepository<Customer, Long> {
List<Customer> findByLastName(String lastName);
}
- 第 9 行允许通过名称 [lastName] 查找 [Customer];
关于 [DAO] 层的内容就到此为止。该接口没有实现类,它由 [Spring Data] 在运行时自动生成。 接口 [CrudRepository] 的方法会被自动实现。至于在接口 [CustomerRepository] 中添加的方法,则视情况而定。让我们回到 [Customer] 的定义:
private long id;
private String firstName;
private String lastName;
第 9 行中的方法由 [Spring Data] 自动实现,因为它引用了 [Customer] 中的字段 [lastName](第 3 行)。 当在待实现的接口中遇到 [findBySomething] 方法时,Spring Data 会通过以下 JPQL(Java 持久化查询语言)查询来实现它:
因此,类型 T 必须包含一个名为 [something] 的字段。这样,方法
将通过类似以下代码实现:
return [em].createQuery("select c from Customer c where c.lastName=:value").setParameter("value",lastName).getResultList()
其中 [em] 指代持久化上下文 JPA。这仅在类 [Customer] 拥有名为 [lastName] 的字段时才可行,而实际情况正是如此。
综上所述,在简单情况下,Spring Data 允许我们通过一个简单的接口来实现 [DAO] 层。
5.1.4. [console] 层
![]() |
![]() |
[Application] 类如下:
package hello;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application implements CommandLineRunner {
@Autowired
CustomerRepository repository;
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
@Override
public void run(String... strings) throws Exception {
// 保存部分客户
repository.save(new Customer("Jack", "Bauer"));
repository.save(new Customer("Chloe", "O'Brian"));
repository.save(new Customer("Kim", "Bauer"));
repository.save(new Customer("David", "Palmer"));
repository.save(new Customer("Michelle", "Dessler"));
// 获取所有客户
System.out.println("Customers found with findAll():");
System.out.println("-------------------------------");
for (Customer customer : repository.findAll()) {
System.out.println(customer);
}
System.out.println();
// 通过 ID 获取单个客户
Customer customer = repository.findOne(1L);
System.out.println("Customer found with findOne(1L):");
System.out.println("--------------------------------");
System.out.println(customer);
System.out.println();
// 按姓氏检索客户
System.out.println("Customer found with findByLastName('Bauer'):");
System.out.println("--------------------------------------------");
for (Customer bauer : repository.findByLastName("Bauer")) {
System.out.println(bauer);
}
}
}
- 第 9 行:该类实现了接口 [CommandLineRunner],该接口是 [Spring Boot] 的子接口(第 4 行)。该接口仅有一个方法,即第 19 行中的方法;
- 第 8 行:@SpringBootApplication 是一个聚合了多个 [Spring Boot] 注解的注解:
- @Configuration:表示该类是配置类;
- @EnableAutoConfiguration:要求 [Spring Boot] 根据各种属性(特别是项目 Classpath 的内容)自行创建一定数量的 Bean。 由于 Hibernate 库位于 Classpath 中,因此 [entityManagerFactory] Bean 将通过 Hibernate 进行实现。 由于 SGBD 的库位于 Classpath 中,因此 H2 Bean 将使用 H2 实现。 在 [dataSource] Bean 中,还需定义用户及其密码。 此时 Spring Boot 将使用 H2 的默认管理员,该管理员无密码。由于类库 [spring-tx] 位于类路径中,因此将使用 Spring 的事务管理器;
- @EnableWebMvc:如果类路径中包含 [spring-mvc] 库。此时,将为 Web 应用程序进行自动配置;
- @ComponentScan:该注解告知 Spring 在何处查找其他 Bean、配置和服务。 默认情况下,系统会在包含该标记类所在的包中进行查找,即 [hello] 包。因此,系统将找到 [Customer] 和 [CustomerRepository] 这两个类。 由于第一个类带有 [@Entity] 注解,它将被归类为由 Hibernate 管理的实体。由于第二个类继承了 [CrudRepository] 接口,它将被注册为 Spring Bean;
- 第 11-12 行:[CustomerRepository] Bean 被注入到主类的代码中;
- 第 15 行:执行 Spring Boot 项目中 [SpringApplication] 类的静态方法 [run]。其参数是带有 [Configuration] 或 [EnableAutoConfiguration] 注解的类。 随后将执行上述所有说明的内容。结果是一个 Spring 应用上下文,即一组由 Spring 管理的 Bean;
- 第 19-48 行:后续操作仅调用实现 [CustomerRepository] 接口的 Bean 中的方法;
控制台输出结果如下:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.2.2.RELEASE)
2015-03-10 15:35:43.661 INFO 5784 --- [ main] hello.Application : Starting Application on Gportpers3 with PID 5784 (started by ST in C:\Users\Serge Tahé\Documents\workspace-sts-3.6.3.RELEASE\gs-accessing-data-jpa-complete)
2015-03-10 15:35:43.708 INFO 5784 --- [ main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@5d11346a: startup date [Tue Mar 10 15:35:43 CET 2015]; root of context hierarchy
2015-03-10 15:35:45.230 INFO 5784 --- [ main] j.LocalContainerEntityManagerFactoryBean : Building JPA container EntityManagerFactory for persistence unit 'default'
2015-03-10 15:35:45.254 INFO 5784 --- [ main] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [
name: default
...]
2015-03-10 15:35:45.331 INFO 5784 --- [ main] org.hibernate.Version : HHH000412: Hibernate Core {4.3.8.Final}
2015-03-10 15:35:45.332 INFO 5784 --- [ main] org.hibernate.cfg.Environment : HHH000206: hibernate.properties not found
2015-03-10 15:35:45.334 INFO 5784 --- [ main] org.hibernate.cfg.Environment : HHH000021: Bytecode provider name : javassist
2015-03-10 15:35:45.651 INFO 5784 --- [ main] o.hibernate.annotations.common.Version : HCANN000001: Hibernate Commons Annotations {4.0.5.Final}
2015-03-10 15:35:45.754 INFO 5784 --- [ main] org.hibernate.dialect.Dialect : HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
2015-03-10 15:35:45.877 INFO 5784 --- [ main] o.h.h.i.ast.ASTQueryTranslatorFactory : HHH000397: Using ASTQueryTranslatorFactory
2015-03-10 15:35:46.154 INFO 5784 --- [ main] org.hibernate.tool.hbm2ddl.SchemaExport : HHH000227: Running hbm2ddl schema export
2015-03-10 15:35:46.169 INFO 5784 --- [ main] org.hibernate.tool.hbm2ddl.SchemaExport : HHH000230: Schema export complete
2015-03-10 15:35:46.779 INFO 5784 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup
Customers found with findAll():
-------------------------------
Customer[id=1, firstName='Jack', lastName='Bauer']
Customer[id=2, firstName='Chloe', lastName='O'Brian']
Customer[id=3, firstName='Kim', lastName='Bauer']
Customer[id=4, firstName='David', lastName='Palmer']
Customer[id=5, firstName='Michelle', lastName='Dessler']
Customer found with findOne(1L):
--------------------------------
Customer[id=1, firstName='Jack', lastName='Bauer']
Customer found with findByLastName('Bauer'):
--------------------------------------------
Customer[id=1, firstName='Jack', lastName='Bauer']
Customer[id=3, firstName='Kim', lastName='Bauer']
2015-03-10 15:35:47.040 INFO 5784 --- [ main] hello.Application : Started Application in 3.623 seconds (JVM running for 4.324)
2015-03-10 15:35:47.042 INFO 5784 --- [ Thread-1] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@5d11346a: startup date [Tue Mar 10 15:35:43 CET 2015]; root of context hierarchy
2015-03-10 15:35:47.044 INFO 5784 --- [ Thread-1] o.s.j.e.a.AnnotationMBeanExporter : Unregistering JMX-exposed beans on shutdown
2015-03-10 15:35:47.046 INFO 5784 --- [ Thread-1] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2015-03-10 15:35:47.047 INFO 5784 --- [ Thread-1] org.hibernate.tool.hbm2ddl.SchemaExport : HHH000227: Running hbm2ddl schema export
2015-03-10 15:35:47.051 INFO 5784 --- [ Thread-1] org.hibernate.tool.hbm2ddl.SchemaExport : HHH000230: Schema export complete
- 第 1-8 行:Spring Boot 项目的徽标;
- 第 9 行:执行 [hello.Application] 类;
- 第 10 行:[AnnotationConfigApplicationContext] 是实现 Spring 的 [ApplicationContext] 接口的类。这是一个 Bean 容器;
- 第 11 行:Bean [entityManagerFactory] 通过 Spring 类 [LocalContainerEntityManagerFactory] 实现。它管理 [JPA] 层;
- 第 12 行:出现了 [Hibernate]。最终选定的是该实现 JPA;
- 第 19 行:Hibernate 方言为 SQL,需与 SGBD 配合使用。 此处的 [H2Dialect] 方言表明,Hibernate 将与 SGBD 和 H2 配合使用;
- 第 21-22 行:数据库已创建。 创建了表 [CUSTOMER]。这意味着 Hibernate 已被配置为根据 JPA 定义生成表,此处为类 [Customer] 的定义 JPA;
- 第 26-30 行:接口中 [findAll] 方法的执行结果;
- 第 34 行:接口中 [findOne] 方法的返回结果;
- 第 38-39 行:方法 [findByLastName] 的结果;
- 第 41 行及后续行:Spring 上下文关闭日志。
5.1.5. Spring Data 项目的手动配置
我们将前一个项目复制到 [gs-accessing-data-jpa-02] 项目中:
![]() |
在这个新项目中,我们将不依赖 Spring Boot 的自动配置,而是手动进行配置。如果默认配置不符合我们的需求,这将非常有用。
首先,我们在文件 [pom.xml] 中显式声明所需的依赖项:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework</groupId>
<artifactId>gs-accessing-data-jpa-02</artifactId>
<version>0.1.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.3.RELEASE</version>
</parent>
<dependencies>
<!-- Spring Data -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
</dependency>
<!-- Hibernate -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
</dependency>
<!-- H2 数据库 -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<!-- TomcatJDBC -->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
</dependency>
</dependencies>
<properties>
<!-- 所有内容均使用 UTF-8 -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-releases</id>
<name>Spring Releases</name>
<url>https://repo.spring.io/libs-release</url>
</repository>
<repository>
<id>org.jboss.repository.releases</id>
<name>JBoss Maven Release Repository</name>
<url>https://repository.jboss.org/nexus/content/repositories/releases</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-releases</id>
<name>Spring Releases</name>
<url>https://repo.spring.io/libs-release</url>
</pluginRepository>
</pluginRepositories>
</project>
- 第 10-14 行:父级 Maven 项目,我们将使用其中定义的库;
- 第 18-21 行:用于访问数据库的 Spring Data;
- 第 23-26 行:JPA 规范的 Hibernate 实现;
- 第 28-31 行:SGBD H2;
- 第 33-36 行:数据库通常与连接池配合使用,以避免重复打开/关闭连接。此处使用的实现是 [tomcat-jdbc];
在新项目中,实体 [Customer] 和接口 [CustomerRepository] 保持不变。我们将修改类 [Application],将其拆分为两个类:
- [Config],这将是配置类:
- [Main],该类将作为可执行类;
![]() |
可执行类 [Application] 现为:
package console;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import repositories.CustomerRepository;
import config.AppConfig;
import entities.Customer;
public class Application {
public static void main(String[] args) {
// Spring 上下文实例化
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
CustomerRepository repository = context.getBean(CustomerRepository.class);
// 保存几位客户
repository.save(new Customer("Jack", "Bauer"));
repository.save(new Customer("Chloe", "O'Brian"));
repository.save(new Customer("Kim", "Bauer"));
repository.save(new Customer("David", "Palmer"));
repository.save(new Customer("Michelle", "Dessler"));
...
// 关闭上下文
context.close();
}
}
- 第 9 行:类 [Application] 不再包含配置注解;
- 第 3-7 行:请注意,不再导入 [Spring Boot] 包;
- 第 12 行:实例化 Spring Bean。获取包含所创建 Bean 引用的 Spring 上下文;
- 第 13 行:请求 [CustomerRepository] 类型 Bean 的引用;
用于配置项目的 [Config] 类( )如下所示:
package config;
import javax.persistence.EntityManagerFactory;
import org.apache.tomcat.jdbc.pool.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@EnableTransactionManagement
@EnableJpaRepositories(basePackages = { "repositories" })
@Configuration
// @ComponentScan(basePackages={"package1","package2"})
public class AppConfig {
// 数据库 H2
@Bean
public DataSource dataSource() {
// 数据源 TomcatJdbc
DataSource dataSource = new DataSource();
// 访问配置 JDBC
dataSource.setDriverClassName("org.h2.Driver");
dataSource.setUrl("jdbc:h2:./demo");
dataSource.setUsername("sa");
dataSource.setPassword("");
// 初始打开的连接
dataSource.setInitialSize(1);
// 结果
return dataSource;
}
// 提供商 JPA
@Bean
public JpaVendorAdapter jpaVendorAdapter() {
HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();
hibernateJpaVendorAdapter.setShowSql(false);
hibernateJpaVendorAdapter.setGenerateDdl(true);
hibernateJpaVendorAdapter.setDatabase(Database.H2);
return hibernateJpaVendorAdapter;
}
// EntityManagerFactory
@Bean
public EntityManagerFactory entityManagerFactory(JpaVendorAdapter jpaVendorAdapter, DataSource dataSource) {
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(jpaVendorAdapter);
factory.setPackagesToScan("entities");
factory.setDataSource(dataSource);
factory.afterPropertiesSet();
return factory.getObject();
}
// 事务管理器
@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(entityManagerFactory);
return txManager;
}
}
- 第 17 行:注解 [@EnableTransactionManagement] 表示应解释注解 [@Transactional]。接口 [CrudRepository] 的方法带有这些注解。因此,这些方法将在事务内部执行;
- 第 18 行:注解 [@EnableJpaRepositories] 用于指定包含 Spring Data 接口 [CrudRepository] 的文件夹。这些接口将作为 Spring 组件存在,并在 Spring 上下文中可用;
- 第 19 行:注解 [@Configuration] 将类 [Config] 设为 Spring 配置类;
- 第 20 行:注解 [@ComponentScan] 用于列出需要搜索 Spring 组件的文件夹。 Spring组件是指带有@Service、@Component、@Controller等Spring注解的类。此处除了[AppConfig]类中定义的组件外没有其他组件,因此该注解已被注释掉;
- 第 24-37 行:定义数据源,即数据库 H2。正是第 25 行的 @Bean 注解,使得该方法创建的对象成为由 Spring 管理的组件。 此处方法名称可任意设定。但若第51行的EntityManagerFactory不存在且通过自动配置定义,则该方法必须命名为[dataSource];
- 第 30 行:数据库将命名为 [demo],并生成在项目文件夹中;
- 第40-47行:定义所使用的实现JPA,此处为Hibernate实现。此处方法名可任意设定;
- 第 43 行:不生成 SQL 日志;
- 第 44 行:若数据库不存在,则创建数据库;
- 第50-58行:定义了将管理JPA持久化的EntityManagerFactory。该方法必须命名为[entityManagerFactory];
- 第 51 行:该方法接收两个参数,其类型与之前定义的两个 Bean 相同。Spring 将构建这些 Bean,并将其作为方法参数注入;
- 第 53 行:指定使用的 JPA 实现;
- 第 54 行:指定查找 JPA 实体的文件夹;
- 第 55 行:指定要管理的数据源;
- 第 61-66 行:事务管理器。该方法必须命名为 [transactionManager]。它接收第 51-58 行中的 Bean 作为参数;
- 第 64 行:将事务管理器关联至 EntityManagerFactory;
上述方法可以按任意顺序定义。
运行该项目将得到相同的结果。项目文件夹中会出现一个新文件,即数据库文件 H2:
![]() |
5.1.6. 创建可执行归档
要创建该项目的可执行归档文件,可按以下步骤操作:
![]() |
- 在 [1] 中:创建一个运行配置;
- 在 [2] 中:类型为 [Java Application]
- 在 [3] 中:指定要执行的项目(使用按钮 Browse);
- 在 [4] 中:指定要执行的类;
- [5]:运行配置的名称——可以是任意名称;
![]() |
- 在 [6] 中:导出项目;
- 在 [7] 中:以可执行存档的形式;
- 在 [8] 中:指定要创建的可执行文件的路径和名称;
- 在 [9] 中:在 [5] 中创建的运行配置名称;
10 ![]() |
- 在 [10] 中,生成的归档文件;
完成上述操作后,在包含可执行存档文件的文件夹中打开一个控制台:
按以下方式运行该程序:
.....\dist>java -jar gs-accessing-data-jpa-02.jar
控制台显示的结果如下:
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder 了解更多详情。
mars 10, 2015 5:27:20 PM org.hibernate.jpa.internal.util.LogHelper logPersistenceUnitInformation
INFO: HHH000204: Processing PersistenceUnitInfo [
name: default
...]
mars 10, 2015 5:27:20 PM org.hibernate.Version logVersion
INFO: HHH000412: Hibernate Core {4.3.8.Final}
mars 10, 2015 5:27:20 PM org.hibernate.cfg.Environment <clinit>
INFO: HHH000206: hibernate.properties not found
mars 10, 2015 5:27:20 PM org.hibernate.cfg.Environment buildBytecodeProvider
INFO: HHH000021: Bytecode provider name : javassist
mars 10, 2015 5:27:22 PM org.hibernate.annotations.common.reflection.java.JavaReflectionManager <clinit>
INFO: HCANN000001: Hibernate Commons Annotations {4.0.5.Final}
mars 10, 2015 5:27:22 PM org.hibernate.dialect.Dialect <init>
INFO: HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
mars 10, 2015 5:27:22 PM org.hibernate.hql.internal.ast.ASTQueryTranslatorFactory <init>
INFO: HHH000397: Using ASTQueryTranslatorFactory
mars 10, 2015 5:27:22 PM org.hibernate.tool.hbm2ddl.SchemaUpdate execute
INFO: HHH000228: Running hbm2ddl schema update
mars 10, 2015 5:27:22 PM org.hibernate.tool.hbm2ddl.SchemaUpdate execute
INFO: HHH000102: Fetching database metadata
mars 10, 2015 5:27:22 PM org.hibernate.tool.hbm2ddl.SchemaUpdate execute
INFO: HHH000396: Updating schema
mars 10, 2015 5:27:22 PM org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata
INFO: HHH000262: Table not found: Customer
mars 10, 2015 5:27:22 PM org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata
INFO: HHH000262: Table not found: Customer
mars 10, 2015 5:27:22 PM org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata
INFO: HHH000262: Table not found: Customer
mars 10, 2015 5:27:22 PM org.hibernate.tool.hbm2ddl.SchemaUpdate execute
INFO: HHH000232: Schema update complete
Customers found with findAll():
-------------------------------
Customer[id=1, firstName='Jack', lastName='Bauer']
Customer[id=2, firstName='Chloe', lastName='O'Brian']
Customer[id=3, firstName='Kim', lastName='Bauer']
Customer[id=4, firstName='David', lastName='Palmer']
Customer[id=5, firstName='Michelle', lastName='Dessler']
Customer found with findOne(1L):
--------------------------------
Customer[id=1, firstName='Jack', lastName='Bauer']
Customer found with findByLastName('Bauer'):
--------------------------------------------
Customer[id=1, firstName='Jack', lastName='Bauer']
Customer[id=3, firstName='Kim', lastName='Bauer']
5.1.7. 创建项目 [Spring Data]
要创建一个 Spring Data 项目骨架,可以按以下步骤操作:
![]() |
- 在 [1] 中,创建一个新项目;
- 在 [2] 中:类型为 [Spring Starter Project];
- 生成的项目将是一个Maven项目。在[3]中,指定项目组的名称;
- 在 [4] 中:指定构建项目时将生成的构建产物(此处为一个 JAR 文件)的名称;
- 在 [5] 中:指定项目的 Eclipse 名称——名称可以任意设定(不必与 [4] 相同);
- 在 [7] 中:指定将创建一个包含 [JPA] 层的项目,该层包含 SGBD 和 MySQL。 此类项目所需的依赖项将被包含在文件 [pom.xml] 中;
![]() |
- 在 [8] 中,输入项目文件夹名称;
- 在 [9] 中,完成向导;
![]() |
- 在 [10] 中:生成的项目;
文件 [pom.xml] 包含项目所需的依赖项 JPA:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st.springdata</groupId>
<artifactId>intro-spring-data-01</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>intro-spring-data-01</name>
<description>démo spring data avec table de produits</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.3.RELEASE</version>
<relativePath/> <!-- 从存储库中查找父级 -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<start-class>demo.IntroSpringData01Application</start-class>
<java.version>1.7</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- 第 14-19 行:父 Maven 项目;
- 第 28-31 行:JPA 所需的依赖项——将包含 [Spring Data];
- 第 32-36 行:MySQL 对驱动程序 JDBC 的依赖;
- 第 37-41 行:JUnit 测试所需的依赖项(已集成 Spring);
可执行类 [Application] 本身不执行任何操作,但已预先配置:
package demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class IntroSpringData01Application {
public static void main(String[] args) {
SpringApplication.run(IntroSpringData01Application.class, args);
}
}
- 注解 [@SpringBootApplication] 将该类设为项目的自动配置类;
测试类 [ApplicationTests] 无实际功能,但已预配置:
package demo;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = IntroSpringData01Application.class)
public class IntroSpringData01ApplicationTests {
@Test
public void contextLoads() {
}
}
- 第 9 行:注解 [@SpringApplicationConfiguration] 允许使用配置文件 [IntroSpringData01Application]。因此,该测试类将受益于该文件中定义的所有 Bean;
- 第 8 行:注解 [@RunWith] 实现了 Spring 与 JUnit 的集成:该类将能够作为 JUnit 测试执行。 [@RunWith] 是 JUnit 的注解(第 4 行),而类 [SpringJUnit4ClassRunner] 则是 Spring 类(第 6 行);
现在我们已经有了一个 JPA 应用程序的骨架,可以对其进行完善。




















