”工欲善其事,必先利其器。“—孔子《论语.录灵公》
首页 > 编程 > 使用 JDK 记录促进 Spring 开发的真实示例

使用 JDK 记录促进 Spring 开发的真实示例

发布于2024-11-08
浏览:166

In this article, we'll explore the various scenarios where JDK 14 Records prove to be a game-changer.

Unlocking the Power of JDK 14 Records: A Simplified Introduction

New to JDK 14 Records? Let's start with the fundamentals: Records provide a compact syntax for defining classes that serve as simple, immutable data holders, minus the unnecessary code.

A practical example is the best way to demonstrate this. Consider the following Java class:

public final class Author {
  
  private final String name;
  private final String genre;    
  
  public Author(String name, String genre) {
    this.name = name;
    this.genre = genre;  
  }

    public String getName() {
        return name;
    }

    public String getGenre() {
        return genre;
    }

    @Override    public boolean equals(Object o) {
      ...
    }
            
    @Override    public int hashCode() {
      ...
    }

    @Override    public String toString() {
      ...
    }
}

Thanks to JDK 14, the Records syntax can be utilized to condense the above code into a single, concise line, making your coding experience more efficient:

public record Author(String name, String genre) {}

At t8tech.com, we've seen firsthand how JDK 14 Records can revolutionize the way you approach Spring development. In this article, we'll delve into the various scenarios where this feature comes into play, providing you with a comprehensive understanding of its applications.

And that's the final verdict! Running the javap tool on Author.class produces the following output:

eal-World Examples to Boost Your Spring Development with JDK Records

Upon scrutinizing the properties of an immutable class, we notice that Person.class indeed exhibits immutability:

  • The class must be declared final to prevent subclassing (other classes cannot extend this class, thereby precluding method overriding).
  • All fields should be declared private and final. (They are inaccessible to other classes, and they are initialized solely once in the constructor of this class.)
  • The class should feature a parameterized public constructor (or a private constructor and factory methods for instance creation) that initializes the fields.
  • The class should provide getter methods for fields.
  • The class should not expose setter methods.

You can delve deeper into immutable objects in my book, Java Coding Problems.

eal-World Examples to Boost Your Spring Development with JDK Records

So, JDK 14 Records are not a substitute for mutable JavaBean classes. They cannot be utilized as JPA/Hibernate entities. However, they are an ideal fit for use with Streams. They can be instantiated via the constructor with arguments, and in lieu of getters, we can access the fields via methods with similar names (e.g., the field  name is exposed via the  name() method).

Next, let’s explore several scenarios of utilizing JDK 14 Records in a Spring application. 

JSON Serialization of Records

Let’s assume that an author has penned multiple books. By defining a List in the Author class, we can model this scenario, having the Author and the Book class:

public final class Author {
  
  private final String name;
  private final String genre;
  private final List<Book> books;
  ...
}
    
public final Book {
  
  private String title;
  private String isbn;
  ...
}

If we use Records, then we can eliminate the boilerplate code as below:

public record Author(String name, String genre, List<Book> books) {}
public record Book(String title, String isbn) {}

Let’s consider the following data sample:

List<Author> authors = List.of(
  new Author("Joana Nimar", "History", List.of(
    new Book("History of a day", "JN-001"),
    new Book("Prague history", "JN-002")
  )),
  new Author("Mark Janel", "Horror", List.of(
    new Book("Carrie", "MJ-001"),
    new Book("House of pain", "MJ-002")
  ))
);

If we want to serialize this data as JSON via a Spring REST Controller, then most we will most likely do it, as shown below. First, we have a service that returns the data:

@Servicepublic class BookstoreService {
  
  public List<Author> fetchAuthors() {
    
    List<Author> authors = List.of(
      new Author("Joana Nimar", "History", List.of(
        new Book("History of a day", "JN-001"),
        new Book("Prague history", "JN-002")
      )),
      new Author("Mark Janel", "Horror", List.of(
        new Book("Carrie", "MJ-001"),
        new Book("House of pain", "MJ-002")
      )));
    
    return authors;
  }
}

And, the controller is quite simple:

@RestControllerpublic class BookstoreController {
  
  private final BookstoreService bookstoreService;
  
  public BookstoreController(BookstoreService bookstoreService) {
    this.bookstoreService = bookstoreService;
  }
  
  @GetMapping("/authors")  public List<Author> fetchAuthors() {
    return bookstoreService.fetchAuthors();
  }
}

However, when we access the endpoint, localhost:8080/authors, we encounter the following result:

eal-World Examples to Boost Your Spring Development with JDK Records

