diff --git a/pom.xml b/pom.xml index 7106a77..ca0f0d0 100644 --- a/pom.xml +++ b/pom.xml @@ -6,12 +6,12 @@ org.sadtech.haiti haiti - 0.0.1-RELEASE + 0.0.2-SNAPSHOT org.sadtech.haiti.filter haiti-filter-criteria - 0.0.2-RELEASE + 0.0.3-SNAPSHOT Haiti Filter Criteria Fast creation of filtering requests using the Criteria Api wrapper. @@ -82,6 +82,11 @@ javax.persistence javax.persistence-api + + org.hibernate + hibernate-core + 5.4.29.Final + diff --git a/src/main/java/org/sadtech/haiti/filter/criteria/Container.java b/src/main/java/org/sadtech/haiti/filter/criteria/Container.java index b43486a..5733632 100644 --- a/src/main/java/org/sadtech/haiti/filter/criteria/Container.java +++ b/src/main/java/org/sadtech/haiti/filter/criteria/Container.java @@ -1,9 +1,13 @@ package org.sadtech.haiti.filter.criteria; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; import org.springframework.data.jpa.domain.Specification; +import java.util.List; +import java.util.UUID; + /** * // TODO: 09.11.2020 Добавить описание. * @@ -11,9 +15,20 @@ import org.springframework.data.jpa.domain.Specification; */ @Getter @Setter +@NoArgsConstructor public class Container { - private String joinTable; + private final String uuid = UUID.randomUUID().toString(); + + private List joinTables; private Specification specification; + private Container(List joinTables) { + this.joinTables = joinTables; + } + + public static Container of(List joinTables) { + return new Container<>(joinTables); + } + } diff --git a/src/main/java/org/sadtech/haiti/filter/criteria/CriteriaQuery.java b/src/main/java/org/sadtech/haiti/filter/criteria/CriteriaQuery.java index bedfa9b..acc025a 100644 --- a/src/main/java/org/sadtech/haiti/filter/criteria/CriteriaQuery.java +++ b/src/main/java/org/sadtech/haiti/filter/criteria/CriteriaQuery.java @@ -6,6 +6,7 @@ import lombok.RequiredArgsConstructor; import org.sadtech.haiti.filter.FilterQuery; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Set; import java.util.stream.Collectors; @@ -14,8 +15,8 @@ import java.util.stream.Collectors; public class CriteriaQuery implements FilterQuery { private static final CriteriaQuery EMPTY = new CriteriaQuery<>(new ArrayList<>()); - private final List> specifications; - private String joinTable; + private final List> containers; + private List joinTables = new ArrayList<>(); private final SimpleCriteriaQuery simpleCriteriaQuery = new SimpleCriteriaQuery(); public static FilterQuery create() { @@ -28,15 +29,38 @@ public class CriteriaQuery implements FilterQuery { * * @param fieldName Имя поля сущности, которое отвечает за название таблицы. */ - public CriteriaQuery join(@NonNull String fieldName) { - joinTable = fieldName; + public CriteriaQuery join(@NonNull String... fieldName) { + joinTables = Arrays.stream(fieldName) + .map(JoinTable::of) + .collect(Collectors.toList()); + return this; + } + + public CriteriaQuery join(@NonNull JoinTable... joinTables) { + this.joinTables = Arrays.stream(joinTables).collect(Collectors.toList()); return this; } @Override public > FilterQuery between(@NonNull String field, Y from, Y to) { if (from != null && to != null) { - specifications.add(simpleCriteriaQuery.between(joinTable, field, from, to)); + containers.add(simpleCriteriaQuery.between(joinTables, field, from, to)); + } + return this; + } + + @Override + public > FilterQuery greaterThan(@NonNull String field, Y value) { + if (value != null) { + containers.add(simpleCriteriaQuery.greaterThan(joinTables, field, value)); + } + return this; + } + + @Override + public > FilterQuery lessThan(@NonNull String field, Y value) { + if (value != null) { + containers.add(simpleCriteriaQuery.lessThan(joinTables, field, value)); } return this; } @@ -44,7 +68,7 @@ public class CriteriaQuery implements FilterQuery { @Override public FilterQuery matchPhrase(@NonNull String field, Object value) { if (value != null) { - specifications.add(simpleCriteriaQuery.matchPhrase(joinTable, field, value)); + containers.add(simpleCriteriaQuery.matchPhrase(joinTables, field, value)); } return this; } @@ -52,9 +76,20 @@ public class CriteriaQuery implements FilterQuery { @Override public FilterQuery matchPhrase(@NonNull String field, Set values) { if (values != null && !values.isEmpty()) { - specifications.addAll( + containers.addAll( values.stream() - .map(value -> simpleCriteriaQuery.matchPhrase(joinTable, field, value)) + .map(value -> simpleCriteriaQuery.matchPhrase(joinTables, field, value)) + .collect(Collectors.toList()) + ); + } + return this; + } + + public > CriteriaQuery collectionElementsIn(List values) { + if (values != null && !values.isEmpty()) { + containers.addAll( + values.stream() + .map(value -> simpleCriteriaQuery.collectionElements(joinTables, value)) .collect(Collectors.toList()) ); } @@ -64,17 +99,17 @@ public class CriteriaQuery implements FilterQuery { @Override public FilterQuery exists(String field) { if (field != null) { - specifications.add(simpleCriteriaQuery.exists(joinTable, field)); + containers.add(simpleCriteriaQuery.exists(joinTables, field)); } return this; } @Override public FilterQuery like(@NonNull String field, String value, boolean ignoreCase) { - specifications.add( + containers.add( ignoreCase - ? simpleCriteriaQuery.likeIgnoreCase(joinTable, field, value) - : simpleCriteriaQuery.like(joinTable, field, value) + ? simpleCriteriaQuery.likeIgnoreCase(joinTables, field, value) + : simpleCriteriaQuery.like(joinTables, field, value) ); return this; } @@ -82,8 +117,8 @@ public class CriteriaQuery implements FilterQuery { @Override public FilterQuery checkBoolInt(@NonNull String field, Boolean flag) { if (flag != null) { - specifications.add( - simpleCriteriaQuery.between(joinTable, field, 0, Integer.MAX_VALUE) + containers.add( + simpleCriteriaQuery.between(joinTables, field, 0, Integer.MAX_VALUE) ); } return this; @@ -91,7 +126,7 @@ public class CriteriaQuery implements FilterQuery { @Override public Q build() { - return (Q) specifications.stream() + return (Q) containers.stream() .map(Container::getSpecification) .collect(Collectors.toList()); } diff --git a/src/main/java/org/sadtech/haiti/filter/criteria/JoinTable.java b/src/main/java/org/sadtech/haiti/filter/criteria/JoinTable.java new file mode 100644 index 0000000..736229c --- /dev/null +++ b/src/main/java/org/sadtech/haiti/filter/criteria/JoinTable.java @@ -0,0 +1,28 @@ +package org.sadtech.haiti.filter.criteria; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; + +/** + * // TODO: 15.04.2021 Добавить описание. + * + * @author upagge 15.04.2021 + */ +@Getter +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public class JoinTable { + + private final String tableName; + private final JoinTypeOperation joinTypeOperation; + + public static JoinTable of(@NonNull String tableName) { + return new JoinTable(tableName, JoinTypeOperation.LEFT); + } + + public static JoinTable of(@NonNull String tableName, JoinTypeOperation joinType) { + return new JoinTable(tableName, joinType); + } + +} diff --git a/src/main/java/org/sadtech/haiti/filter/criteria/JoinTypeOperation.java b/src/main/java/org/sadtech/haiti/filter/criteria/JoinTypeOperation.java new file mode 100644 index 0000000..dd8f3a5 --- /dev/null +++ b/src/main/java/org/sadtech/haiti/filter/criteria/JoinTypeOperation.java @@ -0,0 +1,22 @@ +package org.sadtech.haiti.filter.criteria; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import javax.persistence.criteria.JoinType; + +/** + * // TODO: 15.04.2021 Добавить описание. + * + * @author upagge 15.04.2021 + */ +@Getter +@RequiredArgsConstructor +public enum JoinTypeOperation { + + LEFT(JoinType.LEFT), + INNER(JoinType.INNER); + + private final JoinType criteriaType; + +} diff --git a/src/main/java/org/sadtech/haiti/filter/criteria/SimpleCriteriaQuery.java b/src/main/java/org/sadtech/haiti/filter/criteria/SimpleCriteriaQuery.java index ccab6dc..d090e4a 100644 --- a/src/main/java/org/sadtech/haiti/filter/criteria/SimpleCriteriaQuery.java +++ b/src/main/java/org/sadtech/haiti/filter/criteria/SimpleCriteriaQuery.java @@ -1,63 +1,114 @@ package org.sadtech.haiti.filter.criteria; import lombok.NonNull; +import org.hibernate.query.criteria.internal.path.PluralAttributeJoinSupport; -import javax.persistence.criteria.Expression; -import javax.persistence.criteria.Join; +import javax.persistence.criteria.From; +import javax.persistence.criteria.JoinType; +import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; +import javax.persistence.metamodel.Attribute; import java.util.HashMap; +import java.util.HashSet; +import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; public class SimpleCriteriaQuery { - private final Map joinMap = new HashMap<>(); + private From lastJoin; + private final Set unique = new HashSet<>(); + private final Map> joinMap = new HashMap<>(); - public Container matchPhrase(String joinTable, @NonNull String field, @NonNull Object value) { - final Container container = getContainer(joinTable); - container.setSpecification((root, query, cb) -> cb.equal(getPath(root, joinTable, field), value)); + public Container matchPhrase(List joinTables, @NonNull String field, @NonNull Object value) { + final Container container = Container.of(joinTables); + container.setSpecification((root, query, cb) -> cb.equal(getPath(root, container).get(field), value)); return container; } - public Container exists(String joinTable, @NonNull String field) { - final Container container = getContainer(joinTable); - container.setSpecification((root, query, cb) -> cb.isNotNull(getPath(root, joinTable, field))); + public Container collectionElements(List joinTables, List values) { + final Container container = Container.of(joinTables); + container.setSpecification( + (root, query, builder) -> { + Predicate where = builder.conjunction(); + return builder.and(where, getPath(root, container).in(values)); + } + ); return container; } - public Container likeIgnoreCase(String joinTable, @NonNull String field, String value) { - final Container container = getContainer(joinTable); - container.setSpecification((root, query, cb) -> cb.like(cb.lower(getPath(root, joinTable, field)), value)); + public Container exists(List joinTables, @NonNull String field) { + final Container container = Container.of(joinTables); + container.setSpecification((root, query, cb) -> cb.isNotNull(getPath(root, container).get(field))); return container; } - public Container like(String joinTable, @NonNull String field, String value) { - final Container container = getContainer(joinTable); - container.setSpecification((root, query, cb) -> cb.like(getPath(root, joinTable, field), value)); + public Container likeIgnoreCase(List joinTables, @NonNull String field, String value) { + final Container container = Container.of(joinTables); + container.setSpecification((root, query, cb) -> cb.like(cb.lower(getPath(root, container).get(field)), value)); return container; } - public > Container between(String joinTable, @NonNull String field, Y from, Y to) { - final Container container = getContainer(joinTable); - container.setSpecification((root, query, cb) -> cb.between(getPath(root, joinTable, field), from, to)); + public Container like(List joinTables, @NonNull String field, String value) { + final Container container = Container.of(joinTables); + container.setSpecification((root, query, cb) -> cb.like(getPath(root, container).get(field), value)); return container; } - private Container getContainer(String joinTableName) { - final Container container = new Container<>(); - container.setJoinTable(joinTableName); + public > Container between(List joinTables, @NonNull String field, Y from, Y to) { + final Container container = Container.of(joinTables); + container.setSpecification((root, query, cb) -> cb.between(getPath(root, container).get(field), from, to)); return container; } - private Expression getPath(Root root, String joinTable, String field) { - if (joinTable != null) { - Join join = joinMap.get(joinTable); - if (join == null) { - join = root.join(joinTable); - joinMap.put(joinTable, join); + public > Container greaterThan(List joinTables, @NonNull String field, Y value) { + final Container container = Container.of(joinTables); + container.setSpecification(((root, query, cb) -> cb.greaterThan(getPath(root, container).get(field), value))); + return container; + } + + public > Container lessThan(List joinTables, @NonNull String field, Y value) { + final Container container = Container.of(joinTables); + container.setSpecification(((root, query, cb) -> cb.lessThan(getPath(root, container).get(field), value))); + return container; + } + + private From getPath(Root root, Container container) { + final List joinTables = container.getJoinTables(); + final String uuid = container.getUuid(); + From join = root; + if (!joinTables.isEmpty()) { + check(uuid); + for (JoinTable table : joinTables) { + final String tableName = table.getTableName(); + final JoinType criteriaType = table.getJoinTypeOperation().getCriteriaType(); + if (joinMap.containsKey(tableName)) { + join = joinMap.get(tableName); + } else { + final Set attributes = ((Root) root).getModel().getAttributes().stream() + .map(Attribute::getName) + .collect(Collectors.toSet()); + if (attributes.contains(tableName)) { + join = root.join(tableName, criteriaType); + } else { + join = lastJoin.join(tableName, criteriaType); + } + if (join instanceof PluralAttributeJoinSupport && !((PluralAttributeJoinSupport) join).isBasicCollection()) { + lastJoin = join; + } + joinMap.put(tableName, join); + } } - return join.get(field); } - return root.get(field); + return join; + } + + private void check(String uuid) { + if (unique.contains(uuid)) { + joinMap.clear(); + } + unique.add(uuid); } }