Jmix Entities and the Data Model

Jmix Intermediate 4 min read Published
In this lesson

Everything in a Jmix app starts from the data model. Define your entities well, and Studio can generate views, roles and database changes from them. Our example: a course admin app with Courses, Lessons, Instructors and Categories.

Creating an entity#

In the Jmix tool window: *Data Model → right-click → New → JPA Entity*. Name it Course, keep the defaults (UUID id, versioned), and tick the traits you want (audit of creation/modification, soft delete). Studio opens the entity designer where you add attributes with types, mandatory flags and validation.

The generated (and slightly trimmed) code:

@JmixEntity
@Table(name = "COURSE", indexes = {
        @Index(name = "IDX_COURSE_CATEGORY", columnList = "CATEGORY_ID")})
@Entity
public class Course {

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

    @Version
    @Column(name = "VERSION", nullable = false)
    private Integer version;

    @InstanceName
    @NotBlank
    @Size(max = 120)
    @Column(name = "TITLE", nullable = false, length = 120)
    private String title;

    @Column(name = "LEVEL_")
    private String level;                        // stores the enum id (see below)

    @PositiveOrZero
    @Column(name = "PRICE", precision = 10, scale = 2)
    private BigDecimal price;

    @JoinColumn(name = "CATEGORY_ID")
    @ManyToOne(fetch = FetchType.LAZY)
    private Category category;

    @JoinColumn(name = "INSTRUCTOR_ID")
    @ManyToOne(fetch = FetchType.LAZY)
    private Instructor instructor;

    @Composition
    @OneToMany(mappedBy = "course")
    @OrderBy("position")
    private List<Lesson> lessons;

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

    @CreatedDate
    @Column(name = "CREATED_DATE")
    private OffsetDateTime createdDate;

    @LastModifiedDate
    @Column(name = "LAST_MODIFIED_DATE")
    private OffsetDateTime lastModifiedDate;

    @DeletedDate
    @Column(name = "DELETED_DATE")
    private OffsetDateTime deletedDate;

    public CourseLevel getLevel() { return level == null ? null : CourseLevel.fromId(level); }
    public void setLevel(CourseLevel level) { this.level = level == null ? null : level.getId(); }

    // other getters and setters generated by Studio
}

What the Jmix annotations mean#

AnnotationMeaning
@JmixEntityregisters the class in Jmix metadata (used by UI, security, REST)
@JmixGeneratedValueJmix assigns a UUID when the object is created in memory, so it has an id before saving
@Versionoptimistic locking (two users editing the same course get a clear error)
@InstanceNamethe human-readable name shown in dropdowns, titles and links (title here)
@Compositionlessons are parts of a course: edited inside the course’s detail view and saved together
@CreatedBy, @CreatedDate, @LastModifiedDateaudit fields filled automatically
@DeletedDate (and @DeletedBy)soft delete: removing a course marks it deleted and hides it from queries

Validation annotations (@NotBlank, @Size, @PositiveOrZero, @Email) are standard Bean Validation, so forms validate automatically and show messages next to fields.

Enums#

Jmix enums implement EnumClass and store a stable id, not the ordinal or name, so renaming a constant in code is safe:

public enum CourseLevel implements EnumClass<String> {
    BEGINNER("B"), INTERMEDIATE("I"), ADVANCED("A");

    private final String id;
    CourseLevel(String id) { this.id = id; }
    public String getId() { return id; }

    @Nullable
    public static CourseLevel fromId(String id) {
        for (CourseLevel l : values()) if (l.getId().equals(id)) return l;
        return null;
    }
}

Captions for each value live in messages_en.properties, so the UI can show “Beginner” (or a Hindi translation) instead of BEGINNER.

References vs compositions#

  • Reference (@ManyToOne Category): an independent entity. In the course form you pick a category from a dropdown; deleting a course doesn’t delete the category.
  • Composition (@Composition @OneToMany lessons): child entities that only exist inside the parent. The course detail view shows a lessons table with create/edit inside it, and everything is saved in one transaction when the course is saved.

Delete policies control what happens to related rows:

@OnDelete(DeletePolicy.CASCADE)        // on Course.lessons: deleting a course deletes its lessons
@OnDeleteInverse(DeletePolicy.DENY)    // on Course.category: a category can't be deleted while courses use it

Liquibase changelogs#

When you add or change entities, Studio compares them with the database and generates a Liquibase changelog (in src/main/resources/.../liquibase/changelog/). Review it, and it runs automatically on the next start. These changelogs are your versioned database history, the same idea as Flyway in the Spring Boot course.

Model first, screens later. Spend time on names, mandatory fields, relationships and instance names. Changing a data model after users have entered thousands of records is much harder than changing a view.

The full model for our app#

Category (name)            1 ──< Course >── 1  Instructor (name, email, user)
                                  │
                                  └──< Lesson (title, position, durationMinutes, videoUrl)   [composition]

Exercise#

  1. Create Category (name, unique) and Instructor (name, email) entities.
  2. Create Course with the attributes above, a reference to both, and the CourseLevel enum.
  3. Create Lesson as a composition of Course, with position and durationMinutes.
  4. Run the app and check the generated Liquibase changelog and tables.
Hint for 3

In the entity designer, add an attribute lessons of type Lesson with cardinality ONE_TO_MANY and the Composition checkbox ticked; Studio adds the inverse course attribute to Lesson.

Interview corner#

  • Why UUID ids in Jmix? Entities get ids at creation time (before saving), which simplifies UI work and distributed systems.
  • Reference vs composition? References link independent entities; compositions own child entities that are edited and saved with the parent.
  • What is @InstanceName? The attribute(s) used as the entity’s display name in the UI.
  • How is the schema updated? Studio generates Liquibase changelogs from entity changes.

Summary#

  • Entities are JPA classes with Jmix annotations for ids, display names, compositions, audit and soft delete.
  • Enums store stable ids; validation annotations drive form validation.
  • Studio generates Liquibase changelogs for every model change.

Next: generate list and detail views for the courses.

Quick quiz

3 questions. Check what stuck and earn 2 XP for each right answer.

1What does @InstanceName mark?
2A @Composition relationship means children are…
3Who generates schema changes?

Progress is saved in this browser. Create a free account to keep it on every device.