您当前的位置:首页 > 电脑百科 > 程序开发 > 架构

分享Spring Data JPA的一些技巧和最佳实践

时间:2023-10-04 17:12:30  来源:今日头条  作者:迷路的架构师

分享Spring Data JPA的一些技巧和最佳实践
在现代软件开发中,Spring Boot已成为构建稳健和可扩展应用程序的主要框架。当涉及到与数据库的交互时,JAVA持久化API(JPA)提供了一种方便高效的方式来管理关系型数据。为了确保Spring Boot应用程序的可维护性、可读性和可扩展性,在创建使用JPA进行数据访问的Repository接口时,遵循最佳实践至关重要。

命名规范

遵循Spring Data的Repository接口命名惯例。命名惯例应为EntityNameRepository或
EntityNameRepositoryCustom(用于自定义Repository方法)。

public interface UserRepository extends JpaRepository<User, Long> {
    // 此处可以自定义方法
}

领域特定的Repository接口

在软件工程中,关注点分离是一个核心原则,强调每个组件应具有明确定义的责任。在Spring Boot应用程序的上下文中,使用领域特定的存储库接口与该原则一致,允许在不同实体类型及其相应数据访问操作之间保持清晰的区分。

好处

  1. 模块化和清晰性:每个存储库接口专注于一个实体类型。这种模块化确保存储库方法简洁且与其处理的实体类型相关,使代码库更具可读性和可理解性。
  2. 封装性:领域特定的存储库接口封装了与特定实体相关的数据访问操作。这种隔离减少了意外误用或在错误的实体上执行不适当查询的可能性。
  3. 类型安全性:通过为每个实体使用接口,可以获得强类型的好处。这有助于在编译期间捕获错误,而不是在运行时。
  4. 维护性:当需要对特定实体的数据访问方法进行更改或增强时,你知道要去找哪个相应的Repository接口。这种有针对性的方法简化了维护和调试过程。

考虑一个示例,有一个电子商务应用程序,其中有两个主要实体:产品和分类。通过使用领域特定的Repository接口,我们可以将每个实体的数据访问逻辑保持分离并组织良好。

public interface ProductRepository extends JpaRepository<Product, Long> {
    List<Product> findByCategory(Category category);
}

在此示例中,该ProductRepository接口包括一个查询方法findByCategory,该方法根据产品的关联类别来检索产品。

类似地,该CategoryRepository接口可以只关注与类别相关的数据访问操作。

public interface CategoryRepository extends JpaRepository<Category, Long> {
    Category findByName(String name);
}

通过利用领域特定的Repository接口,我们可以创建一个更有组织且易于理解的数据访问层,该层与应用程序的实体结构保持一致。这种关注点分离不仅提高了代码质量,而且随着应用程序随着时间的推移而发展,也有利于维护和扩展。

查询方法

Spring Data JPA 提供了一种遵循命名约定来定义Repository方法的便捷方法,称为“查询方法”。这种方法通过方法名称表达查询,从而无需为常见操作编写显式 SQL 或 JPQL 查询。利用查询方法可以增强代码库的可读性和可维护性。

查询方法的命名约定基于实体的属性名称。通过将findBy、getBy、readBy或 queryBy等前缀与属性名称组合,可以创建有意义的查询方法。

@Entity
public class Book {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String title;
    private String author;
    private int publicationYear;
    private String genre;

    // Getters and setters
}

public interface BookRepository extends JpaRepository<Book, Long> {
    List<Book> findByAuthorAndPublicationYearAndGenre(String author, int publicationYear, String genre);
}

也可以通过向查询方法添加可选参数来进一步扩展。比如需要按作者和流派搜索书籍,而不指定出版年份:

public interface BookRepository extends JpaRepository<Book, Long> {
    List<Book> findByAuthorAndGenreAndPublicationYear(String author, String genre, int publicationYear);
    
    List<Book> findByAuthorAndGenre(String author, String genre);
}

Spring Data JPA 根据方法名称和参数名称自动生成相应的 SQL 查询,使复杂的查询场景变得更加容易,而无需编写原生 SQL 查询。

