Skip to content

5. Spring Data JPA 简介

在本章中,我们将探讨以下架构:

我们在 [DAO] 层与 DBMS 的 JDBC 驱动程序之间插入一个 [JPA](Java Persistence API)层。 从此以后,JPA层负责向DBMS发出SQL命令。[DAO]层不再处理SQL命令,而仅处理称为JPA实体的对象——这些对象代表了所用数据库中的各个表。这些实体的字段通过Java注解与表列建立唯一映射关系。正是这种机制使得JPA层能够将[DAO]层对JPA实体的操作转换为SQL语句。

Spring Data 是 Spring 框架的一个分支,专注于数据访问,无论数据存储在关系型数据库管理系统(RDBMS)、NoSQL 数据库还是其他类型的存储中。在此,我们仅关注 RDBMS 以及通过 JPA 访问它们。 后续内容中,我们有时会使用 [Spring JPA] 来实际指代 [Spring Data JPA]。在上述架构中,[Spring Data] 层为 [DAO] 层提供了管理 JPA 实体的工具。

JPA 实际上是一项规范。我们将测试其三个实现:

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>
        <!-- use UTF-8 for everything -->
        <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 行:定义对 H2 数据库管理系统 (DBMS) 的依赖,它允许您创建和管理内存数据库。

让我们来看看这些依赖项提供的类:

数量众多:

  • 其中一部分属于 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 实现将使用所用数据库管理系统(此处为 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 行)。它继承了 [CrudRepository] 接口,这是一个 Spring Data 接口(第 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 持久化到数据库中。它使用 DBMS 分配的主键将实体持久化。它还允许更新由主键 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 持久化查询语言)查询来实现它:

select t from T t where t.something=:value

因此,类型 T 必须有一个名为 [something] 的字段。因此,该方法

List<Customer> findByLastName(String lastName);

将通过类似以下代码实现:

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. [控制台]层

  

[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 {
        // save a couple of customers
        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"));
 
        // fetch all customers
        System.out.println("Customers found with findAll():");
        System.out.println("-------------------------------");
        for (Customer customer : repository.findAll()) {
            System.out.println(customer);
        }
        System.out.println();
 
        // fetch an individual customer by ID
        Customer customer = repository.findOne(1L);
        System.out.println("Customer found with findOne(1L):");
        System.out.println("--------------------------------");
        System.out.println(customer);
        System.out.println();
 
        // fetch customers by last name
        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] 根据各种属性(特别是项目类路径中的内容)自动创建若干 Bean。由于 Hibernate 库位于类路径中,因此 [entityManagerFactory] Bean 将使用 Hibernate 实现。由于 H2 DBMS 库位于类路径中,因此 [dataSource] 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 行:[entityManagerFactory] Bean 由 Spring 类 [LocalContainerEntityManagerFactory] 实现。它管理 [JPA] 层;
  • 第 12 行:出现了 [Hibernate]。这是所选的 JPA 实现;
  • 第 19 行:Hibernate 方言是与数据库管理系统 (DBMS) 配合使用的 SQL 变体。此处的 [H2Dialect] 方言表明 Hibernate 将与 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 Database -->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
        </dependency>
        <!-- Tomcat JDBC -->
        <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-jdbc</artifactId>
        </dependency>
    </dependencies>
 
    <properties>
        <!-- use UTF-8 for everything -->
        <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 行: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) {
        // instantiation Spring context
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        CustomerRepository repository = context.getBean(CustomerRepository.class);
 
        // save a couple of customers
        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"));
 
        ...

        // closing context
        context.close();
    }
 
}
  • 第 9 行:[Application] 类不再包含任何配置注解;
  • 第 3–7 行:请注意,不再包含任何 [Spring Boot] 包导入;
  • 第 12 行:我们实例化 Spring Bean。我们获取 Spring 上下文,其中包含对已创建 Bean 的引用;
  • 第 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 database
    @Bean
    public DataSource dataSource() {
        // data source TomcatJdbc
        DataSource dataSource = new DataSource();
        // configuration access JDBC
        dataSource.setDriverClassName("org.h2.Driver");
        dataSource.setUrl("jdbc:h2:./demo");
        dataSource.setUsername("sa");
        dataSource.setPassword("");
        // an initially open connection
        dataSource.setInitialSize(1);
        // result
        return dataSource;
    }
 
    // the provider 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();
    }
 
    // Transaction manager
    @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 应用程序] 类型
  • 在 [3] 中:指定要运行的项目(使用“浏览”按钮);
  • 在 [4] 中:指定要运行的类;
  • 在 [5] 中:运行配置的名称——可以是任意名称;
  • 在 [6] 中:导出项目;
  • 在 [7] 中:作为可执行 JAR 归档文件;
  • 在 [8] 中:指定要创建的可执行文件的路径和名称;
  • 在 [9] 中:在 [5] 中创建的运行配置的名称;
10
  
  • 在 [10] 中,生成的归档文件;

完成上述操作后,在包含可执行归档文件的文件夹中打开一个控制台:

.....\dist>dir
12/06/2014  09:11        15 104 869 gs-accessing-data-jpa-02.jar

该归档文件的执行方式如下:


.....\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 for further details.
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] 中:指定您正在创建一个使用 MySQL 数据库管理系统(DBMS)并包含 [JPA] 层的项目。此类项目所需的依赖项随后将被包含在 [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/> <!-- lookup parent from repository -->
    </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 行:集成 Spring 的 JUnit 测试所需的依赖项;

可执行的 [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 应用程序的骨架,可以继续完善它。