LDAP分页查询

基于spring-ldap-core-1.3.1



public List<TestData> findTestData() throws  Exception{
        AndFilter filter = new AndFilter();
        filter.and(new GreaterThanOrEqualsFilter("filterCondition", "your filterCondition"));
        String baseNam = "";//这里可以指定为""或者你要查询的某个根目录, 如:cn=testdata
        
        List<TestData> list = new ArrayList<TestData>();
        //声明搜索控件
        SearchControls schCtrls = new SearchControls();
        // 返回属性设置  
        String[] returnAttrs = { "testName", "testTime", "testUser"};  
        schCtrls.setReturningAttributes(returnAttrs);
        //指定检索范围
        /*
         * 0:OBJECT_SCOPE,搜索指定的命名对象。  
         * 1:ONELEVEL_SCOPE,只搜索指定命名对象的一个级别,这是缺省值。
         * 2:SUBTREE_SCOPE,搜索以指定命名对象为根结点的整棵树  **/
        schCtrls.setSearchScope(SearchControls.SUBTREE_SCOPE);
        
        int pageSize = 500;  
        byte[] cookie = null;
        ContextSource contextSource = ldapTemplate.getContextSource();
        DirContext ctx = contextSource.getReadWriteContext();  
        LdapContext lCtx = (LdapContext) ctx;  
        //分页
        lCtx.setRequestControls(new Control[] { new PagedResultsControl(  pageSize, Control.CRITICAL) });
        int totalResults = 0;
        do {  
            //搜索的结果的枚举属性
            NamingEnumeration<SearchResult> results = lCtx.search(baseNam,  filter.toString(), schCtrls);  
            while (null != results && results.hasMoreElements()) {//结果不为空且有值  
                SearchResult sr = results.next();  
                Attributes attrs = sr.getAttributes();  
                TestData testData = dataConvert(attrs);
                list.add(testData);
                totalResults++;  
            }
            //cookie是一个字节数组,包含了通过PagedResultsControl下一次调用服务器时所需的信息
            cookie = parseControls(lCtx.getResponseControls());  
            lCtx.setRequestControls(new Control[] { new PagedResultsControl(  pageSize, cookie, Control.CRITICAL) });  
        } while ((cookie != null) && (cookie.length != 0));  
        lCtx.close();  
        System.out.println("Total = " + totalResults);  
        
        return list;
    }
    /**
     * 将LDAP的操作日志changlog转换成TestData对象
     * @param attrs
     * @return
     */
    private TestData dataConvert(Attributes attrs) {
        TestData testData = new TestData();
        Map<String, String> testDataMap = new HashMap<String, String>();// 查找TestData对象每个成员变量的类型,用于后续的数据转换
        Class objClass = testData.getClass();
        for (int i = 0; i < objClass.getDeclaredFields().length; i++)
        {
            Field field = objClass.getDeclaredFields()[i];
            if (field.getType().toString().endsWith("List"))
                testDataMap.put(field.getName(), "List");
            else
                testDataMap.put(field.getName(), "String");
        }
        
        testDataMap.put("reqMod", "List");

        NamingEnumeration results = attrs.getAll(); // 检索属性集中属性的枚举

        try
        {
            while (results.hasMoreElements())
            {
                Attribute attr = null;
                try
                {
                    attr = (Attribute) results.next(); // 检索枚举中的下一个元素
                } catch (NamingException e)
                {
                    e.printStackTrace();
                }             
                String attrValue = null;
                try
                {
                    attrValue = attr.get().toString(); // 检索此属性的其中一个值
                } catch (NamingException e)
                {
                    e.printStackTrace();
                }
                if (attrValue != null && attrValue.length() > 0)
                {
                    String attrName = attr.getID(); // 检索此属性的 id
                    List<String> list = new ArrayList<String>();
                    NamingEnumeration map = attr.getAll(); // 用于判断属性值是否为多值属性
                    
                    if (map != null)
                    {
                        while (map.hasMore())
                        {
                            String mod = map.next().toString();
                            list.add(mod);
                        }
                    }
                    if ("List".equals(testDataMap.get(attrName)))
                        BeanUtils.setProperty(testData, attrName, list);// 多值成员变量处理方式
                    else
                        BeanUtils.setProperty(testData, attrName, attrValue);// 单值成员变量处理方式

                }
            }

        } catch (Exception ex)
        {
            ex.printStackTrace();
        }
        
        return testData;
    }

    //下次查询要用的cookie
    private static byte[] parseControls(Control[] controls)  
            throws NamingException {  
        byte[] cookie = null;  
        if (controls != null) {  
            for (int i = 0; i < controls.length; i++) {  
                if (controls[i] instanceof PagedResultsResponseControl) {  
                    PagedResultsResponseControl prrc = (PagedResultsResponseControl) controls[i];  
                    cookie = prrc.getCookie();  
                    System.out.println(">>Next Page \n");  
                }  
            }  
        }  
        return (cookie == null) ? new byte[0] : cookie;  

    }


