Spring Data JPA高级查询技巧

A. 多表复杂查询技巧

需要的类

  • 实体类Entity
  • 用于操作数据库的JPA Repository接口类
  • domain接受查询参数的查询类
  • Predicate类,用于生成查询条件
  • Projection结果类,用于储存查询到的结果
  • Service类,Controller用于执行查询并与前端进行交互

步骤

1.创建实体类及Repository类:

@Entity
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "person")
@Builder
public class Person {

    @Id
    @Column(name = "id")
    private Long id;

    @Column(name = "first_name")
    private String firstName;

    @Column(name = "last_name")
    private String lastName;

    @Column(name = "birthday")
    private LocalDate birthday;

    @Column(name = "age")
    private int age;

    @OneToOne(mappedBy = "person")
    // @OneToMany(mappedBy = "person", cascade = CascadeType.PERSIST)
    private Address address;

}

@Entity
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "address")
@Builder
public class Address {

    @Id
    @Column(name = "id")
    private Long id;

    @OneToOne
    // @ManyToOne
    // @JoinColumn(name = "id")
    private Person person;

    @Column(name = "state")
    private String state;

    @Column(name = "city")
    private String city;

    @Column(name = "street")
    private String street;

    @Column(name = "zip_code")
    private String zipCode;

}

public interface PersonRepository extends JpaRepository<Person, Long> {
}

public interface AddressRepository extends JpaRepository<Person, Long> {
}

2.在domain package中创建查询类:

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Slf4j
public class PersonSearch implements Serializable {

    private String firstName;

    private String lastName;

    private String zipCode;

    private int ageFrom;

    private int ageTo;

}

3.在repository package中创建查询语句类:

@RequiredArgsConstructor
public class PersonSearchSpecification implements Specification<Person> { //设置Root类型 

    private final PersonSearch personSearch;

    @Override
    public Predicate toPredicate(
        @NonNull Root<Person> personRoot,
        @NonNull CriteriaQuery<?> query,
        @NonNull CriteriaBuilder builder
    ) {
        List<Predicate> predicates = new ArrayList<>();

        Path<Adresse> addressRoot = personRoot.get("address");

        if (personSearch.getLastName() != null) {
            predicates.add(isLike(builder, personRoot.get("last_name"), personSearch.getLastName()));
        }
        if (personSearch.getZipCode() != null) {
            predicates.add(isLike(builder, addressRoot.get("zip_code"), personSearch.getZipCode()));
        }

        addTimePredicate(personRoot.get("age"),
            personSearch.getAgeFrom(), personSearch.getAgeTo(),
            builder, predicates);

        return builder.and(predicates.toArray(new Predicate[0]));
    }

    private Predicate isLike(CriteriaBuilder builder, Path<?> path, String value) {
        return builder.like(
            builder.lower(path.as(String.class)),
            "%" + value.toLowerCase() + "%"
        );
    }

    private void addTimePredicate(Path<LocalDate> dateField, LocalDate from, LocalDate to,
                                  CriteriaBuilder builder, List<Predicate> predicates) {
        if (from == null && to == null) {
            return;
        }
        if (from != null) {
            if (to != null) {
                predicates.add(builder.between(dateField, from, to));
            } else {
                predicates.add(builder.greaterThanOrEqualTo(dateField, from));
            }
        } else {
            predicates.add(builder.lessThanOrEqualTo(dateField, to));
        }
    }
}

4.使用JPA Projection 创建联合查询结果类:

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import org.springframework.beans.factory.annotation.Value;

@JsonInclude(Include.NON_NULL)
public interface PersonSearchResult {

    String getFirstName();

    String getLastName();

    @Value("#{target.address.zipCode}") // zipCode来自Address类,需要标注
    String getZipCode();

    @Value("#{target.address.city}")  // city来自Address类,需要标注
    String getCity();
}

5.执行查询,首先使用findAll并传入Predicates将结果查出来,然后再使用ProjectionFactory将结果join好并村委PersonSearchResult对象列表进行返回:

@Service
@RequiredArgsConstructor
@Slf4j
public class PersonSearchService {

    private final PersonRepository countryDataRepository;
    private final ProjectionFactory projectionFactory;

    public List<SearchResult> findSearchResults(PersonSearch personSearch) {
        PersonSearchSpecification searchSpecification = new PersonSearchSpecification(personSearch);
        List<Person> personList = countryDataRepository.findAll(searchSpecification);
        return personList.stream()
            .map(person -> projectionFactory.createProjection(PersonSearchResult.class, person))
            .collect(Collectors.toList());
    }

}

B. 简单多表联合查询

@JsonInclude(Include.NON_NULL)
public interface PersonView {

    String getFirstName();

    String getLastName();

}

@JsonInclude(Include.NON_NULL)
public interface AddressResult {

    @JsonUnwrapped
    PersonView getPerson();

    String getZipCode();

    String getCity();

}

@Repository
public interface AddressRepository extends JpaRepository<Address, Long>,
    JpaSpecificationExecutor<Address> {

    Optional<AddressResult> findByPersonIdAndZipCode(Long id, String zipCode);

}

@Service
@RequiredArgsConstructor
public class AddressResultService {

    private final AddressRepository addressRepository;

    public Optional<AddressResult> findAddressResult(Long id, String zipCode) {
        return addressRepository
            .findByPersonIdAndZipCode(id, zipCode);
    }

}

References

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容