上文讲述了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是目录树,是树结构的数据库。
该框架通过提供和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
需要完成映射的实体类需要使用注释。由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进行标记
/**
* 按部门编号 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注解的约定标注好即可。
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