Skip to content

Commit 067ecd1

Browse files
committed
add basic structures for criteriaBuilder query
1 parent 111f92e commit 067ecd1

8 files changed

Lines changed: 200 additions & 27 deletions

File tree

pom.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,13 @@
7878
<scope>runtime</scope>
7979
</dependency>
8080

81+
<!-- tuples -->
82+
<dependency>
83+
<groupId>org.javatuples</groupId>
84+
<artifactId>javatuples</artifactId>
85+
<version>1.2</version>
86+
</dependency>
87+
8188
<!-- AOP -->
8289
<dependency>
8390
<groupId>org.springframework.boot</groupId>

src/main/java/ir/bigz/springbootreal/commons/util/Utils.java

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
11
package ir.bigz.springbootreal.commons.util;
22

3+
import ir.bigz.springbootreal.dto.PagedQuery;
34
import ir.bigz.springbootreal.dto.SqlOperation;
45
import ir.bigz.springbootreal.dto.ValueCondition;
6+
import ir.bigz.springbootreal.exception.AppException;
7+
import ir.bigz.springbootreal.exception.HttpErrorCode;
8+
import org.javatuples.Quartet;
9+
import org.springframework.data.domain.Sort;
510

11+
import javax.persistence.criteria.CriteriaBuilder;
12+
import javax.persistence.criteria.Predicate;
13+
import javax.persistence.criteria.Root;
14+
import java.lang.reflect.Field;
615
import java.sql.Timestamp;
716
import java.time.LocalDateTime;
817
import java.time.ZoneId;
918
import java.time.ZonedDateTime;
1019
import java.time.format.DateTimeFormatter;
11-
import java.util.Arrays;
12-
import java.util.HashMap;
13-
import java.util.Map;
20+
import java.util.*;
1421

1522
import static ir.bigz.springbootreal.dto.ValueCondition.*;
1623

@@ -64,6 +71,65 @@ public static boolean isEqual(String s1, String s2) {
6471
return (!isNull(s1) && !isNull(s2) && s1.equals(s2));
6572
}
6673

74+
public static <K> Predicate getCriteriaPredicate(CriteriaBuilder criteriaBuilder, Root<K> root,Quartet<String, String, SqlOperation, ValueCondition> rule, String value){
75+
76+
SqlOperation operation = rule.getValue2();
77+
switch (operation){
78+
case GREATER_THAN:
79+
return criteriaBuilder.greaterThan(root.get(rule.getValue1()), createValueFromValueCondition(value, rule.getValue3()));
80+
case GREATER_THAN_OR_EQUAL:
81+
return criteriaBuilder.greaterThanOrEqualTo(root.get(rule.getValue1()), createValueFromValueCondition(value, rule.getValue3()));
82+
case CONTAINS:
83+
return criteriaBuilder.like(root.get(rule.getValue1()), createValueFromValueCondition(value, rule.getValue3()));
84+
case LESS_THAN:
85+
return criteriaBuilder.lessThan(root.get(rule.getValue1()), createValueFromValueCondition(value, rule.getValue3()));
86+
case LESS_THAN_OR_EQUAL:
87+
return criteriaBuilder.lessThanOrEqualTo(root.get(rule.getValue1()), createValueFromValueCondition(value, rule.getValue3()));
88+
default:
89+
return criteriaBuilder.equal(root.get(rule.getValue1()), createValueFromValueCondition(value, EQUAL));
90+
}
91+
}
92+
93+
private static String createValueFromValueCondition(String value, ValueCondition condition){
94+
switch (condition){
95+
case CONTAINS: case NOT_CONTAINS: return "%" + value + "%";
96+
case STARTS_WITH: return value + "%";
97+
case ENDS_WITH: return "%" + value;
98+
default: return value;
99+
}
100+
}
101+
102+
public static List<Sort.Order> getSortOrderFromPagedQuery(PagedQuery pagedQuery, Class clazz){
103+
104+
List<Sort.Order> orders = new ArrayList<>();
105+
106+
pagedQuery.getOrdering().forEach(
107+
orderParam -> {
108+
try {
109+
orderParam = orderParam.trim();
110+
String[] orderField = orderParam.split("_");
111+
Field field = getDeclaredField(clazz, orderField[0]);
112+
if(field == null){
113+
return;
114+
}
115+
String orderColumn = field.getName();
116+
String orderDirection = PagedQuery.ORDER_ASC;
117+
if (orderField.length > 1 && orderField[1].equalsIgnoreCase(PagedQuery.ORDER_DESC)) {
118+
orderDirection = PagedQuery.ORDER_DESC;
119+
}
120+
Sort.Order order = new Sort.Order(Sort.Direction.valueOf(orderDirection), orderColumn);
121+
orders.add(order);
122+
}catch (Exception e){
123+
throw AppException.newInstance(
124+
HttpErrorCode.ERR_10705, String.format("field %s ordering is wrong", orderParam)
125+
);
126+
}
127+
}
128+
);
129+
130+
return orders;
131+
}
132+
67133

