使用 JPA 访问数据

本指南将带你构建一个应用程序,使用 Spring Data JPA 在关系型数据库中存储和读取数据。

你将构建什么

你将构建一个把 Customer 对象存储在内存数据库中的应用程序。

你需要什么

如何完成本指南

与大多数 Spring 入门指南一样,你可以从零开始,逐步完成,也可以跳过已经熟悉的基本配置步骤。两种方式最终都会得到可工作的代码。

要从零开始,请继续阅读“使用 Spring Initializr 开始”。要跳过基础步骤,请:

  1. 下载并解压本指南的源码仓库,或者用 Git 克隆:
git clone https://github.com/spring-guides/gs-accessing-data-jpa.git
  1. 切换到 gs-accessing-data-jpa/initial 目录。
  2. 直接阅读“定义一个简单实体”。

完成之后,可以将结果与 gs-accessing-data-jpa/complete 中的代码进行比较。

使用 Spring Initializr 开始

可以打开这个预先初始化的项目,点击 Generate 下载 ZIP 文件。它已按本教程示例所需的配置设置好。

要手动初始化项目:

  1. 访问 https://start.spring.io。这个服务会引入应用程序所需的全部依赖,并代你完成大部分配置。
  2. 选择 Gradle 或 Maven,以及希望使用的语言。
  3. 在 Artifact 字段中输入 accessing-data-jpa。
  4. 点击 Dependencies,选择 Spring Data JPA,然后选择 H2 Database。
  5. 点击 Generate。
  6. 下载生成的 ZIP 文件,其中是按照你的选择配置好的应用程序归档。

定义一个简单实体

在这个示例中,你会存储 Customer 对象,每个对象对应的类都被标注为 JPA 实体。下面是 Customer 类:

Java

package com.example.accessingdatajpa;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.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);
	}

	public Long getId() {
		return id;
	}

	public String getFirstName() {
		return firstName;
	}

	public String getLastName() {
		return lastName;
	}
}

Kotlin

package com.example.accessingdatajpa

import jakarta.persistence.Entity
import jakarta.persistence.GeneratedValue
import jakarta.persistence.GenerationType
import jakarta.persistence.Id

@Entity
class Customer(
    var firstName: String = "",
    var lastName: String = ""
) {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    var id: Long? = null

    override fun toString(): String =
        "Customer[id=$id, firstName='$firstName', lastName='$lastName']"
}

Customer 类有三个属性:id、firstName 和 lastName。Java 版本还提供两个构造方法。默认构造方法仅供 JPA 使用;你不会直接使用它,所以它被设为 protected。另一个构造方法供你创建要保存到数据库中的 Customer 实例。

Customer 类上的 @Entity 注解表明它是一个 JPA 实体。由于没有 @Table 注解,假定这个实体映射到名为 Customer 的表。

Customer 对象的 id 属性带有 @Id 注解,让 JPA 将它识别为对象的 ID。该属性还带有 @GeneratedValue,表示 ID 应自动生成。

另外两个属性 firstName 和 lastName 没有注解,假定它们映射到与属性同名的列。

便捷的 toString() 方法会输出客户对象的属性。

创建简单查询

Spring Data JPA 专注于使用 JPA 在关系型数据库中存储数据。它最有吸引力的特性,是能够在运行时根据 repository 接口自动创建实现。

要了解它的工作方式,可以创建一个处理 Customer 实体的 repository 接口:

Java

package com.example.accessingdatajpa;

import java.util.List;

import org.springframework.data.repository.CrudRepository;

public interface CustomerRepository extends CrudRepository<Customer, Long> {

	List<Customer> findByLastName(String lastName);

	Customer findById(long id);
}

Kotlin

package com.example.accessingdatajpa

import org.springframework.data.repository.CrudRepository

interface CustomerRepository : CrudRepository<Customer, Long> {
    fun findByLastName(lastName: String): List<Customer>
}

CustomerRepository 扩展 CrudRepository 接口。在 CrudRepository 的泛型参数中,指定了实体类型 Customer 和 ID 类型 Long。通过扩展它,CustomerRepository 继承了多种持久化方法,包括保存、删除与查找 Customer 实体的方法。

Spring Data JPA 还允许你通过声明方法签名来定义其他查询。例如,CustomerRepository 中包含 findByLastName() 方法。

在典型的 Java 应用程序中,你可能需要编写一个实现 CustomerRepository 的类。但这正是 Spring Data JPA 的强大之处:无需编写 repository 接口的实现,Spring Data JPA 会在应用程序运行时创建它。

现在,可以把这个示例组装起来,看看它的运行效果。

创建应用程序类

Spring Initializr 会为应用程序创建一个简单的类。下面是它为此示例创建的类:

Java

package com.example.accessingdatajpa;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class AccessingDataJpaApplication {

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

}

Kotlin

package com.example.accessingdatajpa

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication

@SpringBootApplication
class AccessingDataJpaApplication

fun main(args: Array<String>) {
    runApplication<AccessingDataJpaApplication>(*args)
}

