2. Department schema#

What this step does. Publishes the full CRUD surface for Departments — list, read, create, update, upsert, delete — by writing one Java class plus a model POJO. No SQL, no controllers, no fetch stubs.

Departments are pure master data — small, stable, keyed by a business code (HR, ENG, SALES).

JPA entity#

package com.example.empmgmt.entity;

@Entity
@Table(name = "department")
@Getter @Setter
public class DepartmentEntity {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true, length = 32)  private String code;
    @Column(nullable = false, length = 128)                 private String name;
    @Column(length = 512)                                   private String description;
}

Palmyra model#

package com.example.empmgmt.model;

@Getter @Setter
@PalmyraType(type = "Department", table = "department", preferredKey = "code")
public class DepartmentModel {

    @PalmyraField(primaryKey = true)
    private Long id;

    @PalmyraField(sort = true, search = true, quickSearch = true,
                  mandatory = Mandatory.ALL)
    private String code;

    @PalmyraField(sort = true, search = true,
                  mandatory = Mandatory.ALL)
    private String name;

    @PalmyraField
    private String description;
}

Notes:

  • preferredKey = "code" lets SaveHandler upserts resolve by the natural business key — clients don’t have to know the surrogate id.
  • quickSearch on code — the frontend’s global search box will match against this column.

Handler#

Compose the reads you want plus a SaveHandler for upserts. No per-entity boilerplate needed beyond the class itself:

package com.example.empmgmt.handler;

@Component
@CrudMapping(
    mapping          = "/department",
    type             = DepartmentModel.class,
    secondaryMapping = "/department/{id}"
)
public class DepartmentHandler
        implements QueryHandler, ReadHandler, SaveHandler, UpdateHandler, DeleteHandler { }

That single declaration publishes six routes:

  • GET /api/department / /{id} — list + single-record read
  • POST /api/department — upsert (insert if code is new, update if it matches)
  • POST /api/department/{id} — explicit update by primary key
  • DELETE /api/department/{id} — delete

You don’t need a hook override yet — default behaviour handles the full surface. We’ll revisit this in the next step when audit columns enter the picture.

Variations#

  • Application-managed uniqueness. When the DB doesn’t enforce the code uniqueness constraint (legacy schema, shared table, read-only view), declare it in Java instead and Palmyra honours it during upserts and duplicate-detection:

    @PalmyraType(type = "Department", table = "department", preferredKey = "code")
    @PalmyraMappingConfig(
        type       = "Department",
        uniqueKeys = { @PalmyraUniqueKey(fields = {"code"}) }
    )
    public class DepartmentModel { /* ... */ }

    See @PalmyraMappingConfig for when this is worth reaching for.

  • Add a default order. If you want the list to come back sorted by code every time, override applyQueryFilter:

    @Component
    @CrudMapping(mapping = "/department", type = DepartmentModel.class,
                 secondaryMapping = "/department/{id}")
    public class DepartmentHandler
            implements QueryHandler, ReadHandler, SaveHandler, UpdateHandler, DeleteHandler {
    
        @Override
        public QueryFilter applyQueryFilter(QueryFilter filter, HandlerContext ctx) {
            if (!filter.hasOrderBy()) filter.addOrderAsc("code");
            return filter;
        }
    }
  • Gate writes on a role. Add a single permission key via @Permission:

    @Permission(cruds = "DEPARTMENT")
    public class DepartmentHandler { /* ... */ }

    Now every mutating call needs the DEPARTMENT permission — either resolved against the ACL extension’s tables or through a PermissionEvaluator you register yourself.