【SpringBoot系列】微服务数据持久化方案

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 【4月更文挑战第8天】微服务数据持久化方案Spring Boot JPA + Hiberate

[toc]


介绍

我们从一个简单的hello world应用程序开始,然后介绍了如何设置数据库Schema的Flyway。今天我们准备学习一些将与数据库交互的代码。在我们开始编写代码之前,让我们先看一下历史。

Java 有一个很好的 JDBC API,可以帮助我们查询数据库。以它为基础,许多 ORM 工具应运而生,如Hibernate、Mybatis、Toplink 等等。ORM 弥合了 JDBC 和面向对象之间的差距,以及我们如何执行数据库操作并将它们映射到某些对象。看一下现在的 Java 的应用程序,JPA+Hibernate 已经成为关系数据库事实上的选择。

Spring 的出现带来了更多的实用性,让开发人员的生活变得更加轻松。这篇文章不是 Hibernate 或 JPA 教程,而是一个简单的 Spring 教程,介绍如何使用 Spring 对 JPA 和 Hibernate 的支持。

一、依赖

像往常一样,我们有一个名为 spring-boot-starter-jpa 的启动器,添加依赖项如下:

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

依赖项包含核心依赖项和 JPA 依赖项如下:
image.png

提示:由于命名权限问题,以前称为 Java Persistence API 的 JPA 现在已重命名为 Jakarta Persistence API。

Spring data jpa 提供如下能力:

  • 用于自动生成大多数样板查询模式的 Repository 接口。

  • 支持标注驱动的事务机制。

  • 轻松审计实体。

  • 支持分页、筛选器等。

二、代码

我们已经添加了依赖项,现在开始编写代码,实体类定义如下所示:

@Getter
@Setter
@Entity
@Table(schema = "inv", name = "products")
public class Product{
   
   
    @Id
    @GeneratedValue
    private UUID id;
    private String name;
    private Long stock;
    private String manufacturer;
    @CreatedDate
    private OffsetDateTime createdOn;
}

它是一个简单的 JPA 实体,以 id 字段为标识符。

您需要做的就是定义一个存储库,如下所示 :

@Repository
public interface ProductRepository extends JpaRepository<Product, UUID>{

}

Spring 将生成所有样板基础查询,例如 persists、findAll 等等。JpaRepository 还支持生成查询以通过实体的某些列进行查找,例如 id、name、stock、manufacturer、created on。我们所需要的只是一个名为 findBy 的方法。

下面是 ProductService,它将产品 DTO 作为输入并存储到数据库中。

@Service
@RequiredArgsConstructor
public class ProductService{
    private final ProductRepository productRepository;

    public Product save(Product productDTO){
        ProductEntity productEnity = new ProductEntity();
        productEnity.setName(productDTO.getName());
        productEnity.setManufacturer(productDTO.getManufacturer());
        productEnity.setStock(productDTO.getStock());
        productEnity.setCreatedOn(OffsetDateTime.now());

        ProductEntity savedEntity = productRepository.save(productEnity);

        return toProductDTO(savedEntity);
    }

    private Product toProductDTO(ProductEntity productEntity){
        return Product.builder()
                .id(productEntity.getId())
                .createdOn(productEntity.getCreatedOn())
                .manufacturer(productEntity.getManufacturer())
                .name(productEntity.getName())
                .stock(productEntity.getStock())
                .build();
    }
}

创建实体后,我们需要做的就是调用

productRepository.save(productEnity) 。

我没有使用任何事务,因为 JpaRepository 本身在事务中工作。同样在这个简单示例中,我没有从实体中延迟加载任何属性,因此可以省略事务。

三、日志

我们可能想插卡Hibernate SQL生成的内容,我们可以使用以下属性:

spring:
  jpa:
    show-sql: true
    properties:
      hibernate:
        format_sql: true

输出如下:

Hibernate: 
    insert 
    into
        inv.products
        (created_on, manufacturer, name, stock, id) 
    values
        (?, ?, ?, ?, ?)