spring ldap配置:

<bean id="contextSource" class="org.springframework.ldap.transaction.compensating.manager.TransactionAwareContextSourceProxy">
        <constructor-arg ref="pooledContextSource"/>
    </bean>

    <bean id="pooledContextSource" class="org.springframework.ldap.pool.factory.MutablePoolingContextSource">
        <property name="contextSource" ref="contextSourceTarget"/>
        <property name="dirContextValidator" ref="dirContextValidator"/>
        <property name="testOnBorrow" value="true"/>
        <property name="testWhileIdle" value="true"/>
        <property name="minIdle" value="0"/>
        <property name="maxIdle" value="8"/>
        <property name="maxActive" value="8"/>
        <property name="maxTotal" value="100"/>
        <property name="maxWait" value="-1"/>
    </bean>
    <bean id="dirContextValidator" class="org.springframework.ldap.pool.validation.DefaultDirContextValidator"/>

<bean id="contextSourceTarget" class="org.springframework.ldap.core.support.LdapContextSource">
        <property name="url" value="ldap://127.0.01:389" />
        <property name="userDn" value="cn=root,dc=testdata" />
        <property name="password" value="12345"/>
        <property name="pooled" value="false" />
    </bean>

<bean id="ldapTemplate" class="org.springframework.ldap.core.LdapTemplate">
        <constructor-arg ref="contextSource" />
    </bean>

<!-- 将ldapTemplate注入到testDataDao-->
    <bean id="testDataDao_LDAP" class="com.talkweb.mainSyn.dal.impl.TestDataDaoImpl">
        <property name="ldapTemplate">
            <ref bean="ldapTemplate" />
        </property>
    </bean>


参考资料: http://docs.spring.io/spring-ldap/docs/1.3.2.RELEASE/reference/html/pooling.html

                  http://blog.youkuaiyun.com/xufaxi/article/details/5840691

                  http://docs.spring.io/spring-ldap/docs/1.3.2.RELEASE/reference/html/index.html


-----------------------------------------------------------------------------------------------------------------------

9. Pooling Support

9.1. Introduction

Pooling LDAP connections helps mitigate the overhead ofcreating a new LDAP connection for each LDAP interaction.WhileJava LDAP pooling supportexists it is limited in its configuration options andfeatures, such as connection validation and poolmaintenance. Spring LDAP provides support for detailed poolconfiguration on a per-ContextSourcebasis.

Pooling support is provided byPoolingContextSourcewhich can wrap anyContextSourceand pool both read-only and read-writeDirContextobjects.Jakarta Commons-Poolis used to provide the underlying pool implementation.

9.2. DirContext Validation

Validation of pooled connections is the primary motivationfor using a custom pooling library versus the JDK providedLDAP pooling functionality. Validation allows pooledDirContextconnections to be checked to ensure they are still properlyconnected and configured when checking them out of the pool,in to the pool or while idle in the pool

TheDirContextValidatorinterface is used by thePoolingContextSourcefor validation andDefaultDirContextValidatoris provided as the default validation implementation.DefaultDirContextValidatordoes aDirContext.search(String, String, SearchControls), with an empty name, a filter of"objectclass=*"andSearchControlsset to limit a single result with the only the objectclassattribute and a 500ms timeout. If the returnedNamingEnumerationhas results theDirContextpasses validation, if no results are returned or anexception is thrown theDirContextfails validation. TheDefaultDirContextValidatorshould work with no configuration changes on most LDAPservers and provide the fastest way to validate theDirContext.

9.3. Pool Properties

The following properties are available on thePoolingContextSourcefor configuration of the DirContext pool. ThecontextSourceproperty must be set and thedirContextValidatorproperty must be set if validation is enabled, all otherproperties are optional.