自定义查询方法

虽然查询方法提供了一种强大的机制,可以根据命名约定从方法名称生成查询,但在某些情况下需要更复杂的查询。对于这些场景,Spring Data JPA 可以使用@Query注解定义自己的自定义查询方法。

@Query注解能够直接在Repository接口中定义 JPQL(Java 持久性查询语言)或原生 SQL 查询。这种方法可以更加灵活地构建涉及多个实体复杂联接或其他非标准操作的查询。

public interface UserRepository extends JpaRepository<User, Long> {

    @Query("SELECT u FROM User u WHERE CONCAT(u.firstName, ' ', u.lastName) LIKE %:keyword%")
    List<User> findUsersByFullNameKeyword(@Param("keyword") String keyword);
}

在使用 JPQL 还无法实现的特定于数据库的功能时,还可以使用原生 SQL 查询。以下是自定义原生 SQL 查询方法的示例:

public interface ProductRepository extends JpaRepository<Product, Long> {

    @Query(value = "SELECT * FROM products p WHERE p.price > :minPrice", nativeQuery = true)
    List<Product> findProductsAboveMinPrice(@Param("minPrice") BigDecimal minPrice);
}

在此示例中,@Query 注解使用 nativeQuery = true来表示查询是用原生 SQL 编写的。该查询的意图是检索价格高于指定最低价格的产品。

自定义查询方法有几个好处:

  • 灵活性:可以创建更适合特定要求的查询。
  • 复杂操作:自定义查询适合复杂的连接操作或需要使用特定于数据库的函数。
  • 性能:在某些情况下,原生 SQL 查询可能会为特定场景提供更好的性能。

使用原生 SQL 查询时必须谨慎,因为如果处理不当,可能会导致特定于数据库的代码和潜在的安全漏洞(例如 SQL 注入)。

通过使用自定义查询方法,我们可以在查询方法命名约定的便利性和处理更复杂或专门的数据检索场景的灵活性之间取得平衡。

自定义Repository接口

在Repository层中分离关注点是一个好习惯,这意味着需要将标准 Spring Data JPA 方法与自定义方法分开。这种分离增强了代码组织可读性可维护性,并促进了单一职责原则。

首先创建一个自定义Repository接口来保存专用方法。该接口不应直接扩展JpaRepository,因为这会导致自定义方法与标准方法混杂在一起。

public interface UserRepositoryCustom {
    List<User> findActiveUsers();
}

接下来,为自定义Repository接口创建一个实现类。实现类遵循命名规范<EntityName>RepositoryImpl,并且应该放置在与Repository接口相同的包中。

@Repository
public class UserRepositoryImpl implements UserRepositoryCustom {

    @PersistenceContext
    private EntityManager entityManager;

    @Override
    public List<User> findActiveUsers() {
        TypedQuery<User> query = entityManager.createQuery("SELECT u FROM User u WHERE u.active = true", User.class);
        return query.getResultList();
    }
}

最后,通过扩展标准 Spring Data JPA Repository接口 ( JpaRepository) 和自定义Repository接口 ( UserRepositoryCustom) 来创建主Repository接口。

public interface UserRepository extends JpaRepository<User, Long>, UserRepositoryCustom {
    // Spring Data JPA 方法与自定义方法
}

通过遵循这种方法,我们可以在 Spring Data JPA 提供的常见 CRUD 操作与自定义专用方法之间保持清晰的分离。使代码更加模块化且更易于理解。

分页和排序

在处理大型数据集时,高效的分页和排序机制对于提供流畅的用户体验和优化查询性能至关重要。

分页涉及将结果集划分为较小的页面,以避免一次获取所有数据。Spring Data JPA 的Pageable接口提供了一种定义分页参数的方法,包括所需的页码、每页的条目数(页面大小)和排序方式。

public interface ProductRepository extends JpaRepository<Product, Long> {
    Page<Product> findByCategory(Category category, Pageable pageable);
}

在此示例中,该方法findByCategory返回一个Page<Product>.

排序可以根据一个或多个字段以特定顺序排列查询结果。Spring Data JPA 的Sort类能够为Repository方法指定排序方式。

public interface ProductRepository extends JpaRepository<Product, Long> {
    List<Product> findByCategoryOrderByPriceAsc(Category category);
}

在此示例中,
findByCategoryOrderByPriceAsc方法根据给定类别检索产品,并按价格升序,后缀OrderByPriceAsc表示排序顺序。

可以结合分页和排序来按特定顺序检索分页结果。例如:

public interface ProductRepository extends JpaRepository<Product, Long> {
    Page<Product> findByCategoryOrderByPriceAsc(Category category, Pageable pageable);
}

通过提供Pageable参数,可以指定页码、页面大小和排序标准。Spring Data JPA 负责生成适当的查询来获取请求的数据。

@Service
public class ProductService {

    @Autowired
    private ProductRepository productRepository;

    public Page<Product> getProductsByCategoryWithPaginationAndSorting(Category category, int pageNumber, int pageSize) {
        // 创建 Pageable 对象来进行分页和排序
        Pageable pageable = PageRequest.of(pageNumber, pageSize, Sort.by(Sort.Direction.ASC, "price"));
        
        return productRepository.findByCategoryOrderByPriceAsc(category, pageable);
    }
}

最后,我们可以在 Controller 中使用Service方法来检索分页和排序的结果,如下所示:

@RestController
@RequestMApping("/products")
public class ProductController {

    @Autowired
    private ProductService productService;

    @GetMapping
    public ResponseEntity<Page<Product>> getProductsByCategory(
            @RequestParam("category") Long categoryId,
            @RequestParam("page") int pageNumber,
            @RequestParam("size") int pageSize) {

        Category category = new Category();
        category.setId(categoryId);

        Page<Product> products = productService.getProductsByCategoryWithPaginationAndSorting(category, pageNumber, pageSize);
        return ResponseEntity.ok(products);
    }
}

幂等方法

设计和实现Repository方法时,特别是那些修改数据的方法时,遵循幂等原则非常重要,以确保多次调用具有相同参数的相同方法不会导致意外行为。

例如,在电子商务应用程序中,如果多次下具有相同详细信息的订单,则应该产生相同的结果,并且不会创建多个重复订单。

使用事务性操作

修改数据时,使用事务操作是一个很好的做法。Spring Data JPA 提供开箱即用的事务管理。使用事务确保操作期间发生错误,则回滚更改,从而保持数据的完整性。

@Service
public class OrderService {

    @Autowired
    private OrderRepository orderRepository;

    @Transactional
    public void placeOrder(User user, Product product) {
        // 检查订单是否已存在
        Order existingOrder = orderRepository.findByUserAndProduct(user, product);

        if (existingOrder == null) {
            // 创建一个新订单
            Order newOrder = new Order();
            newOrder.setUser(user);
            newOrder.setProduct(product);
            newOrder.setOrderDate(LocalDateTime.now());

            orderRepository.save(newOrder);
        } else {
            // 如果订单已存在,在不需要操作
        }
    }
}

幂等性检查

对于某些操作,我们可能需要实施幂等性检查。意味着在执行某个操作之前,需要检查该操作是否已经执行过,以防止多余的操作。

@Service
public class OrderService {

    @Autowired
    private OrderRepository orderRepository;

    @Transactional
    public void processOrder(Long orderId) {
        Order order = orderRepository.findById(orderId).orElse(null);
        if (order != null && !order.isProcessed()) {
            // 执行处理逻辑
            order.setProcessed(true);
        }
    }
}

在此示例中,processOrder方法检查订单是否已被处理,以防止多次处理同一订单。

通过遵循这些实践,可以确保Repository方法保持幂等性。这不仅可以防止意外的副作用,还可以使应用程序更加健壮和可预测,特别是在处理意外错误或故障时。

单元测试

为Repository接口编写单元测试对于确保其正确性至关重要。使用JUnitMockito等工具来模拟数据库交互并验证Repository方法是否按预期运行。

// User.java
@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String username;
    private String emAIl;

    // getters and setters
}

