问题
在工作过程中,需要获取LDAP中某个组织节点下的组织或用户,考虑到返回的结果较多,希望采用分页的方式返回。
探究
Spring Ldap是提供分页查询结果的,在spring-ldap 2.4.0版本中,可以使用PagedResultsDirContextProcessor
查询分页结果,之前版本使用PagedResultsRequestControl
。但是需要注意的是,LDAP的分页查询,与对mysql等关系型数据库的分页查询是有区别的。
当面对mysql的关系型数据库时,分页查询往往需要借助对应的SQL语句实现,比如mysql的分页查询SQL语句可以是
SELECT * FROM Organ LIMIT 10 OFFSET 0;
但是LDAP却需要通过提供PagedResultsCookie
,来确定分页的位置。同时,在使用过程中要保证PagedResultsDirContextProcessor
依赖的LdapTemplate是同一个,且不能关闭与LDAP的连接,因此相比于mysql等的分页查询是无状态的分页查询,可以将其LDAP的分页查询认为是有状态的分页查询。
试验
在OpenLDAP中创建组织结构目录如下所示:
- 公司A
| - 部门A01
| - 小组A01-01
| - 小组A01-02
| - 小组A01-03
| - 部门A02
| - 小组A02-01
| - 小组A02-02
| - 小组A02-03
| - 部门A03
| - 小组03-01
| - 小组03-02
| - 小组03-03
下面是通过PagedResultsDirContextProcessor
实现的分页查询
1)第1次分页查询
final SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
SingleContextSource singleContextSource;
singleContextSource = new SingleContextSource(contextSource.getReadOnlyContext());
LdapTemplate ldapTemplate = new LdapTemplate(singleContextSource);
System.out.println("第1次分页查询:");
final PagedResultsDirContextProcessor processor01 = new PagedResultsDirContextProcessor(4);
LdapOperationsCallback<Map<String,Object>> callback01= new LdapOperationsCallback<Map<String,Object>>() {
@Override
public Map<String,Object> doWithLdapOperations(LdapOperations operations) {
Map<String,Object> resultMap=new HashMap<>();
List<OrganEntity> result = operations.search(
"ou=公司A",
"(&(objectclass=organizationalUnit))",
searchControls,
ORGAN_ENTITY_CONTEXT_MAPPER,
processor01);
PagedResultsCookie cookie=processor01.getCookie();
resultMap.put("data",result);
resultMap.put("cookie",cookie.getCookie());
return resultMap;
}
};
Map<String,Object> resultMap01=callback01.doWithLdapOperations(ldapTemplate);
byte[] cookie01=(byte[])resultMap01.get("cookie");
List<OrganEntity> data01=(List<OrganEntity>)resultMap01.get("data");
System.out.println(data01);
其中ORGAN_ENTITY_CONTEXT_MAPPER
是做的属性映射,代码如下所示
private final static ContextMapper<OrganEntity> ORGAN_ENTITY_CONTEXT_MAPPER = new AbstractContextMapper<OrganEntity>() {
@Override
public OrganEntity doMapFromContext(DirContextOperations context) {
OrganEntity organEntity = new OrganEntity();
organEntity.setDn(context.getDn());
organEntity.setOrganName(context.getStringAttribute("ou"));
organEntity.setCreatedAt(context.getStringAttribute("createTimestamp"));
organEntity.setUpdatedAt(context.getStringAttribute("modifyTimestamp"));
return organEntity;
}
};
由于当前配置的搜索范围是SearchControls.SUBTREE_SCOPE
,因此如果分页数是4(即pageSize=4),则第一次打印的结果是下面组织的内容(当前所有组织的ObjectClass都是organizationalUnit)
| - 部门A01
| - 小组A01-01
| - 小组A01-02
| - 小组A01-03
2)第2次分页查询
第二次分页查询需要使用第一次分页结果返回的cookie(即上述代码的cookie01),相同的代码就不赘述,这里只贴出构建新的PagedResultsDirContextProcessor
的代码
System.out.println("第2次分页查询:");
final PagedResultsDirContextProcessor processor02 = new PagedResultsDirContextProcessor(4,new PagedResultsCookie(cookie01));
......
第二次分页查询打印的结果就是下面组织结构的内容
| - 部门A02
| - 小组A02-01
| - 小组A02-02
| - 小组A02-03
3)第3次分页查询
继续分页查询,这时使用第二次分页查询生成的cookie(即cookie02)
System.out.println("第3次分页查询:");
final PagedResultsDirContextProcessor processor03 = new PagedResultsDirContextProcessor(4,new PagedResultsCookie(cookie02));
......
打印结果是下面组织的内容
| - 部门A03
| - 小组03-01
| - 小组03-02
| - 小组03-03
4) 第4次分页查询
此时已经将所有的结果都遍历一遍,如果继续以第三次分页查询结果的cookie查询,则返回如第一次分页查询的内容(这与之前的面向mysql等的分页查询并不同)
System.out.println("第4次分页查询:");
final PagedResultsDirContextProcessor processor04 = new PagedResultsDirContextProcessor(4,new PagedResultsCookie(cookie03));
......
5)第5次分页查询
如果此时采用之前使用的cookie02或cookie03进行查询,则会报错,但是如果使用cookie04或cookie01则会返回如第二次分页查询的结果内容
结论
由此试验可以看出,spring ldap的分页查询依赖cookie来维持分页状态,同时要保持ldap操作的连接是同一个连接且不可关闭。这与面向mysql等的分页查询并不相同。
如果是使用rest api方式调用,就需要返回结果中保持cookie信息。另外,也不能自由的选择页码,因此LDAP的分页查询与面向mysql等的分页查询存在较大差别,其当前实现并不能满足功能需求。