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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 230,527评论 6 544
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 99,687评论 3 429
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 178,640评论 0 383
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 63,957评论 1 318
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 72,682评论 6 413
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 56,011评论 1 329
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 44,009评论 3 449
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 43,183评论 0 290
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 49,714评论 1 336
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 41,435评论 3 359
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 43,665评论 1 374
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 39,148评论 5 365
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,838评论 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 35,251评论 0 28
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 36,588评论 1 295
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 52,379评论 3 400
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 48,627评论 2 380

推荐阅读更多精彩内容