jakartaee / jakartaee/persistence

Introduce application-provided custom Generators

Open
#342 3 comments 0 reactions 0 assignees View on GitHub
candidate-for-4.1
Dominant language
Java
Stars
267
Forks
78
Avg merge
1d 6h
Merged PRs (30d)
13

Description

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:*
```java
import java.util.UUID;

import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import jakarta.annotation.Resource;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;

import jakarta.persistence.CustomGenerator;
import jakarta.persistence.IDGenerator;
import jakarta.persistence.PersistenceUnitSetup;

@ApplicationScoped
@CustomGenerator(name="WidgetUUIDGenerator", generates=UUID.class)
public class WidgetUUIDGenerator {
/*
* Everything here is optional
*/

@Resource
private PersistenceUnitSetup puSetup; // Persistence unit configuration

@Resource
private DataSource injectableDataSource; // Generator may need to talk to a database

@Inject
private MyService myService; // Some CDI injected dependency

@PostConstruct
public void initialize() {
// Initialization Code
}

@PreDestroy
public void shutdown() {
// Perform cleanup such as releasing resources
}

/*
* Everything here is required, as the generator should define at least one
* method annotated with @IDGenerator and ideally there should be a default
* generator with no arguments.
*
* Each generator method returns a value of the same type identified in the
* @CustomGenerator annotation's generates value.
*/

@IDGenerator
public UUID generate() {
// Default @IDGenerator

UUID newID;
// ...
return newID;
}

@IDGenerator
public UUID generate(AnotherWidgetEntity entity) {
// @IDGenerator specific for entities of type AnotherWidgetEntity

UUID newID;
// ...
return newID;
}
}
```

*Class WidgetEntity:*
```java
import jakarta.persistence.*;

@Entity
public class WidgetEntity {
@Id
@GeneratedValue(strategy=CUSTOM, generator="WidgetUUIDGenerator")
private UUID widgetId;

// etc
}
```

*Class AnotherWidgetEntity:*
```java
import jakarta.persistence.*;

@Entity
public class AnotherWidgetEntity {
@Id
@GeneratedValue(strategy=CUSTOM, generator="WidgetUUIDGenerator")
private UUID widgetId;

// etc
}
```

In the example above, a custom generator by the name `WidgetUUIDGenerator` is defined to generate new identities of type `UUID`. It requires for there to be at least one method annotated with `@IDGenerator` which returns the same type as declared by `@CustomGenerator`'s `generates` 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 the `PersistenceUnitSetup` class, which exposes some, if not all, of the content of the `PersistenceUnitInfo`. I considered simply using the `PersistenceUnitInfo` class, but opted not to since that resides in `jakarta.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.

Contributor guide

Open the contributing guide

Research direction

No implementation files or tests are named. Start by reviewing the proposed @CustomGenerator, @IDGenerator, and @GeneratedValue interactions and the PersistenceUnitSetup requirements; done means an agreed API and lifecycle behavior, including generation timing and rollback considerations.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
api, backend, database
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.