Spring Boot 2和JPA入门


在本教程中,我们将构建一个简单的Spring Boot 2应用程序,可以使用JPA访问Postgres数据库。

背景: 
在我们开始之前,让我们先了解一些定义。
 
什么是JPA?
JPA代表“Java Persistence API”。它是一个java规范,定义了如何在Java平台上管理关系数据库中的数据。
 
什么是Hibernate?
Hibernate ORM,通常简称为“Hibernate”,是JPA规范的一种实现。虽然直接使用Hibernate API很常见,但现在建议使用JPA接口,Hibernate作为底层框架。人们可以将JPA视为汽车中的控件,将Hibernate视为引擎。
 
什么是Spring Data?
Spring数据是Spring提供的一组技术,它们提供抽象和实用程序框架,旨在促进数据访问配置并减少样板代码。更多信息可以在这里找到   。

设置Spring-boot应用程序:
在我们的示例中,我们将使用JPA / Postgres堆栈设置Spring-boot应用程序。为了开始,让我们添加所需的依赖项。请注意,Spring-boot-starter-parent已被用作父项目。

<dependencies>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </dependency>
        
    </dependencies>

注意列表中的前两个依赖项。第一个是“spring-boot-starter-data-jpa”。添加此依赖项将自动引入Java持久性库,Hibernate-core,Spring-data和spring-boot-starter-jdbc。第二个依赖“postgresql”将添加所需的Postgres驱动程序,并且需要能够与底层的Postgres数据库管理系统进行交互。如果您正在使用其他DBMS,则需要为相应的驱动程序添加依赖项。

下一步是配置Spring启动应用程序以访问Postgres数据库管理系统。为此,我们将设置必须将jdbc url,用户名,密码和hibernate.dll-auto属性(稍后将详细介绍)添加到application.properties文件中。同样,JDBC URL依赖于所使用的DBMS。我们的数据库服务器上未配置密码,因此将其留空(不要在prod环境中执行此操作)。

spring.datasource.url=jdbc:postgresql://localhost/nullbeansdemo
spring.datasource.username=postgres
spring.datasource.password=
spring.jpa.hibernate.ddl-auto=create

DDL代表“数据定义语言”。它是一种用于定义数据库结构和模式的编程语言。spring.jpa.hibernate.ddl-auto的值可以设置属性以指示Hibernate创建我们的数据库模式,更新它,验证它,或者什么都不做。在我们的示例中,我们将值设置为“create”。这意味着在应用程序启动时,Hibernate将检查我们创建的数据模型,并尝试创建与这些模型匹配的数据库模式。这样做的好处是您不需要主要的不同数据库创建脚本集。如果您的应用程序部署在具有不同数据库系统供应商的不同平台上,则效益更高,因为您不需要为不同的供应商系统创建脚本。当然,可以通过将值设置为“none”来关闭此功能。在这种情况下,您需要确保数据库模式与所需的数据库表一起存在。 这里

建立我们的第一个实体:
现在我们已经完成了应用程序配置,现在是时候创建我们的第一个持久性实体了。在我们的示例中,我们将创建一个“银行帐户”实体。我们将在后面的文章中扩展该示例。目前,我们的银行账户实体将如下所示:

package com.nullbeans.persistence.models;

import javax.persistence.*;

@Entity
public class BankAccount {

    private long id;

    private int version;

    private String accountNumber;

    private boolean isActive;

    public BankAccount() {
    }

    @Id
    @GeneratedValue(strategy= GenerationType.AUTO)
    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    @Version
    public int getVersion() {
        return version;
    }

    public void setVersion(int version) {
        this.version = version;
    }

    public String getAccountNumber() {
        return accountNumber;
    }

    public void setAccountNumber(String accountNumber) {
        this.accountNumber = accountNumber;
    }

    public boolean isActive() {
        return isActive;
    }

    public void setActive(boolean active) {
        isActive = active;
    }

    @Override
    public String toString() {
        return "BankAccount{" +
               
"id=" + id +
               
", version=" + version +
               
", accountNumber='" + accountNumber + '\'' +
               
", isActive=" + isActive +
                '}';
    }
}

我们来看看这个类中使用的一些注释:
 
@Entity: 使用@Entity注释的类被视为持久性实体定义。通常这意味着类映射到数据库表。由于Hibernate使用此批注扫描类的模型包,因此需要此批注。
@Id:  指定带注释的变量将被视为实体的主键。
@GeneratedValue:此批注指示给定值是生成的值。换句话说,用户不应该明确设置此值。它将自动生成。生成主键有多种策略,例如使用序列中的值,使用基础表或让数据库决定如何创建值。为简单起见,我们使用“AUTO”,这意味着Hibernate将决定如何生成密钥。
@Version:  版本注释表示给定实体的版本号。每次修改实体的实例时,该实例的版本都会递增。版本号从0开始。

设置存储库:
需要编写和实现DAO的日子已经一去不复返了,一次只有一个接口,类和方法,以便有办法访问和管理特定实体的数据。通过Spring-Data CrudRepository接口,数据访问对象的实现得到了极大的简化。所需要的只是创建一个实现“CrudRepository”接口的接口,您就完成了。

package com.nullbeans.persistence.repositories;

import com.nullbeans.persistence.models.BankAccount;
import org.springframework.data.repository.CrudRepository;

import java.util.List;

public interface BankAccountRepository extends CrudRepository<BankAccount, Long> {

    List<BankAccount> findByAccountNumber(String accountNumber);

}

