本周精心挑选9篇Java和Spring文章

精心挑选了一些值得一读的有趣 Java 和 Spring 文章。这些文章包括并行流、JPA、缓存、OpenAI API、Java 安全、Mockito 等主题。

1. 在 Spring Data JPA 中保存后刷新并获取实体
本文讨论了如何在 Spring Data JPA 中管理实体,特别关注保存、获取和刷新实体。它涵盖了如何设置 Spring Data JPA、定义实体以及使用方法save()来持久化数据。可以使用类似 的方法获取实体findById(),并深入了解即时加载和延迟加载。

本文还介绍了refresh()更新陈旧实体的方法以及OptimisticLockException发生并发修改时的处理方法。这确保了应用程序中实体管理的稳健性和可靠性。

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class ProductService {
    @PersistenceContext
    private EntityManager entityManager;

    @Transactional
    public Product refreshProduct(Product product) {
        entityManager.refresh(product);
        return product;
    }
}


2.使用Spring实现两级缓存
在 Spring 中实现两级缓存可以显著提高应用程序的性能和可扩展性。通过结合第一级内存缓存和第二级分布式缓存,开发人员可以确保更快的数据访问并减少数据库的负载。

这种方法尤其有利于读取需求高且数据相对稳定的应用程序。第一级缓存提供对常用数据的快速访问,而第二级缓存提供可扩展的解决方案,用于处理更大的数据集并确保多个实例之间的数据一致性。

  • caffeine:第一级缓存
  • Redis:第二级缓存

package com.jcg.example.config;

@Configuration
public class TwoLevelCacheConfig extends CachingConfigurerSupport {
   @Bean
   public RedisConnectionFactory redisConnectionFactory() {
       return new LettuceConnectionFactory();
   }

   @Bean
   public RedisTemplate redisTemplate() {
       RedisTemplate template = new RedisTemplate();
       template.setConnectionFactory(redisConnectionFactory());
       return template;
   }

   @Bean
   public CacheManager cacheManager() {
       CompositeCacheManager cacheManager = new CompositeCacheManager();
       CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager("items");
       caffeineCacheManager.setCaffeine(Caffeine.newBuilder()
               .initialCapacity(100)
               .maximumSize(500)
               .expireAfterAccess(10, TimeUnit.MINUTES));

       RedisCacheConfiguration redisCacheConfig = RedisCacheConfiguration.defaultCacheConfig()
               .entryTtl(Duration.ofMinutes(10));
       RedisCacheManager redisCacheManager = RedisCacheManager.builder(redisConnectionFactory())
               .cacheDefaults(redisCacheConfig)
               .build();

       cacheManager.setCacheManagers(Arrays.asList(caffeineCacheManager, redisCacheManager));
       return cacheManager;
   }
}


3. Java 22 概览:多文件源代码程序
Java 22 对多文件源代码程序的支持是 Java 朝着更易于访问和更灵活的方向迈出的重要一步。它允许直接执行复杂的多文件程序,而无需进行大量设置,从而简化了初学者和经验丰富的开发人员的开发工作流程。

这营造了一种有利于学习、快速原型设计和实验的环境,使 Java 与其他现代编程语言的易用性更加接近。

// MyAwesomeApp.java
class MyAwesomeApp {

  public static void main(String... args) {
    new AwesomeFeature().doTheWork(args);
  }
}

// AwesomeFeature.java
class AwesomeFeature {

  void doTheWork(String... args) {
   
// ...
  }
}

4. Java 中的 OpenAI API 客户端
它介绍了设置 Spring Boot 应用程序以调用 OpenAI ChatGPT API 的过程,该 API 根据给定的提示生成响应。关键步骤包括配置必要的依赖项、为请求和响应创建数据传输对象 (DTO) 以及使用 API 密钥处理 API 身份验证。

该指南强调使用 Spring Boot 的功能来有效管理 API 请求和响应,并提供了实际示例和代码片段以方便实施。

5. 使用 IdClass 将主键实现为Record记录
本文探讨了如何IdClass在 Java 中(特别是使用 Hibernate ORM 6.5.0 及更高版本)将主键实现为记录。这种方法非常高效,提供不变性和内置的equals和hashCode方法。在 中建模的主键IdClass必须与实体的主键属性相匹配。

由于内部实例化机制,Hibernate 要求记录字段按字母顺序排列。本文还介绍了如何使用这些记录及其局限性,特别是在 Hibernate 6.5.0 之前的版本中。

有多种方式来实现您自己的主键:

  • 主键可由多个属性组成。
  • 或者您遵循领域驱动的设计原则并希望使用自定义类型为其添加语义含义。

在这些情况下,实现记录并将其映射为 IdClass 似乎是显而易见的。记录高效、易于实现、不可变(就像主键一样),并提供 equals 和 hashCode 方法。

如果将其映射为 IdClass,则只需定义实体用作主键属性的同一组字段,并实现 equals和hashCode方法。其他一切都由 Hibernate 处理。

@Entity
@IdClass(ChessGameId.class)
public class ChessGame {

    @Id
    private int round;

    @Id
    private String playerWhite;
    
    @Id
    private String playerBlack;

    private ZonedDateTime dateTime;

    @Version
    private int version;

    ...
}

解释:

  • ChessGame实体的主键由玩家的姓名和他们玩游戏的回合组成。
  • @Id注释注释了这 3 个属性, 以将它们标记为主键属性。

Hibernate 和 Jakarta Persistence 规范要求每个实体对象的主键值都可以由单个主键对象表示。实现此目的的一种方法是提供 IdClass。