// UserRepository.java
public interface UserRepository extends JpaRepository<User, Long> {
    User findByUsername(String username);
}

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;

@RunWith(MockitoJUnitRunner.class)
public class UserRepositoryTest {

    @InjectMocks
    private UserService userService;

    @Mock
    private UserRepository userRepository;

    @Test
    public void testFindByUsername() {
        // Arrange
        String username = "zhangsan";
        User user = new User();
        user.setId(1L);
        user.setUsername(username);
        user.setEmail("zhangsan@example.com");

        when(userRepository.findByUsername(username)).thenReturn(user);

        // Act
        User foundUser = userService.findByUsername(username);

        // Assert
        assertEquals(username, foundUser.getUsername());
    }
}

@DataJpaTest是一个专门用于JPA测试的 Spring Boot 注解。当使用 @DataJpaTest 时,Spring Boot 会设置一个最小的 Spring 应用程序上下文,其中仅包含 JPA 测试所需的组件。这样测试就可以访问已配置的内存数据库,并且 Spring 将自动配置和管理 EntityManager 和Repository,这样可以以更集成的方式进行测试。

@DataJpaTest
public class UserRepositoryTest {

    @Autowired
    private TestEntityManager entityManager;

    @Autowired
    private UserRepository userRepository;

    @Test
    public void testFindByUsername() {
        // Arrange
        String username = "zhangsan";
        User user = new User();
        user.setUsername(username);
        user.setEmail("zhangsan@example.com");
        entityManager.persist(user);

        // Act
        User foundUser = userRepository.findByUsername(username);

        // Assert
        assertEquals(username, foundUser.getUsername());
    }
}

使用@DataJpaTest优点:

  1. 为JPA相关组件提供轻量级、针对性的测试环境。
  2. 它使用必要的配置设置 Spring 应用程序上下文,从而更容易编写Repository测试。
  3. 自动配置内存数据库,确保测试的隔离性和速度。

错误处理

在进行数据访问操作时,可能会发生异常,例如数据库连接问题、约束违规和数据完整性问题。Spring Data JPA 通过自动将 JPA 特定的异常转换为 Spring 的DataAccessException来简化错误处理,这样就可以在整个应用程序中以一致的方式处理异常。

考虑一个示例,尝试将重复的用户名插入数据库,这时​会发生唯一约束冲突。

@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(unique = true)
    private String username;

    private String email;

    // getters and setters
}

public interface UserRepository extends JpaRepository<User, Long> {
    User findByUsername(String username);
}

在这种情况下,如果尝试插入具有重复用户名的新用户名,则会抛出
ConstraintViolationException异常。

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    public User createUser(User user) {
        try {
            return userRepository.save(user);
        } catch (DataAccessException ex) {
            // 处理异常
            throw new CustomDataAccessException("An error occurred while saving the user.", ex);
        }
    }
}

在UserService中,我们使用一个try-catch块来捕获DataAccessException。Spring Data JPA 自动将底层 JPA 异常转换为更通用的DataAccessException. 然后,我们可以根据应用程序的要求处理此异常。

为了使错误处理的信息更丰富,我们可以创建扩展 SpringDataAccessException或其子类的自定义异常类。例如:

public class CustomDataAccessException extends DataAccessException {

    public CustomDataAccessException(String msg, Throwable cause) {
        super(msg, cause);
    }
}

接下来,在Controller中处理异常:

@RestController
@RequestMapping("/users")
public class UserController {

    @Autowired
    private UserService userService;

    @PostMapping
    public ResponseEntity<?> createUser(@RequestBody User user) {
        try {
            User createdUser = userService.createUser(user);
            return ResponseEntity.status(HttpStatus.CREATED).body(createdUser);
        } catch (CustomDataAccessException ex) {
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ex.getMessage());
        }
    }
}

总结

在 Spring Boot 应用程序中使用 JPA 创建Repository接口时遵循最佳实践对于实现可维护、可扩展和有组织的代码至关重要。通过遵循最佳实践,开发人员可以构建强大的数据访问层,与更广泛的应用程序架构无缝集成。这种方法促进了模块化,增强了可测试性,确保了明确的职责分离,并最终有助于开发高质量的 Spring Boot 应用程序。



