Spring Data JPA进阶(二):Projections 逃离我推掉我的手 2022-01-26 07:21 311阅读 0赞 在使用Spring Data JPA的时候,我们可能需要查询某个实体的部分字段。比如我的个人网站,文章内容其实是存在数据库里面的,这个字段很大。但是我们在展示文章列表的时候,其实是没必要把文章内容查出来的,只需要查询其它字段。 在上篇自定义实现中,示例代码里展示了如何查询某个实体的单个字段: @Query("select u.name from User u where u.id=?1") String getNameFromId(Long id); 那假如有查询多个字段的需求呢?可以用以下方法: 首先,根据自己需要查询的字段创建一个相应的构造方法: public User(String name, int age) { this.name = name; this.age = age; } 然后,在自定义的接口上面这样写Query: @Query("select new User(u.name, u.age) from User u where u.id=?1") User getNameAndAgeFromId(Long id); 在查询的字段较少时,这样做很方便。但是如果遇到我上面提到的那种需求,只有一两个字段不查,其它的都要查。那再使用这种方式就会让查询语句变得非常长,不利于阅读。 Spring Data JPA提供了Projections功能来做这个事情。具体怎么操作呢? ##### 查询部分字段 ##### * 创建一个接口 这个接口发挥了一个类似“视图”的作用。比如我们的实体定义是这个样子的: class Person \{ @Id UUID id; String firstname, lastname; Address address; static class Address { String zipCode, city, street; } \} 如果我们不想查询Address字段,那我们可以创建这样一个接口,这个接口里面应该有你需要的所有属性的get方法: interface NamesOnly { String getFirstname(); String getLastname(); } > Tips: 不用自己去写get方法,可以先在实体里面生成get方法,然后复制过来,删掉不需要的即可。 * 将这个接口作为返回值 然后就可以在自定义的Repository里面使用这个接口类型作为返回值。 interface PersonRepository extends Repository<Person, UUID> { Collection<NamesOnly> findByLastname(String lastname); } 这样我们就可以查询到部分字段了。 ##### 使用DTO ##### 除了前面提到的声明一个带get方法的接口以外,你还可以使用一个DTO类来做这个事情: class NamesOnly { private final String firstname, lastname; NamesOnly(String firstname, String lastname) { this.firstname = firstname; this.lastname = lastname; } String getFirstname() { return this.firstname; } String getLastname() { return this.lastname; } // equals(…) and hashCode() implementations } * 使用Lombok来简化代码 Lombok提供了对DTO的注解@Value。注意这个注解跟Spring的@Value注解不是同一个,自己引包的时候要注意一下。上面一段代码简化之后是这个样子: import lombok.Value; @Value class NamesOnly { String firstname, lastname; } 看起来比使用接口更简洁! ##### 字段重组 ##### 除了查询部分字段以外,Projections还可以对你的字段进行重组。比如: import org.springframework.beans.factory.annotation.Value; interface NamesOnly { @Value("#{target.firstname + ' ' + target.lastname}") String getFullName(); … } 注意这个@Value注解是用的Spring的注解,跟上面的Lombok的@Value注解不是同一个,引包的时候需要注意。 这里使用的是SpEL的语法,target即原始类型的对象。在Java 8以后的版本,除了使用@Value注解,你还可以使用接口的default方法: interface NamesOnly { String getFirstname(); String getLastname(); default String getFullName() { return getFirstname.concat(" ").concat(getLastname()); } } 前面提到了使用SpEL,你可以利用SpEL做更多更炫酷的事情,比如使用一个Bean的方法: @Component class MyBean { String getFullName(Person person) { … } } interface NamesOnly { @Value("#{@myBean.getFullName(target)}") String getFullName(); … } 再比如: interface NamesOnly { @Value("#{args[0] + ' ' + target.firstname + '!'}") String getSalutation(String prefix); } ##### 动态的Projection ##### 可不可以有很多Projection都用同一个方法,但是返回值根据Projection动态调整呢?我们可以使用泛型来实现这个功能。 interface PersonRepository extends Repository<Person, UUID> { <T> Collection<T> findByLastname(String lastname, Class<T> type); } 然后再使用的时候就可以根据类型来返回自己所需要的字段: void someMethod(PersonRepository people) { Collection<Person> aggregates = people.findByLastname("Matthews", Person.class); Collection<NamesOnly> aggregates = people.findByLastname("Matthews", NamesOnly.class); }
还没有评论,来说两句吧...