Entity definition
An Entity in Exposed maps a database table record to a Kotlin object. This ensures type safety and allows you to work with database records just like regular Kotlin objects, taking full advantage of Kotlin's language features.
When you use the Data Access Object (DAO) approach, the IdTable needs to be associated with an Entity. This is because every database record in this table needs to be mapped to an Entity instance, identified by its primary key.
Defining an Entity
You define an entity by creating a class. In the following example, StarWarsFilmEntity is the entity class linked to the table StarWarsFilmsTable:
Entity type
The entity type determines how the Entity class interacts with the table’s primary key. Since StarWarsFilmsTable is an IntIdTable, the StarWarsFilmsEntity class extends from IntEntity, which indicates that the ID and primary key of StarWarsFilmsTable is of type Int.
The following entity types are supported:
IntEntityBase class for an entity instance identified by an ID comprised of a wrapped
Intvalue.LongEntityBase class for an entity instance identified by an ID comprised of a wrapped
Longvalue.UIntEntityBase class for an entity instance identified by an ID comprised of a wrapped
UIntvalue.ULongEntityBase class for an entity instance identified by an ID comprised of a wrapped
ULongvalue.UUIDEntityBase class for an entity instance identified by an ID comprised of a wrapped
UUIDvalue.CompositeEntityBase class for an entity instance identified by an ID comprised of multiple wrapped values.
Entity class
The EntityClass in the companion object block is responsible for managing Entity instances, such as creating, querying, and deleting records. It also maintains the relationship between the entity class (StarWarsFilmEntity in this example) and the database table (StarWarsFilmsTable).
Each entity type is supported by a corresponding EntityClass, which accepts the following parameters:
- table
The
IdTablecontaining rows mapped to entities that are managed by this class.- entityType
(Optional) The expected type of the
Entityclass. This can be omitted if it is the class of the type argument provided to thisEntityClassinstance.- entityCtor
(Optional) The function that instantiates an
Entityusing the providedEntityID. If not provided, reflection is used to determine the primary constructor on the first access (which may affect performance).
In the example above, IntEntityClass is specified in the companion object:
This setup enables you to use functions provided by IntEntityClass to manage instances of StarWarsFilmEntity effectively.
Properties
Each column in the table is represented as a property in the entity class, where the by keyword ensures the data is fetched or updated from the corresponding column when accessed.
Once the entity class is defined, instances of this class allow you to manipulate individual records from the corresponding table. For example, by creating a new record, retrieving a row based on its primary key, updating values, or deleting records.
Immutable entities
For defining entities that are immutable, Exposed provides the additional ImmutableEntityClass and ImmutableCachedEntityClass.
The ImmutableCachedEntityClass uses an internal cache to store entity loading states by the associated database. This ensures that entity updates are synchronized with this class as the lock object.
To create an immutable entity, use either ImmutableEntityClass or ImmutableCachedEntityClass as a companion object in your entity class. For example, here’s how to define a CityEntity class:
Properties are defined using
valinstead ofvar. This enforces immutability, asvalproperties cannot be reassigned after initial assignment.The primary function of the entity class in this context is to query data from the associated table, not to modify it. Therefore, inserts can only be performed using the DSL
.insert()method and updates can be done either through the DSL.update()or theImmutableEntityClass.forceUpdateEntity()methods.
Class method overrides
You can use override methods in both the Entity class and the EntityClass companion object to extend functionality or manage entity behavior.
For example, here’s how to override the delete() method in a User entity:
In this example, a custom message is printed before the .delete() function of the superclass (IntEntity) completes the deletion.
Field transformations
As databases typically store only basic types, such as integers and strings, it's not always convenient to keep the same simplicity on DAO level.
For example, you might need to parse JSON from a VARCHAR column, or retrieve values from a cache based on data from the database. In such cases, we recommend to use column transformations.
Example: Defining an Unsigned Integer field
Suppose that you want to define an unsigned integer field on an entity, but Exposed doesn't have such a column type yet. You can achieve this by using the following implementation:
The .transform() function accepts two lambdas that convert values to and from the original column type. In this case, you make sure to store only UInt instances in the uint field.
Although it is still possible to insert or update values with negative integers via DAO, this approach assures a cleaner business logic.
Memoized transformations
If your transformation function involves complex operations that impact performance, you can use .memoizedTransform() to cache the result of the transformation.
With memorized transformation, the value is unwrapped only once and then cached for future reads. This cache remains valid for the lifetime of the entity. If the transaction's entity cache is cleared or the entity is reloaded in a new transaction (creating a new cache without the existing value), the value will be wrapped again. However, if the original entity is kept alive outside of the transaction, the cached value persists to avoid re-wrapping.