Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions content/morphia/3.0/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,16 @@ Or pass `-Dmorphia.mapper=critter` on the Maven command line when running tests.
For production deployments, consider using the [critter-maven plugin](../maven-plugin/) to generate models at build time and eliminate the startup cost entirely.
Runtime generation requires your entities to be loaded by the same class loader (or be in the same module) as Morphia; if they aren't, as in some application server or JPMS layouts, use the plugin. See [Class loaders and modules](../maven-plugin/#class-loaders-and-modules).

## Global entity listeners

- `morphia.listeners`

A comma-separated list of listener classes to run for every entity they accept, such as an auditing listener or
`dev.morphia.validation.ValidationExtension`. Each class implements one or more of the per-event listener interfaces, such as
`PrePersistListener`, and has a public no-argument constructor. In code, `MorphiaConfig.listeners(List.of(...))` takes instances
instead. See [Life Cycle Methods](../life-cycle-methods/#global-listeners) for details. This replaces `Mapper.addInterceptor()`, which
was removed in 3.0.

## User-defined Codecs

- `morphia.codec-provider`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ morphia.database=morphia
######
morphia.date-storage=utc
######
# default=1000
######
morphia.decode-session-cache-size=1000
######
# default=simpleName
# possible values=className, lowerClassName, lowerSimpleName, simpleName, fqcn
######
Expand All @@ -46,6 +50,10 @@ morphia.enable-polymorphic-queries=false
######
morphia.ignore-finals=false
######
# Optional
######
morphia.listeners=
######
# default=reflection
# possible values=reflection, critter
######
Expand Down
132 changes: 91 additions & 41 deletions content/morphia/3.0/life-cycle-methods.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,65 +3,115 @@ title: "Life Cycle Methods"
weight: 180
---

There are various annotations which can be used to register callbacks on certain life cycle events.
These include Pre/Post-Persist (Save), and Pre/Post-Load.
Morphia can call your code at four points in an entity's life cycle:

1. `@PrePersist` - Called before save, it can return a `Document` in place of an empty one.
2. `@PostPersist` - Called after the save call to the database
3. `@PreLoad` - Called before mapping the document from the database to the entity; the Document is passed as an argument (you can add/remove/change values)
4. `@PostLoad` - Called after populating the entity with the values from the document
1. `@PrePersist` - before the entity is encoded for saving
2. `@PostPersist` - after the entity is encoded, before the result is sent to the database
3. `@PreLoad` - before the entity's properties are decoded from a document read from the database
4. `@PostLoad` - after the entity's properties are decoded

## Examples
There are three places to put that code:

[Here](https://github.com/MorphiaOrg/morphia/tree/master/core/src/test/java/dev/morphia/TestQuery.java) is a one of the test classes.
1. methods on the entity itself, annotated with the event's annotation
2. [`@EntityListeners`](++javadoc/dev/morphia/annotations/EntityListeners.html++) classes named on the entity, for code shared by
several entities
3. global listeners, listed in the [configuration](../configuration/), which run for every entity they accept

All parameters and return values are optional in your implemented methods.
{{< admonition type="note" title="Note" >}}
**No Delete Support** Because deletes are usually done with queries there is no way to support a Delete lifecycle event.
{{< /admonition >}}

## Example
## Annotated methods

Here is a simple example of an entity that always saves the Date it was last updated.
Alternatively, the resulting serialized form can be passed back in just prior to sending the document to the database.
An entity can annotate any of its methods with an event's annotation. Such a method can take an `org.bson.Document` and a
`Datastore`, in any order, or neither. Here is an entity that always saves the date it was last updated:

```java
class BankAccount {
@Id
String id;
Date lastUpdated = new Date();

@PrePersist
public void trackUpdate() {
lastUpdated = new Date();
}

@PrePersist
public void prePersist( Document document) {
// perform operations on serialized form of the entity
}
@Entity
public class BankAccount {
@Id
String id;
Date lastUpdated = new Date();

@PrePersist
void trackUpdate() {
lastUpdated = new Date();
}
}
```

## EntityListeners
An `@EntityListeners` class can do the same, with methods that also take the entity. A class used this way must implement
`EntityListener`, for example `EntityListener<BankAccount>`, even when it only has annotated methods, and needs a no-argument
constructor; Morphia creates one instance of it per entity type.

## Listener interfaces

Each event has a listener interface, which `@EntityListeners` classes, global listeners, and entities themselves can implement:

| Event | Listener | Gets the `Document` |
|----------------|---------------------------------------------------------------------------------|-----------------------------------------------------------|
| `@PrePersist` | [`PrePersistListener<T>`](++javadoc/dev/morphia/PrePersistListener.html++) | no: it's still empty |
| `@PostPersist` | [`PostPersistListener<T>`](++javadoc/dev/morphia/PostPersistListener.html++) | no |
| | [`PostPersistDocumentListener<T>`](++javadoc/dev/morphia/PostPersistDocumentListener.html++) | the encoded document, and what it changes is saved |
| `@PreLoad` | [`PreLoadListener<T>`](++javadoc/dev/morphia/PreLoadListener.html++) | the document being decoded |
| `@PostLoad` | [`PostLoadListener<T>`](++javadoc/dev/morphia/PostLoadListener.html++) | no |
| | [`PostLoadDocumentListener<T>`](++javadoc/dev/morphia/PostLoadDocumentListener.html++) | the document it was decoded from |

If your application has more generalized life cycle events, these methods can be stored on classes external to your model.
For example's sake, let's assume there's a need to digitally sign all documents before storing it in the database.
A listener may implement any number of these, but only one per event: a class that implements both `PostLoadListener` and
`PostLoadDocumentListener` is rejected when it's registered.

The type argument decides which entities a listener sees: a `PrePersistListener<Auditable>` is called for `Auditable` entities alone,
and a `PrePersistListener<Object>` for every entity. A lambda can't keep its type argument, so it's called for every entity; type a
lambda `Object` and check the entity's type in its body, or use a named or anonymous class.

Here is a listener that signs each account's encoded form before it's saved. It has to be a `@PostPersist` listener: at
`@PrePersist`, nothing has been encoded yet.

```java
@Entity
@EntityListeners(DigitalSigner.class)
public class BankAccount {
@Id
String id;
Date lastUpdated = new Date();
@Id
String id;
Date lastUpdated = new Date();
}

class DigitalSigner {
@PrePersist
void prePersist( Object entity, Document document) {
document.put("signature", sign(document));
}

public class DigitalSigner implements PostPersistDocumentListener<BankAccount> {
@Override
public void postPersist(BankAccount entity, Document document, Datastore datastore) {
document.put("signature", sign(document));
}
}
```

{{< admonition type="note" title="Note" >}}
**No Delete Support** Because deletes are usually done with queries there is no way to support a Delete lifecycle event.
{{< /admonition >}}
## Global listeners

Listeners that apply across entities, such as auditing or validation, are listed in the `morphia.listeners` setting, by class name:

```properties
morphia.listeners=com.example.Auditing, dev.morphia.validation.ValidationExtension
```

Each class must implement at least one listener interface and have a public no-argument constructor. In code, pass instances instead,
which can take constructor arguments:

```java
MorphiaConfig config = MorphiaConfig.load()
.listeners(List.of(new Auditing(clock)));
```

The listeners are fixed when the datastore is created. They run after the entity's own methods and its `@EntityListeners` classes.

## Avoiding the `Document`

When no listener for an event takes the `Document`, Morphia encodes and decodes the entity directly. When one does, Morphia builds
the whole entity as a `Document` first, which costs noticeably more for every entity the listener applies to. Use the interfaces
without the `Document`, and leave the `Document` parameter off annotated methods, unless you need it.

## Entities created through a constructor

An entity whose values are passed to its constructor, such as a record, doesn't exist until all of its properties have been decoded.
Its `@PreLoad` listeners therefore run after decoding, just before its `@PostLoad` listeners, and changes they make to the `Document`
don't affect the entity. An entity with life cycle methods of its own, or with `@EntityListeners`, needs a no-argument constructor;
without one, Morphia can't map it. To listen to an entity that is created only through a constructor, use a
[global listener](#global-listeners) instead.
32 changes: 32 additions & 0 deletions content/morphia/3.0/migrating.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,38 @@ An OpenRewrite recipe is available to automate this migration:

Run the `dev.morphia.rewrite.recipes.config.MorphiaConfigMigration` recipe to convert `MapperOptions` usages to `MorphiaConfig` automatically.

### Entity listeners

`EntityListener` is now an empty marker interface. Each life cycle event has its own listener interface, and a listener implements the
ones for the events it handles. `hasAnnotation()` is gone: implementing an event's interface is what makes a listener handle it.

| 2.x `EntityListener` method | 3.0 interface |
|-----------------------------------------------------|-----------------------------------------------------------------------------------|
| `prePersist(T entity, Document document, Datastore)` | `PrePersistListener<T>`: `prePersist(T entity, Datastore)` |
| `postPersist(T entity, Document document, Datastore)` | `PostPersistListener<T>`, or `PostPersistDocumentListener<T>` to keep the `Document` |
| `preLoad(T entity, Document document, Datastore)` | `PreLoadListener<T>`: unchanged |
| `postLoad(T entity, Document document, Datastore)` | `PostLoadListener<T>`, or `PostLoadDocumentListener<T>` to keep the `Document` |

`Mapper.addInterceptor()` has been removed, along with `Mapper.getListeners()` and `Mapper.hasListeners()`. List global listeners in
the `morphia.listeners` setting, or pass them to `MorphiaConfig.listeners(...)`, instead. Listeners are now fixed when the datastore is
created. See [Life Cycle Methods](../life-cycle-methods/) for details.

The `dev.morphia.UpgradeToMorphia30` recipe migrates `EntityListener` implementations to the new interfaces, choosing
`PostPersistDocumentListener` or `PostLoadDocumentListener` for a `postPersist` or `postLoad` that uses its `Document`. It removes
`hasAnnotation()` overrides and updates calls to the migrated methods. It marks what it can't migrate for you to finish by hand:

- each `addInterceptor()` call
- a `prePersist` that uses its `Document`, which was always empty: to change what's saved, use a `PostPersistDocumentListener`
- a method that uses the old `Mapper` parameter: use `datastore.getMapper()`
- an override that uses the `Document` when the method it overrides migrates without it
- an anonymous listener that handles more than one event, since it can implement only one interface
- calls it can't update, such as one that would drop a `Document` argument that does something, and calls to `hasAnnotation()`

Calls to `getListeners()` and `hasListeners()` aren't marked and will fail to compile.

The order of `@PreLoad` listeners has changed for entities created through a constructor, such as records. They now run after the
entity's properties are decoded, just before its `@PostLoad` listeners, because the instance can't exist any earlier.

## Migrating to 2.5
Morphia 2.5 is primarily a driver upgrade. Changes in the driver are incompatible with the 2.4 driver and so Morphia 2.5 requires a
driver version of 5.x or greater. If you need a 4.x driver for some reason, you can use Morphia 2.4.x. The 2.5.x series can only
Expand Down
Loading