جاوا و تکنولوژی های آن

java programming language

در این وبلاگ به بررسی نکات موجود در جاوا و تکنولوژی های آن می پردازیم

طبقه بندی موضوعی


در این بخش خواهیم دید چطور میتوان بوسیله Spring Data JPA و QueryDSL کوئری های داینامیک روی دیتا ایجاد کرد و QueryDSL چه ویژگی ای را فراهم میکند



در ابتدا لازم است که کتابخانه مورد نیاز QueryDSL را به pom اضافه کنیم :

<dependency> 
    <groupId>com.querydsl</groupId> 
    <artifactId>querydsl-apt</artifactId> 
    <version>4.1.4</version>
    </dependency>
<dependency> 
    <groupId>com.querydsl</groupId> 
    <artifactId>querydsl-jpa</artifactId> 
    <version>4.1.4</version> 
</dependency>


همچنین نیاز داریم که تنظیماتی برای APT یا Annotation Processing Tool انجام دهیم :

APT به عنوان یک پلاگین استفاده خواهد شد

<plugin>
    <groupId>com.mysema.maven</groupId>
    <artifactId>apt-maven-plugin</artifactId>
    <version>1.1.3</version>
    <executions>
        <execution>
            <goals>
                <goal>process</goal>
            </goals>
            <configuration>
                <outputDirectory>target/generated-sources/java</outputDirectory>
                <processor>com.mysema.query.apt.jpa.JPAAnnotationProcessor</processor>
            </configuration>
        </execution>
    </executions>
</plugin>


Entity ای که میخواهیم search بر اساس فیلد های آن انجام شود :


@Entity
public class MyUser {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
 
    private String firstName;
    private String lastName;
    private String email;
 
    private int age;
}



ساخت Predicate سفارشی بوسیله PathBuilder :


برای داشتن Predicate منعطف تر نیاز است که حالت abstract تری داشته باشیم و از این رو از PathBuilder کمک میگیریم 

public class MyUserPredicate {
 
    private SearchCriteria criteria;
 
    public BooleanExpression getPredicate() {
        PathBuilder<MyUser> entityPath = new PathBuilder<>(MyUser.class, "user");
 
        if (isNumeric(criteria.getValue().toString())) {
            NumberPath<Integer> path = entityPath.getNumber(criteria.getKey(), Integer.class);
            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 {
            StringPath path = entityPath.getString(criteria.getKey());
            if (criteria.getOperation().equalsIgnoreCase(":")) {
                return path.containsIgnoreCase(criteria.getValue().toString());
            }
        }
        return null;
    }
}


حالا میتوانیم ببینیم که عملیات متنوع تری توسط Predicate قابل انجام هستند 



Search Criteria :

از این کلاس برای نگهداری اطلاعات سرچ دریافتی از کلاینت استفاده میکنیم :

public class SearchCriteria {
    private String key;
    private String operation;
    private Object value;
}


حالا نیاز به اینترفیسی داریم که عملیات سرچ را اجرا کند از این رو نیاز است علاوه بر ارث بری از JpaRepository از QuerydslPredicateExecutor هم ارث بری کند 

public interface MyUserRepository extends JpaRepository<MyUser, Long>, 
  QuerydslPredicateExecutor<MyUser>, QuerydslBinderCustomizer<QMyUser> {
    @Override
    default public void customize(
      QuerydslBindings bindings, QMyUser root) {
        bindings.bind(String.class)
          .first((SingleValueBinding<StringPath, String>) StringExpression::containsIgnoreCase);
        bindings.excluding(root.email);
      }
}


حالا میتوانیم با ترکیب کردن Predicate ها سرچ های پیچیده تری را پوشش دهیم 


public class MyUserPredicatesBuilder {
    private List<SearchCriteria> params;
 
    public MyUserPredicatesBuilder() {
        params = new ArrayList<>();
    }
 
    public MyUserPredicatesBuilder with(
      String key, String operation, Object value) {
   
        params.add(new SearchCriteria(key, operation, value));
        return this;
    }
 
