【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的更多功能。

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
1月前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
163 2
|
12天前
|
SQL 前端开发 关系型数据库
SpringBoot使用mysql查询昨天、今天、过去一周、过去半年、过去一年数据
SpringBoot使用mysql查询昨天、今天、过去一周、过去半年、过去一年数据
44 9
|
1月前
|
SQL JSON Java
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
这篇文章介绍了如何在Spring Boot项目中整合MyBatis和PageHelper进行分页操作,并且集成Swagger2来生成API文档,同时定义了统一的数据返回格式和请求模块。
54 1
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
|
17天前
|
存储 easyexcel Java
SpringBoot+EasyExcel轻松实现300万数据快速导出!
本文介绍了在项目开发中使用Apache POI进行数据导入导出的常见问题及解决方案。首先比较了HSSFWorkbook、XSSFWorkbook和SXSSFWorkbook三种传统POI版本的优缺点,然后根据数据量大小推荐了合适的使用场景。接着重点介绍了如何使用EasyExcel处理超百万数据的导入导出,包括分批查询、分批写入Excel、分批插入数据库等技术细节。通过测试,300万数据的导出用时约2分15秒,导入用时约91秒,展示了高效的数据处理能力。最后总结了公司现有做法的不足,并提出了改进方向。
|
20天前
|
消息中间件 存储 监控
微服务日志监控的挑战及应对方案
【10月更文挑战第23天】微服务化带来模块独立与快速扩展,但也使得日志监控复杂。日志作用包括业务记录、异常追踪和性能定位。
|
1月前
|
easyexcel Java UED
SpringBoot中大量数据导出方案:使用EasyExcel并行导出多个excel文件并压缩zip后下载
在SpringBoot环境中,为了优化大量数据的Excel导出体验,可采用异步方式处理。具体做法是将数据拆分后利用`CompletableFuture`与`ThreadPoolTaskExecutor`并行导出,并使用EasyExcel生成多个Excel文件,最终将其压缩成ZIP文件供下载。此方案提升了导出效率,改善了用户体验。代码示例展示了如何实现这一过程,包括多线程处理、模板导出及资源清理等关键步骤。
|
1月前
|
Web App开发 JavaScript Java
elasticsearch学习五:springboot整合 rest 操作elasticsearch的 实际案例操作,编写搜索的前后端,爬取京东数据到elasticsearch中。
这篇文章是关于如何使用Spring Boot整合Elasticsearch,并通过REST客户端操作Elasticsearch,实现一个简单的搜索前后端,以及如何爬取京东数据到Elasticsearch的案例教程。
174 0
elasticsearch学习五:springboot整合 rest 操作elasticsearch的 实际案例操作,编写搜索的前后端,爬取京东数据到elasticsearch中。
|
1月前
|
前端开发 Java 数据库
springBoot:template engine&自定义一个mvc&后端给前端传数据&增删改查 (三)
本文介绍了如何自定义一个 MVC 框架,包括后端向前端传递数据、前后端代理配置、实现增删改查功能以及分页查询。详细展示了代码示例,从配置文件到控制器、服务层和数据访问层的实现,帮助开发者快速理解和应用。
|
10天前
|
设计模式 Java API
微服务架构演变与架构设计深度解析
【11月更文挑战第14天】在当今的IT行业中,微服务架构已经成为构建大型、复杂系统的重要范式。本文将从微服务架构的背景、业务场景、功能点、底层原理、实战、设计模式等多个方面进行深度解析,并结合京东电商的案例,探讨微服务架构在实际应用中的实施与效果。
51 6
|
10天前
|
设计模式 Java API
微服务架构演变与架构设计深度解析
【11月更文挑战第14天】在当今的IT行业中,微服务架构已经成为构建大型、复杂系统的重要范式。本文将从微服务架构的背景、业务场景、功能点、底层原理、实战、设计模式等多个方面进行深度解析,并结合京东电商的案例,探讨微服务架构在实际应用中的实施与效果。
27 1