Entities

There are the following types of entities in Jmix:

Entities are characterized by their attributes.

JPA and DTO entities are defined by Java classes and have some annotations specific to Jmix.

Use Studio entity designer to create JPA and DTO entities.

JPA Entities

JPA entity is a Java class annotated according to JPA rules. JPA entities are stored in a relational database connected as a main or additional data store.

JPA annotations define mapping between database table fields and entity attributes. Jmix imposes the following restrictions on mapping annotations:

  • Attribute annotations must be placed only on fields (AccessType.FIELD).

  • Not supported: @IdClass, @ElementCollection.

Below is an example of a typical JPA entity class:

Customer.java
@JmixEntity (1)
@Table(name = "CUSTOMER") (2)
@Entity (3)
public class Customer {

    @JmixGeneratedValue (4)
    @Id (5)
    @Column(name = "ID", nullable = false) (6)
    private UUID id;

    @Version (7)
    @Column(name = "VERSION")
    private Integer version;

    @InstanceName (8)
    @NotNull (9)
    @Column(name = "NAME", nullable = false)
    private String name;

    @Email (9)
    @Column(name = "EMAIL", unique = true) (10)
    private String email;

    public UUID getId() {
        return id;
    }

    public void setId(UUID id) {
        this.id = id;
    }

    // other getters and setters
1 Mandatory @JmixEntity annotation.
2 @jakarta.persistence.Table annotation specifies the database table name.
3 @jakarta.persistence.Entity annotation indicates that the class is a JPA entity.
4 The @JmixGeneratedValue annotation indicates that the primary key must be generated and assigned by Jmix when creating entity instance in memory.
5 @jakarta.persistence.Id annotation indicates the primary key.
6 @jakarta.persistence.Column annotation specifies a mapping to a table column. The nullable = false parameter indicates that the database migration mechanism should create the column with the NOT NULL constraint.
7 @jakarta.persistence.Version indicates that the entity must be optimistically locked using the value of this attribute. The attribute should be of Integer type. Studio creates such an attribute automatically if you select the Versioned trait for the entity.
8 The @InstanceName annotation here indicates a single attribute chosen as the instance name.
9 @NotNull and @Email annotations from the javax.validation.constraints package are the examples of using Bean Validation annotations in entities.
10 The unique = true parameter of the @Column annotation indicates that the database migration mechanism should add the unique constraint to the column.

Both table and entity name can have a prefix to eliminate name conflicts with entities of other modules. Studio inserts this prefix if the project has the jmix.projectId property in build.gradle.

Traits

A trait is a set of attributes that gives the entity some specific system-level behavior. These attributes are handled by the framework and are not intended to be edited by users or your application code.

Studio entity designer helps you assign available traits to an entity. You can also do it manually by creating corresponding attributes and annotating them as described below.

Has UUID Trait

The Has UUID trait provides a globally unique identifier, assigned automatically when an instance is created in memory. The trait is implemented by an attribute of the UUID type annotated with @JmixGeneratedValue.

When creating an entity in Studio, the Has UUID trait is selected automatically if you choose UUID in Id type. Add the Has UUID trait if you choose a different type and Id value different from Generated by Jmix.

This trait does not require an additional attribute if you choose UUID type for the entity identifier.

Adding the Has UUID trait is highly recommended if you use an identifier attribute whose value is not assigned right at the creation of entity instance in memory. This is the case for Long or Integer identifiers mapped to an identity column, and for identifiers of any type assigned by users. If such an entity has no @JmixGeneratedValue attribute, its hashCode() method always returns a constant value, which affects the performance of collections based on hash tables.

Versioned Trait

The Versioned trait provides optimistic locking on the JPA level. It is implemented by an integer attribute annotated with @Version.

Never change the value of the @Version attribute in the application code. It will lead to the inability to update the instance in the database.

Audit Traits

The Audit of creation and Audit of modification traits provide tracking of who and when created and modified an entity instance. They are implemented by attributes of appropriate types annotated with @CreatedBy, @CreatedDate, @LastModifiedBy, @LastModifiedDate annotations from the Spring Data project.

For example:

@CreatedBy
@Column(name = "CREATED_BY")
private String createdBy;

@CreatedDate
@Temporal(TemporalType.DATE)
@Column(name = "CREATED_DATE")
private Date createdDate;

@LastModifiedBy
@Column(name = "LAST_MODIFIED_BY")
private String lastModifiedBy;

@LastModifiedDate
@Temporal(TemporalType.DATE)
@Column(name = "LAST_MODIFIED_DATE")
private Date lastModifiedDate;

Audit attributes are assigned automatically when the framework saves entity instances.

Soft Delete Trait

The Soft Delete trait provides soft deletion of entity instances. It is implemented by a pair of attributes annotated with @DeletedDate and @DeletedBy, for example:

@DeletedBy
@Column(name = "DELETED_BY")
private String deletedBy;

@DeletedDate
@Temporal(TemporalType.DATE)
@Column(name = "DELETED_DATE")
private Date deletedDate;

See more information in the Soft Deletion section.

Entity Inheritance

Jmix framework uses principles of JPA inheritance. Studio entity designer automatically generates the necessary annotations in accordance with the chosen inheritance strategy. The main annotations are listed below.

@DiscriminatorColumn

Is used for defining a database column responsible for the distinction of entity types in the cases of SINGLE_TABLE and JOINED inheritance strategies.

Parameters:

