spring ldap分页探究

505 阅读4分钟

问题

在工作过程中,需要获取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等的分页查询存在较大差别,其当前实现并不能满足功能需求。

引用