diff --git a/pom.xml b/pom.xml
index 09935fc..9bc5c33 100644
--- a/pom.xml
+++ b/pom.xml
@@ -9,7 +9,7 @@
jar
demo
-
+
org.springframework.boot
@@ -22,9 +22,39 @@
UTF-8
UTF-8
1.8
+ 4.1.3
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.data
+ spring-data-commons
+
+
+
+ com.querydsl
+ querydsl-apt
+ ${querydsl.version}
+ provided
+
+
+
+ com.querydsl
+ querydsl-jpa
+ ${querydsl.version}
+
+
+
+ org.slf4j
+ slf4j-log4j12
+ 1.6.1
+
+
+
com.h2database
h2
@@ -39,10 +69,7 @@
org.springframework.boot
spring-boot-starter-data-jpa
-
- org.springframework.boot
- spring-boot-starter-web
-
+
mysql
@@ -63,13 +90,71 @@
google-collections
1.0
+
+
+ io.rest-assured
+ rest-assured
+ 3.0.0
+ test
+
+
+ io.rest-assured.examples
+ scala-example
+ 3.0.0
+ test
+
+
+
- org.springframework.boot
- spring-boot-maven-plugin
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ com.mysema.maven
+ apt-maven-plugin
+ 1.1.3
+
+
+
+ process
+
+
+ target/generated-sources/java
+ com.querydsl.apt.jpa.JPAAnnotationProcessor
+
+
+
diff --git a/src/main/java/com/example/demo/persistence/dao/MyUserPredicate.java b/src/main/java/com/example/demo/persistence/dao/MyUserPredicate.java
new file mode 100644
index 0000000..248af3d
--- /dev/null
+++ b/src/main/java/com/example/demo/persistence/dao/MyUserPredicate.java
@@ -0,0 +1,57 @@
+package com.example.demo.persistence.dao;
+
+import com.example.demo.persistence.model.MyUser;
+import com.example.demo.web.util.SearchCriteria;
+import com.querydsl.core.types.dsl.BooleanExpression;
+import com.querydsl.core.types.dsl.NumberPath;
+import com.querydsl.core.types.dsl.PathBuilder;
+import com.querydsl.core.types.dsl.StringPath;
+
+public class MyUserPredicate {
+
+ private SearchCriteria criteria;
+
+ public MyUserPredicate(final SearchCriteria criteria) {
+ this.criteria = criteria;
+ }
+
+ public BooleanExpression getPredicate() {
+ final PathBuilder entityPath = new PathBuilder<>(MyUser.class, "myUser");
+
+ if (isNumeric(criteria.getValue().toString())) {
+ final NumberPath path = entityPath.getNumber(criteria.getKey(), Integer.class);
+ final int value = Integer.parseInt(criteria.getValue().toString());
+ switch (criteria.getOperation()) {
+ case ":":
+ return path.eq(value);
+ case ">":
+ return path.goe(value);
+ case "<":
+ return path.loe(value);
+ }
+ } else {
+ final StringPath path = entityPath.getString(criteria.getKey());
+ if (criteria.getOperation().equalsIgnoreCase(":")) {
+ return path.containsIgnoreCase(criteria.getValue().toString());
+ }
+ }
+ return null;
+ }
+
+ public SearchCriteria getCriteria() {
+ return criteria;
+ }
+
+ public void setCriteria(final SearchCriteria criteria) {
+ this.criteria = criteria;
+ }
+
+ public static boolean isNumeric(final String str) {
+ try {
+ Integer.parseInt(str);
+ } catch (final NumberFormatException e) {
+ return false;
+ }
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/demo/persistence/dao/MyUserPredicateBuilder.java b/src/main/java/com/example/demo/persistence/dao/MyUserPredicateBuilder.java
new file mode 100644
index 0000000..4fc7a11
--- /dev/null
+++ b/src/main/java/com/example/demo/persistence/dao/MyUserPredicateBuilder.java
@@ -0,0 +1,42 @@
+package com.example.demo.persistence.dao;
+
+import com.example.demo.web.util.SearchCriteria;
+import com.querydsl.core.types.dsl.BooleanExpression;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public final class MyUserPredicateBuilder {
+ private final List params;
+
+ public MyUserPredicateBuilder() {
+ params = new ArrayList<>();
+ }
+
+ public MyUserPredicateBuilder with(final String key, final String operation, final Object value) {
+ params.add(new SearchCriteria(key, operation, value));
+ return this;
+ }
+
+ public BooleanExpression build() {
+ if (params.size() == 0) {
+ return null;
+ }
+
+ final List predicates = new ArrayList<>();
+ MyUserPredicate predicate;
+ for (final SearchCriteria param : params) {
+ predicate = new MyUserPredicate(param);
+ final BooleanExpression exp = predicate.getPredicate();
+ if (exp != null) {
+ predicates.add(exp);
+ }
+ }
+
+ BooleanExpression result = predicates.get(0);
+ for (int i = 1; i < predicates.size(); i++) {
+ result = result.and(predicates.get(i));
+ }
+ return result;
+ }
+}
diff --git a/src/main/java/com/example/demo/persistence/dao/repository/MyUserRepository.java b/src/main/java/com/example/demo/persistence/dao/repository/MyUserRepository.java
new file mode 100644
index 0000000..623433c
--- /dev/null
+++ b/src/main/java/com/example/demo/persistence/dao/repository/MyUserRepository.java
@@ -0,0 +1,26 @@
+package com.example.demo.persistence.dao.repository;
+
+import com.example.demo.persistence.model.MyUser;
+import com.example.demo.persistence.model.QMyUser;
+import com.querydsl.core.types.dsl.StringExpression;
+import com.querydsl.core.types.dsl.StringPath;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.querydsl.QueryDslPredicateExecutor;
+import org.springframework.data.querydsl.binding.QuerydslBinderCustomizer;
+import org.springframework.data.querydsl.binding.QuerydslBindings;
+import com.querydsl.core.types.dsl.StringExpression;
+
+import com.querydsl.core.types.dsl.StringPath;
+import org.springframework.data.querydsl.binding.SingleValueBinding;
+
+public interface MyUserRepository extends JpaRepository,
+ QueryDslPredicateExecutor, QuerydslBinderCustomizer {
+
+ @Override
+ default void customize(final QuerydslBindings bindings, final QMyUser root) {
+ bindings.bind(String.class)
+ .first((SingleValueBinding) StringExpression::containsIgnoreCase);
+ bindings.excluding(root.email);
+ }
+}
+
diff --git a/src/main/java/com/example/demo/persistence/model/MyUser.java b/src/main/java/com/example/demo/persistence/model/MyUser.java
new file mode 100644
index 0000000..c74b51d
--- /dev/null
+++ b/src/main/java/com/example/demo/persistence/model/MyUser.java
@@ -0,0 +1,31 @@
+package com.example.demo.persistence.model;
+
+import lombok.*;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+
+@Entity
+@Setter
+@Getter
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@ToString
+@EqualsAndHashCode
+public class MyUser {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.AUTO)
+ private int id;
+
+ private String firstName;
+
+ private String lastName;
+
+ private String email;
+
+ private int age;
+}
diff --git a/src/main/java/com/example/demo/persistence/model/User.java b/src/main/java/com/example/demo/persistence/model/User.java
index 94f046f..9c89dbe 100644
--- a/src/main/java/com/example/demo/persistence/model/User.java
+++ b/src/main/java/com/example/demo/persistence/model/User.java
@@ -14,6 +14,8 @@
@Builder
@AllArgsConstructor
@NoArgsConstructor
+@ToString
+@EqualsAndHashCode
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
diff --git a/src/main/java/com/example/demo/web/controller/UserController.java b/src/main/java/com/example/demo/web/controller/UserController.java
index 8a7894e..9c2d945 100644
--- a/src/main/java/com/example/demo/web/controller/UserController.java
+++ b/src/main/java/com/example/demo/web/controller/UserController.java
@@ -1,15 +1,20 @@
package com.example.demo.web.controller;
import com.example.demo.persistence.dao.IUserDao;
+import com.example.demo.persistence.dao.MyUserPredicateBuilder;
import com.example.demo.persistence.dao.UserSpecificationBuilder;
+import com.example.demo.persistence.dao.repository.MyUserRepository;
import com.example.demo.persistence.dao.repository.UserRepository;
+import com.example.demo.persistence.model.MyUser;
import com.example.demo.persistence.model.User;
import com.example.demo.web.util.SearchCriteria;
+import com.querydsl.core.types.dsl.BooleanExpression;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.domain.Specification;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.data.querydsl.binding.QuerydslPredicate;
+import org.springframework.web.bind.annotation.*;
+import com.querydsl.core.types.Predicate;
+
import java.util.ArrayList;
import java.util.List;
@@ -21,10 +26,12 @@ public class UserController {
private final IUserDao api;
private final UserRepository repo;
+ private final MyUserRepository myUserRepository;
@Autowired
- public UserController(IUserDao api, UserRepository repo) {
+ public UserController(IUserDao api, UserRepository repo, MyUserRepository myUserRepository) {
this.api = api;
this.repo = repo;
+ this.myUserRepository = myUserRepository;
}
@GetMapping("/users") //users?search=lastName:Bozenka,age>25
@@ -44,7 +51,7 @@ public List findAll(@RequestParam(value = "search", required = false)Strin
@GetMapping("/users/specs") //users/specs?search=lastName:Bozenka,age>25
public List search(@RequestParam(value = "search") String search) {
UserSpecificationBuilder builder = new UserSpecificationBuilder();
- Pattern pattern = Pattern.compile("(\\w+?)(:|<|>)(\\w+?),");
+ Pattern pattern = Pattern.compile("(\\w+?)([:<>])(\\w+?),");
Matcher matcher = pattern.matcher(search + ",");
while (matcher.find()) {
builder.with(matcher.group(1), matcher.group(2), matcher.group(3));
@@ -53,4 +60,25 @@ public List search(@RequestParam(value = "search") String search) {
Specification spec = builder.build();
return repo.findAll(spec);
}
+
+ @RequestMapping(method = RequestMethod.GET, value = "/myusers")
+ @ResponseBody//http://localhost:8080/users?search=lastName:doe,age>25
+ public Iterable findAllByQuerydsl(@RequestParam(value = "search") String search) {
+ MyUserPredicateBuilder builder = new MyUserPredicateBuilder();
+ if (search != null) {
+ Pattern pattern = Pattern.compile("(\\w+?)([:<>])(\\w+?),");
+ Matcher matcher = pattern.matcher(search + ",");
+ while (matcher.find()) {
+ builder.with(matcher.group(1), matcher.group(2), matcher.group(3));
+ }
+ }
+ BooleanExpression exp = builder.build();
+ return myUserRepository.findAll(exp);
+ }
+
+ @RequestMapping(method = RequestMethod.GET, value = "/api/myusers")
+ @ResponseBody
+ public Iterable findAllByWebQuerydsl(@QuerydslPredicate(root = MyUser.class) Predicate predicate) {
+ return myUserRepository.findAll(predicate);
+ }
}
diff --git a/src/test/java/com/example/demo/persistence/dao/MyUserPredicateTest.java b/src/test/java/com/example/demo/persistence/dao/MyUserPredicateTest.java
new file mode 100644
index 0000000..93515c8
--- /dev/null
+++ b/src/test/java/com/example/demo/persistence/dao/MyUserPredicateTest.java
@@ -0,0 +1,78 @@
+package com.example.demo.persistence.dao;
+
+import com.example.demo.config.PersistenceConfig;
+import com.example.demo.persistence.dao.repository.MyUserRepository;
+import com.example.demo.persistence.model.MyUser;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.annotation.Rollback;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+import javax.transaction.Transactional;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.collection.IsEmptyIterable.emptyIterable;
+import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder;
+import static org.hamcrest.collection.IsIterableContainingInOrder.contains;
+import static org.hamcrest.core.IsNot.not;
+
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(classes = { PersistenceConfig.class })
+@Transactional
+@Rollback
+public class MyUserPredicateTest {
+
+ @Autowired
+ private MyUserRepository repo;
+
+ private MyUser userJohn;
+ private MyUser userTom;
+
+ @Before
+ public void setUp() throws Exception {
+ userJohn = new MyUser();
+ userJohn.setFirstName("John");
+ userJohn.setLastName("Doe");
+ userJohn.setEmail("john@doe.com");
+ userJohn.setAge(22);
+ repo.save(userJohn);
+
+ userTom = new MyUser();
+ userTom.setFirstName("Tom");
+ userTom.setLastName("Doe");
+ userTom.setEmail("tom@doe.com");
+ userTom.setAge(26);
+ repo.save(userTom);
+
+ }
+
+ @Test
+ public void givenLast_whenGettingListOfUsers_thenCorrect() {
+ final MyUserPredicateBuilder builder = new MyUserPredicateBuilder().with("lastName", ":", "Doe");
+
+ final Iterable results = repo.findAll(builder.build());
+ assertThat(results, containsInAnyOrder(userJohn, userTom));
+ }
+
+ @Test
+ public void givenFirstAndLastName_whenGettingListOfUsers_thenCorrect() {
+ final MyUserPredicateBuilder builder = new MyUserPredicateBuilder().with("firstName", ":", "john").with("lastName", ":", "doe");
+
+ final Iterable results = repo.findAll(builder.build());
+
+ assertThat(results, contains(userJohn));
+ assertThat(results, not(contains(userTom)));
+ }
+
+ @Test
+ public void givenWrongFirstAndLast_whenGettingListOfUsers_thenCorrect() {
+ final MyUserPredicateBuilder builder = new MyUserPredicateBuilder().with("firstName", ":", "adam").with("lastName", ":", "fox");
+
+ final Iterable results = repo.findAll(builder.build());
+ assertThat(results, emptyIterable());
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/example/demo/web/controller/MyUserQueryDslWebTest.java b/src/test/java/com/example/demo/web/controller/MyUserQueryDslWebTest.java
new file mode 100644
index 0000000..01027eb
--- /dev/null
+++ b/src/test/java/com/example/demo/web/controller/MyUserQueryDslWebTest.java
@@ -0,0 +1,51 @@
+package com.example.demo.web.controller;
+
+import com.example.demo.persistence.model.MyUser;
+import static org.junit.Assert.assertEquals;
+import io.restassured.RestAssured;
+import io.restassured.response.Response;
+import io.restassured.specification.RequestSpecification;
+
+import org.junit.Test;
+import org.springframework.test.context.ActiveProfiles;
+
+@ActiveProfiles("test")
+public class MyUserQueryDslWebTest {
+
+ private final MyUser userJohn = new MyUser(1,"john", "doe", "john@doe.com", 11);
+ private String URL_PREFIX = "http://localhost:8082/spring-rest-query-language/auth/api/myusers";
+
+ @Test
+ public void whenGettingListOfUsers_thenCorrect() {
+ final Response response = givenAuth().get(URL_PREFIX);
+ final MyUser[] result = response.as(MyUser[].class);
+ assertEquals(result.length, 2);
+ }
+
+ @Test
+ public void givenFirstName_whenGettingListOfUsers_thenCorrect() {
+ final Response response = givenAuth().get(URL_PREFIX + "?firstName=john");
+ final MyUser[] result = response.as(MyUser[].class);
+ assertEquals(result.length, 1);
+ assertEquals(result[0].getEmail(), userJohn.getEmail());
+ }
+
+ @Test
+ public void givenPartialLastName_whenGettingListOfUsers_thenCorrect() {
+ final Response response = givenAuth().get(URL_PREFIX + "?lastName=do");
+ final MyUser[] result = response.as(MyUser[].class);
+ assertEquals(result.length, 2);
+ }
+
+ @Test
+ public void givenEmail_whenGettingListOfUsers_thenIgnored() {
+ final Response response = givenAuth().get(URL_PREFIX + "?email=john");
+ final MyUser[] result = response.as(MyUser[].class);
+ assertEquals(result.length, 2);
+ }
+
+ private RequestSpecification givenAuth() {
+ return RestAssured.given().auth().preemptive().basic("user1", "user1Pass");
+ }
+
+}
\ No newline at end of file