IdClass 必须由 @IdClass 注释引用,并且必须为所有主键属性建模,但不包括其他属性。所有 IdClass 属性的名称和类型都必须与其对应的实体属性相匹配。

因此,在这个例子中,ChessGameId必须对String类型的属性playerWhite、String类型的属性 playerBlack和int类型的属性 round进行建模。

从 Hibernate ORM 6.5.0 开始,您可以将 IdClass 实现为记录:

public record ChessGameId(String playerBlack, String playerWhite, int round) {}

在内部,Hibernate 使用EmbeddableInstantiator来实例化表示主键值的记录。这对IdClass记录的设计造成了重大限制。

  • 实例化新的 IdClass 记录时,Hibernate 默认的EmbeddableInstantiator按照属性名称的字母顺序提供主键属性的值。
  • 在定义记录时,您必须牢记这一点,并确保按字母顺序定义其字段。

使用 IdClass:

ChessGame game = new ChessGame();
game.setRound(1);
game.setPlayerWhite("Fabiano Caruana");
game.setPlayerBlack(
"Hikaru Nakamura");
game.setDateTime(ZonedDateTime.of(2024, 4, 4, 14, 30, 0, 0, ZoneId.of(
"America/Toronto")));
em.persist(game);

然后,Hibernate 将每个属性映射到数据库列:

16:02:34,659 DEBUG [org.hibernate.SQL] - 
    insert 
    into
        ChessGame
        (dateTime, version, playerBlack, playerWhite, round) 
    values
        (?, ?, ?, ?, ?)
16:02:34,662 TRACE [org.hibernate.orm.jdbc.bind] - binding parameter (1:TIMESTAMP_UTC) <- [2024-04-04T14:30-04:00[America/Toronto]]
16:02:34,662 TRACE [org.hibernate.orm.jdbc.bind] - binding parameter (2:INTEGER) <- [0]
16:02:34,662 TRACE [org.hibernate.orm.jdbc.bind] - binding parameter (3:VARCHAR) <- [Hikaru Nakamura]
16:02:34,663 TRACE [org.hibernate.orm.jdbc.bind] - binding parameter (4:VARCHAR) <- [Fabiano Caruana]
16:02:34,663 TRACE [org.hibernate.orm.jdbc.bind] - binding parameter (5:INTEGER) <- [1]

并且,当您调用EntityManager.find方法或使用 Hibernate 的任何其他基于主键的 API 时,您必须提供 IdClass 的对象。因此,在此示例中,我必须实例化ChessGameId记录并将其作为参数提供给EntityManager.find方法。

em.find(ChessGame.class, new ChessGameId("Hikaru Nakamura", "Fabiano Caruana", 1));

然后,Hibernate 为主键的每个部分生成一个具有相等谓词的查询。


6. Java 安全性的简单提示和应避免的错误
本文提供了增强 Java 应用程序安全性的实用技巧,并强调了应避免的常见错误。它涵盖的主题包括使用安全编码实践、安全地管理依赖项、实施适当的身份验证和授权机制、加密敏感数据、安全地处理异常以及及时更新安全补丁。

通过遵循这些提示并避免常见的陷阱,开发人员可以显著改善其 Java 应用程序的安全态势。

7.如何在Java中测试私有方法
本文介绍了测试私有 Java 方法的方法,这些方法很难从类外部直接访问。建议通过更改访问级别、使用反射间接访问它们或在同一类中创建嵌套测试类或辅助方法,使它们可供测试访问。

每种方法都有其优缺点,例如可读性降低或潜在的性能开销。关键是根据项目需求选择最合适的方法,同时确保全面的测试覆盖率而不损害代码完整性。

8. 使用 Mockito 在 Java 单元测试中模拟依赖关系的基础知识
本文全面介绍了如何使用 Mockito 在 Java 单元测试中模拟依赖项。通过利用 Mockito 创建模拟对象和模拟依赖项的功能,开发人员可以编写强大、独立的单元测试,专注于各个组件的行为,最终提高代码质量和可维护性。


9.JobRunr 入门:强大的后台作业处理库
本文为有兴趣利用 JobRunr 在其 Java 应用程序中进行任务调度和后台处理的开发人员提供了全面的指南。通过提供详细的说明、代码示例和集成技巧,它旨在促进顺利的入职流程,并帮助开发人员充分利用 JobRunr 的潜力。

优点:

  • 开发人员友好: JobRunr 的 API 简单、灵活、直接 - 设置简单但广泛。
  • 简单采用: JobRunr 适用于任何软件架构,并且只需要对代码库进行很少的更改,同时只需要很少的依赖关系。
  • 框架集成: JobRunr 支持最流行的 Java 框架:Spring、Quarkus 和 Micronaut。
  • 各种存储选项:持久存储通过 RDBMS(例如 Postgres、MariaDB/MySQL、Oracle、SQL Server、DB2 和 SQLite)或 NoSQL(ElasticSearch、MongoDB 和 Redis)完成
  • 云原生和云无关:可在任何地方部署,无论是在您首选的云提供商还是在本地
  • 分布式处理:规模不是问题——JobRunr 支持跨集群的作业分配。
  • 容错性:JobRunr 也具有容错性 - 外部 Web 服务发生故障?不用担心,该作业会使用智能退避策略自动重试 10 次。重试策略当然是可配置的!
  • 实时监控: JobRunr 提供了一个仪表板,允许用户监控作业、触发或删除重复作业以及重新排队或删除作业。
  • 虚拟线程: JobRunr 支持虚拟线程,以增加 I/O 作业的吞吐量。
  • 全面的文档:文档涵盖了从设置到高级功能的所有内容,简化了学习曲线。

代码: fork the example project