BankAccountRepository只需要扩展CrudRepository接口,使用BankAccount类和Long(主键类)作为类型化参数。默认情况下,CrudRepository接口提供了方便的方法,如  save,saveAll,findById,findall,delete,deleteAll等。 如果我们需要额外的功能,例如通过银行帐号进行搜索,该怎么办?在这种情况下,我们只需要创建一个名为“findByAccountNumber”的方法。由于我们使用与实体类中定义的相同的变量名,因此Spring数据可以自动计算出搜索参数和搜索查询。

测试
让我们通过尝试实体和我们刚刚创建的相应存储库将所有内容放在一起。为此,我们将创建一个“CommandLineRunner”bean。一旦我们启动Spring-boot应用程序,这个bean就会运行,当需要启动整个应用程序上下文并针对特定过程进行测试时,它是一个有用的开发功能。我们将从应用程序上下文中获取银行存储库,并尝试执行一些保存/搜索操作。

package com.nullbeans;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.nullbeans.persistence.models.BankAccount;
import com.nullbeans.persistence.repositories.BankAccountRepository;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class NullbeansPersistenceApplication {

    private static final Logger log = LoggerFactory.getLogger(NullbeansPersistenceApplication.class);

    public static void main(String[] args) {
        SpringApplication.run(NullbeansPersistenceApplication.class, args);
    }


    @Bean
    public CommandLineRunner example1(BankAccountRepository bankAccountRepository){

        return new CommandLineRunner() {
            @Override
            public void run(String... args) throws Exception {
                BankAccount bankAccount = new BankAccount();
                bankAccount.setAccountNumber("SAVINGS100");
                bankAccount.setActive(true);
                bankAccountRepository.save(bankAccount);

                BankAccount bankAccount1 = new BankAccount();
                bankAccount1.setAccountNumber(
"CREDIT101");
                bankAccount1.setActive(true);
                bankAccountRepository.save(bankAccount1);

                log.info(
"Saved the bank accounts successfully\r\n");

                log.info(
"Quering DB for bank accounts");
                for(BankAccount persistentAccount: bankAccountRepository.findAll()){
                    log.info(
"Found account: {}\r\n", persistentAccount);
                }

                log.info(
"Searching by bank account number");
                BankAccount searchResult = bankAccountRepository.findByAccountNumber(
"SAVINGS100").get(0);
                log.info(
"Found bank account: {}", searchResult);


            }
        };

    }
}

在我们的示例中,我们创建了两个银行帐户。使用存储库的save方法保存每个银行帐户。然后,我们将使用find all方法使用findall方法获取数据库中的所有帐户。最后一步是使用我们的其他搜索方法,使用帐号搜索银行帐户。如果我们运行程序,我们将得到以下输出:

2019-01-14 21:51:49.975  INFO 10292 --- [           main] j.LocalContainerEntityManagerFactoryBean : 
Initialized JPA EntityManagerFactory for persistence unit 'default'
2019-01-14 21:51:50.658  INFO 10292 --- [           main] c.n.NullbeansPersistenceApplication      : 
Started NullbeansPersistenceApplication in 5.523 seconds (JVM running for 6.361)
2019-01-14 21:51:50.751  INFO 10292 --- [           main] c.n.NullbeansPersistenceApplication      : 
Saved the bank accounts successfully
2019-01-14 21:51:50.751  INFO 10292 --- [           main] c.n.NullbeansPersistenceApplication      : 
Quering DB for bank accounts
2019-01-14 21:51:50.775  INFO 10292 --- [           main] o.h.h.i.QueryTranslatorFactoryInitiator  : 
HHH000397: Using ASTQueryTranslatorFactory
2019-01-14 21:51:50.927  INFO 10292 --- [           main] c.n.NullbeansPersistenceApplication      : 
Found account: BankAccount{id=1, version=0, accountNumber='SAVINGS100', isActive=true}
2019-01-14 21:51:50.928  INFO 10292 --- [           main] c.n.NullbeansPersistenceApplication      : 
Found account: BankAccount{id=2, version=0, accountNumber='CREDIT101', isActive=true}
2019-01-14 21:51:50.928  INFO 10292 --- [           main] c.n.NullbeansPersistenceApplication      : 
Searching by bank account number
2019-01-14 21:51:50.977  INFO 10292 --- [           main] c.n.NullbeansPersistenceApplication      : 
Found bank account: BankAccount{id=1, version=0, accountNumber='SAVINGS100', isActive=true}
2019-01-14 21:51:50.981  INFO 10292 --- [       Thread-3] j.LocalContainerEntityManagerFactoryBean : 
Closing JPA EntityManagerFactory for persistence unit 'default'
2019-01-14 21:51:50.984  INFO 10292 --- [       Thread-3] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2019-01-14 21:51:50.988  INFO 10292 --- [       Thread-3] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.

如您所见,存储库能够成功使用帐号找到银行帐户。findall方法也能够在保存之后找到我们所有的实体。请注意,如果您的应用程序有多个带有JPA注释实体的软件包,那么Spring引导将自动扫描它们。如果您只想扫描特定的包,则可以使用  @EntityScan 注释。

EntityScan({"com.nullbeans.persistence.models"})
@SpringBootApplication
public class NullbeansPersistenceApplication {

现在,让我们快速浏览数据库方面。我们将看到Hibernate已经自动创建了我们的模式,并且我们的数据已经保留。

总结
在本教程中,我们探讨了如何使用JPA后端启动并运行Spring启动应用程序。我们在POM.xml中添加了所需的Spring JPA依赖项和所需的DBMS驱动程序。然后我们将所需的配置添加到application.properties文件中。我们使用JPA注释定义了持久性实体,并创建了一个用于访问该实体数据的Repository。最后,我们能够使用CommandLineRunner bean测试应用程序,并且我们能够确认应用程序是否按预期运行。点击标题见原文