在Spring Data JPA中使用投影

605 阅读2分钟

Spring Data查询方法通常返回由存储库管理的聚合根的一个或多个实例。但是,有时可能需要基于这些类型的某些属性创建投影。Spring数据允许对专用返回类型进行建模,以便更有选择性地检索托管聚合的部分视图

准备

一个Entity:

@Getter
@Setter
@Entity
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    @Column(unique = true, nullable = false)
    private String username;

    @Column(nullable = false)
    private String password;

    private Boolean sex;

    private LocalDateTime birthday;
}

一个UserRepository

public interface UserRepository extends PagingAndSortingRepository<User, Integer> {

}

基于接口的投影

准备

  1. 接口
public interface UsernameOnly {
    Optional<String> getUsername();
}

投影接口中的getter可以使用可为空的包装器,以提高null-safety的安全性。当前支持的包装器类型为:

  • java.util.Optional
  • com.google.common.base.Optional
  • scala.Option
  • io.vavr.control.Option
  1. repository
Optional<UsernameOnly> findByUsername(String username);

测试

@SpringBootTest
class UserRepositoryTest {
    @Resource
    private UserRepository userRepository;
    
    @Test
    void findByUsername() {
        Optional<UsernameOnly> usernameOnly = userRepository.findByUsername("Atjtkj");
        if (usernameOnly.isPresent()) {
            System.out.println(usernameOnly.get().getUsername());
        } else {
            System.err.println("No data");
        }
    }
}

输出:

只查询了username
Hibernate: select user0_.username as col_0_0_ from user user0_ where user0_.username=?
Atjtkj

结论

这个例子中使用接口只获取一个username,也就是只有一个方法,使用Optionalrepositoryinterface中的方法都进行了包装,为什么呢?对于repository来说,我查询一条数据映射到接口之中,如果没有查到数据则返回的接口就是null,即使对接口中的方法使用Optional包装也没有用,依然会java.lang.NullPointerException,所以必须对repository中的方法进行包装

而对于接口中的方法而言,如果只有一个方法的话(只查一个字段),其实也不用Optional包装,因为在repository中包装后,返回的接口不为null,那么接口中的方法也一定不会为null,要是接口中有多个方法的话,则加上Optional还是有必要的。

基于类的投影

准备

一个DTO:

这里使用Project Lombok@Value注解(属性用private final修饰,并且公开一个构造函数,该构造函数接受所有字段并自动获取equals(…)hashCode()实现方法)

@Value
@ToString
public class UserDTO {

    String username;
    
    String password;

    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    LocalDateTime birthday;
}

UserRepository:

Optional<UserDTO> findDtoById(int id);

测试

Hibernate: select user0_.username as col_0_0_, user0_.password as col_1_0_, user0_.birthday as col_2_0_ from user user0_ where user0_.id=?
UserDTO(username=Svhyeo, password=1583, birthday=1994-12-08T08:14:40.033)

结论

  1. 基于类的查询要使用Dto关键字(predicate),例如findDto,readDto,getDto,queryDto...,否则就会出现java.lang.ClassCastException: xxx.User cannot be cast to xxx.UserDTO

  2. Optional无所谓