Skip to content

Instantly share code, notes, and snippets.

@josergdev
Last active August 24, 2024 12:03
Show Gist options
  • Save josergdev/f67b27d4d8f561f2836ad73c7b01e8cf to your computer and use it in GitHub Desktop.
Save josergdev/f67b27d4d8f561f2836ad73c7b01e8cf to your computer and use it in GitHub Desktop.
Spring Data Specification to find data by pairs of attributes
package dev.joserg.util.jpa;
import static org.springframework.data.jpa.domain.Specification.allOf;
import static org.springframework.data.jpa.domain.Specification.anyOf;
import java.util.List;
import java.util.Optional;
import jakarta.annotation.Nonnull;
import jakarta.persistence.criteria.Root;
import jakarta.persistence.metamodel.SingularAttribute;
import org.springframework.data.jpa.domain.Specification;
public interface GenericSpecification {
static <E, T> Specification<E> by(final SingularAttribute<E, T> attribute, @Nonnull final T value) {
return (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get(attribute), value);
}
static <E, T> Specification<E> byOptional(final SingularAttribute<E, T> attribute, final Optional<T> valueOpt) {
return (root, query, criteriaBuilder) -> valueOpt
.map(value -> criteriaBuilder.equal(root.get(attribute), value))
.orElseGet(() -> Specification.where(null).toPredicate((Root<Object>) root, query, criteriaBuilder));
}
static <E, T> Specification<E> beingNull(final SingularAttribute<E, T> attribute) {
return (root, query, criteriaBuilder) -> criteriaBuilder.isNull(root.get(attribute));
}
static <E, L, R> Specification<E> byPair(
final Pair<SingularAttribute<E, L>, SingularAttribute<E, R>> pairAttribute,
final Pair<L, R> pairValue) {
return allOf(
by(pairAttribute.first(), pairValue.first()),
by(pairAttribute.second(), pairValue.second()));
}
static <E, A1, A2, A3> Specification<E> byTriple(
final Triple<SingularAttribute<E, A1>, SingularAttribute<E, A2>, SingularAttribute<E, A3>> tripleAttribute,
final Triple<A1, A2, A3> tripleValue) {
return allOf(
by(tripleAttribute.first(), tripleValue.first()),
by(tripleAttribute.second(), tripleValue.second()),
by(tripleAttribute.third(), tripleValue.third()));
}
static <E, A1> Specification<E> byAnySingle(
final SingularAttribute<E, A1> attribute,
final List<A1> singleValues) {
return anyOf(singleValues.stream().map(value -> by(attribute, value)).toList());
}
static <E, A1, A2> Specification<E> byAnyPair(
final Pair<SingularAttribute<E, A1>, SingularAttribute<E, A2>> pairAttribute,
final List<Pair<A1, A2>> pairValues) {
return anyOf(pairValues.stream().map(value -> byPair(pairAttribute, value)).toList());
}
static <E, A1, A2, A3> Specification<E> byAnyTriple(
final Triple<SingularAttribute<E, A1>, SingularAttribute<E, A2>, SingularAttribute<E, A3>> tripleAttribute,
final List<Triple<A1, A2, A3>> tripleValues) {
return anyOf(tripleValues.stream().map(value -> byTriple(tripleAttribute, value)).toList());
}
record Pair<A1, A2>(A1 first, A2 second) {
public static <A1, A2> Pair<A1, A2> ofPair(final A1 first, final A2 second) {
return new Pair<>(first, second);
}
}
record Triple<A1, A2, A3>(A1 first, A2 second, A3 third) {
public static <A1, A2, A3> Triple<A1, A2, A3> ofTriple(final A1 first, final A2 second, final A3 third) {
return new Triple<>(first, second, third);
}
}
}
@josergdev
Copy link
Author

josergdev commented Dec 19, 2023

Given a Jpa Entity Class:

@Entity
public class ExampleEntity {
  @Id
  private Integer id;

  private Integer integerAttribute;
  
  private String stringAttribute;
}

With a generated MetaModel:

@Generated(value = "org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor")
@StaticMetamodel(ExampleEntity.class)
public abstract class ExampleEntity_ {
  
  public static volatile SingularAttribute<ExampleEntity, Integer> id;
  public static volatile SingularAttribute<ExampleEntity, Integer> integerAttribute;
  public static volatile SingularAttribute<ExampleEntity, String> stringAttribute;
  
  public static final String ID = "id";
  public static final String INTEGER_ATTRIBUTE = "integerAttribute";
  public static final String STRING_ATTRIBUTE = "stringAttribute";

}

You can use ByAnyPairSpecification.byAnyPair(...) as argument of a JpaSpecificaitonExecutor:

public interface ExampleEntityRepository extends JpaRepository<ExampleEntity,Integer>, JpaSpecificationExecutor<ExampleEntity> {

 default List<ExampleEntity> findByAnyPairOfIntegerAttributeAndStringAttribute(final List<Pair<Integer, String>> pairsOfIntegerAttributeAndStringAttribute) {
    return this.findAll(
        GenericSpecification.byAnyPair(
            Pair.ofPair(ExampleEntity_.integerAttribute, ExampleEntity_.stringAttribute),
            pairsOfIntegerAttributeAndStringAttribute
        )
    );
    
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment