学习目标
(如果没有了解可以去我主页看看 第一章的内容来学习)我们已经对Spring Boot 的基本概念、约定优于配置、自动配置工作原理等做了了解,掌握了项目构建、配置文件、单元测试等知识,发现使用Spring Boot之后,仅仅三步即可快速搭建起一个项目:1.构建项目;2、编写代码、测试;3.启动运行。对于开发一个完整的项目,按照MVC设计模式,我们首先开发模型,先完成数据访问层代码。
本章我们学习数据访问层技术,先学习JPA。JPA是Sun官方提出的Java持久化规范。它为Java开发人员提供了一种对象/关系映射工具来管理Java应用中的关系数据。通过Spring Data JPA,我们仅仅通过少量配置即可实现单表大部分CRUD操作,进行分页等,还能自定义QL语句,自定义动态查询完成复杂查询。
2.1 相关概念
2.1.1 JPA由来
JPA(Java Persistence API)是一个用于对象关系映射(ORM)的Java API规范,它允许开发者将Java对象映射到关系数据库中的表,并处理对象数据的持久化。JPA是Java EE 5规范的一部分,并随后被包括在Jakarta EE(原Java EE)中。JPA提供了一个标准的框架来管理Java类和数据库表之间的映射关系,以及执行CRUD(创建、读取、更新、删除)操作。
要使用JPA,你通常会使用一个JPA提供者,如Hibernate、EclipseLink或OpenJPA。这些提供者实现了JPA规范,并提供了额外的特性和优化。
以下是一个简单的示例,展示了如何使用JPA(通过Hibernate实现)来编写Java代码以实现基本的数据库操作。
首先,你需要在你的项目中包含JPA和Hibernate的依赖。如果你使用Maven,你的pom.xml可能包含类似下面的依赖:
<!-- JPA API -->
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>javax.persistence-api</artifactId>
<version>2.2</version>
</dependency>
<!-- Hibernate -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.4.32.Final</version>
</dependency>
<!-- 数据库驱动(以H2为例) -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.200</version>
</dependency>
然后,你需要定义一个实体类(Entity),例如一个Person类:
import javax.persistence.Entity;
import javax.persistence.Id;
@Entity
public class Person {
@Id
private Long id;
private String name;
// 构造函数、getter和setter省略
}
接下来,你需要配置JPA提供者(Hibernate)以及数据库连接。这可以通过在src/main/resources/META-INF/persistence.xml中定义一个持久化单元(persistence unit)来完成:
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd"
version="2.2">
<persistence-unit name="myPersistenceUnit">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<properties>
<!-- 数据库连接属性 -->
<property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
<property name="javax.persistence.jdbc.url" value="jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1"/>
<property name="javax.persistence.jdbc.user" value="sa"/>
<property name="javax.persistence.jdbc.password" value=""/>
<!-- Hibernate属性 -->
<property name="hibernate.hbm2ddl.auto" value="update"/>
<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
</properties>
</persistence-unit>
</persistence>
最后,你可以编写Java代码来执行数据库操作,使用EntityManager来管理实体:
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
public class Main {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("myPersistenceUnit");
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
// 创建并保存一个Person实例
Person person = new Person();
person.setName("John Doe");
em.persist(person);
em.getTransaction().commit();
em.close();
emf.close();
}
}
注意:这个示例为了简化而省略了一些细节,如错误处理和实体类的完整实现(包括构造函数、getter和setter)。在实际应用中,你应该包含这些部分,并确保你的应用能够处理可能出现的异常和错误情况。
2.1.2 JPA 是什么
JPA(Java Persistence API)本身不是Java代码,而是一个Java API的规范,它定义了一系列用于对象关系映射(ORM)的接口和注解。JPA使得Java开发者能够以面向对象的方式操作数据库,而无需直接编写SQL语句。
然而,要使用JPA,你需要在你的Java项目中包含JPA提供者(如Hibernate、EclipseLink等)的库,并通过编写Java代码来使用这些库提供的API和注解。
以下是一个简化的例子,展示了如何在Java代码中使用JPA(通常通过JPA提供者如Hibernate)来定义一个实体类(Entity)并使用EntityManager来执行数据库操作。
- 定义实体类
首先,你需要定义一个实体类,该类使用JPA注解来映射到数据库中的表。
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
@Entity
public class Person {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
// 构造函数、getter和setter省略
// 例如:
public Person() {}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
-
配置JPA提供者
接下来,你需要在项目中配置JPA提供者(如Hibernate)和数据库连接。这通常通过persistence.xml文件(位于src/main/resources/META-INF目录下,或者在基于Spring Boot的项目中,可能通过application.properties或application.yml文件来配置)来完成。 -
使用EntityManager
然后,在你的Java代码中,你可以使用EntityManager来管理实体并执行数据库操作。
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
public class JPADemo {
public static void main(String[] args) {
// 创建EntityManagerFactory
EntityManagerFactory emf = Persistence.createEntityManagerFactory("yourPersistenceUnit");
// 创建EntityManager
EntityManager em = emf.createEntityManager();
// 开始事务
em.getTransaction().begin();
try {
// 创建并保存一个Person实例
Person person = new Person();
person.setName("John Doe");
em.persist(person);
// 提交事务
em.getTransaction().commit();
} catch (Exception e) {
// 如果发生异常,则回滚事务
if (em.getTransaction().isActive()) {
em.getTransaction().rollback();
}
e.printStackTrace();
} finally {
// 关闭EntityManager和EntityManagerFactory
em.close();
emf.close();
}
}
}
请注意:你需要将"yourPersistenceUnit"替换为你的persistence.xml文件中定义的持久化单元的名称。
此外,上面的例子为了简化而省略了一些细节,如错误处理、事务管理(在实际应用中,你可能会使用Spring框架的声明式事务管理来简化事务的处理)以及实体类的完整实现(包括构造函数、getter和setter)。在实际项目中,你应该包含这些部分,并确保你的代码能够处理可能出现的异常和错误情况。
2.1.3 Spring Data JPA
Spring Data JPA 是 Spring 框架的一个子项目,旨在简化数据访问层的开发。它提供了一套丰富的接口和注解,使得开发者可以轻松地定义数据访问层,而无需编写大量的样板代码。Spring Data JPA 建立在 JPA 之上,因此你可以使用 JPA 的所有特性,同时享受 Spring Data 提供的便利。
以下是一个使用 Spring Data JPA 的 Java 代码示例,包括定义一个实体类、一个仓库接口,以及一个简单的服务类来演示如何使用这些组件。
- 定义实体类
首先,定义一个 JPA 实体类,该类映射到数据库中的一张表。
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class Person {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
// 构造函数、getter和setter省略
public Person() {}
// 示例getter和setter
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
- 定义仓库接口
然后,定义一个继承自 JpaRepository(或 CrudRepository)的接口。Spring Data JPA 会为你实现这个接口,提供一系列用于数据访问的方法。
import org.springframework.data.jpa.repository.JpaRepository;
public interface PersonRepository extends JpaRepository<Person, Long> {
// 这里可以定义额外的查询方法,Spring Data JPA 会根据方法名自动生成SQL
// 例如:List<Person> findByFirstName(String firstName);
}
- 使用服务类
最后,在服务层中使用这个仓库接口来执行数据操作。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class PersonService {
@Autowired
private PersonRepository personRepository;
public Person savePerson(Person person) {
return personRepository.save(person);
}
// 这里可以添加其他业务逻辑方法
}
- 配置
你需要在 Spring Boot 应用的 application.properties 或 application.yml 文件中配置数据库连接信息,以及(可选地)JPA 的特定设置。
# application.properties 示例
spring.datasource.url=jdbc:mysql://localhost:3306/yourdatabase
spring.datasource.username=root
spring.datasource.password=secret
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
- 运行和测试
现在,你可以运行你的 Spring Boot 应用,并使用 PersonService 类中的方法来保存、查询或更新 Person 实体。
请注意:以上示例仅展示了 Spring Data JPA 的基本用法。Spring Data JPA 还支持更复杂的查询,包括使用 @Query 注解定义自定义查询、使用 Specification 和 Example 进行动态查询等。此外,你还可以利用 Spring Data JPA 的审计、分页和排序等高级特性来增强你的数据访问层。
2.2 快速上手
JPA(Java Persistence API)是一种用于对象关系映射(ORM)的Java API,它允许开发者将Java对象映射到数据库表中,而无需编写大量的JDBC代码。下面是一个使用JPA快速上手的Java代码示例,包括实体类、仓库接口、服务类和基本的配置。
- 添加依赖
首先,确保你的项目中已经添加了JPA和数据库相关的依赖。如果你正在使用Maven,可以在pom.xml中添加如下依赖(以Hibernate作为JPA实现和MySQL数据库为例):
<dependencies>
<!-- JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- MySQL 数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
注意:虽然这里使用了Spring Boot的starter,但JPA本身并不依赖于Spring Boot。如果你不使用Spring Boot,你需要添加JPA和Hibernate的依赖,以及数据库驱动。
- 配置数据库连接
在application.properties或application.yml中配置数据库连接信息(如果你使用的是Spring Boot):
# application.properties 示例
spring.datasource.url=jdbc:mysql://localhost:3306/yourdatabase
spring.datasource.username=root
spring.datasource.password=secret
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
- 实体类
定义一个JPA实体类,映射到数据库中的表:
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class Person {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
// 构造函数、getter和setter省略
// 示例getter和setter
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
- 仓库接口
定义一个继承自JpaRepository的接口,用于操作数据库:
import org.springframework.data.jpa.repository.JpaRepository;
public interface PersonRepository extends JpaRepository<Person, Long> {
// 这里可以定义额外的查询方法,例如:
// List<Person> findByFirstName(String firstName);
}
- 服务类
在服务层中使用仓库接口:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class PersonService {
@Autowired
private PersonRepository personRepository;
public Person savePerson(Person person) {
return personRepository.save(person);
}
// 这里可以添加其他业务逻辑方法
}
- 运行和测试
现在,你可以在你的应用程序中创建Person对象,并通过PersonService的savePerson方法将其保存到数据库中。你可以使用JUnit或Spring Boot的测试框架来编写测试用例,验证你的代码是否按预期工作。
请注意:这个示例使用了Spring Boot来简化配置和依赖管理。如果你不使用Spring Boot,你需要自己配置JPA和数据库连接,并可能需要编写更多的样板代码来管理JPA的EntityManager。然而,基本的JPA用法(如定义实体、使用仓库接口)在两种情况下都是相同的。
2.3 核心功能
2.3.1 基本操作
我们可以将Spring Data JPA 基本操作分为两种,一种是Spring Data JPA 默认实现的,另一种是需要根据查询的情况来自行构建。
在Java中,使用JPA进行数据库操作主要涉及定义实体类、配置数据源和事务管理器、创建仓库接口以及使用这些接口在服务层或控制器层中执行CRUD(创建、读取、更新、删除)操作。下面是一个简化的示例,展示如何使用JPA进行基本操作。
- 实体类
首先,定义一个实体类,该类映射到数据库中的表。使用@Entity注解标记类为JPA实体,并使用@Id和@GeneratedValue注解标记主键。
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class Person {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
// 构造函数、getter和setter省略
public Person() {}
public Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
// getter和setter
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
- 仓库接口
接着,创建一个继承自JpaRepository的接口,用于定义数据访问层的方法。JpaRepository已经包含了基本的CRUD方法,你也可以定义自己的查询方法。
import org.springframework.data.jpa.repository.JpaRepository;
public interface PersonRepository extends JpaRepository<Person, Long> {
// 这里可以定义额外的查询方法
}
- 服务层
在服务层中,注入仓库接口,并使用它来执行数据库操作。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class PersonService {
@Autowired
private PersonRepository personRepository;
public Person savePerson(Person person) {
return personRepository.save(person);
}
public List<Person> findAllPersons() {
return personRepository.findAll();
}
public Person findPersonById(Long id) {
return personRepository.findById(id).orElse(null);
}
public void deletePerson(Long id) {
personRepository.deleteById(id);
}
// 可以在这里添加更多的业务逻辑
}
-
配置
如果你不是使用Spring Boot,你需要自己配置数据源、JPA提供商、事务管理器等。但在Spring Boot中,你通常只需要在application.properties或application.yml中配置数据库连接信息,Spring Boot会自动配置其余部分。 -
使用
最后,在你的控制器或任何其他需要访问数据库的地方,注入PersonService并使用它的方法。
请注意,这个示例假设你正在使用Spring Boot,因为Spring Boot大大简化了配置和依赖管理。如果你不使用Spring Boot,你需要手动配置JPA环境,这通常涉及更多的步骤和代码。
注意事项
- 确保你的项目中包含了JPA和数据库相关的依赖。
- 如果你的数据库表名、列名与实体类的属性名不匹配,你可能需要使用@Table、@Column等注解来指定映射关系。
- 对于复杂的查询,你可以使用@Query注解在仓库接口中定义JPQL(Java Persistence Query Language)或原生SQL查询。
- 在生产环境中,确保你的数据库连接信息(如用户名和密码)是安全的,并且不要硬编码在源代码中。
2.3.1.1 预生成方法
在Spring Boot JPA中,“预生成方法”(可能更准确地称为"派生查询"或"基于方法名的查询")是指那些你只需在JPA仓库接口中声明方法名,而不需要编写任何查询逻辑(如JPQL或SQL语句),Spring Data JPA就能够根据方法名自动解析并生成相应查询的方法。这些方法的命名遵循一定的规则,Spring Data JPA能够识别这些规则并将其转换为数据库查询。
下面是一个简单的Spring Boot JPA仓库接口示例,其中包含了几个预生成方法的例子:
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface PersonRepository extends JpaRepository<Person, Long> {
// 根据firstName查找Person列表
List<Person> findByFirstName(String firstName);
// 根据firstName和lastName查找唯一的Person(假设firstName和lastName的组合是唯一的)
Person findByFirstNameAndLastName(String firstName, String lastName);
// 查找所有firstName以特定字符串开头的Person列表
List<Person> findByFirstNameStartingWith(String firstNamePrefix);
// 查找所有lastName以特定字符串结尾的Person列表
List<Person> findByLastNameEndingWith(String lastNameSuffix);
// 查找所有firstName包含特定子字符串的Person列表
List<Person> findByFirstNameContaining(String firstNameSubstring);
// 查找所有firstName为null的Person列表
List<Person> findByFirstNameIsNull();
// 查找所有firstName不为null的Person列表
List<Person> findByFirstNameIsNotNull();
// 根据年龄查找Person列表,使用自定义查询参数
List<Person> findByAge(int age);
// 使用分页和排序的查询(虽然这里没有直接体现在方法名中,但Spring Data JPA支持)
// List<Person> findByFirstName(String firstName, Pageable pageable);
// List<Person> findByFirstNameOrderByLastNameAsc(String firstName);
// 注意:对于更复杂的查询,你可能需要使用@Query注解来定义JPQL或原生SQL查询
}
在这个例子中,PersonRepository 继承自 JpaRepository,它提供了基本的CRUD操作。然后,我们在 PersonRepository 中定义了一系列基于方法名的查询方法。这些方法的名字遵循了Spring Data JPA的查询解析规则,比如 findBy 前缀、属性名(如 firstName、lastName)、比较操作符(如 StartingWith、EndingWith、Containing、IsNull、IsNotNull)以及可选的排序和分页参数(尽管在上面的例子中没有直接展示)。
2.3.1.2 自定义查询
在Spring Boot JPA中,当你需要执行更复杂的查询,而基于方法名的查询功能无法满足需求时,你可以使用@Query注解来定义自定义查询。这个注解可以放在JPA仓库接口的方法上,允许你编写JPQL(Java Persistence Query Language)或原生SQL查询。
下面是一个使用@Query注解来定义自定义查询的Java代码示例:
示例:使用JPQL的自定义查询
首先,假设你有一个Person实体和一个PersonRepository仓库接口。
Person.java
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class Person {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
private int age;
// 构造方法、getter和setter省略
}
PersonRepository.java
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface PersonRepository extends JpaRepository<Person, Long> {
// 使用JPQL查询所有年龄大于指定值的Person
@Query("SELECT p FROM Person p WHERE p.age > :age")
List<Person> findByAgeGreaterThan(@Param("age") int age);
// 使用JPQL进行更复杂的查询,比如根据多个条件查找
@Query("SELECT p FROM Person p WHERE p.firstName = :firstName AND p.lastName LIKE :lastName%")
List<Person> findByFirstNameAndLastNameStartsWith(@Param("firstName") String firstName, @Param("lastName") String lastName);
// 注意:JPQL查询使用的是实体类的属性名,而不是数据库表的列名
}
示例:使用原生SQL的自定义查询
如果你需要编写原生SQL查询(可能是因为查询中包含了数据库特定的功能或是因为性能考虑),你可以将@Query注解的nativeQuery属性设置为true。
PersonRepository.java(添加原生SQL查询)
// ...之前的代码...
@Query(value = "SELECT * FROM persons WHERE age > ?", nativeQuery = true)
List<Person> findByAgeGreaterThanNative(@Param("age") int age);
// 注意:当使用原生SQL时,你需要确保查询的字段名与数据库表的列名一致
// 并且,如果查询结果需要映射到Java对象上,你可能需要定义一个ResultTransformer或使用@SqlResultSetMapping
// 但对于Spring Data JPA的Repository,通常建议使用JPQL,因为Spring Data JPA会为你处理结果映射
注意:在大多数情况下,推荐使用JPQL而不是原生SQL,因为JPQL是独立于数据库的,这意呀着你的查询在不同的数据库之间是可移植的。然而,在某些情况下,如果JPQL无法满足你的需求(比如使用了数据库特定的函数或特性),那么你可以考虑使用原生SQL。
2.3.2 复杂操作
在Spring Boot中处理复杂操作时,通常涉及多个数据访问层的调用、业务逻辑的处理以及可能的异常管理和事务控制。这些操作可能包括数据的聚合、转换、验证以及与外部服务的交互等。
以下是一个简化的示例,展示了如何在Spring Boot应用中处理一个复杂的业务操作。我们将以一个订单系统为例,其中涉及创建订单、更新库存、发送通知等操作。
- 定义实体
首先,定义相关的实体类,比如Order和Product。
@Entity
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Date orderDate;
private BigDecimal total;
// 省略getter和setter
}
@Entity
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private int stock;
// 省略getter和setter
}
- 创建Repository
为实体创建Repository接口。
public interface OrderRepository extends JpaRepository<Order, Long> {
// 可以添加自定义查询
}
public interface ProductRepository extends JpaRepository<Product, Long> {
@Modifying
@Query("UPDATE Product p SET p.stock = p.stock - 1 WHERE p.id = :productId")
void decreaseStock(@Param("productId") Long productId);
}
- 服务层
在服务层中处理复杂的业务逻辑。
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private ProductRepository productRepository;
@Autowired
private EmailService emailService; // 假设有一个用于发送电子邮件的服务
@Transactional
public Order createOrder(Order order, List<Long> productIds) {
// 验证订单和产品ID
// ...
// 检查库存
for (Long productId : productIds) {
Product product = productRepository.findById(productId).orElseThrow(() -> new RuntimeException("Product not found"));
if (product.getStock() <= 0) {
throw new RuntimeException("Insufficient stock for product " + productId);
}
}
// 保存订单
order = orderRepository.save(order);
// 更新库存
for (Long productId : productIds) {
productRepository.decreaseStock(productId);
}
// 发送订单确认邮件(假设订单ID作为邮件内容的一部分)
emailService.sendOrderConfirmation(order.getId());
return order;
}
// 其他业务方法...
}
- 控制器
在控制器中调用服务层的方法。
@RestController
@RequestMapping("/orders")
public class OrderController {
@Autowired
private OrderService orderService;
@PostMapping
public ResponseEntity<Order> createOrder(@RequestBody Order orderDto, @RequestParam List<Long> productIds) {
// 可能需要将DTO转换为实体或其他处理
Order order = // 转换DTO到Order实体或直接从DTO创建新实体
Order createdOrder = orderService.createOrder(order, productIds);
return ResponseEntity.ok(createdOrder);
}
// 其他API端点...
}
- 异常处理和事务管理
- 异常处理:可以在控制器、服务层或仓库层使用@ExceptionHandler注解来处理异常,或者全局异常处理器。
- 事务管理:通过@Transactional注解在服务层方法上确保数据一致性。如果方法中的任何操作失败(例如,由于库存不足而抛出异常),则整个事务将回滚,确保数据不会处于不一致的状态。
- 测试
编写单元测试和集成测试来验证服务层和控制器层的行为。确保覆盖各种边界情况和异常情况。
这个示例仅展示了复杂操作的一个方面,实际项目中可能还需要考虑更多的因素,如安全性、性能优化、日志记录等。
2.3.2.1 自定义QL查询
在Spring Boot中,如果你正在使用JPA(Java Persistence API)来操作数据库,你可能会想要执行自定义的SQL或JPQL(Java Persistence Query Language)查询。下面,我将向你展示如何在Spring Boot项目中通过JPA来定义和使用自定义的JPQL查询。
步骤 1: 创建实体类
首先,你需要有一个或多个实体类映射到你的数据库表。假设我们有一个Product实体类:
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
// 省略getter和setter方法
}
步骤 2: 创建Repository接口
然后,你需要创建一个继承自JpaRepository的接口来定义你的数据访问层。你可以在这个接口中直接定义JPQL查询。
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import java.util.List;
public interface ProductRepository extends JpaRepository<Product, Long> {
// 使用JPQL查询所有价格大于指定值的产品
@Query("SELECT p FROM Product p WHERE p.price > ?1")
List<Product> findByPriceGreaterThan(double price);
}
注意:?1是一个参数占位符,对应于方法参数列表中的第一个参数(double price)。
步骤 3: 使用Repository
最后,在你的服务层或控制器中注入ProductRepository,并使用它执行查询。
2.3.2.2 命名QL查询
在Spring Boot中,当你提到“命名QL查询”(虽然更常见的术语是“命名查询”或“JPA命名查询”),你通常是指在你的JPA仓库接口中定义的,通过@NamedQuery或@NamedQueries注解(在实体类上)或在JPA仓库接口中使用@Query注解(但这不是严格意义上的命名查询,因为它直接定义在接口方法上)来指定的查询。然而,对于JPA来说,@NamedQuery和@NamedQueries通常用于实体类上,而仓库接口中更常用的是@Query注解。
不过,为了演示如何在Spring Boot项目中使用命名查询(尽管我们将通过@Query注解在仓库接口中定义它,因为这是一种更常见且灵活的方法),我们可以这样做:
实体类(可选的@NamedQuery定义)
虽然在这个例子中我们不会直接在实体类上使用@NamedQuery,但这里是如果你选择在实体类上定义命名查询的方式:
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
@Entity
@NamedQueries({
@NamedQuery(name = "Product.findByPriceGreaterThan",
query = "SELECT p FROM Product p WHERE p.price > :price")
})
public class Product {
// ... 实体类的其余部分
}
但是,请注意,在Spring Data JPA中,你通常不会在实体类上定义这样的查询,而是会在仓库接口中定义。
仓库接口
在仓库接口中,你可以使用@Query注解来定义一个查询,虽然这不是传统意义上的“命名查询”(因为它不是通过@NamedQuery在实体上定义的),但你可以将其视为在Spring Data JPA上下文中的命名查询,因为它允许你通过方法名来引用查询。
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import java.util.List;
public interface ProductRepository extends JpaRepository<Product, Long> {
// 使用@Query注解定义一个查询
@Query("SELECT p FROM Product p WHERE p.price > :price")
List<Product> findByPriceGreaterThan(@Param("price") double price);
}
请注意,在这个例子中,@Query注解直接定义在仓库接口的方法上。@Param(“price”)注解用于指定查询参数的名称,这样它就可以与方法参数price绑定。
服务层
在服务层,你可以像之前一样注入ProductRepository并使用它:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class ProductService {
@Autowired
private ProductRepository productRepository;
public List<Product> getProductsPriceGreaterThan(double price) {
return productRepository.findByPriceGreaterThan(price);
}
}
总结:虽然在这个例子中我们没有直接在实体类上使用@NamedQuery,但@Query注解在Spring Data JPA仓库接口中提供了一种定义和命名查询的灵活方式。这些查询可以通过方法名来引用,并且在服务层中通过仓库接口来调用。
2.3.2.3 分页查询
在Spring Boot中,实现分页查询的一个常用方式是使用Spring Data JPA提供的分页支持。Spring Data JPA通过Page接口和Pageable接口,以及继承自JpaRepository或PagingAndSortingRepository的仓库接口,来简化分页查询的实现。
以下是一个使用Spring Boot和Spring Data JPA进行分页查询的基本示例:
- 实体类
首先,你需要有一个实体类,比如Product:
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
// 省略getter和setter方法
}
- 仓库接口
然后,创建一个继承自JpaRepository的仓库接口,并在需要分页的方法中添加Pageable参数:
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ProductRepository extends JpaRepository<Product, Long> {
// 使用Pageable进行分页查询
Page<Product> findAll(Pageable pageable);
}
注意:findAll(Pageable pageable)方法是JpaRepository接口中预定义的,因此你不需要自己实现它。但是,你也可以定义其他带Pageable参数的自定义查询方法。
- 服务层
在服务层中,你可以注入仓库接口,并使用它来执行分页查询:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
@Service
public class ProductService {
@Autowired
private ProductRepository productRepository;
// 分页查询方法
public Page<Product> getProducts(int page, int size) {
Pageable pageable = PageRequest.of(page, size);
return productRepository.findAll(pageable);
}
}
在这个例子中,PageRequest.of(page, size)用于创建一个Pageable实例,其中page是页码(从0开始),size是每页显示的记录数。
- 控制器层
最后,在控制器层中,你可以调用服务层的方法来处理HTTP请求,并返回分页结果:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ProductController {
@Autowired
private ProductService productService;
@GetMapping("/products")
public Page<Product> getProducts(@RequestParam(value = "page", defaultValue = "0") int page,
@RequestParam(value = "size", defaultValue = "10") int size) {
return productService.getProducts(page, size);
}
}
在这个控制器中,我们定义了一个/products的GET请求处理方法,它接受page和size作为请求参数,并调用服务层的方法来获取分页结果。@RequestParam注解用于将请求参数绑定到方法参数上,并提供了默认值。
现在,当你访问/products端点时,你可以通过page和size参数来控制分页的页码和每页的记录数。
2.3.2.4 复杂查询
在Spring Boot中处理复杂查询,通常会使用Spring Data JPA提供的@Query注解来定义自定义的JPQL(Java Persistence Query Language)或原生SQL查询,或者利用Specifications和Criteria API来构建更灵活的查询条件。以下是几种在Spring Boot中实现复杂查询的方法。
- 使用@Query注解
你可以在你的Repository接口中使用@Query注解来定义复杂的查询。这可以是JPQL查询或原生SQL查询。
示例:JPQL查询
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import java.util.List;
public interface ProductRepository extends JpaRepository<Product, Long> {
@Query("SELECT p FROM Product p WHERE p.price > :price AND p.name LIKE :name%")
List<Product> findByPriceAndNameStartingWith(@Param("price") double price, @Param("name") String name);
}
示例:原生SQL查询
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface ProductRepository extends JpaRepository<Product, Long> {
@Query(value = "SELECT * FROM products WHERE price > :price AND name LIKE CONCAT(:name, '%')", nativeQuery = true)
List<Product> findByPriceAndNameStartingWithNative(@Param("price") double price, @Param("name") String name);
}
- 使用Specifications
Specifications提供了一种类型安全的方式来构建复杂的查询条件。你可以通过组合多个Specification来构建复杂的查询逻辑。
示例:
import org.springframework.data.jpa.domain.Specification;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
public class ProductSpecifications {
public static Specification<Product> priceGreaterThan(double price) {
return (root, query, cb) -> cb.greaterThan(root.get("price"), price);
}
public static Specification<Product> nameStartsWith(String name) {
return (root, query, cb) -> cb.like(root.get("name"), name + "%");
}
// 可以组合多个Specification
public static Specification<Product> complexSpecification(double price, String name) {
return priceGreaterThan(price).and(nameStartsWith(name));
}
}
// 在Repository中使用
public interface ProductRepository extends JpaRepository<Product, Long>, JpaSpecificationExecutor<Product> {
// JpaSpecificationExecutor提供了findAll方法,接受Specification作为参数
}
// 在Service层中调用
public List<Product> findComplexProducts(double price, String name) {
Specification<Product> spec = ProductSpecifications.complexSpecification(price, name);
return productRepository.findAll(spec);
}
- 使用Criteria API
Criteria API提供了另一种构建类型安全查询的方式,但它通常比Specifications更加冗长和复杂。
示例:
// 通常,你不会在Repository中直接使用Criteria API,而是在Service层或自定义查询方法中
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
// 注入EntityManager
@Autowired
private EntityManager entityManager;
public List<Product> findProductsUsingCriteria(double price, String name) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Product> cq = cb.createQuery(Product.class);
Root<Product> productRoot = cq.from(Product.class);
cq.select(productRoot)
.where(cb.and(
cb.greaterThan(productRoot.get("price"), price),
cb.like(productRoot.get("name"), "%" + name + "%")
));
TypedQuery<Product> query = entityManager.createQuery(cq);
return query.getResultList();
}
在选择哪种方法时,通常@Query注解对于简单的查询来说是最简单的。而Specifications提供了更好的可重用性和灵活性,适合构建复杂的查询逻辑。Criteria API则提供了最大的灵活性,但代码通常更复杂。
标签:JPA,spring,boot,查询,Spring,import,public,persistence From: https://blog.csdn.net/2301_78884095/article/details/141903265