This suggests that the objects are not serializable. The solution involves adding the Jackson annotations, JsonProperty, to facilitate serialization:

import com.fasterxml.jackson.annotation.JsonProperty;
      
public record Author(
  @JsonProperty("name") String name, 
  @JsonProperty("genre") String genre, 
  @JsonProperty("books") List books
) {}
        
public record Book(
  @JsonProperty("title") String title, 
  @JsonProperty("isbn") String isbn
) {}

This time, accessing the localhost:8080/authors endpoint yields the following JSON output:

[
  {
    "name": "Joana Nimar",
    "genre": "History",
    "books": [
      {
        "title": "History of a day",
        "isbn": "JN-001"
      },
      {
        "title": "Prague history",
        "isbn": "JN-002"
      }
    ]
  },
  {
    "name": "Mark Janel",
    "genre": "Horror",
    "books": [
      {
        "title": "Carrie",
        "isbn": "MJ-001"
      },
      {
        "title": "House of pain",
        "isbn": "MJ-002"
      }
    ]
  }
]

The complete code is available on GitHub.

Records and Dependency Injection: A Closer Look

Let’s revisit our controller and explore how records and dependency injection work together:

@RestControllerpublic class BookstoreController {
  
  private final BookstoreService bookstoreService;
  
  public BookstoreController(BookstoreService bookstoreService) {
    this.bookstoreService = bookstoreService;
  }
  
  @GetMapping("/authors")  public List fetchAuthors() {
    return bookstoreService.fetchAuthors();
  }
}

In this controller, we utilize Dependency Injection to inject a BookstoreService instance. Alternatively, we could have employed @Autowired. However, we can explore the use of JDK 14 Records, as demonstrated below:

@RestControllerpublic record BookstoreController(BookstoreService bookstoreService) {
    
  @GetMapping("/authors")  public List<Author> fetchAuthors() {
    return bookstoreService.fetchAuthors();
  }
}

The complete code is available on GitHub.

DTOs via Records and Spring Data Query Builder

Let’s reiterate this crucial point:

JDK 14 Records are incompatible with JPA/Hibernate entities due to the absence of setters.

Now, let’s examine the following JPA entity:

@Entitypublic class Author implements Serializable {
  
  private static final long serialVersionUID = 1L;
  
  @Id  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
  
  private int age;
  private String name;
  private String genre;
    
  // getters and setters 
}

Our goal is to retrieve a read-only list of authors, including their names and ages. To achieve this, we require a DTO. We can define a Spring projection, a POJO, or a Java Record, as shown below:

public record AuthorDto(String name, int age) {}

The query that populates the DTO can be crafted using Spring Data Query Builder:

public interface AuthorRepository extends JpaRepository<Author, Long> {
  
  @Transactional(readOnly = true)    
  List<AuthorDto> retrieveAuthorsByGenre(String genre);
}

The complete application is available on GitHub.

DTOs via Records and Constructor Expression and JPQL

Given the same Author entity and the same AuthorDto record, we can construct the query via Constructor Expression and JPQL, as follows:

public interface AuthorRepository extends JpaRepository<Author, Long> {
  
  @Transactional(readOnly = true)
  @Query(value = "SELECT new com.bookstore.dto.AuthorDto(a.name, a.age) FROM Author a")
  List<AuthorDto> retrieveAuthors();
}

The comprehensive application is accessible on GitHub.

DTOs via Records and Hibernate ResultTransformer

In certain scenarios, we need to retrieve a DTO comprising a subset of properties (columns) from a parent-child association. For such cases, we can utilize a SQL JOIN that can extract the desired columns from the involved tables. However, JOIN returns a List, and most likely, you will need to represent it as a ListParentDto>, where a ParentDto instance has a ListChildDto>.

Such an example is the below bidirectional @OneToMany relationship between Author and Book entities:

eal-World Examples to Boost Your Spring Development with JDK Records

@Entitypublic class Author implements Serializable {
  
  private static final long serialVersionUID = 1L;
  
  @Id  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
  
  private String name;
  private String genre;
  private int age;
  
  @OneToMany(cascade = CascadeType.ALL,
             mappedBy = "author", orphanRemoval = true)
  private List<Book> books = new ArrayList<>();
     
  // getters and setters
}
@Entitypublic class Book implements Serializable {
  
  private static final long serialVersionUID = 1L;
  
  @Id  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
  
  private String title;
  private String isbn;
  
  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "author_id")
  private Author author;
     
  // getters and setters
}