@SpringBootApplication 是一个便捷注解,它包含以下功能:

  • @Configuration:把这个类标记为应用程序上下文的 bean 定义来源。
  • @EnableAutoConfiguration:告诉 Spring Boot 根据类路径设置、其他 bean 和各种属性设置添加 bean。例如,当类路径中存在 spring-webmvc 时,这个注解会将应用程序识别为 Web 应用程序,并启用关键行为,例如设置 DispatcherServlet。
  • @ComponentScan:让 Spring 在 com/example 包中寻找其他组件、配置和服务,从而找到控制器。

main() 方法通过 Spring Boot 的 SpringApplication.run() 启动应用程序。你注意到了吗?其中没有一行 XML,也没有 web.xml 文件。这个应用程序完全用 Java 编写,你无需处理底层装配或基础设施配置。

现在需要修改 Initializr 创建的简单类。要生成输出——本例中输出到控制台——需要设置 logger,然后准备一些数据,并用这些数据生成输出。完整的 AccessingDataJpaApplication 类如下:

Java

package com.example.accessingdatajpa;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 AccessingDataJpaApplication {

	private static final Logger logger = LoggerFactory.getLogger(AccessingDataJpaApplication.class);

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

	@Bean
	public CommandLineRunner demo(CustomerRepository repository) {
		return (args) -> {
			// save a few 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
			logger.info("Customers found with findAll():");
			logger.info("-------------------------------");
			repository.findAll().forEach(customer -> {
				logger.info(customer.toString());
			});
			logger.info("");

			// fetch an individual customer by ID
			Customer customer = repository.findById(1L);
			logger.info("Customer found with findById(1L):");
			logger.info("--------------------------------");
			logger.info(customer.toString());
			logger.info("");

			// fetch customers by last name
			logger.info("Customer found with findByLastName('Bauer'):");
			logger.info("--------------------------------------------");
			repository.findByLastName("Bauer").forEach(bauer -> {
				logger.info(bauer.toString());
			});
			logger.info("");
		};
	}

}

Kotlin

package com.example.accessingdatajpa

import org.slf4j.LoggerFactory
import org.springframework.boot.CommandLineRunner
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.context.annotation.Bean

private val logger = LoggerFactory.getLogger(AccessingDataJpaApplication::class.java)

@SpringBootApplication
class AccessingDataJpaApplication {

    @Bean
    fun demo(repository: CustomerRepository) = CommandLineRunner {
        // save a few customers
        repository.saveAll(
            listOf(
                Customer("Jack", "Bauer"),
                Customer("Chloe", "O'Brian"),
                Customer("Kim", "Bauer"),
                Customer("David", "Palmer"),
                Customer("Michelle", "Dessler")
            )
        )

        // fetch all customers
        logger.info("Customers found with findAll():")
        logger.info("-------------------------------")
        repository.findAll().forEach { customer ->
            logger.info(customer.toString())
        }
        logger.info("")

        // fetch an individual customer by ID
        val customer = repository.findById(1L).orElseThrow()
        logger.info("Customer found with findById(1L):")
        logger.info("--------------------------------")
        logger.info(customer.toString())
        logger.info("")

        // fetch customers by last name
        logger.info("Customer found with findByLastName('Bauer'):")
        logger.info("--------------------------------------------")
        repository.findByLastName("Bauer").forEach { bauer ->
            logger.info(bauer.toString())
        }
        logger.info("")
    }
}

fun main(args: Array<String>) {
    runApplication<AccessingDataJpaApplication>(*args)
}

AccessingDataJpaApplication 类包含一个 demo() 方法,用来演示 CustomerRepository 的几种操作。它首先从 Spring 应用程序上下文中取得 CustomerRepository,然后保存几个 Customer 对象,展示 save() 方法,并准备后续操作所需的数据。接着调用 findAll() 从数据库取得全部 Customer 对象,再调用 findById() 按 ID 查找单个客户。

最后,它调用 findByLastName(),找出姓氏为 Bauer 的全部客户。demo() 返回一个 CommandLineRunner bean,应用程序启动时会自动运行其中的代码。

构建可执行 JAR

可以用 Gradle 或 Maven 从命令行运行应用程序,也可以构建单个可执行 JAR 文件,其中包含所有必要的依赖、类与资源,再运行它。构建可执行 JAR 后,更容易在整个开发生命周期中,跨不同环境将服务作为应用程序交付、进行版本管理与部署。

如果使用 Gradle,可以通过 ./gradlew bootRun 运行应用程序;也可以使用 ./gradlew build 构建 JAR,然后运行:

java -jar build/libs/gs-accessing-data-jpa-0.0.1-SNAPSHOT.jar

如果使用 Maven,可以通过 ./mvnw spring-boot:run 运行应用程序;也可以使用 ./mvnw clean package 构建 JAR,然后运行:

java -jar target/gs-accessing-data-jpa-0.0.1-SNAPSHOT.jar

按指南运行应用程序后,应看到与下列内容相似的输出:

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 findById(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']

小结

你已经编写了一个简单应用程序,使用 Spring Data JPA 将对象保存到数据库并取回它们,整个过程无需编写具体的 repository 实现。

另请参阅

以下指南也可能有帮助:

希望编写新的指南,或为现有指南作贡献?请参阅贡献指南。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容