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> {
}
基于接口的投影
准备
- 接口
public interface UsernameOnly {
Optional<String> getUsername();
}
投影接口中的getter可以使用可为空的包装器,以提高null-safety的安全性。当前支持的包装器类型为:
- java.util.Optional
- com.google.common.base.Optional
- scala.Option
- io.vavr.control.Option
- 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,也就是只有一个方法,使用Optional对repository和interface中的方法都进行了包装,为什么呢?对于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)
结论
-
基于类的查询要使用
Dto关键字(predicate),例如findDto,readDto,getDto,queryDto...,否则就会出现java.lang.ClassCastException: xxx.User cannot be cast to xxx.UserDTO -
Optional无所谓