  • name - the discriminator column name.

  • discriminatorType - the discriminator column type.

  • length - the column length for String-based discriminator types.

Example:

@DiscriminatorColumn(name = "TYPE", discriminatorType = DiscriminatorType.INTEGER)

@DiscriminatorValue

Defines the discriminator column value for this entity.

Example:

@DiscriminatorValue("0")

@Inheritance

Defines the inheritance strategy to be used for an entity class hierarchy. It is specified on the entity class that is the root of the entity class hierarchy.

Parameters:

  • strategy - the inheritance strategy, SINGLE_TABLE by default.

@MappedSuperclass

Defines that the class is an ancestor for some entities, and its attributes must be used as part of descendant entities. Such a class is not associated with any particular database table.

@PrimaryKeyJoinColumn

Is used in the case of JOINED inheritance strategy to specify a foreign key column for the entity, which refers to the primary key of the ancestor entity.

Parameters:

  • name - the name of the foreign key column of the entity.

  • referencedColumnName - the name of a primary key column of the ancestor entity

Example:

@PrimaryKeyJoinColumn(name = "CARD_ID", referencedColumnName = "ID")

DTO Entities

The data model of your application can contain entities that exist only in memory or are mapped to some external data using mechanisms different from JPA. We call such entities DTO because they are often used as Data Transfer Objects in parameters and return values in Generic REST and when communicating with external APIs.

A DTO entity can be as simple as that:

OperationResult.java
@JmixEntity (1)
public class OperationResult {

    private String result; (2)

    private Integer errorCode; (2)

    private String errorMessage; (2)

    public String getResult() {
        return result;
    }

    public void setResult(String result) {
        this.result = result;
    }

    // other getters and setters
1 Mandatory @JmixEntity annotation.
2 All object properties (fields with accessor methods) become entity attributes.

Entity attributes can have annotations to specify some details about them:

ProductPart.java
@JmixEntity (1)
public class ProductPart {

    @JmixProperty(mandatory = true) (2)
    @InstanceName (3)
    private String name;

    private Integer quantity; (4)

    // getters and setters
1 The @JmixEntity annotation defines the entity name explicitly.
2 The @JmixProperty annotation with mandatory = true parameter indicates that the attribute is required, i.e. it must contain a value.
3 The @InstanceName annotation here indicates a single attribute chosen as the instance name.
4 An attribute without annotations.

DTO entities can be associated with a custom data store for generic CRUD operations via DataManager and automatic resolving of references to the DTO entity from JPA entities.

In the example below, you can also see how to exclude some object properties from being entity attributes (more on this in the Entity Attributes section):

Metric.java
@Store(name = "inmem") (1)
@JmixEntity(annotatedPropertiesOnly = true) (2)
public class Metric {