To retrieve the id, name, and age of each author, along with the id and title of their associated books, the application leverages DTO and the Hibernate-specific ResultTransformer. This interface enables the transformation of query results into the actual application-visible query result list, supporting both JPQL and native queries, and is a remarkably powerful feature. 

The initial step involves defining the DTO class. The ResultTransformer can fetch data in a DTO with a constructor and no setters or in a DTO with no constructor but with setters. To fetch the name and age in a DTO with a constructor and no setters, a DTO shaped via JDK 14 Records is required:

import java.util.List;

public record AuthorDto(Long id, String name, int age, List books) {
  
  public void addBook(BookDto book) {
    books().add(book);
  }
}
public record BookDto(Long id, String title) {}

However, assigning the result set to AuthorDto is not feasible through a native ResultTransformer. Instead, you need to convert the result set from Object[] to List, which necessitates the custom AuthorBookTransformer, an implementation of the ResultTransformer interface. 

This interface specifies two methods — transformTuple() and transformList(). The transformTuple() method facilitates the transformation of tuples, which constitute each row of the query result. The transformList() method, on the other hand, enables you to perform the transformation on the query result as a whole:

public class AuthorBookTransformer implements ResultTransformer {

  private Map authorsDtoMap = new HashMap();

  @Override  public Object transformTuple(Object[] os, String[] strings) {
    
    Long authorId = ((Number) os[0]).longValue();
    
    AuthorDto authorDto = authorsDtoMap.get(authorId);
    
    if (authorDto == null) {
      authorDto = new AuthorDto(((Number) os[0]).longValue(), 
         (String) os[1], (int) os[2], new ArrayList());
    }
    
    BookDto bookDto = new BookDto(((Number) os[3]).longValue(), (String) os[4]);
    
    authorDto.addBook(bookDto);
    
    authorsDtoMap.putIfAbsent(authorDto.id(), authorDto);
    
    return authorDto;
  }
  
  @Override  public List transformList(List list) {
    return new ArrayList(authorsDtoMap.values());
  }
}

The bespoke DAO implementation that leverages this custom ResultTransformer is presented below:

@Repositorypublic class DataAccessObject implements AuthorDataAccessObject {
  
  @PersistenceContext  private EntityManager entityManager;
  
  @Override  @Transactional(readOnly = true)
  public List retrieveAuthorWithBook() {
    Query query = entityManager
      .createNativeQuery(
        "SELECT a.id AS author_id, a.name AS name, a.age AS age, "
          "b.id AS book_id, b.title AS title "
          "FROM author a JOIN book b ON a.id=b.author_id")
      .unwrap(org.hibernate.query.NativeQuery.class)
      .setResultTransformer(new AuthorBookTransformer());
    
    List authors = query.getResultList();
    
    return authors;
  }
}

In the end, we can obtain the data in the following service:

@Servicepublic class BookstoreBusinessService {
  
  private final DataAccessObject dao;
  
  public BookstoreBusinessService(DataAccessObject dao) {
    this.dao = dao;
  }
  
  public List retrieveAuthorWithBook() {
    
    List authors = dao.retrieveAuthorWithBook();        
    
    return authors;
  }
}
@Servicepublic record BookstoreBusinessService(DataAccessObject dao) {
    
  public List retrieveAuthorWithBook() {
    
    List authors = dao.retrieveAuthorWithBook();        
    
    return authors;
  }
}

The complete application is available on GitHub.

Starting with Hibernate 5.2,  ResultTransformer is deprecated. Until a replacement is available (in Hibernate 6.0), it can be used.Read further here.

Data Transfer Objects via Records, JdbcTemplate, and ResultSetExtractor

Achieving a similar mapping via  JdbcTemplate and  ResultSetExtractor can be accomplished as follows. The  AuthorDataTransferObject and  BookDataTransferObject are the same from the previous section:

public record AuthorDto(Long id, String name, int age, List books) {
     
  public void addBook(BookDto book) {
    books().add(book);
  }
}
public record BookDto(Long id, String title) {}
@Repository@Transactional(readOnly = true)
public class AuthorExtractor {
  
  private final JdbcTemplate jdbcTemplate;
  
  public AuthorExtractor(JdbcTemplate jdbcTemplate) {
    this.jdbcTemplate = jdbcTemplate;
  }
  
