This is an internal documentation. There is a good chance you’re looking for something else. See Disclaimer.
Entity class generation¶
Introduction¶
All entities in nice2 are currently defined by *.xml files, which are then parsed into an EntityModel instance.
There are two different RepresentationMode in hibernate:
POJO (a different class per entity)
MAP (entities based on maps)
As the two modes cannot be mixed and we would like to be able to use typed entities (instead of just the Entity interface) in the future we need to dynamically generate classes for the entity models.
The javassist library is used to generate the classes. Bytebuddy would be a more modern alternative, which is also used by Hibernate itself, but the newest version is much slower than javassist.
Class generation¶
Since these classes are generated during application startup it is important that they are loaded in the correct
class loader. Since the Spring Boot migration it is no longer possible to use the context class loader
- the class loader of the current class will be used instead.
During test cases the class loader to be used is set through the static property ENTITY_CLASS_LOADER_OVERRIDE
(because we cannot use the context class loader for this purpose anymore). As each test class might have
a different data model, it is important to set a new class loader for each test class.
The classes are generated during startup by the JavassistEntityPojoFactory. At first an ‘empty’ class is generated for each entity model, so that they can already be referenced by other classes.
All generated entity classes inherit from the same base class (AbstractPojoEntity), which implements all of the logic required by the Entity interface.
The generated classes contain the following:
a private field of the corresponding type for each field and relation
a getter and setter for each field (if the method name already exists e.g. getDate from the Entity interface an underscore is added as prefix)
JPA annotations, which define the hibernate data model, on the fields
If the annotations are placed on the fields (instead of the getters), hibernate reads and writes from the fields directly, without using the setters and getters.
This has the advantage that we can use the entity interceptors (e.g. security) in the getters and setters, which
is necessary if we want to be able to use the created classes directly (instead of using the Entity
interface)
in the future. It is also required for the ‘script listener’ functionality, which also uses the getters and setters directly.
Transient entities¶
There are so called ‘session-only’ entities in nice2 which are not mapped to the database (used only for data binding and the like). A different base class (AbstractSessionOnlyEntity) is used for those entities and no JPA annotations are added. They are basically normal java beans that implement the Entity interface. If such an entity is used in an association with a normal entity, no JPA annotations may be used on both sides, only a normal field (or collection) is created.
Entities¶
Apart from the usual Entity annotation, the database table name is explicitly defined with the Table annotation (we need to use the same naming strategy to be compatible with existing databases). A custom EntityPersister is defined as well (see Custom persisters for details).
Fields¶
All fields are annotated with the Column annotation to define the column name of this field (we need to use the same naming strategy to be compatible with existing databases).
Primary Key
The primary key must be annotated with Id. If the key value is generated
by the database the annotation GeneratedValue is required as well.
For autoincrement columns, the correct strategy is IDENTITY
.
Version
Fields of type version are annotated with Version, which enables optimistic locking for this entity.
Counter fields
The counter
datatype is a numeric type whose value is automatically generated. The value is incremented for every new entity instance.
The counter values are managed in the nice_counter
table.
Counter fields are annotated with Counter, which configures the CounterGeneration value generator. This generator is only applied whenever a new entity is inserted (not when an entity is updated).
If the value of a counter field is manually set in the transaction it will not be overwritten.
At first, the counter entity (for the relevant entity type, field and business unit) is fetched from the database
using the PESSIMISTIC_WRITE
lock mode.
The counter value is then updated using a stateless session to make sure that
database is updated immediately. This is necessary if the same counter is used multiple times in the same transaction.
It is important that the connection of the current session is also used in the stateless session to make sure that they use
the same database transaction.
Note
It would probably make sense to use a database sequence
for this purpose in the future.
Custom java types
Custom java types are mapped using the Type annotation. See the chapter Custom java types for more details.
Other fields
The nullable
, unique
and if applicable precision
and scale
properties are set on the Column annotation.
These properties are only used for schema generation in test cases (databases are setup by liquibase), not for
validation!
The type decimal
(without precision and scale) is handled specially, because Hibernate would use a default
precision and scale, but in this case we want to use the column type decimal
without any precision or scale.
The text datatype is a String that should be saved into a column with datatype varchar using VarcharJdbcType.
Generated fields¶
It is possible to define custom data types whose values are automatically set when an entity is saved or updated. These fields are annotated either with the AlwaysGeneratedValue for fields which should be updated on create and update or the InsertGeneratedValue for fields which should only be updated when the entity is created.
Associations¶
Associations (relations) are annotated with one of the following JPA annotations (depending on the type):
So far all associations are bi-directional (even if this does not always make sense). In a ManyToOne/OneToMany association, the ManyToOne side is always the owning side. In a ManyToMany association, the owning side needs to be explicitly specified (with the JoinTable annotation). The owning side is responsible for persisting the relationship - if a change is only done on the inverse side of an association, it will not be persisted! For example in a ManyToMany association, entities must always be added and removed from the owning side, otherwise the mapping table won’t be updated.
For collections a LinkedHashSet is used, because we want LinkedHashSet semantics (no duplicates), but need to iterate over the elements in the same order as they were inserted (to support sorting by the database).
All associations (including ManyToOne) are configured to be loaded lazily by specifying the FetchType on the annotation. Per default only to many associations are loaded lazily, that’s why we need to explicitly configure it for to one associations.
When a collection has been initialized it cannot be reloaded from the database (unless the entire object is reloaded). However when a Relation is resolved, the data should always be loaded from the database (because this was the behaviour of the old persistence implementation). To support this behaviour we use a custom collection type (CollectionType).
See Collections chapter for more details.
A custom CollectionPersister is also configured (see Custom persisters for details).
Class loading¶
The ClassUtils can be used to load the generated classes by name. The classes are retrieved from the hibernate Metamodel. The reason for this is that those classes are generated during the initialization of Hibernate and getting them from the Metamodel ensures that the classes have been properly initialized (in contrast to loading them directly from the class loader).
Class file caching¶
As generating the classes during runtime takes a significant amount of time they are cached when possible. After the class has been generated a checksum of the entity model is generated which is saved to disk together with the generated byte code. If the entity model (and thus the checksum) has not changed when the application is started the next time, the class will be loaded directly from disk, which is much faster.