首页 > 其他分享 >第二章 数据访问:JPA spring boot

第二章 数据访问:JPA spring boot

时间:2024-09-05 09:21:55浏览次数:11  
标签:JPA spring boot 查询 Spring import public persistence

学习目标


  (如果没有了解可以去我主页看看 第一章的内容来学习)我们已经对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来执行数据库操作。

  1. 定义实体类
    首先,你需要定义一个实体类,该类使用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;  
    }  
}
  1. 配置JPA提供者
    接下来,你需要在项目中配置JPA提供者(如Hibernate)和数据库连接。这通常通过persistence.xml文件(位于src/main/resources/META-INF目录下,或者在基于Spring Boot的项目中,可能通过application.properties或application.yml文件来配置)来完成。

  2. 使用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 代码示例,包括定义一个实体类、一个仓库接口,以及一个简单的服务类来演示如何使用这些组件。

  1. 定义实体类
    首先,定义一个 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;  
    }  
}
  1. 定义仓库接口
    然后,定义一个继承自 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);  
}
  1. 使用服务类
    最后,在服务层中使用这个仓库接口来执行数据操作。
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);  
    }  
  
    // 这里可以添加其他业务逻辑方法  
}
  1. 配置
    你需要在 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
  1. 运行和测试
    现在,你可以运行你的 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代码示例,包括实体类、仓库接口、服务类和基本的配置。

  1. 添加依赖
    首先,确保你的项目中已经添加了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的依赖,以及数据库驱动。

  1. 配置数据库连接
    在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
  1. 实体类
    定义一个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;  
    }  
}
  1. 仓库接口
    定义一个继承自JpaRepository的接口,用于操作数据库:
import org.springframework.data.jpa.repository.JpaRepository;  
  
public interface PersonRepository extends JpaRepository<Person, Long> {  
    // 这里可以定义额外的查询方法,例如:  
    // List<Person> findByFirstName(String firstName);  
}
  1. 服务类
    在服务层中使用仓库接口:
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);  
    }  
  
    // 这里可以添加其他业务逻辑方法  
}
  1. 运行和测试
    现在,你可以在你的应用程序中创建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进行基本操作。

  1. 实体类
    首先,定义一个实体类,该类映射到数据库中的表。使用@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;  
    }  
}
  1. 仓库接口
    接着,创建一个继承自JpaRepository的接口,用于定义数据访问层的方法。JpaRepository已经包含了基本的CRUD方法,你也可以定义自己的查询方法。
import org.springframework.data.jpa.repository.JpaRepository;  
  
public interface PersonRepository extends JpaRepository<Person, Long> {  
    // 这里可以定义额外的查询方法  
}
  1. 服务层
    在服务层中,注入仓库接口,并使用它来执行数据库操作。
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);  
    }  
  
    // 可以在这里添加更多的业务逻辑  
}
  1. 配置
    如果你不是使用Spring Boot,你需要自己配置数据源、JPA提供商、事务管理器等。但在Spring Boot中,你通常只需要在application.properties或application.yml中配置数据库连接信息,Spring Boot会自动配置其余部分。

  2. 使用
    最后,在你的控制器或任何其他需要访问数据库的地方,注入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应用中处理一个复杂的业务操作。我们将以一个订单系统为例,其中涉及创建订单、更新库存、发送通知等操作。

  1. 定义实体
    首先,定义相关的实体类,比如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  
}
  1. 创建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);  
}
  1. 服务层
    在服务层中处理复杂的业务逻辑。
@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;  
    }  
  
    // 其他业务方法...  
}
  1. 控制器
    在控制器中调用服务层的方法。
@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端点...  
}
  1. 异常处理和事务管理
  • 异常处理:可以在控制器、服务层或仓库层使用@ExceptionHandler注解来处理异常,或者全局异常处理器。
  • 事务管理:通过@Transactional注解在服务层方法上确保数据一致性。如果方法中的任何操作失败(例如,由于库存不足而抛出异常),则整个事务将回滚,确保数据不会处于不一致的状态。
  1. 测试
    编写单元测试和集成测试来验证服务层和控制器层的行为。确保覆盖各种边界情况和异常情况。

这个示例仅展示了复杂操作的一个方面,实际项目中可能还需要考虑更多的因素,如安全性、性能优化、日志记录等。

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进行分页查询的基本示例:

  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方法  
}
  1. 仓库接口
    然后,创建一个继承自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参数的自定义查询方法。

  1. 服务层
    在服务层中,你可以注入仓库接口,并使用它来执行分页查询:
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是每页显示的记录数。

  1. 控制器层
    最后,在控制器层中,你可以调用服务层的方法来处理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中实现复杂查询的方法。

  1. 使用@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);  
}
  1. 使用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);  
}
  1. 使用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

相关文章