  public List<AuthorDto> extract() {
    
    String sql = "SELECT a.id, a.name, a.age, b.id, b.title "
        "FROM author a INNER JOIN book b ON a.id = b.author_id";
    
    List<AuthorDto> result = jdbcTemplate.query(sql, (ResultSet rs) -> {
      
      final Map<Long, AuthorDto> authorsMap = new HashMap<>();
      while (rs.next()) {
        Long authorId = (rs.getLong("id"));
        AuthorDto author = authorsMap.get(authorId);
           
        if (author == null) {
          author = new AuthorDto(rs.getLong("id"), rs.getString("name"),
            rs.getInt("age"), new ArrayList());
        }
        
        BookDto book = new BookDto(rs.getLong("id"), rs.getString("title"));
        author.addBook(book);
        authorsMap.putIfAbsent(author.id(), author);
      }
        
      return new ArrayList<>(authorsMap.values());
    });
    
    return result;
  }
}

The complete application is available on GitHub.

Refine the Implementation 

Java Records allow us to validate the arguments of the constructor, therefore the following code is ok:

public record Author(String name, int age) {
  
  public Author {
    if (age <=18 || age > 70)
      throw new IllegalArgumentException("...");
  }
}

For a deeper understanding, I suggest exploring this valuable resource. Furthermore, you may also find it advantageous to examine over 150 key considerations for optimizing persistence performance in Spring Boot, as detailed in Spring Boot Persistence Best Practices:

eal-World Examples to Boost Your Spring Development with JDK Records

