Spring Data 查询方法通常返回由存储库管理的聚合根的一个或多个实例。但是,有时可能需要基于这些类型的某些属性创建投影。Spring Data 允许对专用返回类型进行建模,以便更有选择性地检索托管聚合的部分视图。
准备工作
一个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.Optionalcom.google.common.base.Optionalscala.Optionio.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无所谓。