Skip to content

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 实际上是一项规范。我们将测试其三个实现:

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 持久化查询语言)查询来实现它:

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. [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>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 了解更多详情。
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 应用程序的骨架,可以对其进行完善。