在《??Spring Boot 2.6新特性:使用Java 17的Record作為配置屬性??》,我們提到了使用Java Records來作為Spring Boot的配置屬性(configuration properties),從而減少了大量樣板代碼的編寫,我們本篇將進一步拓展Records在Spring Boot下的應用場景,從而進一步減少我們的樣板代碼,使代碼看上去更簡潔清晰。

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) {
}
我們創建一個簡單的演示項目,依賴如圖所示:

2、使用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,插入兩條數據:

按照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));
}
}
代碼比構造器注入更精簡。
文章出自:??愛科學的衛斯理??,如有轉載本文請聯系愛科學的衛斯理今日頭條號。