Table 9.1. Pooling Configuration Properties

ParameterDefaultDescription
contextSourcenullTheContextSourceimplementation to getDirContexts from to populate the pool.
dirContextValidatornullTheDirContextValidatorimplementation to use when validatingconnections. This is required iftestOnBorrow,testOnReturn, ortestWhileIdleoptions are set totrue.
maxActive8The maximum number of active connections ofeach type (read-only|read-write) that can beallocated from this pool at the same time,or non-positive for no limit.
maxTotal-1The overall maximum number of activeconnections (for all types) that can beallocated from this pool at the same time,or non-positive for no limit.
maxIdle8The maximum number of active connections ofeach type (read-only|read-write) that canremain idle in the pool, without extra onesbeing released, or non-positive for nolimit.
minIdle0The minimum number of active connections ofeach type (read-only|read-write) that canremain idle in the pool, without extra onesbeing created, or zero to create none.
maxWait-1The maximum number of milliseconds that thepool will wait (when there are no availableconnections) for a connection to be returnedbefore throwing an exception, ornon-positive to wait indefinitely.
whenExhaustedAction1 (BLOCK)Specifies the behaviour when the pool isexhausted.
  • TheFAIL (0)option will throw aNoSuchElementExceptionwhen the pool is exhausted.

  • The BLOCK (1) option will wait until a new object is available. IfmaxWait is positive a NoSuchElementExceptionis thrown if no new object is available after the maxWait time expires.

  • TheGROW (2)option will create and return anew object (essentially makingmaxActivemeaningless).

testOnBorrowfalseThe indication of whether objects will bevalidated before being borrowed from thepool. If the object fails to validate, itwill be dropped from the pool, and anattempt to borrow another will be made.
testOnReturnfalseThe indication of whether objects will bevalidated before being returned to the pool.
testWhileIdlefalseThe indication of whether objects will bevalidated by the idle object evictor (ifany). If an object fails to validate, itwill be dropped from the pool.
timeBetweenEvictionRunsMillis-1The number of milliseconds to sleep betweenruns of the idle object evictor thread. Whennon-positive, no idle object evictor threadwill be run.
numTestsPerEvictionRun3The number of objects to examine during eachrun of the idle object evictor thread (ifany).
minEvictableIdleTimeMillis1000 * 60 * 30The minimum amount of time an object may sitidle in the pool before it is eligible foreviction by the idle object evictor (ifany).

9.4. Configuration

Configuring pooling should look very familiar if you're usedto Jakarta Commons-Pool or Commons-DBCP. You will firstcreate a normalContextSourcethen wrap it in aPoolingContextSource.

<beans>
   ...
   <bean id="contextSource" class="org.springframework.ldap.pool.factory.PoolingContextSource">
      <property name="contextSource" ref="contextSourceTarget" />
   </bean>
    
   <bean id="contextSourceTarget" class="org.springframework.ldap.core.support.LdapContextSource">
      <property name="url" value="ldap://localhost:389" />
      <property name="base" value="dc=example,dc=com" />
      <property name="userDn" value="cn=Manager" />
      <property name="password" value="secret" />
      <property name="pooled" value="false"/>
   </bean>
   ...
</beans>

In a real world example you would probably configure thepool options and enable connection validation; the aboveserves as an example to demonstrate the general idea.

[Note]Note

Ensure that thepooledproperty is set tofalseon anyContextSourcethat will be wrapped in aPoolingContextSource. ThePoolingContextSourcemust be able to create new connections when neededand ifpooledis set totruethat may not be possible.

[Note]Note

You'll notice that the actualContextSourcegets an id with a "Target" suffix. The bean you willactually refer to is thePoolingContextSourcethat wraps the targetcontextSource

9.4.1. Validation Configuration

Adding validation and a few pool configuration tweaks tothe above example is straight forward. Inject aDirContextValidatorand set when validation should occur and the pool isready to go.

