# 用Java 17的Records加速Spring Boot开发

在《Spring Boot 2.6新特性：使用Java 17的Record作为配置属性》，我们提到了使用Java Records来作为Spring Boot的配置属性（configuration properties），从而减少了大量样板代码的编写，我们本篇将进一步拓展Records在Spring Boot下的应用场景，从而进一步减少我们的样板代码，使代码看上去更简洁清晰。


![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1668160736017/UfANf3lLj.png align="left")

## 1、什么是Records
record是一种特殊类型的类声明，目的是为了减少样板代码。record引入的主要目的是快速创建数据载体类。

这种类的主要目的就是在不同的模块或者层之间包含并传递数据，它们表现为POJO（Plain Old Java Objects）和DTO（Data Transfer Objects）。

record声明有专门的的关键字record，我们比较下一个简单的POJO类和record上语法的区别：

POJO类：

```
@Data
public class Point {
    private String x;
    private String y;
}
```

record：

```
public record Point(String x, String y) {
}
```
我们创建一个简单的演示项目，依赖如图所示：


![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1668160777497/rQb-_28Am.png align="left")

## 使用record替代普通DTO

我们在Spring MVC的控制器中可以用一个record的DTO来接受前端传递来的数据：

```
@RestController
@RequestMapping("/people")
public class PersonController {

    private final PersonService personService;

    public PersonController(PersonService personService) {
        this.personService = personService;
    }

    @PostMapping
    public ResponseEntity<Person> save(@RequestBody PersonDto personDto){
        return ResponseEntity.ok(personService.save(personDto));
    }

    @GetMapping("/findByLastName")
    public ResponseEntity<List<PersonOnlyWithName>> findByLastName(String lastName){
        return ResponseEntity.ok(personService.findByLastName(lastName));
    }
}
```

上面的PersonDto是一个record：

```
public record PersonDto(String firstName, String lastName,Integer age) {
}
```

## 3、使用record作为Spring的Bean

上面注入的PersonService，是一个Spring的Bean，它同样可以是一个record，我们只需要在record的参数里写上要被注入的bean，这个bean就会自动被注入：

```
@Service
public record PersonService(PersonRepository personRepository) {

  //保存person
    public Person save(PersonDto personDto){
        Person person = new Person(personDto.firstName(), personDto.lastName(), personDto.age());
        return personRepository.save(person);
    }
	
  //按照lastName查询people，返回值只有firstName和lastName
    public List<PersonOnlyWithName> findByLastName(String lastName){
        return personRepository.findByLastName(lastName);
    }
}
```

在这里的PersonRepository的bean可以自动被注入，代码上比属性@Autowired注入，甚至构造器注入代码更简洁。

Spring Data JPA用作数据访问的Repository：

```
public interface PersonRepository extends JpaRepository<Person, Long> {

    List<PersonOnlyWithName> findByLastName(String lastName);
}
```
使用record来声明bean，有一些潜在的问题：

> 1、record中，被注入的对象在当前对象里其实是有一个隐藏的get方法：“
personService.personRepository()”，这违反了信息隐藏的封装原则。

> 2、record定义了equals和hasCode方法，作为service并不需要。

> 3、service的变量属性一般都是final。

> 如果上述的东西对你并没有什么影响，你可以自由决定是否使用。

## 3、使用record作为Spring Data JPA的projection

Spring Data JPA的projection目的是定制查询的数据返回，而不是返回整个实体。一般情况下都是使用接口或者dto类，现在支持使用record。

定制的返回的record内容为：

```
public record PersonOnlyWithName(String firstName, String lastName) {
}
```
即我们查询返回的结果，不需要id和age，只需要firstName和lastName。

## 4、演示应用

启动程序，保存Person，插入两条数据：


![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1668161036673/2tzMWBWHS.png align="left")


按照lastName查询，查看我们projection的效果：

## 用record改造Controller控制器

在上面我们的Controller用的还是普通的class，既然record可以声明为bean并注入bean，那我们改造一下上面的Controller。

```
@RestController
@RequestMapping("/people")
public record PersonController(PersonService personService) {

    @PostMapping
    public ResponseEntity<Person> save(@RequestBody PersonDto personDto){
        return ResponseEntity.ok(personService.save(personDto));
    }

    @GetMapping("/findByLastName")
    public ResponseEntity<List<PersonOnlyWithName>> findByLastName(String lastName){
        return ResponseEntity.ok(personService.findByLastName(lastName));
    }
}
```

代码比构造器注入更精简。

感谢对我的书《从企业级开发到云原生微服务：Spring Boot实战》的支持。

头条原文地址：https://www.toutiao.com/article/7161419177252127239