    @JmixProperty(mandatory = true) (3)
    @JmixId (4)
    @JmixGeneratedValue (5)
    private UUID id;

    @JmixProperty (6)
    private String name;

    @JmixProperty (6)
    private Double value;

    private Object ephemeral; (7)

    public UUID getId() {
        return id;
    }

    public void setId(UUID id) {
        this.id = id;
    }

    // other getters and setters
1 The @Store annotation specifies a custom data store.
2 The annotatedPropertiesOnly = true parameter of @JmixEntity annotation indicates that object properties not annotated with @JmixProperty will not be entity attributes.
3 The @JmixProperty annotation with mandatory = true parameter indicates that the attribute is required, i.e. it must contain a value.
4 The @JmixId annotation indicates that the attribute is the entity identifier.
5 The @JmixGeneratedValue annotation indicates that the identifier must be generated and assigned by Jmix when creating the entity instance in memory.
6 The @JmixProperty annotation here just indicates that the property is an entity attribute.
7 Not annotated property is not an entity attribute because of annotatedPropertiesOnly = true parameter of @JmixEntity annotation.

Key-Value Entity

KeyValueEntity lets you represent arbitrary sets of named values as entities and hence work with data that is not directly represented by Java classes (JPA or DTO entities).

Consider an example: you have Order entity in your data model, and you need to calculate sum of order amounts aggregated by customers and show this data in UI. Then you can execute a JPQL query and load the result list as a list of KeyValueEntity instances using DataManager:

List<KeyValueEntity> entities = dataManager.loadValues(
            "select e.customer, sum(e.amount) from Order_ e group by e.customer")
        .properties("customer", "total")
        .list();

The returned KeyValueEntity instances will have two attributes which you specified in the properties() method: customer with the value of the first field in the query result set, and total with the value of the second field. You can get them as follows:

for (KeyValueEntity entity : entities) {
    Customer customer = entity.getValue("customer");
    BigDecimal totalAmount = entity.getValue("total");
    // ...
}

Jmix UI has special key-value data containers for binding UI components to KeyValueEntity instances.

Entity Attributes

There is another term for entity attributes: entity properties. It’s often used in the Jmix codebase, for example in annotations: @JmixProperty, @DependsOnProperties, etc.

Each entity attribute should have an appropriate type. Jmix supports the following types out-of-the-box:

  • java.lang.String

  • java.lang.Character

  • java.lang.Boolean

  • java.lang.Integer

  • java.lang.Long

  • java.lang.Double

  • java.math.BigDecimal

  • java.util.Date

  • java.time.LocalDate

  • java.time.LocalTime

  • java.time.LocalDateTime

  • java.time.OffsetTime

  • java.time.OffsetDateTime

  • java.sql.Date

  • java.sql.Time

  • java.util.UUID

  • java.net.URI

  • byte[] (byte array)

  • Enumeration

  • Entity or a collection of entities (a reference attribute)

You can use a type not from the list above if you create an appropriate Datatype implementation and make sure your type is supported by the underlying data store.

Note that Java primitive types (int, boolean, etc.) cannot be used for entity attributes.

In JPA and DTO entities, there are two types of attributes:

  • Field-based attribute corresponds to a field and a pair of accessor methods (getter / setter) of the field. The field name becomes the attribute name.

    The setter can be omitted, then the attribute is read-only.

    Example of a field-based attribute:

    User.java
    @Column(name = "FIRST_NAME")
    protected String firstName;
    
    public String getFirstName() {
        return firstName;
    }
    
    public void setFirstName(final String firstName) {
        this.firstName = firstName;
    }
  • Method-based attribute corresponds to a method without parameters, returning a supported type, and with a name starting from get, for example getCustomer(). The method name without get with the first letter in lower case becomes the attribute name: getFullName()fullName.

    Example of a method-based attribute:

    User.java
    @JmixProperty
    @DependsOnProperties({"firstName", "lastName"})
    public String getFullName() {
        return this.firstName + " " + this.lastName;
    }

An entity class can have properties (field + getter/setter) and methods that are not entitiy attributes, that is not included in metadata. So you can use such properties and methods in your application code, but the framework will not recognize them and will not display them in UI or transfer through the REST API.

Whether a property or a qualifying method becomes an entity attribute is subject to the following rules:

  • If the annotatedPropertiesOnly parameter of @JmixEntity annotation is false (which is the default), the following object properties become entity attributes:

    • For JPA entities: all properties except annotated with @jakarta.persistence.Transient.

    • For DTO entities: all properties.

    • For both: all properties and methods annotated with @JmixProperty.

  • If the annotatedPropertiesOnly parameter is set to true, only the properties and methods annotated with @JmixProperty become entity attributes.

References

Reference attributes define relationships between entities. References can be single-value (to-one relationships) or collections (to-many relationships).

By default, a relationship is an association, which means that both entities can exist independently of each other, without any ownership. For example, in a Customer - Order relationship, Order has an attribute which is a reference to Customer. Users create customers and orders independently, select a customer for an order, and change the reference to another customer if needed.

Jmix also supports a stronger connection between entities called composition. Composition implies ownership, when an entity instance can exist only as a part of its owning entity instance. For example, in a Order - OrderLine relationship, Order has an attribute which is a collection of OrderLine instances. Each OrderLine instance is created for a particular Order and becomes its part, it cannot belong to another Order.

Entities belonging to a composition are edited together in UI. For example, a user opens an Order edit screen and can create and edit OrderLines in their separate edit screens, but all changes both for the Order and all its OrderLines are saved to the database together in one transaction, and only after the user confirms saving of the owning entity - Order.

Composition relationship is specified by the @Composition annotation on the reference attribute.

Studio entity designer allows you to select a relationship type in the Attribute type field.

Cross-Datastore References

DataManager can automatically maintain To-One references between entities from different data stores, if they are properly defined.

Studio entity designer automatically defines the set of attributes for cross-datastore references when you select an entity from a different data store as an association.

Let’s take an example: you have Customer entity in the main data store and Address entity in an additional data store, and you want to have a reference from Customer to Address. Then Customer entity should contain the following two attributes:

@SystemLevel
@Column(name = "ADDRESS_ID")
private UUID addressId; (1)

@Transient
@JmixProperty
@DependsOnProperties("addressId")
private Address address; (2)

public UUID getAddressId() {
    return addressId;
}

public void setAddressId(UUID addressId) {
    this.addressId = addressId;
}

public Address getAddress() {
    return address;
}

public void setAddress(Address address) {
    this.address = address;
}
1 The addressId attribute stores the identifier of Address. This attribute is annotated with @SystemLevel to indicate to the framework that the attribute should not be displayed to users.
2 The address attribute contains the reference to the Address entity. This attribute is transient (not stored in the database) and annotated with @DependsOnProperties to indicate to the framework that the attribute value depends on another attribute.

After that, when you load Customer with a fetch plan including address attribute, DataManager automatically loads related Address from the additional data store. The framework optimizes loading of collections for performance: after loading a list of customers, it loads references from the additional data store in batches. The size of the batch is defined by the jmix.core.cross-data-store-reference-loading-batch-size application property.

When you save an entity graph which includes Customer with Address, DataManager saves the instances via corresponding DataStore implementations, and then saves the identifier of the address in the customer’s addressId attribute.

Instantiating Entities

When creating instances of JPA and DTO entities, use the appropriate framework interface instead of invoking the class constructor with the new operator. Only in this case the framework can initialize fields annotated with @JmixGeneratedValue and invoke @PostConstruct methods.

The most common core method for instantiating entities is Metadata.create():

@Autowired
private Metadata metadata;

Order createOrder() {
    return metadata.create(Order.class);
}

If you write business logic and already have DataManager in your code, use its create() method, which just saves you from additionally injecting the Metadata bean. For example:

@Autowired
private DataManager dataManager;

Order createAndSaveOrder(String number) {
    Order order = dataManager.create(Order.class);
    order.setNum(number);
    dataManager.save(order);
    return order;
}

In a UI view, you can use either of two methods described above. But you may also want the created instance to be saved automatically by DataContext of the view. Then use the DataContext.create() method which creates an instance and immediately merges it to start tracking its changes. In the example below, we create an instance of ProductPart entity, merge it into DataContext and add to a data container to display in a UI table:

@ViewComponent
private DataContext dataContext;

@ViewComponent
private CollectionPropertyContainer<ProductPart> partsDc;

@Subscribe("createProductPart")
public void onCreateProductPartClick(final ClickEvent<JmixButton> event) {
    ProductPart part = dataContext.create(ProductPart.class);
    partsDc.getMutableItems().add(part);
}

Uniqueness

Jmix relies on database unique constraints for maintaining uniqueness of entity instances. So if you want to make an entity attribute or a set of attributes unique, you should create an appropriate index for the database table.

Studio Entity Designer contains the Indexes tab where you can define unique indexes. The index definitions are stored in the @Table annotation of the entity and later used by Liquibase changelog generator for creating indexes in the database schema.

The unique = true attribute of the @Column annotation is not interpreted by Jmix in any way.

If you want to define a unique attribute for an entity with the Soft Delete trait, refer to the Soft Deletion section.

Jmix Entity Annotations

Jmix entity annotations are described below in alphabetical order.

Jmix entities can also have annotations for JPA mappings, audit traits and soft deletion.

@Composition

@Composition annotation on a reference attribute indicates that the relationship is a composition. For example:

@Composition
@OneToMany(mappedBy = "order")
private List<OrderLine> lines;

@DbView

@DbView annotation indicates whether a JPA entity is mapped to a database view. Database migration scripts are not generated for such entities.

@DdlGeneration

@DdlGeneration annotation defines whether development tools should generate DDL scripts for this entity.

The scripts generation mode is set with the DbScriptGenerationMode enumeration:

  • CREATE_AND_DROP - full generation of initialization and update scripts;

  • CREATE_ONLY - full generation of initialization scripts. Update scripts are generated without statements to drop columns;

  • DISABLED - initialization and update scripts are not generated.

Default value: CREATE_AND_DROP.

In addition, you can fine-tune the scripts generation using the following attributes:

  • unmappedColumns - the list of columns that exist in the database but should not be mapped to the entity. Drop scripts for these columns will not be generated;

  • unmappedConstraints - the list of constraints and indexes that exist in the database but should not be mapped to the entity. Drop scripts for these columns will not be generated.

@DependsOnProperties

@DependsOnProperties annotation specifies entity attributes that the annotated attribute depends on. These properties are taken into account when building fetch plans and when loading/saving references to entities from different data stores. Also, if the annotated property is read-only (without a setter), EntityPropertyChangeEvent is sent for this attribute when the specified attributes are changed.

You can specify only immediate local and reference properties. Property paths like customer.name are not supported.

@InstanceName

Instance name is a human-readable text that represents an entity instance. Think of it as of an application-level toString() method. It is used extensively in UI when displaying an entity instance in a single field or table cell. You can also get the instance name programmatically using the MetadataTools.getInstanceName() method.

The @InstanceName annotation can be present on a single field or a method of the object.

In the former case, the annotated attribute value is used as the instance name. For example:

@InstanceName
@Column(name = "NAME")
private String name;

If you want to generate something more complex than a single attribute value, create a method returning String in the entity class. For example:

@JmixEntity(name = "sample_GeoPoint")
@Embeddable
public class GeoPointEntity {

    @Column(name = "LAT")
    protected Double latitude;

    @Column(name = "LON")
    protected Double longitude;

