优秀的编程知识分享平台

网站首页 > 技术文章 正文

基于Spring和JPA标准的REST查询语言

nanyue 2024-10-01 13:05:15 技术文章 4 ℃

1. 概述

在这篇文章中,我们将探讨REST API的简单查询语言。我们将充分利用Spring和JPA持久化标准。

为什么使用查询语言?因为 ,对于任何复杂的API来说 ,通过非常简单的字段搜索/过滤资源是不够的。使用查询语言显得更加灵活,并且允许您精确筛选所需的资源。

2. 创建用户实体

首先,我们先创建一个简单的实体类,他将会用到我们的过滤和搜索的API。

User实体类:

@Entity

public class User {

@Id

@GeneratedValue(strategy = GenerationType.AUTO)

private Long id;

private String firstName;

private String lastName;

private String email;

private int age;

}

3. 使用CriteriaBuilder进行过滤

接下来让我们看下持久层中的查询操作

首先,需要提供一个抽象的接口,提供接口时要考虑:程序的可伸缩的灵活性,另一方面要使程序的复杂性控制在可控范围内。这里的功能很简单,通过一些条件得到一些结果:

UserDAO类代码:

@Repository

public class UserDAO implements IUserDAO {

@PersistenceContext

private EntityManager entityManager;

@Override

public List<User> searchUser(List<SearchCriteria> params) {

CriteriaBuilder builder = entityManager.getCriteriaBuilder();

CriteriaQuery<User> query = builder.createQuery(User.class);

Root r = query.from(User.class);

Predicate predicate = builder.conjunction();

for(SearchCriteria param : params) {

if(param.getOperation().equalsIgnoreCase(">")) {

predicate = builder.and(predicate,builder.greaterThanOrEqualTo(r.get(param.getKey()),param.getValue().toString()));

}else if(param.getOperation().equalsIgnoreCase("<")) {

predicate = builder.and(predicate,builder.lessThanOrEqualTo(r.get(param.getKey()),param.getValue().toString()));

}else if(param.getOperation().equalsIgnoreCase(":")) {

if(r.get(param.getKey()).getJavaType() == String.class) {

predicate = builder.and(predicate, builder.like(r.get(param.getKey()),"%"+ param.getValue() + "%"));

}else{

predicate = builder.and(predicate,builder.equal(r.get(param.getKey()), param.getValue()));

}

}

}

query.where(predicate);

List<User> result = entityManager.createQuery(query).getResultList();

return result;

} @Override

public void save(User entity) {

entityManager.persist(entity);

}}

正如你所看到的,searchUser API会获取非常简单的搜索约束列表,根据这些约束组成查询,执行搜索并返回结果。

搜索约束类也很简单:

public class SearchCriteria {

private String key;

private String operation;

private Object value;

}

这个类里面包含了我们的一些查询参数:

key: 用于保存字段名称。例如:姓名、年龄、性别等

operation: 用于保存操作,

value: 用于保存字段值。例如:tom、18 、男等

4. 测试搜索查询

现在让我们来测试一下我们的搜索功能。

首先,让我们初始化两个用户到我们的数据库,代码如下:

@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration(classes = { PersistenceConfig.class})

@Transactional

@TransactionConfiguration

public class JPACriteriaQueryTest {

@Autowired

private IUserDAO userApi;

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);

userApi.save(userJohn);

userTom = new User();

userTom.setFirstName("Tom");

userTom.setLastName("Doe");

userTom.setEmail("tom@doe.com");

userTom.setAge(26);

userApi.save(userTom);

}

}

现在,让我们得到一个具有特定firstName和lastName的用户 。如下代码:

@Test

public void givenFirstAndLastName_whenGettingListOfUsers_thenCorrect() {

List<SearchCriteria> params = new ArrayList<SearchCriteria>();

params.add(new SearchCriteria("firstName", ":", "John"));

params.add(new SearchCriteria("lastName", ":", "Doe"));

List<User> results = userApi.searchUser(params);

assertThat(userJohn, isIn(results));

assertThat(userTom, not(isIn(results)));

}

接下来,让我们获取具有相同lastName的用户列表:

@Test

public void givenLast_whenGettingListOfUsers_thenCorrect() {

List<SearchCriteria> params = new ArrayList<SearchCriteria>();

params.add(new SearchCriteria("lastName", ":", "Doe"));

List<User> results = userApi.searchUser(params);

assertThat(userJohn, isIn(results));

assertThat(userTom, isIn(results));

}

接下来,让我们获得年龄大于或等于25岁的用户:

@Test

public void givenLastAndAge_whenGettingListOfUsers_thenCorrect() {

List<SearchCriteria> params = new ArrayList<SearchCriteria>();

params.add(new SearchCriteria("lastName", ":", "Doe"));

params.add(new SearchCriteria("age", ">", "25"));

List<User> results = userApi.searchUser(params);

assertThat(userTom, isIn(results));

assertThat(userJohn, not(isIn(results)));

}

再接下来,让我们搜索不存在的用户:

@Test

public void givenWrongFirstAndLast_whenGettingListOfUsers_thenCorrect() {

List<SearchCriteria> params = new ArrayList<SearchCriteria>();

params.add(new SearchCriteria("firstName", ":", "Adam"));

params.add(new SearchCriteria("lastName", ":", "Fox"));

List<User> results = userApi.searchUser(params);

assertThat(userJohn, not(isIn(results)));

assertThat(userTom, not(isIn(results)));

}

最后,让我们以不完整firstName模糊搜索用户:

@Test

public void givenPartialFirst_whenGettingListOfUsers_thenCorrect() {

List<SearchCriteria> params = new ArrayList<SearchCriteria>();

params.add(new SearchCriteria("firstName", ":", "jo"));

List<User> results = userApi.searchUser(params);

assertThat(userJohn, isIn(results));

assertThat(userTom, not(isIn(results)));

}

6. UserController

现在让我们把这个持久性支持的灵活的搜索放在我们的REST API中。

我们创建一个简单的UserController, 用findAll()使用“ search ”作为参数传入整个搜索/筛选表达式:

@Controller

public class UserController {

@Autowired

private IUserDao api;

@RequestMapping(method = RequestMethod.GET, value = "/users")

@ResponseBody

public List<User> findAll(@RequestParam(value = "search", required = false) String search) {

List<SearchCriteria> params = new ArrayList<SearchCriteria>();

if(search != null) {

Pattern pattern = Pattern.compile("(\w+?)(:|<|>)(\w+?),");

Matcher matcher = pattern.matcher(search + ",");

while(matcher.find()) {

params.add(new SearchCriteria(matcher.group(1),matcher.group(2), matcher.group(3)));

}

}

return api.searchUser(params);

}

}

接下来可以启动程序,使用一下路径访问应用:

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

响应结果:

[{
"id":2,
"firstName":"tom",
"lastName":"doe",
"email":"tom@doe.com",

"age":26}]

7. 总结

这个简单但功能强大的实现,可以在REST API上实现相当多的智能筛选

最近发表
标签列表