    public BooleanExpression build() {
        if (params.size() == 0) {
            return null;
        }
 
        List predicates = params.stream().map(param -> {
            MyUserPredicate predicate = new MyUserPredicate(param);
            return predicate.getPredicate();
        }).filter(Objects::nonNull).collect(Collectors.toList());
         
        BooleanExpression result = Expressions.asBoolean(true).isTrue();
        for (BooleanExpression predicate : predicates) {
            result = result.and(predicate);
        }        
        return result;
    }
}


تست :

برای تست ابتدا دیتاهایی وارد دیتابیس میکنیم 

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { PersistenceConfig.class })
@Transactional
@Rollback
public class JPAQuerydslIntegrationTest {
 
    @Autowired
    private MyUserRepository repo;
 
    private MyUser userJohn;
    private MyUser userTom;
 
    @Before
    public void init() {
        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);
    }
}


تست سرچ بر اساس lastName :

@Test
public void givenLast_whenGettingListOfUsers_thenCorrect() {
    MyUserPredicatesBuilder builder = new MyUserPredicatesBuilder().with("lastName", ":", "Doe");
 
    Iterable<MyUser> results = repo.findAll(builder.build());
    assertThat(results, containsInAnyOrder(userJohn, userTom));
}


تست بر اساس firstName و lastName :


@Test
public void givenFirstAndLastName_whenGettingListOfUsers_thenCorrect() {
    MyUserPredicatesBuilder builder = new MyUserPredicatesBuilder()
      .with("firstName", ":", "John").with("lastName", ":", "Doe");
 
    Iterable<MyUser> results = repo.findAll(builder.build());
 
    assertThat(results, contains(userJohn));
    assertThat(results, not(contains(userTom)));
}

تست سرچ بر اساس lastName و حداقل age :

@Test
public void givenLastAndAge_whenGettingListOfUsers_thenCorrect() {
    MyUserPredicatesBuilder builder = new MyUserPredicatesBuilder()
      .with("lastName", ":", "Doe").with("age", ">", "25");
 
    Iterable<MyUser> results = repo.findAll(builder.build());
 
    assertThat(results, contains(userTom));
    assertThat(results, not(contains(userJohn)));
}


تست سرچی که نتیجه ای در بر نداشته باشد :

@Test
public void givenWrongFirstAndLast_whenGettingListOfUsers_thenCorrect() {
    MyUserPredicatesBuilder builder = new MyUserPredicatesBuilder()
      .with("firstName", ":", "Adam").with("lastName", ":", "Fox");
 
    Iterable<MyUser> results = repo.findAll(builder.build());
    assertThat(results, emptyIterable());
}


سرچ بر اساس قسمتی از firstName :

@Test
public void givenPartialFirst_whenGettingListOfUsers_thenCorrect() {
    MyUserPredicatesBuilder builder = new MyUserPredicatesBuilder().with("firstName", ":", "jo");
 
    Iterable<MyUser> results = repo.findAll(builder.build());
 
    assertThat(results, contains(userJohn));
    assertThat(results, not(contains(userTom)));
}



ساخت و تست سرچ در مدل واقعی روی کنترلر و درخواست سرچ کلاینت :

@Controller
public class UserController {
 
    @Autowired
    private MyUserRepository myUserRepository;
 
    @RequestMapping(method = RequestMethod.GET, value = "/myusers")
    @ResponseBody
    public Iterable<MyUser> search(@RequestParam(value = "search") String search) {
        MyUserPredicatesBuilder builder = new MyUserPredicatesBuilder();
 
        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);
    }
}

درخواست سرچ کلاینت :

http://localhost:8080/myusers?search=lastName:doe,age>25

نمونه نتیجه ای که انتظار داریم ببینیم :

[{
    "id":2,
    "firstName":"tom",
    "lastName":"doe",
    "email":"tom@doe.com",
    "age":26
}]





نظرات  (۰)

هیچ نظری هنوز ثبت نشده است

ارسال نظر

ارسال نظر آزاد است، اما اگر قبلا در بیان ثبت نام کرده اید می توانید ابتدا وارد شوید.
شما میتوانید از این تگهای html استفاده کنید:
<b> یا <strong>، <em> یا <i>، <u>، <strike> یا <s>، <sup>، <sub>، <blockquote>، <code>، <pre>، <hr>، <br>، <p>، <a href="" title="">، <span style="">، <div align="">
تجدید کد امنیتی