Spring ldap ODM

Spring ldap ODM

上文讲述了Spring-ldap基本操作,通过定义LdapTemplate这个bean到IOC容器,使用时注入LdapTemplate即可完成对LDAP目录树的CRUD及筛选、过滤等。

  • 但是对于筛选查询出来的内容,JNDI是封装在Attributes中,尽管spring-ldap提供了AttributesMapper接口,让你自己去实现具体的从Attributes转成Java对象的逻辑,但是随着业务变化,对于不同的查询列,转化逻辑必须重新写。

  • 那有没有办法,让查询出来的内容,自动转成我们想要Java对象呢,就像关系型数据库ORM,把从MySQL数据库查询的结果集,自动完成和实体类的映射,执行查询时,直接得到对象列表。

  • spring-ldap同样提供相应的支持(从spring-ldap 2.x版本开始),ODM (Object-Directory Mapping)对象目录映射。不同于MySQL数据库的是,LDAP是目录树,是树结构的数据库。

spring-ldap该框架通过提供和ORM中相似的机制对LDAP相关操作进行封装,主要包括:
1、类比SessionFactory的LdapContextSource;
2、类比HibernateTemplate等的LdapTemplate;
3、伪事务支持,能否与tx框架的TransactionManager混用未知;
4、类比JPA的使用@Entry、@Attribute、@Id标注的注解。

ORM框架,例如Hibernate 或者JPA,使得我们可以使用注解,映射一张表到一个Java实体。Spring-ldap提供相似的功能以完成对LDAP目录树的操作,LdapOperations接口提供了很多类似的方法:

<T> T findByDn(Name dn, Class<T> clazz)

<T> T findOne(LdapQuery query, Class<T> clazz)

<T> List<T> find(LdapQuery query, Class<T> clazz)

<T> List<T> findAll(Class<T> clazz)

<T> List<T> findAll(Name base, SearchControls searchControls, Class<T> clazz)

<T> List<T> findAll(Name base, Filter filter, SearchControls searchControls, Class<T> clazz)

void create(Object entry)
void update(Object entry)
void delete(Object entry)

LdapTemplate实现了该接口,这些方法均可在完成LdapTemplate bean定义后,注入使用。首先按完成基本工程搭建http://www.jianshu.com/p/3aeb49a9befd

ODM注解

需要完成映射的实体类需要使用注释。由org.springframework.ldap.odm.annotations package提供。

  • @Entry - 用于标注实体类(required)
  • @Id - 指明实体DN; 是javax.naming.Name类型(required)
  • @Attribute - 标识实体类需要映射的字段
  • @DnAttribute -
  • @Transient - 标识实体类不需要映射的字段
示例:
@Entry(objectClasses = { "person", "top" }, base="ou=someOu")
public class Person {
   @Id
   private Name dn;

   @Attribute(name="cn")
   @DnAttribute(value="cn", index=1)
   private String fullName;

   // No @Attribute annotation means this will be bound to the LDAP attribute
   // with the same value
   private String description;

   @DnAttribute(value="ou", index=0)
   @Transient
   private String company;

   @Transient
   private String someUnmappedField;
}

@Entry标记,其中的objectClasses定义必须与objectClass完全一致,并且可以指定多个值。在新建和查询object时,ODM会根据此标记进行匹配,无需再指定objectClass。

每个entry必须指定@Id字段,类型为javax.naming.Name,其实就是DN。但是若在LdapContextSource中指定了base,则DN将会按照base截取相对路径。比如,DN为cn=user,ou=users,dc=jayxu,dc=com,base为dc=jayxu,dc=com,则取出的user对象DN为cn=user,ou=users。

对于不需要与LDAP进行映射的字段使用@Transient进行标记