如果我们想查看 insert 语句中传递的实际输入,该怎么办?好吧,没有直接属性,但我们可以启用如下日志:

logging:
  level:
    org.hibernate.type.descriptor.sql.BasicBinder: TRACE

日志输出如下:

2024-04-08 12:09:36.682 TRACE 40492 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [TIMESTAMP] - [2022-05-08T12:09:36.651572+02:00]
2025-04-08 12:09:36.683 TRACE 40492 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [VARCHAR] - [test-mfg1]
2024-04-08 12:09:36.683 TRACE 40492 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder      : binding parameter [3] as [VARCHAR] - [test-product5]
2024-04-08 12:09:36.683 TRACE 40492 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder      : binding parameter [4] as [BIGINT] - [100]
2024-04-08 12:09:36.683 TRACE 40492 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder      : binding parameter [5] as [OTHER] - [dd7d89fa-c201-4338-871f-a00fed464bd5]

让我们尝试查询所有产品 ,我们将再次从 Spring JPA 存储库中获取信息,代码如下:

public List<Product> getAllProducts(){
   return productRepository.findAll()
            .stream()
            .map(this::toProductDTO)
            .collect(Collectors.toList());
}

四、分页

假如我们查询的产品可能数量很大,在这种情况下,我们需要分页支持。我们需要稍微修改一下我们的 ProductRepository 类 -

@Repository
public interface ProductRepository extends PagingAndSortingRepository<ProductEntity, UUID>{

}

我们的方法返回分页数据,采用 Pageable 类型,修改后的方法如下所示:

public Page<Product> getAllProducts(Pageable pageRequest){
       return productRepository.findAll(pageRequest)
                .map(this::toProductDTO);
    }

请注意返回类型如何从 List 更改为 Page ,页面类型包含总页数和总项目数等信息。

我们还可以在应用程序日志中验证 select 查询是否未使用 limit 和 offset,而不是执行 select all 。

Hibernate: 
    select
        productent0_.id as id1_0_,
        productent0_.created_on as created_2_0_,
        productent0_.manufacturer as manufact3_0_,
        productent0_.name as name4_0_,
        productent0_.stock as stock5_0_ 
    from
        inv.products productent0_ limit ? offset ?

五、审计

如果我们在 ProductService 中查看我们的保存方法,我们会将 createdOn 字段的值设置为当前日期时间,尽管演示上下文中这样做没有错,但有一种更好的方法来填充此字段,Spring data jpa 通过 AuditingEntityListener 提供审计功能。这提供了一堆在事件之前或之后填充字段的注释。

让我们尝试填充我们的 createdOn 字段。

  • 1.我们首先需要将 @EntityListeners(AuditingEntityListener.class) 添加到我们的 ProductEntity 类中。

  • 2.我们需要提供一个 DateTimeProvider 类型的 bean,它将负责提供当前时间。因为我们使用的是 OffsetDatetime,所以我们创建了一个如下所示的 bean,它给出了一个 OffsetDatetime。

 @Bean
    public DateTimeProvider dateTimeProvider(){
        return ()-> Optional.of(OffsetDateTime.now());
    }
  • 3.最后,我们添加以下注解 @EnableJpaAuditing(dateTimeProviderRef = “dateTimeProvider”) 。就像时间戳一样,我们还可以添加一个 auditorAwareRef,它返回一个 AuditorAware 。让我们向 ProductEntity 添加一个新列
    @CreatedBy
    private String createdBy;

创建Bean如下:

  @Bean
    public AuditorAware<String> auditorAwareRef(){
        return () -> Optional.of("test-user");
    }

我们现在创建一个新产品,我们将看到 test-user 已在数据库中设置为 createdBy。

注意:添加常量 test-user 仅用于示例目的。获取真实用户名可能涉及从 ThreadLocal、SecurityContext、Auth Header 或适合您的上下文的任何其他内容获取它。

我们还有其他注释 LastModifiedBy 和 LastModifiedOn 来捕获修改审计。

