前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【SpringBoot】微服务数据持久化方案(SpringBootJPA+Hiberate)

【SpringBoot】微服务数据持久化方案(SpringBootJPA+Hiberate)

原创
作者头像
Freedom123
发布2024-04-24 15:29:49
1600
发布2024-04-24 15:29:49
举报
文章被收录于专栏:Spring

介绍

我们从一个简单的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 的启动器,添加依赖项如下:

代码语言:shell
复制
  <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

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

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

Spring data jpa 提供如下能力:

  • 用于自动生成大多数样板查询模式的 Repository 接口。
  • 支持标注驱动的事务机制。
  • 轻松审计实体。
  • 支持分页、筛选器等。

二、代码

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

代码语言:java
复制
@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 字段为标识符。

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

代码语言:shell
复制
@Repository
public interface ProductRepository extends JpaRepository<Product, UUID>{

}

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

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

代码语言:shell
复制
@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();
    }
}

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

代码语言:shell
复制
productRepository.save(productEnity) 。

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

三、日志

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

代码语言:shell
复制
spring:
  jpa:
    show-sql: true
    properties:
      hibernate:
        format_sql: true

输出如下:

代码语言:shell
复制
Hibernate: 
    insert 
    into
        inv.products
        (created_on, manufacturer, name, stock, id) 
    values
        (?, ?, ?, ?, ?)

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

代码语言:shell
复制
logging:
  level:
    org.hibernate.type.descriptor.sql.BasicBinder: TRACE

日志输出如下:

代码语言:shell
复制
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 存储库中获取信息,代码如下:

代码语言:shell
复制
public List<Product> getAllProducts(){
   return productRepository.findAll()
            .stream()
            .map(this::toProductDTO)
            .collect(Collectors.toList());
}

四、分页

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

代码语言:shell
复制
@Repository
public interface ProductRepository extends PagingAndSortingRepository<ProductEntity, UUID>{

}

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

代码语言:shell
复制
public Page<Product> getAllProducts(Pageable pageRequest){
       return productRepository.findAll(pageRequest)
                .map(this::toProductDTO);
    }

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

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

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

创建Bean如下:

代码语言:shell
复制
  @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的更多功能。

我正在参与2024腾讯技术创作特训营最新征文,快来和我瓜分大奖!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 介绍
  • 一、依赖
  • 二、代码
  • 三、日志
  • 四、分页
  • 五、审计
  • 六、更多特性
  • 小结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档