Open jgrassel opened 2 years ago
While an application can always simply assign an id value to any new entity it constructs, there could be use-cases where it is desirable for the identity to be generated when the persistence provider is about to INSERT the row into the database (the typical behavior for the standard identity generators <= JPA 3.0), which typically occurs during a flush to the database or transaction commit.
I think this is the key for me. What usecases can benefit from such a feature that cannot already be addressed with "custom" application code? It could be that we want to define that boilerplate, custom application generator code with our own API, but I'd like some ideas on what circumstances users would find this feature useful.
@dazey3 The problem with using callbacks is because of the variability as to when they get called. For example, the "pre-persist" callback. From the spec:
The PrePersist and PreRemove callback methods are invoked for a given entity before the respective
EntityManager persist and remove operations for that entity are executed. For entities to which the
merge operation has been applied and causes the creation of newly managed instances, the PrePersist
callback methods will be invoked for the managed instance after the entity state has been copied to it.
These PrePersist and PreRemove callbacks will also be invoked on all entities to which these operations
are cascaded. The PrePersist and PreRemove methods will always be invoked as part of the
synchronous persist, merge, and remove operations.
The thing here is, is that exactly when the prePersist callback is called is vendor specific. It could be invoked before em.persist(myNewEntity)
returns control back to the application, or it could be deferred to whenever the new entity is flushed to the database, whether from an implicit or explicit flush or as part of the before-completion phase of transaction commit.
Now, the JPA spec doesn't exactly define when the persistence context fulfills generating identities for entities using @GeneratedValue but in my experience with OpenJPA and EclipseLink, it waits until flush/tx commit -- which is why calling the getter method for the identity field for those entities usually returns null until that point in the persistence context lifecycle has been reached. Thus, having a custom generator that executes at points consistent with the provided generators may be advantageous.
That and while the callback contract does say "A lifecycle callback method may modify the non-relationship state of the entity on which it is invoked.", it may be iffy in a vendor specific way for a callback to make changes to the entity's identity field. By having a custom generator, even if it feels a lot like a PrePersist callback, feels a lot clearer in purpose, and can be laser targeted for use with the @GeneratedValue annotation.
The one thing I would like to mention here is my proposal here that generators by applied via meta-annotating an annotation type, rather than by explicitly specifying the generator class in the @GeneratedValue
annotation.
So instead of the code above, we would have:
Annotation WidgetGenerator
@Target({FIELD,METHOD})
@Retention(RUNTIME)
@Generator(WidgetUUIDGenerator.class)
public @interface GeneratedWidgetUUID {}
Class WidgetEntity:
import jakarta.persistence.*;
@Entity
public class WidgetEntity {
@Id
@GeneratedWidgetUUID
private UUID widgetId;
// etc
}
Class AnotherWidgetEntity:
import jakarta.persistence.*;
@Entity
public class AnotherWidgetEntity {
@Id
@GeneratedWidgetUUID
private UUID widgetId;
// etc
}
Which cleans up the usage-side of the code and raises the level of abstraction slightly.
During the discussions for #319 I had thrown out the idea of application provided id generators. While an application can always simply assign an id value to any new entity it constructs, there could be use-cases where it is desirable for the identity to be generated when the persistence provider is about to INSERT the row into the database (the typical behavior for the standard identity generators <= JPA 3.0), which typically occurs during a flush to the database or transaction commit.
Callbacks might be able to do this job some of the time, however given the rule that they should not invoke EntityManager and query operations [JPA 2.2: 3.5.2] means that they cannot call EnityManager.unwrap(Connection.class) in order to get at the underlying current connection, in order to access the database with a Connection already enlisted with the current transaction. There is also the fact that the spec grants vendors a great deal of freedom to decide when a lifecycle callback can be invoked, where a generator should be called specifically when the identity needs to be generated (ie, so if a new application persists a new entity, then later decides to roll back the transaction, a potentially expensive generator call is not exercised, nor is the generated namespace wasted.)
Therefore, I would like to propose the addition of custom generators which can be defined by applications that have a need for them. A custom generator could follow much of the same declarative format as entity listeners, and could conceivably even benefit from being bean managed which could make this a very powerful feature.
An example generator could be as follows:
Class WidgetUUIDGenerator:
Class WidgetEntity:
Class AnotherWidgetEntity:
In the example above, a custom generator by the name
WidgetUUIDGenerator
is defined to generate new identities of typeUUID
. It requires for there to be at least one method annotated with@IDGenerator
which returns the same type as declared by@CustomGenerator
'sgenerates
value. We could even overload the generator method with a parameter which defines which specific type of entity, to make it easier to customize generation by entity class if that is desired.By making it optional for the generator class to accept CDI bean management (taking the precedence established by callback and converters), this could be a very powerful capability.
The
@PostConstruct
and@PreDestroy
annotated methods are optional elements, which would be important if the custom generator needs to initialize resources on creation and dispose of them cleanly when the custom generator itself is deactivated when the EntityManagerFactory is closed.One last thing is the
PersistenceUnitSetup
class. Given that an application may be composed of multiple persistence units, it may be important for a custom generator to be able to distinguish which persistence unit it is servicing. That is the role of thePersistenceUnitSetup
class, which exposes some, if not all, of the content of thePersistenceUnitInfo
. I considered simply using thePersistenceUnitInfo
class, but opted not to since that resides injakarta.persistence.spi
and referencing a spi class might not be good form. I've presently left it undefined at the moment, but the most likely minumum content it should provide is the persistence unit name and the persistence unit properties associated with the persistence unit.