Mapping

Classes

Mapping is triggered by annotating your type with the @Entity annotation.This instructs Morphia to inspect your type compute various metadata items about it.If you wish use a particular type as "top level" type, you will need to annotate one field with the @Id annotation.Not every type will need to serve as a top level type (read: be mapped to a collection) and so this ID field is optional with the caveat mentioned in the call out note below.Top level types have their own collections whose names can be mapped via the value parameter.Leaving this value blank will leave Morphia free to compute the collection name as defined by the collection naming strategy defined in MapperOptions.That default is currently the camel case value of the class’s simple name.For example, to map a UserProfile entity under this strategy, the collection name would be mapped to userProfile.

Any type annotated with @Entity may optionally have a field annotated with @Id.The type of the field can be any type so long as Morphia, or the driver, has a codec that can map the data to and from mongodb.When mapping an entity, one can also define indexes and schema validations as part of the entity declaration as well.

As of 2.2, @Embedded has been deprecated in favor of @Entity. The distinction between the two annotations has been, historically, much more significant than it is at present. Due to the confusion about when to use which annotation and the meaning of "embedded" in general, it is being decommissioned. The two annotations have grown to mean essentially the same thing and so have become increasingly redundant.

Entities annotated with @Entity that lack an @Id are not eligible to serve as "top level" types. This means they will not be mapped to a collection nor can they serve as the basis for querying or be saved directly (only as fields on top level types). ID fields are used to perform proper updates on previously persisted entities. Without that ID Morphia, can not properly restrict the update to the correct document in the database.

Constructors

Morphia has traditionally required a 0-argument constructor on any mapped entity. In fact, Morphia still prefers to use such a constructor should one be available. However, as of 2.1, Morphia can also make use of another constructor that meets certain criteria:

  1. The number of arguments in the constructor must match the number of fields on the class. That is, every field must be represented in the constructor signature.

  2. The names of the arguments must match the names of the fields. Here, there is a bit of flexibility. The argument name can match either the field name as found in the source or the mapped name as determined by any @Property annotation. While this is likely to be the most common case as it is the simplest, for various this won’t always be possible or preferable. If a constructor argument name can’t mirror the field name, using the @Name annotation, an explict name can be given so that the argument can be properly matched to a field.

Changing the mapping configuration can interfere with Morphia’s ability to map arguments and fields so be careful when making changes to such things as the default field naming strategy or @Property definitions.

For this mapping to take place, the source must be compiled to include the parameters in the generated bytecode. With javac this is typically already configured by default. However, Kotlin users will likely need to configure their builds to include the appropriate option. For maven users, it’s as simple as adding one line to the Kotlin maven plugin <configuration>:

    <javaParameters>true</javaParameters>

External types

Sometimes persisted types come from external libraries whose source is either unavailable or simply can’t be modified. Using these types would be impossible give then annotation requirements as stated above. Morphia 2.3 introduces a new experimental API that loosens these restrictions a bit. Using @ExternalEntity these external types can be mapped using a proxy type.

Consider the following two types. This first type is an external entity which, for whatever reason, can not be annotated with Morphia’s annotations.

public class ThirdPartyEntity {
    public String field;
    public Long number;

    // other methods

    @Override
    public int hashCode() {
        // ...
    }

    @Override
    public boolean equals(Object o) {
       // ...
    }
}

This class could contain any amount of other code but what we’re interested in is mapping those two fields. To accomplish this, we define a mix-in type:

@ExternalEntity(value = "extEnt",
    discriminator = "ext",
    discriminatorKey = "_xt",
    concern = "JOURNALED",
    cap = @CappedAt(count = 123, value = 456),
    target = ThirdPartyEntity.class)
public class ThirdPartyEntityMixIn {
    public String field;
    @Id
    public Long number;
}

Here you’ll note the use of the new annotation. ExternalEntity looks just like Entity but has a new field called targetType. As you can see here, we list the target type as our original ThirdPartyEntity. But this mix-in type otherwise looks just like the external type we’re trying to map. Annotations can be applied to the fields and methods on the mix-in types and those values will be applied to the mapping data as normal. Using this approach, we can define alternate names for fields and map collection names, etc., as if we owned the original type all without touching it. As with all top level entities, if you want to store this type in its own collection, you’ll need to mark a field as the ID field. But once this mapping is done, you can then query against the original type just as you would any other Morphia entity. The mix-in type can happily be ignored once you’re done with mapping.

This API is experimental and is likely to shift a bit as it sees usage and feedback from the community.

Versioning

Entities can be versioned to ensure that changes are applied serially and that no other processes are modifying an object in between the time it’s fetched, modified, and written back to the database. To achieve this, simply add a field to a type, it can be a long or a Long, and annotate that field with @Version. Morphia will take care of the rest. If an object is fetched and another process updates the corresponding document in the database before it can be persisted back, an exception will be thrown when the write is attempted.

This field must not be initialized to anything other than zero or null or the ID field must be null. Morphia checks the value of the version field or that the ID is null in order properly save the first version. If neither of these conditions are true, the entity will not be persisted.

Fields

By default, any non-static field on a mapped class will be processed for persistence. If a field is to be excluded from the mapping, it can be decorated with the transient keyword, annotated with @Transient, or with the java.beans.Transient annotation. Otherwise all fields will be included that are defined on the mapped class and any super type. However, Morphia will ignore fields in any super types found in java* packages which includes the standard JDK classes and the Java EE APIs.

There are times when it is necessary to modify a field mapping’s name, e.g. Using the @Property annotation, a new name can be defined that will be used when writing to and reading from the database. During a schema evolution, it is possible to load a field from an old name as well using the @AlsoLoad annotation. Using this annotation, multiple old names can be used to find a field’s value in a returned document from query. However, only the field’s name or the value specified in the @Property annotation will be used when writing documents back to the database. Similarly, if data is only intended to be loaded from the database but never written back, that field can be annotated with @LoadOnly

If you do not specify a name via @Property, the default field naming strategy will be used. The default strategy is to use the field’s name as defined in the source. This strategy can be changed globally via the field naming strategy option on MapperOptions. Simple indexes can be defined on a field if all that is needed for the index is a single field. This can be done via the @Indexed annotation.

Methods

Method-based mapping is new in 2.2. While testing has been done to ensure everything works the same as with fields, there may some esoteric combinations of features that reveal a gap in functionality. Please file an issue if you encounter such a case.

As of 2.2, mapping information can be defined on methods instead of fields. All the same rules apply, simply the location of the annotations are moved to the getters and/or setters.Annotations can live on either the get or the set method and Morphia will detect them. If the same annotation is applied to both the get and the set behavior is unpredictable so care should be taken to avoid such a scenario. Customs in other libraries tend to favor annotating the get methods and this is also recommended here for consistency.

In order for methods to be considered as definitions of persistable properties, the Java Bean Spec largely applies here. This has some very basic rules for your methods:

  1. get methods take no parameters

  2. set methods take one parameter and the type of that parameter should generally match the return type of the getter.These setter methods also return void.

  3. If a property is a boolean type, the getter often uses is rather than get yielding a method named, e.g., isEnabled() rather than getEnabled().

  4. The type of the property is determined by the return value of the getter.

  5. The name of the property is determined by stripping the get prefix (or is for boolean types) and lower casing the first letter. This is the default name of the property subject to property naming rules as defined in MapperOptions

To enable method mapping, a method has been added to MapperOptions.Builder allowing for the configuration of either field or method mapping.

Mapping is currently done via either fields or by methods.It is not currently allowed to map using both schemes simultaneously.This will likely change in the future but for now is not allowed.