版本声明 本文转载于:https://dev.to/jackwilltech/5-real-world-examples-to-boost-your-spring-development-with-jdk-14-records-21g9?1如有侵犯,请联系[email protected]删除
最新教程 更多>
  • 为什么我的 MySQL 查询在 PHP 中返回“资源 id #6”?
    为什么我的 MySQL 查询在 PHP 中返回“资源 id #6”?
    在 PHP 中回显 MySQL 响应的资源 ID #6在 PHP 中使用 MySQL 扩展查询数据库时,您可能会遇到“Resource id #6”输出而不是预期结果。发生这种情况是因为查询返回资源,而不是字符串或数值。回显结果要显示预期结果,您必须首先使用以下命令获取数据提供的获取函数之一。其中一...
    编程 发布于2024-11-08
  • (SQL 查询)Express.js 中的缓存与索引
    (SQL 查询)Express.js 中的缓存与索引
    开发者您好,这是我在这个平台上的第一篇文章! ? 我想分享我在 Express.js 和 SQL 方面的令人惊讶的体验。我是一名初学者开发人员,在为我的项目开发 API 时,我每天处理超过 20 万个 API 请求。最初,我使用 Express.js API 设置了一个 SQLite 数据库(约 4...
    编程 发布于2024-11-08
  • 以下是一些适合您文章内容的基于问题的标题:

* 如何为 Spring Boot 应用程序配置上下文路径?
* 如何使用自定义 Con 访问我的 Spring Boot 应用程序
    以下是一些适合您文章内容的基于问题的标题: * 如何为 Spring Boot 应用程序配置上下文路径? * 如何使用自定义 Con 访问我的 Spring Boot 应用程序
    如何向 Spring Boot 应用程序添加上下文路径Spring Boot 提供了一种简单的方法来设置应用程序的上下文根,允许它通过 localhost:port/{app_name} 访问。操作方法如下:使用应用程序属性:在 src/main/resources 目录中创建一个 applicat...
    编程 发布于2024-11-08
  • 代码日数:高级循环
    代码日数:高级循环
    2024 年 8 月 30 日星期五 我目前正在学习 Codecademy 全栈工程师路径的第二门课程。我最近完成了 JavaScript 语法 I 课程,并完成了 JavaScript 语法 II 中的数组和循环作业。接下来是对象、迭代器、错误和调试、练习和三个挑战项目。 今天的主要亮点是学习对我...
    编程 发布于2024-11-08
  • Angular Addicts # Angular 隐式库,未来是独立的等等
    Angular Addicts # Angular 隐式库,未来是独立的等等
    ?嘿,Angular Addict 伙伴 这是 Angular Addicts Newsletter 的第 29 期,这是一本每月精选的引起我注意的 Angular 资源合集。 (这里是第28期、27期、26期) ?发布公告 ? Angular 18...
    编程 发布于2024-11-08
  • 如何在 Java HashMap 中将多个值映射到单个键?
    如何在 Java HashMap 中将多个值映射到单个键?
    HashMap 中将多个值映射到单个键在 Java 的 HashMap 中,每个键都与单个值关联。但是,在某些情况下,您可能需要将多个值映射到单个键。以下是实现此目的的方法:多值映射方法:最简单、最直接的方法是使用列表映射。这涉及创建一个 HashMap,其中的值是包含多个值的 ArrayList。...
    编程 发布于2024-11-08
  • 如何使用 PHP 高效地检查文件中的字符串?
    如何使用 PHP 高效地检查文件中的字符串?
    如何在 PHP 中检查文件是否包含字符串要确定文件中是否存在特定字符串,让我们探索一下解决方案和更有效的替代方案。原始代码:提供的代码尝试检查通过逐行读取文件来判断文件中是否存在由变量 $id 表示的字符串。但是,while 循环中的条件 (strpos($buffer, $id) === fals...
    编程 发布于2024-11-08
  • 小型 Swoole 实体管理器
    小型 Swoole 实体管理器
    我很高兴向大家介绍 Small Swoole Entity Manager。 它是一个围绕 Swoole(和 OpenSwoole)构建的 ORM。 它支持异步连接到: MySQL Postgres Small Swoole Db(Swoole Tables 之上的关系层) 目前仅提供核心包; S...
    编程 发布于2024-11-08
  • WebCodec - 发送和接收
    WebCodec - 发送和接收
    介绍 你好! ? 在本教程中,我将向您展示如何使用 WebCodec API 发送和接收视频。 首先让我们对服务器进行编码。 设置服务器 为了在对等点之间发送和接收数据包,我们需要一个 websocket 服务器。 为此,我们将使用 Nodejs 创建一个非常基...
    编程 发布于2024-11-08
  • 为什么 PHP ftp_put() 失败:分析和解决问题
    为什么 PHP ftp_put() 失败:分析和解决问题
    PHP ftp_put 失败:分析问题并解决它传输时 ftp_put() 无法正常运行可能是一个令人沮丧的问题通过 FTP 传输文件。在 PHP 中,此问题的常见原因在于默认使用主动模式。主动与被动模式传输主动模式指示 FTP 服务器连接到指定端口上的客户端。另一方面,被动模式让服务器侦听随机端口,...
    编程 发布于2024-11-08
  • 如何从字符串中删除非数字字符,同时保留 Java 中的小数分隔符?
    如何从字符串中删除非数字字符,同时保留 Java 中的小数分隔符?
    在删除 Java 字符串中的非数字字符时保留小数分隔符使用 Java 字符串时,可能会出现您需要的情况删除所有非数字字符,同时保留小数点分隔符。使用正则表达式和replaceAll()方法可以有效地实现这一点。为了解决这个问题,我们可以使用以下代码片段:String str = "a12....
    编程 发布于2024-11-08
  • 如何重新排列 MySQL 中的列以提高数据可视性和查询效率?
    如何重新排列 MySQL 中的列以提高数据可视性和查询效率?
    有效地重新排列 MySQL 列以增强可见性当列没有最佳排序时,查询大型数据库可能会很麻烦。本文提供了一个全面的解决方案,可以轻松地重新排列现有列,优化表的可见性而不影响其数据完整性。要修改列的位置,请使用“ALTER TABLE”命令,后跟“MODIFY”子句。此语法允许您通过在指定的引用列之后指定...
    编程 发布于2024-11-08
  • 如何正确使用 getElementsByClassName 并根据事件更改元素样式?
    如何正确使用 getElementsByClassName 并根据事件更改元素样式?
    使用 getElementsByClassName 更改元素样式getElementsByClassName 允许您选择具有相同类名的多个元素。在给出的示例中,代码旨在当事件发生在具有特定类名的所有 div 之外时更改这些 div 的背景颜色。问题诊断The提供的代码有一些问题: getElemen...
    编程 发布于2024-11-08
  • 为什么我的画布图像无法绘制?异步图像加载的重要性。
    为什么我的画布图像无法绘制?异步图像加载的重要性。
    绘图前等待图像加载尝试将图像添加到画布时,确保图像在绘制之前加载至关重要试图画它。您在代码中遇到的问题是由于图像加载的异步性质造成的。要解决此问题,您需要向图像的 onload 事件添加回调函数。该回调函数将在图像加载完成时执行,确保在尝试绘制图像之前图像数据可用。下面更正的代码将等待图像加载,然后...
    编程 发布于2024-11-08
  • Golang 中的 LeetCode:解析布尔表达式
    Golang 中的 LeetCode:解析布尔表达式
    这是我喜欢解决的 LeetCode 问题之一。我用 Golang 解决了这个问题,而且我已经是一个 Go 新手了,刚开始学习一周。 直觉 这个问题是实现计算器程序的另一个版本,该程序接受一个字符串并对其进行计算。您必须通过评估内部括号和外部括号来解决问题,直到得到最终结果。这些问题...
    编程 发布于2024-11-08

免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。

Copyright© 2022 湘ICP备2022001581号-3