背景
用户表User有一个字段Tags,用于描述用户的个人特点/能力。
业务需求:
- 查询满足标签列表的所有用户
- 请求体中的标签列表是 JSON字符串
分析
实现方式
- SQL查询(实现简单,可以通过拆分查询进一步优化)
- 内存查询(灵活,可以通过并发进一步优化)
根据查询效率选择不同的查询方法:
建议通过实际测试来分析哪种查询比较快,数据量大的时候验证效果更明显!
- 如果参数可以分析,根据用户的参数去选择查询方式,比如标签数(标签数 > 个临界值时,使用 某个方法查询更快)
- 考虑使用不同的连接池
- 考虑使用SQL查询、内存查询时间
- 如果参数不可分析,并且数据库连接足够、内存空间足够,可以并发同时查询,谁先返回结果用谁
- 还可以SQL查询与内存查询相结合,比如先用SQL过滤掉部分条件(例如:tag)
实现
SQL查询
/**
* 根据标签搜索用户
*
* @param tagNameList 标签列表数组( 已通过GSON 将 JSON -> List )
* @return 脱敏后的用户列表
*/
public List<User> searchUsersByTagsToSql(List<String> tagNameList) {
// long startTime = System.currentTimeMillis();
// 参数校验
if (CollectionUtils.isEmpty(tagNameList)) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
// 拼接 and 查询
// 封装条件构造器
QueryWrapper<User> userQueryWapper = new QueryWrapper<>();
for (String tagName : tagNameList) {
userQueryWapper = userQueryWapper.like("tags", tagName);
}
// 查询
List<User> userList = userMapper.selectList(userQueryWapper);
// 脱敏
List<User> resultList = userList.stream().map(this::getSafetyUser).collect(Collectors.toList());
// log.info( " sql query time = "+ (System.currentTimeMillis()-startTime) );
return resultList;
}
- 拼接 and 查询:tags LIKE ? AND tags LIKE ? AND tags LIKE ?
- this::getSafetyUser:
userList.forEach( user ->{
getSafetyUser(user);
});
- userList.stream().map(this::getSafetyUser).collect(Collectors.toList()):
内存查询
先全量将数据加载内存中,再根据条件查询
public List<User> searchUsersByTagsToMemory(List<String> tagNameList) {
// long startTime = System.currentTimeMillis();
// 参数校验
if (CollectionUtils.isEmpty(tagNameList)) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
// 全量查询
QueryWrapper<User> queryWrap = new QueryWrapper<>();
List<User> userList = userMapper.selectList(queryWrap);
Gson gson = new Gson();
// Json转Java对象 (数据库中存储的Tags是Json格式,需要转为Java对象才能操作)
// for (User user : userList) {
// String tagsJson = user.getTags();
// // Json 反序列化为 Java对象
// Set<String> tempTagsNameSet = gson.fromJson(tagsJson, new TypeToken<Set<String>>(){}.getType());
// for( String tagName : tagNameList){
// if( tempTagsNameSet.contains(tagName)){
// return false;
// }
// }
// return true;
// }
// 使用语法塘代替 foreach
userList.stream().filter((user) -> {
String tagsJson = user.getTags();
if( StringUtils.isBlank(tagsJson)){
return false;
}
// Json 反序列化为 Java对象
Set<String> tempTagsNameSet = gson.fromJson(tagsJson, new TypeToken<Set<String>>() {
}.getType());
for (String tagName : tagNameList) {
if (!tempTagsNameSet.contains(tagName)) {
return false;
}
}
return true;
}).map(this::getSafetyUser).collect(Collectors.toList());
// log.info( " memory query time = "+ (System.currentTimeMillis()-startTime) );
return userList;
}
解析JSON字符串:
序列化:Java对象 转成 json
反序列化:json 转称 Java对象
常用java json序列化库:
gson(google)
astjson (阿里,速度快,漏洞多)
jackson
kryo
测试
注意:测试查询速度时,需要考虑数据库首次连接使用的时间,避免影响对不同方式的性能比较。 解决:可以先使用一次查询,避免这种情况的发生。