Pavel
Pavel

Reputation: 2101

Why filter for Spring Data JPA Specification doesn't work?

I try select data from the table by a filter with Spring Data JPA Specification I think what my implementation is correct, But it doesn't work. Help me please understand my mistake and fix my example.

I have very strange SQL query in log :

select phone0_.id as id1_0_, phone0_.note as note2_0_, phone0_.number as number3_0_, phone0_.operator_login as operator4_0_, phone0_.operator_pass as operator5_0_, phone0_.operator_name as operator6_0_, phone0_.operator_url as operator7_0_, phone0_.reg_date as reg_date8_0_, phone0_.status as status9_0_ from phone phone0_ where 0=1 limit ?

In the end: where 0=1 it's crash my mind. Where did that come from?

Here I fill CriteriaBuilder if filter field not null. I expect to get correctly built Specification object and send it to findAll(Specifications.where(specification), Pageable p) method. But something incorrect.

My repo and specification impl:

public interface PhoneRepository extends CrudRepository<Phone, Integer>, JpaRepository<Phone, Integer>, JpaSpecificationExecutor<Phone> {
    class PhoneSpecification implements Specification<Phone> {

        private final @NonNull PhoneService.PhoneFilter filter;

        public PhoneSpecification(@NonNull PhoneService.PhoneFilter filter) {
            this.filter = filter;
        }

        @Override
        public Predicate toPredicate(Root<Phone> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
            Predicate predicate = cb.disjunction();
            if (nonNull(filter.getId())) {
                cb.disjunction().getExpressions().add(cb.equal(root.get("id"), filter.getId()));
            }
            if (nonNull(filter.getNote())) {
                cb.disjunction().getExpressions().add(cb.like(root.get("note"), filter.getNote()));
            }
            if (nonNull(filter.getNumber())) {
                cb.disjunction().getExpressions().add(cb.like(root.get("number"), filter.getNumber()));
            }
            if (nonNull(filter.getStatus())) {
                cb.disjunction().getExpressions().add(cb.like(root.get("status"), filter.getStatus()));
            }
            if (nonNull(filter.getOpName())) {
                cb.disjunction().getExpressions().add(cb.like(root.get("operatorName"), filter.getOpName()));
            }
            if (nonNull(filter.getOpLogin())) {
                cb.disjunction().getExpressions().add(cb.like(root.get("operatorAccLogin"), filter.getOpLogin()));
            }
            if (nonNull(filter.getOpPassword())) {
                cb.disjunction().getExpressions().add(cb.like(root.get("operatorAccPassword"), filter.getOpPassword()));
            }
            if (nonNull(filter.getRegFrom()) && nonNull(filter.getRegTo())) {
                cb.disjunction().getExpressions().add(cb.between(root.get("regDate"), filter.getRegFrom(), filter.getRegTo()));
            }
            return predicate;
        }
    }
}

This is service level:

@Service
public class PhoneService {

    @Autowired
    private PhoneRepository phoneRepository;

    public Phone get(int id) {
        Phone phone = phoneRepository.findOne(id);
        return nonNull(phone) ? phone : new Phone();
    }

    public Page<Phone> list(@NonNull PhoneFilter filter) {
        PhoneSpecification specification = new PhoneSpecification(filter);
        return phoneRepository.findAll(Specifications.where(specification), filter.getPageable());
    }

    @Data
    public static class PhoneFilter {

        private Pageable pageable;

        private Integer id;

        private Timestamp regFrom;

        private Timestamp regTo;

        private String number;

        private String opLogin;

        private String opPassword;

        private String opName;

        private String status;

        private String note;

    }
}

And entity

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "phone")
@ToString(exclude = {"accounts"})
@EqualsAndHashCode(exclude = {"accounts"})
public class Phone {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;

    @OneToMany(mappedBy = "phone", cascade = CascadeType.DETACH)
    private Collection<SocialAccount> accounts;

    @Column(name = "reg_date")
    private Timestamp regDate;

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

    @Column(name = "operator_url")
    private String operatorUrl;

    @Column(name = "operator_login")
    private String operatorAccLogin;

    @Column(name = "operator_pass")
    private String operatorAccPassword;

    @Column(name = "operator_name")
    private String operatorName;

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

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

}

Upvotes: 0

Views: 2978

Answers (1)

Pavel
Pavel

Reputation: 2101

I find the mistake.

Method CriteriaBuilder.disjunction() this is factory and each time when I call him I got new Predicate object.

This implementation CriteriaBuilderImpl:

public Predicate disjunction() {
    return new CompoundPredicate(this, BooleanOperator.OR);
}

Be careful with it.

Upvotes: 1

Related Questions