<beans>
   ...
   <bean id="contextSource" class="org.springframework.ldap.pool.factory.PoolingContextSource">
      <property name="contextSource" ref="contextSourceTarget" />
      <property name="dirContextValidator" ref="dirContextValidator" />
      <property name="testOnBorrow" value="true" />
      <property name="testWhileIdle" value="true" />
   </bean>

   <bean id="dirContextValidator"
         class="org.springframework.ldap.pool.validation.DefaultDirContextValidator" />
    
   <bean id="contextSourceTarget" class="org.springframework.ldap.core.support.LdapContextSource">
      <property name="url" value="ldap://localhost:389" />
      <property name="base" value="dc=example,dc=com" />
      <property name="userDn" value="cn=Manager" />
      <property name="password" value="secret" />
      <property name="pooled" value="false"/>
   </bean>
   ...
</beans>

The above example will test eachDirContextbefore it is passed to the client application and testDirContexts that have been sitting idle in the pool.

9.5. Known Issues

9.5.1. Custom Authentication

The PoolingContextSource assumes that all DirContext objects retrieved from ContextSource.getReadOnlyContext() will have the same environment and likewise that allDirContext objects retrieved from ContextSource.getReadWriteContext() will have the same environment. This means that wrapping aLdapContextSource configured with an AuthenticationSource in a PoolingContextSource will not function as expected. The pool would be populated using the credentials of the first user and unless new connections were needed subsequent context requests would not be filled for the user specified by the AuthenticationSource for the requesting thread.



