skip to content

How does Spring LDAP ODM work with @Entry, @Attribute, @Id and @DnAttribute?

level: middleimportance: should knowfreq 18%

answer

  1. ODM = JPA for LDAP
  2. @Entry(objectClasses, base)
  3. @Id must be javax.naming.Name (the DN)
  4. @Attribute(name=...) per field
  5. @DnAttribute builds/reads DN component

basics

~20 s

ODM (Object-Directory Mapping) maps a Java class to a directory entry, like JPA for LDAP. @Entry declares the objectClasses/base, @Attribute maps a field to an LDAP attribute, and @Id marks the field holding the entry's Distinguished Name.

solid answer

~40 s

ODM = Object-Directory Mapping, an annotation-driven layer over LdapTemplate that treats directory entries as POJOs. You annotate a class with `@Entry(objectClasses = {...}, base = "ou=people")` to declare which objectClasses it maps and where it lives. Each persistent field gets `@Attribute(name = "cn")` to bind it to an LDAP attribute; `@Id` marks the field of type `javax.naming.Name` that holds the entry's DN. `@DnAttribute` lets you populate a field from a component of the DN (e.g. the `uid` or `ou` in the path) and, with a declared order, lets ODM *construct* the DN when creating entries. `@Transient` excludes a field. You then call `ldapTemplate.findOne`, `find`, `findAll`, `create`, `update`, `delete` with objects instead of raw attributes. Spring converts attribute types (String, arrays for multi-valued) automatically.

code

java · 32 lines
java
import javax.naming.Name;
import org.springframework.ldap.odm.annotations.*;

@Entry(objectClasses = {"top", "person", "inetOrgPerson"}, base = "ou=people")
public class Person {

    @Id
    private Name dn;                 // full DN — MUST be javax.naming.Name

    @Attribute(name = "uid")
    @DnAttribute(value = "uid", index = 2) // also the RDN component
    private String uid;

    @Attribute(name = "cn")
    private String fullName;

    @Attribute(name = "sn")
    private String lastName;

    @Attribute(name = "mail")
    private String[] emails;         // multi-valued

    @Transient
    private String cachedLabel;      // not persisted
    // getters/setters omitted
}

// CRUD via ODM methods on LdapTemplate
Person p = ldapTemplate.findByDn(LdapNameBuilder.newInstance("uid=jdoe,ou=people").build(), Person.class);
ldapTemplate.create(newPerson);   // DN built from @DnAttribute components
ldapTemplate.update(p);
ldapTemplate.delete(p);

go deeper

for a junior

Recognize ODM as the annotation-based, JPA-like way to map directory entries to objects.

for a middle

Correctly annotate a class, know @Id must be Name, and use findOne/create/update/delete.

for a senior

Use @DnAttribute for DN construction, handle multi-valued/binary attributes, and choose ODM vs ContextMapper appropriately.

for a principal

Set org-wide conventions for directory entry modeling, converters for AD binary attrs, and understand ODM's non-transactional, no-dirty-checking limits.

**What ODM is.** ODM (**Object-Directory Mapping**) is Spring LDAP's declarative mapping layer — conceptually JPA/Hibernate for an LDAP directory. Instead of hand-writing `AttributesMapper`s and building DNs by hand, you annotate a plain Java class and let `LdapTemplate`'s ODM methods marshal between the object and the directory entry. **The annotations (package `org.springframework.ldap.odm.annotations`).** - **`@Entry`** — class-level, marks the POJO as a directory entry. Key elements: `objectClasses` (the array of LDAP objectClasses the entry must have, e.g. `{"top","person","inetOrgPerson"}`) and `base` (an optional relative base DN under which these entries live, e.g. `"ou=people"`). When reading, ODM filters on these objectClasses; when writing, it sets them. - **`@Id`** — field-level, marks the field that holds the entry's full **Distinguished Name**. This field **must be of type `javax.naming.Name`** (commonly `LdapName`), *not* String. It is the primary key of the entry. - **`@Attribute`** — field-level, binds a field to an LDAP attribute via `name` (the attribute id, e.g. `cn`, `sn`, `mail`) and optionally `syntax`. Multi-valued attributes map to collections/arrays. - **`@DnAttribute`** — field-level, ties a field to a *named component within the DN itself* (e.g. the `uid` in `uid=jdoe,ou=people,...`). Given `value` (the RDN attribute name) and an `index` (position in the DN, root = 0), ODM can both populate the field from the DN on read **and reconstruct the DN on create** so you don't have to set `@Id` manually. A field can be both `@Attribute` and `@DnAttribute` if the value appears in the DN and as an attribute. - **`@Transient`** — field-level, excludes a field from mapping. **Operations.** With a mapped class `Person`, `LdapTemplate` exposes: `findOne(LdapQuery, Class<T>)`, `find(LdapQuery, Class<T>)` (list), `findAll(Class<T>)`, `findByDn(Name, Class<T>)`, `create(entry)`, `update(entry)`, and `delete(entry)`. Internally these delegate to an `OdmManager` that reads the annotations, builds filters/DNs, and converts attribute value types. **Type conversion & multi-value.** Single-valued attributes map to `String`, `Integer`, etc.; multi-valued attributes map to arrays or `List`/`Set`. ODM uses a `ConverterManager` for type coercion. Binary attributes (e.g. `objectGUID` in AD) may need custom converters or `byte[]`. **Gotchas.** - The `@Id` field **must** be `javax.naming.Name` — using `String` fails at startup. - If you rely on `@DnAttribute` ordering to build DNs on `create`, every DN component must be represented; otherwise supply the full DN via the `@Id` field yourself. - `objectClasses` in `@Entry` must exactly match what the server enforces, or reads return nothing / writes fail schema validation. - ODM has no dirty-checking or persistence context like JPA — `update` rewrites the entry's mapped attributes; there is no transactional first-level cache. - Renaming an entry (moving its DN) is not a plain `update`; you must `rename`/`modifyAttributes` appropriately. **When to use.** ODM shines for CRUD over a small, well-known set of entry types. For ad-hoc searches returning projections or computed values, a `ContextMapper`/`AttributesMapper` with `LdapQueryBuilder` is often simpler.

  • Why must the @Id field be javax.naming.Name rather than String?
    The DN is a structured, hierarchical name; ODM needs LdapName semantics to parse RDN components, compare, and build child DNs. A String would lose that structure and ODM rejects it at bootstrap.
  • How can ODM build the DN automatically on create()?
    By using @DnAttribute with an index on each RDN component plus the @Entry base; ODM assembles the components in order to form the full DN, so you don't set the @Id field manually.
  • Does ODM give you JPA-style dirty checking and a persistence context?
    No. There is no unit-of-work/first-level cache or automatic change tracking; update() explicitly rewrites the entry's mapped attributes.

saying these in an interview costs you the question

  • Declaring the @Id field as String instead of javax.naming.Name
  • Assuming ODM has JPA-like dirty checking, cascades, or a persistence context
  • Forgetting that @Entry objectClasses must match the server schema or reads silently return nothing
  • Thinking @DnAttribute and @Attribute are mutually exclusive

context