ODM操作

    /**
     * 按部门编号 o 查询部门
     */
    @Test
    public void findOne(){
        LdapQuery ldapQuery = query().where("o").is("039cb846de0e40e08901e85293f642bf");
        LdapDept dept = ldapTemplate.findOne(ldapQuery, LdapDept.class);
        System.out.println(dept);
    }

    /**
     * 按条件过滤部门
     */
    @Test
    public void filter(){
        LdapQueryBuilder ldapQueryBuilder = query();
        Filter filter = new GreaterThanOrEqualsFilter("modifyTimestamp",timestamp);
        ldapQueryBuilder.filter(filter);
        List<LdapDept> depts = ldapTemplate.find(ldapQueryBuilder, LdapDept.class);
        for (LdapDept dept: depts ) {
            System.out.println(dept);
        }
    }

    /**
     * 查询所有部门
     * 类型和base,由LdapDept上的@Entry指定
     */
    @Test
    public void getAllDepts(){
        List<LdapDept> depts = ldapTemplate.findAll(LdapDept.class);
        for (LdapDept dept: depts ) {
            System.out.println(dept);
        }
    }

其中LdapDept是自定义实体类,按照Entry、@Attribute、@Id注解的约定标注好即可。

使用org.springframework.ldap.pool.factory.PoolingContextSource引入连接池

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.ldap.core.ContextSource;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.support.LdapContextSource;
import org.springframework.ldap.pool.factory.PoolingContextSource;
import org.springframework.ldap.pool.validation.DefaultDirContextValidator;
import org.springframework.ldap.transaction.compensating.manager.TransactionAwareContextSourceProxy;

import java.util.HashMap;
import java.util.Map;

/**
 * LDAP 的自动配置类
 *
 * 完成连接LDAP 及LdapTemplate 的定义
 */
@ConfigurationProperties(prefix = "ldap")
@PropertySource("classpath:/application.yml")
@Configuration
public class LdapConfiguration {

    private LdapTemplate ldapTemplate;

    @Value("${maxActive}")
    private int maxActive;

    @Value(value = "${maxTotal}")
    private int maxTotal;

    @Value(value = "${maxIdle}")
    private int maxIdle;

    @Value(value = "${minIdle}")
    private int minIdle;

    @Value(value = "${maxWait}")
    private int maxWait;

    @Value(value = "${url}")
    private String LDAP_URL;

    @Value(value = "${base}")
    private String BASE_DC;

    @Value(value = "${dbusername}")
    private String USER_NAME;

    @Value(value = "${password}")
    private String PASS_WORD;

    @Bean
    public LdapContextSource contextSource() {
        LdapContextSource contextSource = new LdapContextSource();
        Map<String, Object> config = new HashMap();

        contextSource.setUrl(LDAP_URL);
        contextSource.setBase(BASE_DC);
        contextSource.setUserDn(USER_NAME);
        contextSource.setPassword(PASS_WORD);

        //  解决 乱码 的关键一句
        config.put("java.naming.ldap.attributes.binary", "objectGUID");

        //当需要连接时,池是否一定创建新连接
        contextSource.setPooled(true);
        contextSource.setBaseEnvironmentProperties(config);
        return contextSource;
    }

    /**
     * LDAP pool 配置
     * @return
     */
    @Bean
    public ContextSource poolingLdapContextSource() {
        PoolingContextSource poolingContextSource = new PoolingContextSource();
        poolingContextSource.setDirContextValidator(new DefaultDirContextValidator());
        poolingContextSource.setContextSource(contextSource());
        poolingContextSource.setTestOnBorrow(true);//在从对象池获取对象时是否检测对象有效
        poolingContextSource.setTestWhileIdle(true);//在检测空闲对象线程检测到对象不需要移除时,是否检测对象的有效性

        poolingContextSource.setMaxActive(maxActive <= 0 ? 20:maxActive);
        poolingContextSource.setMaxTotal(maxTotal <= 0 ? 40:maxTotal);
        poolingContextSource.setMaxIdle(maxIdle <= 0 ? 5:maxIdle);
        poolingContextSource.setMinIdle(minIdle <= 0 ? 5:minIdle);
        poolingContextSource.setMaxWait(maxWait <= 0 ? 5:maxWait);

        TransactionAwareContextSourceProxy proxy = new TransactionAwareContextSourceProxy(poolingContextSource);
        return proxy;
    }

    @Bean
    public LdapTemplate ldapTemplate() {
        if (null == ldapTemplate)
            ldapTemplate = new LdapTemplate(poolingLdapContextSource());
        return ldapTemplate;
    }
}

ODM官方文档
https://docs.spring.io/spring-ldap/docs/2.3.2.RELEASE/reference/#odm

推荐阅读更多精彩内容