<think>我们正在使用Rust的ldap3库(版本0.11.5)来实现分页查询分页查询用于处理大量结果,避免一次性返回所有条目导致内存问题或性能下降。 根据ldap3的文档,分页查询可以通过`Stream`的`with_controls`方法配合分页控制来实现。具体步骤包括: 1. 建立LDAP连接。 2. 创建分页控制(PagedResults)对象,指定每页的大小。 3. 执行搜索操作,并将分页控制作为控制项传入。 4. 循环获取结果直到分页控制指示结束(即返回的cookie为空)。 注意:在ldap3 0.11.5中,分页控制是通过`Control`枚举的`PagedResults`变体来使用的。 下面是一个示例代码,展示如何使用ldap3库进行分页查询: 首先,确保在Cargo.toml中添加依赖: ```toml [dependencies] ldap3 = "0.11.5" ``` 然后,编写代码: ```rust use ldap3::{LdapConn, LdapError, Scope, SearchEntry, controls::PagedResults}; use ldap3::result::Result as LdapResult; use std::str; fn main() -> Result<(), LdapError> { // 连接到LDAP服务器 let mut ldap = LdapConn::new("ldap://localhost:389")?; // 绑定(如果需要认证) ldap.simple_bind("cn=admin,dc=example,dc=com", "password")?.success()?; // 设置搜索参数 let base_dn = "dc=example,dc=com"; let filter = "(objectClass=person)"; let attrs = ["cn", "mail"]; // 要返回的属性 // 每页的大小 let page_size = 100; let mut cookie = Some(Vec::new()); // 初始cookie为空,表示第一页 loop { // 创建分页控制 let ctrl = PagedResults { size: page_size as u32, cookie: cookie.clone().unwrap_or_default(), }; // 执行搜索,传入分页控制 let (rs, ctrls) = ldap .with_controls(vec![ctrl.into()]) .search(base_dn, Scope::Subtree, filter, attrs.iter().copied())? .success()?; // 处理当前页的结果 for entry in rs { let entry = SearchEntry::construct(entry); println!("DN: {}", entry.dn); for attr in entry.attrs.iter() { println!("{}: {:?}", attr.0, attr.1); } } // 从返回的控制中提取分页控制,获取下一页的cookie cookie = None; for ctrl in ctrls { if let Some(paged) = PagedResults::from_ldap_control(ctrl) { cookie = paged.cookie; break; } } // 如果cookie为空,表示没有更多页面 if cookie.as_ref().map(|c| c.is_empty()).unwrap_or(true) { break; } } // 关闭连接(可选,因为ldap连接在离开作用域时会自动关闭) ldap.unbind()?; Ok(()) } ``` 说明: 1. 我们使用`PagedResults`结构体作为分页控制。每次请求设置页面大小和上一次返回的cookie(第一次请求时cookie为空)。 2. 在每次搜索后,我们从返回的控制中寻找分页控制,并从中获取新的cookie。如果新的cookie为空,则结束循环。 3. 注意:`PagedResults`结构体在`ldap3::controls`模块中,需要引入。 4. 错误处理:这里使用了`?`操作符来传播错误,实际应用中可能需要更详细的错误处理。 注意:上面的代码中,我们使用了`from_ldap_control`方法来尝试将返回的通用控制转换为`PagedResults`。如果服务器返回了分页控制,则我们可以从中获取cookie。 另外,ldap3库在0.11.5版本中,分页控制的使用方式如上所示。请确保你的服务器支持分页查询。 如果遇到问题,请检查LDAP服务器是否支持分页控制,以及是否在绑定后具有足够的权限。 希望这个示例能帮助你实现分页查询功能。 相关问题: 1. 如何处理LDAP分页查询中的错误? 2. 在Rust中如何调整LDAP分页查询的性能? 3. 除了分页查询ldap3库还支持哪些高级查询功能?</think>### Rust ldap3 分页查询实现(版本 0.11.5) 以下是使用 `ldap3` 库实现分页查询的完整示例代码: ```rust use ldap3::{LdapConn, Scope, controls::PagedResults, result::Result, SearchEntry}; use std::str; fn main() -> Result<()> { // 1. 建立LDAP连接 let mut ldap = LdapConn::new("ldap://localhost:389")?; // 2. 绑定认证(替换为实际凭证) ldap.simple_bind("cn=admin,dc=example,dc=com", "password")?.success()?; // 3. 设置分页参数 let page_size = 100; // 每页条目数 let base_dn = "ou=users,dc=example,dc=com"; // 搜索基础DN let filter = "(objectClass=person)"; // 搜索过滤器 let attrs = ["cn", "mail"]; // 返回的属性 // 4. 初始化分页控制 let mut cookie = Some(Vec::new()); // 初始cookie为空 let mut total_results = 0; loop { // 5. 创建分页控制对象 let ctrl = PagedResults { size: page_size as u32, cookie: cookie.clone().unwrap_or_default(), }; // 6. 执行分页搜索 let (search_result, controls) = ldap .with_controls(vec![ctrl.into()]) .search(base_dn, Scope::Subtree, filter, attrs.iter().copied())? .success()?; // 7. 处理当前页结果 for entry in search_result { let entry = SearchEntry::construct(entry); println!("DN: {}", entry.dn); for attr in entry.attrs { println!("{}: {:?}", attr.0, attr.1); } println!("---"); total_results += 1; } // 8. 获取下一页cookie cookie = None; for ctrl in controls { if let Some(paged) = PagedResults::from_ldap_control(ctrl) { cookie = paged.cookie; break; } } // 9. 检查是否还有更多页面 if cookie.as_ref().map(|c| c.is_empty()).unwrap_or(true) { break; } } println!("Total results: {}", total_results); ldap.unbind()?; Ok(()) } ``` ### 关键点说明 1. **分页控制对象**: - 使用 `PagedResults` 控制结构: $$ \text{size: 每页大小} $$ $$ \text{cookie: 分页标识符} $$ 2. **分页流程**: - 首次请求:cookie 为空向量 `Vec::new()` - 后续请求:使用服务器返回的新 cookie - 终止条件:当返回的 cookie 为空时结束循环 3. **错误处理**: - 使用 Rust 的 `?` 操作符传播错误 - 检查每次操作的 `success()` 状态 4. **结果处理**: - 使用 `SearchEntry::construct` 解析条目 - 属性访问:`entry.attrs` 返回 `(属性名, 值列表)` 元组 ### 常见问题解决 1. **证书验证问题**:如需忽略证书验证,使用: ```rust let mut ldap = LdapConn::with_settings( ldap3::LdapSettings::new().set_no_tls_verify(true), "ldaps://ldap.example.com:636", )?; ``` 2. **分页失败检查**: - 确保 LDAP 服务器支持分页控制(RFC 2696) - 检查服务器返回的控制信息: ```rust println!("Controls: {:?}", controls); ``` 3. **性能优化**: - 调整 `page_size` 值平衡网络请求和内存使用 - 使用异步连接处理大规模查询[^2] ### 相关问题 1. 如何在 LDAP 分页查询中处理超过 1000 条目的结果集? 2. Rust 的 ldap3 库如何实现异步分页查询? 3. 分页查询过程中连接断开该如何恢复? 4. 如何优化 LDAP 查询的性能?[^2] [^1]: 引用参考:LDAP 分页控制标准 RFC 2696 [^2]: 引用参考:Rust 官方文档关于系统级编程的性能优化建议
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值