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

java programming language

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

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


در این بخش به کمک Spring Data JPA به ایجاد Search / Filtering REST Api می پردازیم و خواهیم دید چطوری با JPA Criteria کوئری مورد نظر را بسازیم 



User Entity :
@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
 
    private String firstName;
    private String lastName;
    private String email;
 
    private int age;
     
    // standard getters and setters
}


در کلاس UserSpecification که اینترفیس Specification را پیاده سازی کرده است کوئری مورد نظر را با استفاده از SearchCreteria میسازیم :


public class UserSpecification implements Specification<User> {
 
    private SearchCriteria criteria;
 
    @Override
    public Predicate toPredicate
      (Root<User> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
  
        if (criteria.getOperation().equalsIgnoreCase(">")) {
            return builder.greaterThanOrEqualTo(
              root.<String> get(criteria.getKey()), criteria.getValue().toString());
        } 
        else if (criteria.getOperation().equalsIgnoreCase("<")) {
            return builder.lessThanOrEqualTo(
              root.<String> get(criteria.getKey()), criteria.getValue().toString());
        } 
        else if (criteria.getOperation().equalsIgnoreCase(":")) {
            if (root.get(criteria.getKey()).getJavaType() == String.class) {
                return builder.like(
                  root.<String>get(criteria.getKey()), "%" + criteria.getValue() + "%");
            } else {
                return builder.equal(root.get(criteria.getKey()), criteria.getValue());
            }
        }
        return null;
    }
}


کلاس SearchCreteria که نام فیلد و نوع عملیات و مقداری که در حین عملیات نیاز است را نگهداری میکند

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


ساخت JPARespository به عنوان Bean برای کار با دیتابیس :


public interface UserRepository 
  extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {}



قبل از تست اصلی یکسری Entity در دیتابیس ذخیره میکنیم :


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { PersistenceJPAConfig.class })
@Transactional
@TransactionConfiguration
public class JPASpecificationsTest {
 
    @Autowired
    private UserRepository repository;
 
    private User userJohn;
    private User userTom;
 
    @Before
    public void init() {
        userJohn = new User();
        userJohn.setFirstName("John");
        userJohn.setLastName("Doe");
        userJohn.setEmail("john@doe.com");
        userJohn.setAge(22);
        repository.save(userJohn);
 
        userTom = new User();
        userTom.setFirstName("Tom");
        userTom.setLastName("Doe");
        userTom.setEmail("tom@doe.com");
        userTom.setAge(26);
        repository.save(userTom);
    }
}


تست دریافت Entity ها بر اساس کوئری روی lastName :

@Test
public void givenLast_whenGettingListOfUsers_thenCorrect() {
    UserSpecification spec = 
      new UserSpecification(new SearchCriteria("lastName", ":", "doe"));
     
    List<User> results = repository.findAll(spec);
 
    assertThat(userJohn, isIn(results));
    assertThat(userTom, isIn(results));
}



تست کوئری بر اساس lastName و  firstName :

@Test
public void givenFirstAndLastName_whenGettingListOfUsers_thenCorrect() {
    UserSpecification spec1 = 
      new UserSpecification(new SearchCriteria("firstName", ":", "john"));
    UserSpecification spec2 = 
      new UserSpecification(new SearchCriteria("lastName", ":", "doe"));
     
    List<User> results = repository.findAll(Specification.where(spec1).and(spec2));
 
    assertThat(userJohn, isIn(results));
    assertThat(userTom, not(isIn(results)));
}

دقت کنید که در کد بالا از عبارات شرطی where و and استفاده شده


تست کوئری بر اساس lastName , firstName و age :


@Test
public void givenLastAndAge_whenGettingListOfUsers_thenCorrect() {
    UserSpecification spec1 = 
      new UserSpecification(new SearchCriteria("age", ">", "25"));
    UserSpecification spec2 = 
      new UserSpecification(new SearchCriteria("lastName", ":", "doe"));
 
    List<User> results = 
      repository.findAll(Specification.where(spec1).and(spec2));
 
    assertThat(userTom, isIn(results));
    assertThat(userJohn, not(isIn(results)));
}


تست کوئری ای که هیچ مقداری را پیدا نخواهد کرد :


@Test
public void givenWrongFirstAndLast_whenGettingListOfUsers_thenCorrect() {
    UserSpecification spec1 = 
      new UserSpecification(new SearchCriteria("firstName", ":", "Adam"));
    UserSpecification spec2 = 
      new UserSpecification(new SearchCriteria("lastName", ":", "Fox"));
 
    List<User> results = 
      repository.findAll(Specification.where(spec1).and(spec2));
 
    assertThat(userJohn, not(isIn(results)));
    assertThat(userTom, not(isIn(results)));  
}


تست کوئری بر اساس قسمتی از firstName :

@Test
public void givenPartialFirst_whenGettingListOfUsers_thenCorrect() {
    UserSpecification spec = 
      new UserSpecification(new SearchCriteria("firstName", ":", "jo"));
     
    List<User> results = repository.findAll(spec);
 
    assertThat(userJohn, isIn(results));
    assertThat(userTom, not(isIn(results)));
}


تست کوئری پیچیده تر و ترکیبی :

public class UserSpecificationsBuilder {
     
    private final List<SearchCriteria> params;
 
    public UserSpecificationsBuilder() {
        params = new ArrayList<SearchCriteria>();
    }
 
    public UserSpecificationsBuilder with(String key, String operation, Object value) {
        params.add(new SearchCriteria(key, operation, value));
        return this;
    }
 
    public Specification<User> build() {
        if (params.size() == 0) {
            return null;
        }
 
        List<Specification> specs = params.stream()
          .map(UserSpecification::new)
          .collect(Collectors.toList());
         
        Specification result = specs.get(0);
 
        for (int i = 1; i < params.size(); i++) {
            result = params.get(i)
              .isOrPredicate()
                ? Specification.where(result)
                  .or(specs.get(i))
                : Specification.where(result)
                  .and(specs.get(i));
        }       
        return result;
    }
}



کوئری دریافتی از کلاینت روی کنترلر :


@Controller
public class UserController {
 
    @Autowired
    private UserRepository repo;
 
    @RequestMapping(method = RequestMethod.GET, value = "/users")
    @ResponseBody
    public List<User> search(@RequestParam(value = "search") String search) {
        UserSpecificationsBuilder builder = new UserSpecificationsBuilder();
        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));
        }
         
        Specification<User> spec = builder.build();
        return repo.findAll(spec);
    }
}


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

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


نمونه جواب دریافتی از کنترلر :

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


* نکته : از آنجا که مقادیر مورد نیاز برای فیلتر کرد توسط کاما جدا میشود ما نمیتوانیم این کاراکتر را جز موارد فیلتر شونده قرار دهیم چون در Pattern از آن به عنوان splitter استفاده شده است و چنانچه بخواهیم کاما جز موارد فیلتر شونده باشد میتوانیم کاراکتر splitter را به کاراکتر دیگری تغییر دهیم مثلا سمی کالمن







نظرات  (۰)

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

ارسال نظر

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