diff --git a/documentation/src/main/asciidoc/introduction/Interacting.adoc b/documentation/src/main/asciidoc/introduction/Interacting.adoc index 33e7a91c71e5..17fb9b9b1ba5 100644 --- a/documentation/src/main/asciidoc/introduction/Interacting.adoc +++ b/documentation/src/main/asciidoc/introduction/Interacting.adoc @@ -1393,6 +1393,57 @@ The `Connection` passed to the work is the same connection being used by the ses In a container environment where transactions and database connections are managed by the container, this might not be the easiest way to obtain the JDBC connection. ==== +[[callbacks]] +=== Lifecycle callbacks and entity listeners + +The annotations `@PrePersist`, `@PreRemove`, `@PreUpdate`, `@PostPersist`, `@PostRemove`, `@PostUpdate`, and `@PostLoad` allow an entity to respond to persistence lifecycle operations and maintain its transient internal state. +For example: + +[source,java] +---- +@Entity +class Order { + ... + transient double total; + + @PostLoad + void computeTotal() { + total = items.stream().mapToDouble(i -> i.price * i.quantity).sum(); + } + + ... +} +---- + +If we need to interact with technical objects, we can place the lifecycle callback on a separate class, called an _entity listener_. +The `@EntityListeners` annotation specifies the listeners for a given entity class: + +[source,java] +---- +@Entity +@EntityListeners(OrderEvents.class) +class Order { ... } +---- + +An entity listener may inject CDI beans: + +[source,java] +---- +// entity listener class +class OrderEvents { + @Inject + Event newOrderEvent; + + @PostPersist + void newOrder(Order order) { + // send a CDI event + newOrderEvent.fire(new NewOrder(order)); + } +} +---- +A single entity listener class may even be a generic listener that receives lifecycle callbacks for multiple different entity classes. + + [[advice]] === What to do when things go wrong diff --git a/documentation/src/main/asciidoc/introduction/Introduction.adoc b/documentation/src/main/asciidoc/introduction/Introduction.adoc index 1b293678f327..e2c1721885e3 100644 --- a/documentation/src/main/asciidoc/introduction/Introduction.adoc +++ b/documentation/src/main/asciidoc/introduction/Introduction.adoc @@ -116,10 +116,33 @@ Take your time with this code, and try to produce a Java model that's as close a When in the slightest doubt, map a foreign key relationship using `@ManyToOne` with `@OneToMany(mappedBy=...)` in preference to more complicated association mappings. ==== +.What sort of logic belongs in an entity? +**** +There exists an extensive online literature which posits that there are _rich domain models_, where entities have methods implementing interesting business logic, and _anemic domain models_, where the entities are pure data holders, and that a developer should hold an opinion that one or the other of these sorts of domain model is "better". + +We do not hold any such opinion, and if you ask us for one, we will most likely suddenly discover somewhere else we need to be. + +A more interesting question is not _how much_ logic belongs in the entity class, but _what sort_ of logic belongs there. +We think the answer is that an entity should never implement technical concerns, and should never obtain references to framework objects. +Nor should it hold extra mutable state which is not very directly related to its role in representing persistent state. +For example: + +- an entity may compute totals and averages, even caching them if necessary, enforce its invariants, interact with and construct other entities, and so on, +- but the entity should never call the `EntityManager` or a Jakarta Data repository, build a criteria query, send a JMS message, start a transaction, publish events to the CDI event bus, maintain a stateful queue of events to be published later, or anything of a similar nature. + +One way to summarize this is: + +> Entities do business logic; but they don't do orchestration. + +Later, we'll discuss various ways to <>, <>, and <>. +Such code will always be external to the entity itself. +**** + The second part of the code is much trickier to get right. This code must: - manage transactions and sessions, - interact with the database via the Hibernate session, +- publish CDI events and send JMS messages, - fetch and prepare data needed by the UI, and - handle failures. @@ -128,6 +151,12 @@ The second part of the code is much trickier to get right. This code must: Responsibility for transaction and session management, and for recovery from certain kinds of failure, is best handled in some sort of framework code. ==== +// [TIP] +// ==== +// A great way to handle CDI event publication is via a <>. +// Whereas we would never want to inject a CDI https://jakarta.ee/specifications/cdi/3.0/apidocs/[event publisher] into an entity object, it's perfectly fine to inject them in an entity listener. +// ==== + We're going to <> to the thorny question of how this persistence logic should be organized, and how it should fit into the rest of the system. // First we want to make the ideas above concrete by seeing a simple example program that uses Hibernate in isolation.