    @InstanceName
    @DependsOnProperties({"latitude", "longitude"})
    public String getDisplayName(Messages messages) {
        return messages.formatMessage(
                getClass(), "GeoPointEntity.instanceName", this.latitude, this.longitude);
    }

The method can accept any Spring beans as parameters. In the example above, the Messages bean is used to format the instance name according to the current user locale.

The @DependsOnProperties annotation on the instance name method is necessary, because it specifies attributes of the built-in _instance_name fetch plan.

@JmixEntity

@JmixEntity is a mandatory annotation indicating that the class is a Jmix entity.

If the class has @jakarta.persistence.Entity annotation, the framework obtains entity name for the metadata from it, and @JmixEntity should not specify the name parameter. Otherwise, specify the entity name in the name parameter. If neither @JmixEntity nor @jakarta.persistence.Entity have name parameter, the entity name equals the Java class simple name.

The annotatedPropertiesOnly parameter specifies what object properties become entity attributes, see Entity Attributes for more details.

@JmixGeneratedValue

@JmixGeneratedValue annotation indicates that the entity attribute value must be generated and assigned by the framework when creating entity instance in memory.

The annotated attribute must be of Long, Integer or UUID type, and the entity should not have more than one UUID attribute marked with this annotation.

Note that the @JmixGeneratedValue annotation doesn’t take any effect if you create an entity instance using the new operator. See instantiating entities for proper methods of creating new instances.

@JmixId

@JmixId annotation specifies an entity identifier for DTO entities. You should explicitly choose an identifier if your DTO entity is mapped to some external data and you need to load/save its instances repeatedly, because in this case you need to maintain the object identity through the entity lifecycle.

You can use an existing attribute for an identifier if the attribute contains unique values, for example:

@JmixId
private String code;

If there is no such naturally unique attribute, define one and annotate it also with @JmixGeneratedValue to assign a unique value on instance creation:

@JmixId
@JmixGeneratedValue
private UUID id;

@JmixProperty

@JmixProperty annotation indicates that an object field or method is an entity attribute. See Entity Attributes for more details.

Use the mandatory parameter if you want to specify that the attribute requires a value, and the object field has no JPA @Column annotation where you could set nullable = false.

@NumberFormat

Specifies a format for an attribute of the Number type (it can be BigDecimal, Integer, Long, or Double). Values of such attribute will be formatted and parsed throughout the UI according to the provided annotation parameters:

  • pattern - the format pattern as described for DecimalFormat.

  • decimalSeparator - the symbol to use for decimal separator (optional).

  • groupingSeparator - the symbol to use for grouping (thousands) separator (optional).

If decimalSeparator and/or groupingSeparator are not specified, their values are obtained from the current user’s locale for locale-dependent formatting, or from server default locale for locale-independent formatting.

Examples:

@Column(name = "PRECISE_NUMBER", precision = 19, scale = 4)
@NumberFormat(pattern = "0.0000")
protected BigDecimal preciseNumber;

@Column(name = "WEIRD_NUMBER", precision = 19, scale = 4)
@NumberFormat(pattern = "#,##0.0000", decimalSeparator = "_", groupingSeparator = "`")
protected BigDecimal weirdNumber;

@Column(name = "SIMPLE_NUMBER")
@NumberFormat(pattern = "#")
protected Integer simpleNumber;

@Column(name = "PERCENT_NUMBER", precision = 19, scale = 4)
@NumberFormat(pattern = "#%")
protected BigDecimal percentNumber;

@PostConstruct

Use jakarta.annotation.PostConstruct annotation on a method that performs initialization of a new entity instance. For example:

@PostConstruct
void init() {
    setGrade(CustomerGrade.BRONZE);
}

The annotated method can accept any Spring beans. In the example below, we use TimeSource bean to initialize a date attribute:

@PostConstruct
void init(TimeSource timeSource) {
    setDate(timeSource.now().toLocalDate());
}
Note that the @PostConstruct annotated method is not invoked if you create an entity instance using the new operator. See instantiating entities for proper methods of creating new instances.

@PropertyDatatype

If you have multiple datatypes for a Java type of an entity attribute, @PropertyDatatype annotation lets you specify a Datatype implementation explicitly by its id. For example:

@PropertyDatatype("year")
@Column(name = "YEAR_")
private Integer productionYear;

@Store

Use @Store annotation on an entity class to associate the entity with an additional data store.

@SystemLevel

@SystemLevel annotation indicates that annotated entity or its attribute is low-level and should not be displayed in UI.