Tags:Spring   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
Spring Security:保障应用安全的利器
SpringSecurity作为一个功能强大的安全框架,为Java应用程序提供了全面的安全保障,包括认证、授权、防护和集成等方面。本文将介绍SpringSecurity在这些方面的特性和优势,以及它...【详细内容】
2024-02-27  Search: Spring  点击:(52)  评论:(0)  加入收藏
Spring Security权限控制框架使用指南
在常用的后台管理系统中,通常都会有访问权限控制的需求,用于限制不同人员对于接口的访问能力,如果用户不具备指定的权限,则不能访问某些接口。本文将用 waynboot-mall 项目举例...【详细内容】
2024-02-19  Search: Spring  点击:(39)  评论:(0)  加入收藏
详解基于SpringBoot的WebSocket应用开发
在现代Web应用中,实时交互和数据推送的需求日益增长。WebSocket协议作为一种全双工通信协议,允许服务端与客户端之间建立持久性的连接,实现实时、双向的数据传输,极大地提升了用...【详细内容】
2024-01-30  Search: Spring  点击:(9)  评论:(0)  加入收藏
Spring实现Kafka重试Topic,真的太香了
概述Kafka的强大功能之一是每个分区都有一个Consumer的偏移值。该偏移值是消费者将读取的下一条消息的值。可以自动或手动增加该值。如果我们由于错误而无法处理消息并想重...【详细内容】
2024-01-26  Search: Spring  点击:(84)  评论:(0)  加入收藏
SpringBoot如何实现缓存预热?
缓存预热是指在 Spring Boot 项目启动时,预先将数据加载到缓存系统(如 Redis)中的一种机制。那么问题来了,在 Spring Boot 项目启动之后,在什么时候?在哪里可以将数据加载到缓存系...【详细内容】
2024-01-19  Search: Spring  点击:(86)  评论:(0)  加入收藏
Spring Boot2.0深度实践 核心原理拆解+源码分析
Spring Boot2.0深度实践:核心原理拆解与源码分析一、引言Spring Boot是一个基于Java的轻量级框架,它简化了Spring应用程序的创建过程,使得开发者能够快速搭建一个可运行的应用...【详细内容】
2024-01-15  Search: Spring  点击:(93)  评论:(0)  加入收藏
SpringBoot3+Vue3 开发高并发秒杀抢购系统
开发高并发秒杀抢购系统:使用SpringBoot3+Vue3的实践之旅随着互联网技术的发展,电商行业对秒杀抢购系统的需求越来越高。为了满足这种高并发、高流量的场景,我们决定使用Spring...【详细内容】
2024-01-14  Search: Spring  点击:(90)  评论:(0)  加入收藏
Spring Boot 3.0是什么?
Spring Boot 3.0是一款基于Java的开源框架,用于简化Spring应用程序的构建和开发过程。与之前的版本相比,Spring Boot 3.0在多个方面进行了改进和增强,使其更加易用、高效和灵活...【详细内容】
2024-01-11  Search: Spring  点击:(132)  评论:(0)  加入收藏
GraalVM与Spring Boot 3.0:加速应用性能的完美融合
在2023年,SpringBoot3.0的发布标志着Spring框架对GraalVM的全面支持,这一支持是对Spring技术栈的重要补充。GraalVM是一个高性能的多语言虚拟机,它提供了Ahead-of-Time(AOT)编...【详细内容】
2024-01-11  Search: Spring  点击:(124)  评论:(0)  加入收藏
Spring Boot虚拟线程的性能还不如Webflux?
早上看到一篇关于Spring Boot虚拟线程和Webflux性能对比的文章,觉得还不错。内容较长,抓重点给大家介绍一下这篇文章的核心内容,方便大家快速阅读。测试场景作者采用了一个尽可...【详细内容】
2024-01-10  Search: Spring  点击:(115)  评论:(0)  加入收藏
▌简易百科推荐
对于微服务架构监控应该遵守的原则
随着软件交付方式的变革,微服务架构的兴起使得软件开发变得更加快速和灵活。在这种情况下,监控系统成为了微服务控制系统的核心组成部分。随着软件的复杂性不断增加,了解系统的...【详细内容】
2024-04-03  步步运维步步坑    Tags:架构   点击:(5)  评论:(0)  加入收藏
大模型应用的 10 种架构模式
作者 | 曹洪伟在塑造新领域的过程中,我们往往依赖于一些经过实践验证的策略、方法和模式。这种观念对于软件工程领域的专业人士来说,已经司空见惯,设计模式已成为程序员们的重...【详细内容】
2024-03-27    InfoQ  Tags:架构模式   点击:(13)  评论:(0)  加入收藏
哈啰云原生架构落地实践
一、弹性伸缩技术实践1.全网容器化后一线研发的使用问题全网容器化后一线研发会面临一系列使用问题,包括时机、容量、效率和成本问题,弹性伸缩是云原生容器化后的必然技术选择...【详细内容】
2024-03-27  哈啰技术  微信公众号  Tags:架构   点击:(10)  评论:(0)  加入收藏
DDD 与 CQRS 才是黄金组合
在日常工作中,你是否也遇到过下面几种情况: 使用一个已有接口进行业务开发,上线后出现严重的性能问题,被老板当众质疑:“你为什么不使用缓存接口,这个接口全部走数据库,这怎么能扛...【详细内容】
2024-03-27  dbaplus社群    Tags:DDD   点击:(11)  评论:(0)  加入收藏
高并发架构设计(三大利器:缓存、限流和降级)
软件系统有三个追求:高性能、高并发、高可用,俗称三高。本篇讨论高并发,从高并发是什么到高并发应对的策略、缓存、限流、降级等。引言1.高并发背景互联网行业迅速发展,用户量剧...【详细内容】
2024-03-13    阿里云开发者  Tags:高并发   点击:(6)  评论:(0)  加入收藏
如何判断架构设计的优劣?
架构设计的基本准则是非常重要的,它们指导着我们如何构建可靠、可维护、可测试的系统。下面是这些准则的转换表达方式:简单即美(KISS):KISS原则的核心思想是保持简单。在设计系统...【详细内容】
2024-02-20  二进制跳动  微信公众号  Tags:架构设计   点击:(36)  评论:(0)  加入收藏
详解基于SpringBoot的WebSocket应用开发
在现代Web应用中,实时交互和数据推送的需求日益增长。WebSocket协议作为一种全双工通信协议,允许服务端与客户端之间建立持久性的连接,实现实时、双向的数据传输,极大地提升了用...【详细内容】
2024-01-30  ijunfu  今日头条  Tags:SpringBoot   点击:(9)  评论:(0)  加入收藏
PHP+Go 开发仿简书,实战高并发高可用微服务架构
来百度APP畅享高清图片//下栽のke:chaoxingit.com/2105/PHP和Go语言结合,可以开发出高效且稳定的仿简书应用。在实现高并发和高可用微服务架构时,我们可以采用一些关键技术。首...【详细内容】
2024-01-14  547蓝色星球    Tags:架构   点击:(115)  评论:(0)  加入收藏
GraalVM与Spring Boot 3.0:加速应用性能的完美融合
在2023年,SpringBoot3.0的发布标志着Spring框架对GraalVM的全面支持,这一支持是对Spring技术栈的重要补充。GraalVM是一个高性能的多语言虚拟机,它提供了Ahead-of-Time(AOT)编...【详细内容】
2024-01-11    王建立  Tags:Spring Boot   点击:(124)  评论:(0)  加入收藏
Spring Boot虚拟线程的性能还不如Webflux?
早上看到一篇关于Spring Boot虚拟线程和Webflux性能对比的文章,觉得还不错。内容较长,抓重点给大家介绍一下这篇文章的核心内容,方便大家快速阅读。测试场景作者采用了一个尽可...【详细内容】
2024-01-10  互联网架构小马哥    Tags:Spring Boot   点击:(115)  评论:(0)  加入收藏
站内最新
站内热门
站内头条