68134
@SuppressWarnings("unchecked")
69135
public static <T> T getQueryString(Map<String, String> queryString,
@@ -180,4 +246,16 @@ public static <T> void buildNativeQueryCondition(Map<String, String> queryString
180246
public static String getWhereSimple(){
181247
return " where 1=1";
182248
}
249+
250+
public static Field getDeclaredField(Class className, String fieldName) {
251+
Field field = null;
252+
while (className != null && field == null) {
253+
try {
254+
field = className.getDeclaredField(fieldName);
255+
} catch (NoSuchFieldException ex) {
256+
}
257+
className = className.getSuperclass();
258+
}
259+
return field;
260+
}
183261
}

src/main/java/ir/bigz/springbootreal/controller/SampleController.java

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -76,14 +76,6 @@ public ResponseEntity<?> getUserWithSearch(@RequestBody UserSearchDto userSearch
7676
return ResponseEntity.ok(userPageResult);
7777
}
7878

79-
@GetMapping(path = "/v2/user/search", produces = MediaType.APPLICATION_JSON_VALUE)
80-
@ResponseStatus(HttpStatus.OK)
81-
public ResponseEntity<?> getUserWithSearchV2() {
82-
PageResult<UserModel> userPageResult = userService.getUserSearchV2(getQueryString(), getPagedQuery());
83-
return ResponseEntity.ok(userPageResult);
84-
}
85-
86-
8779
@GetMapping(path = "/v1/user/all/pagerquest", produces = MediaType.APPLICATION_JSON_VALUE)
8880
@ResponseStatus(HttpStatus.OK)
8981
public ResponseEntity<?> getAllUserPage(@RequestParam(name = "sortorder", required = false) String sortOrder,
@@ -94,4 +86,18 @@ public ResponseEntity<?> getAllUserPage(@RequestParam(name = "sortorder", requir
9486
return ResponseEntity.ok(userPageResult);
9587
}
9688

89+
@GetMapping(path = "/v2/user/search", produces = MediaType.APPLICATION_JSON_VALUE)
90+
@ResponseStatus(HttpStatus.OK)
91+
public ResponseEntity<?> getUserSearchWithNativeQuery() {
92+
PageResult<UserModel> userPageResult = userService.getUserSearchWithNativeQuery(getQueryString(), getPagedQuery());
93+
return ResponseEntity.ok(userPageResult);
94+
}
95+
96+
@GetMapping(path = "/v3/user/search", produces = MediaType.APPLICATION_JSON_VALUE)
97+
@ResponseStatus(HttpStatus.OK)
98+
public ResponseEntity<?> getUserSearchWithCriteriaBuilder() {
99+
Page<UserModel> userPageResult = userService.getUserSearchWithCriteriaBuilder(getQueryString(), getPagedQuery());
100+
return ResponseEntity.ok(userPageResult);
101+
}
102+
97103
}

src/main/java/ir/bigz/springbootreal/dal/DaoRepositoryImpl.java

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
package ir.bigz.springbootreal.dal;
2+
import ir.bigz.springbootreal.commons.util.Utils;
23
import ir.bigz.springbootreal.dto.PageResult;
34
import ir.bigz.springbootreal.dto.PagedQuery;
45
import ir.bigz.springbootreal.exception.AppException;
@@ -152,7 +153,7 @@ public PageResult<T> pageCreateQuery(String nativeQuery, PagedQuery pagedQuery,
152153
try {
153154
orderParam = orderParam.trim();
154155
String[] orderField = orderParam.split("_");
155-
Field field = getDeclaredField(daoType, orderField[0]);
156+
Field field = Utils.getDeclaredField(daoType, orderField[0]);
156157
if(field == null){
157158
return;
158159
}
@@ -270,7 +271,8 @@ public List<T> genericSearch(CriteriaQuery<T> criteriaQuery) {
270271
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
271272
public Page<T> genericSearch(CriteriaQuery<T> query, Pageable pageable){
272273

273-
long totalCount = totalCountOfEntities();
274+
TypedQuery<T> countQuery = entityManager.createQuery(query.toString(), daoType);
275+
int totalCount = countQuery.getResultList().size();
274276

275277
TypedQuery<T> typedQuery = entityManager.createQuery(query);
276278
List<T> resultList = typedQuery
@@ -329,18 +331,6 @@ protected List<Order> orderByClauseBuilder(Root<T> root, Sort sort){
329331
return orders;
330332
}
331333

332-
protected Field getDeclaredField(Class className, String fieldName) {
333-
Field field = null;
334-
while (className != null && field == null) {
335-
try {
336-
field = className.getDeclaredField(fieldName);
337-
} catch (NoSuchFieldException ex) {
338-
}
339-
className = className.getSuperclass();
340-
}
341-
return field;
342-
}
343-
344334
protected static String removeDefaultOrderBy(String query) {
345335
int defaultOrderIndex = query.toLowerCase().lastIndexOf("order by");
346336
int lastParenthesisIndex = query.lastIndexOf(")");
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,24 @@
11
package ir.bigz.springbootreal.dal;
22

3+
import ir.bigz.springbootreal.dto.SqlOperation;
4+
import ir.bigz.springbootreal.dto.ValueCondition;
35
import ir.bigz.springbootreal.dto.entity.User;
46
import ir.bigz.springbootreal.viewmodel.search.UserSearchDto;
7+
import org.javatuples.Quartet;
58
import org.springframework.data.domain.Page;
69
import org.springframework.data.domain.Pageable;
710
import org.springframework.data.domain.Sort;
811
import org.springframework.stereotype.Repository;
912

13+
import java.util.List;
14+
import java.util.Map;
15+
1016
@Repository
1117
public interface UserRepository extends DaoRepository<User,Long> {
1218

1319
User getUserWithNationalCode(String nationalCode);
1420

1521
Page<User> getUserSearchResult(UserSearchDto userSearchDto, Sort.Order order, Pageable pageable);
22+
23+
Page<User> getUserQueryWithCriteriaBuilder(Map<String, String> queryString, List<Quartet<String, String, SqlOperation, ValueCondition>> rules, Pageable pageable);
1624
}

src/main/java/ir/bigz/springbootreal/dal/UserRepositoryImpl.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
package ir.bigz.springbootreal.dal;
22

33
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import ir.bigz.springbootreal.commons.util.Utils;
5+
import ir.bigz.springbootreal.dto.SqlOperation;
6+
import ir.bigz.springbootreal.dto.ValueCondition;
47
import ir.bigz.springbootreal.dto.entity.User;
58
import ir.bigz.springbootreal.viewmodel.search.UserSearchDto;
9+
import org.javatuples.Quartet;
610
import org.springframework.data.domain.Page;
711
import org.springframework.data.domain.Pageable;
812
import org.springframework.data.domain.Sort;
@@ -30,6 +34,49 @@ public User getUserWithNationalCode(String nationalCode) {
3034
return null;
3135
}
3236

37+
@Override
38+
public Page<User> getUserQueryWithCriteriaBuilder(Map<String, String> queryString,
39+
List<Quartet<String, String, SqlOperation, ValueCondition>> rules,
40+
Pageable pageable) {
41+
42+
CriteriaQuery<User> criteriaQuery = criteriaBuilder.createQuery(User.class);
43+
Root<User> userRoot = criteriaQuery.from(User.class);
44+
List<Predicate> sqlCondition = getSqlCondition(criteriaBuilder, userRoot, queryString, rules);
45+
Predicate concatConditions = criteriaBuilder.and(sqlCondition.toArray(Predicate[]::new));
46+
criteriaQuery.where(concatConditions);
47+
pageable.getSort().stream().peek(order -> {
48+
if(order.getDirection().name().equals("ASC")){
49+
criteriaQuery.orderBy(criteriaBuilder.asc(userRoot.get(order.getProperty())));
50+
}
51+
else{
52+
criteriaQuery.orderBy(criteriaBuilder.desc(userRoot.get(order.getProperty())));
53+
}
54+
});
55+
return genericSearch(criteriaQuery, pageable);
56+
}
57+
58+
private <T,K> List<Predicate> getSqlCondition(CriteriaBuilder criteriaBuilder,
59+
Root<K> root,
60+
Map<String, String> queryString,
61+
List<Quartet<String, String, SqlOperation, ValueCondition>> rules){
62+
List<Predicate> predicates = new ArrayList<>();
63+
for (String param : queryString.keySet()) {
64+
Optional<Quartet<String, String, SqlOperation, ValueCondition>> data = rules.stream()
65+
.filter(quartet -> {
66+
if (quartet.getValue0().equals(param)) {
67+
return true;
68+
}
69+
return false;
70+
}).findFirst();
71+
if (data.isPresent()) {
72+
Predicate criteriaPredicate = Utils.getCriteriaPredicate(criteriaBuilder, root, data.get(), queryString.get(param));
73+
predicates.add(criteriaPredicate);
74+
}
75+
}
76+
77+
return predicates;
78+
}
79+
3380
@Override
3481
public Page<User> getUserSearchResult(UserSearchDto userSearchDto, Sort.Order order, Pageable pageable) {
3582

src/main/java/ir/bigz/springbootreal/service/UserService.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,7 @@ public interface UserService {
2828

2929
Page<UserModel> getAllUserPage(String sortOrder, Sort.Direction sortDirection, Integer pageNumber, Integer pageSize);
3030

31-
PageResult<UserModel> getUserSearchV2(Map<String, String> queryString, PagedQuery pagedQuery);
31+
PageResult<UserModel> getUserSearchWithNativeQuery(Map<String, String> queryString, PagedQuery pagedQuery);
32+
33+
Page<UserModel> getUserSearchWithCriteriaBuilder(Map<String, String> queryString, PagedQuery pagedQuery);
3234
}

src/main/java/ir/bigz/springbootreal/service/UserServiceImpl.java

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@
55
import ir.bigz.springbootreal.dto.PageResult;
66
import ir.bigz.springbootreal.dto.PagedQuery;
77
import ir.bigz.springbootreal.dto.SqlOperation;
8+
import ir.bigz.springbootreal.dto.ValueCondition;
89
import ir.bigz.springbootreal.dto.entity.User;
10+
import ir.bigz.springbootreal.dto.entity.User_;
911
import ir.bigz.springbootreal.dto.mapper.UserMapper;
1012
import ir.bigz.springbootreal.exception.AppException;
1113
import ir.bigz.springbootreal.exception.HttpErrorCode;
1214
import ir.bigz.springbootreal.viewmodel.UserModel;
1315
import ir.bigz.springbootreal.viewmodel.search.UserSearchDto;
16+
import org.javatuples.Quartet;
1417
import org.springframework.cache.annotation.CacheEvict;
1518
import org.springframework.cache.annotation.CachePut;
1619
import org.springframework.cache.annotation.Cacheable;
@@ -156,7 +159,7 @@ public Page<UserModel> getAllUserPage(String sortOrder, Sort.Direction direction
156159

157160
@Override
158161
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT)
159-
public PageResult<UserModel> getUserSearchV2(Map<String, String> queryString, PagedQuery pagedQuery) {
162+
public PageResult<UserModel> getUserSearchWithNativeQuery(Map<String, String> queryString, PagedQuery pagedQuery) {
160163

161164
Map<String, Object> parametersMap = new HashMap<>();
162165
Map<String, String> conditionsMap = new HashMap<>();
@@ -190,6 +193,38 @@ public PageResult<UserModel> getUserSearchV2(Map<String, String> queryString, Pa
190193
userPageResult.getTotal());
191194
}
192195

196+
@Override
197+
public Page<UserModel> getUserSearchWithCriteriaBuilder(Map<String, String> queryString, PagedQuery pagedQuery) {
198+
199+
// tuple define for map data from queryString in query search base on sql operation and value condition rules
200+
List<Quartet<String, String, SqlOperation, ValueCondition>> rules = new ArrayList<>();
201+
Quartet<String, String, SqlOperation, ValueCondition> firstNameTuple = new Quartet<>(
202+
"firstName", "firstName", SqlOperation.CONTAINS, ValueCondition.CONTAINS);
203+
Quartet<String, String, SqlOperation, ValueCondition> insertDateTuple = new Quartet<>(
204+
"dateFrom", "insertDate", SqlOperation.GREATER_THAN, ValueCondition.EQUAL);
205+
rules.add(firstNameTuple);
206+
rules.add(insertDateTuple);
207+
208+
// define pageable object base on pagedQuery
209+
List<Sort.Order> orderFromPagedQuery = Utils.getSortOrderFromPagedQuery(pagedQuery, User.class);
210+
Pageable pageable = PageRequest.of(pagedQuery.getPageNumber(),
211+
pagedQuery.getPageSize(),
212+
Sort.by(orderFromPagedQuery));
213+
214+
// call repo
215+
// tuples, queryString, pageable to repository
216+
try{
217+
Page<User> userQueryWithCriteriaBuilder = userRepository.getUserQueryWithCriteriaBuilder(queryString, rules, pageable);
218+
List<UserModel> collect = userQueryWithCriteriaBuilder.get().map(userMapper::userToUserModel).collect(Collectors.toList());
219+
return new PageImpl<>(collect, pageable, userQueryWithCriteriaBuilder.getTotalElements());
220+
}catch (RuntimeException exception) {
221+
throw AppException.newInstance(
222+
HttpErrorCode.ERR_10701,
223+
String.format("getUserSearchWithCriteriaBuilder method has error: %s", exception.getCause())
224+
);
225+
}
226+
}
227+
193228
private void mapUserForUpdate(User sourceUser, User updateUser) {
194229
if (!sourceUser.getFirstName().equals(updateUser.getFirstName())) {
195230
sourceUser.setFirstName(updateUser.getFirstName());

0 commit comments

Comments
 (0)