六、更多特性

  • @Query - 有时存储库方法也不足以满足我们的用例,可能需要一个更复杂的查询,在这种情况下,我们可以添加一个方法并使用@Query注解来指定我们的 sql 查询。如果我们设置 native=true,我们可以提供原生 SQL 查询,而不是 JPQL 查询。

  • 自定义标准 - 我们也可以从 JpaSpecificationExecutor 继承,它提供了采用 Specification 类型的方法。我们可以利用 JPA 标准来构建更细致和复杂的查询。

小结

本节我们学习了Spring Data JPA,我们创建一个实体,并知道如何持久化它并查询它。Spring data jpa 是一个大模块,并不是所有内容都可以在一篇文章中涵盖,在以后的博客中,我们将看到spring-data-jpa的更多功能。

相关实践学习
【涂鸦即艺术】基于云应用开发平台CAP部署AI实时生图绘板
【涂鸦即艺术】基于云应用开发平台CAP部署AI实时生图绘板
目录
相关文章
|
6月前
|
安全 Java Apache
微服务——SpringBoot使用归纳——Spring Boot中集成 Shiro——Shiro 身份和权限认证
本文介绍了 Apache Shiro 的身份认证与权限认证机制。在身份认证部分,分析了 Shiro 的认证流程,包括应用程序调用 `Subject.login(token)` 方法、SecurityManager 接管认证以及通过 Realm 进行具体的安全验证。权限认证部分阐述了权限(permission)、角色(role)和用户(user)三者的关系,其中用户可拥有多个角色,角色则对应不同的权限组合,例如普通用户仅能查看或添加信息,而管理员可执行所有操作。
323 0
|
6月前
|
安全 Java 数据安全/隐私保护
微服务——SpringBoot使用归纳——Spring Boot中集成 Shiro——Shiro 三大核心组件
本课程介绍如何在Spring Boot中集成Shiro框架,主要讲解Shiro的认证与授权功能。Shiro是一个简单易用的Java安全框架,用于认证、授权、加密和会话管理等。其核心组件包括Subject(认证主体)、SecurityManager(安全管理员)和Realm(域)。Subject负责身份认证,包含Principals(身份)和Credentials(凭证);SecurityManager是架构核心,协调内部组件运作;Realm则是连接Shiro与应用数据的桥梁,用于访问用户账户及权限信息。通过学习,您将掌握Shiro的基本原理及其在项目中的应用。
248 0
|
6月前
|
NoSQL Java 关系型数据库
微服务——SpringBoot使用归纳——Spring Boot 中集成Redis——Redis 介绍
本文介绍在 Spring Boot 中集成 Redis 的方法。Redis 是一种支持多种数据结构的非关系型数据库(NoSQL),具备高并发、高性能和灵活扩展的特点,适用于缓存、实时数据分析等场景。其数据以键值对形式存储,支持字符串、哈希、列表、集合等类型。通过将 Redis 与 Mysql 集群结合使用,可实现数据同步,提升系统稳定性。例如,在网站架构中优先从 Redis 获取数据,故障时回退至 Mysql,确保服务不中断。
271 0
微服务——SpringBoot使用归纳——Spring Boot 中集成Redis——Redis 介绍
|
2月前
|
JSON Java 数据格式
Spring Boot返回Json数据及数据封装
在Spring Boot中,接口间及前后端的数据传输通常使用JSON格式。通过@RestController注解,可轻松实现Controller返回JSON数据。该注解是Spring Boot新增的组合注解,结合了@Controller和@ResponseBody的功能,默认将返回值转换为JSON格式。Spring Boot底层默认采用Jackson作为JSON解析框架,并通过spring-boot-starter-json依赖集成了相关库,包括jackson-databind、jackson-datatype-jdk8等常用模块,简化了开发者对依赖的手动管理。
370 3
|
6月前
|
Java 数据安全/隐私保护 微服务
微服务——SpringBoot使用归纳——Spring Boot中使用监听器——Spring Boot中自定义事件监听
本文介绍了在Spring Boot中实现自定义事件监听的完整流程。首先通过继承`ApplicationEvent`创建自定义事件,例如包含用户数据的`MyEvent`。接着,实现`ApplicationListener`接口构建监听器,用于捕获并处理事件。最后,在服务层通过`ApplicationContext`发布事件,触发监听器执行相应逻辑。文章结合微服务场景,展示了如何在微服务A处理完逻辑后通知微服务B,具有很强的实战意义。
391 0
|
6月前
|
缓存 Java 数据库
微服务——SpringBoot使用归纳——Spring Boot中使用监听器——监听器介绍和使用
本文介绍了在Spring Boot中使用监听器的方法。首先讲解了Web监听器的概念,即通过监听特定事件(如ServletContext、HttpSession和ServletRequest的创建与销毁)实现监控和处理逻辑。接着详细说明了三种实际应用场景:1) 监听Servlet上下文对象以初始化缓存数据;2) 监听HTTP会话Session对象统计在线用户数;3) 监听客户端请求的Servlet Request对象获取访问信息。每种场景均配有代码示例,帮助开发者理解并应用监听器功能。
419 0
|
6月前
|
Java 关系型数据库 数据库
微服务——SpringBoot使用归纳——Spring Boot事务配置管理——常见问题总结
本文总结了Spring Boot中使用事务的常见问题,虽然通过`@Transactional`注解可以轻松实现事务管理,但在实际项目中仍有许多潜在坑点。文章详细分析了三个典型问题:1) 异常未被捕获导致事务未回滚,需明确指定`rollbackFor`属性;2) 异常被try-catch“吃掉”,应避免在事务方法中直接处理异常;3) 事务范围与锁范围不一致引发并发问题,建议调整锁策略以覆盖事务范围。这些问题看似简单,但一旦发生,排查难度较大,因此开发时需格外留意。最后,文章提供了课程源代码下载地址,供读者实践参考。
139 0
|
6月前
|
Java 关系型数据库 数据库
微服务——SpringBoot使用归纳——Spring Boot事务配置管理——Spring Boot 事务配置
本文介绍了 Spring Boot 中的事务配置与使用方法。首先需要导入 MySQL 依赖,Spring Boot 会自动注入 `DataSourceTransactionManager`,无需额外配置即可通过 `@Transactional` 注解实现事务管理。接着通过创建一个用户插入功能的示例,展示了如何在 Service 层手动抛出异常以测试事务回滚机制。测试结果表明,数据库中未新增记录,证明事务已成功回滚。此过程简单高效,适合日常开发需求。
919 0
|
6月前
|
Java 数据库 微服务
微服务——SpringBoot使用归纳——Spring Boot事务配置管理——事务相关
本文介绍Spring Boot事务配置管理,阐述事务在企业应用开发中的重要性。事务确保数据操作可靠,任一异常均可回滚至初始状态,如转账、购票等场景需全流程执行成功才算完成。同时,事务管理在Spring Boot的service层广泛应用,但根据实际需求也可能存在无需事务的情况,例如独立数据插入操作。
150 0
|
6月前
|
消息中间件 存储 Java
微服务——SpringBoot使用归纳——Spring Boot中集成ActiveMQ——ActiveMQ安装
本教程介绍ActiveMQ的安装与基本使用。首先从官网下载apache-activemq-5.15.3版本,解压后即可完成安装,非常便捷。启动时进入解压目录下的bin文件夹,根据系统选择win32或win64,运行activemq.bat启动服务。通过浏览器访问`http://127.0.0.1:8161/admin/`可进入管理界面,默认用户名密码为admin/admin。ActiveMQ支持两种消息模式:点对点(Queue)和发布/订阅(Topic)。前者确保每条消息仅被一个消费者消费,后者允许多个消费者同时接收相同消息。
187 0
微服务——SpringBoot使用归纳——Spring Boot中集成ActiveMQ——ActiveMQ安装