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"letsSaveHandlerupserts resolve by the natural business key — clients don’t have to know the surrogateid.quickSearchoncode— 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 readPOST /api/department— upsert (insert ifcodeis new, update if it matches)POST /api/department/{id}— explicit update by primary keyDELETE /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
codeuniqueness 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
@PalmyraMappingConfigfor when this is worth reaching for. -
Add a default order. If you want the list to come back sorted by
codeevery time, overrideapplyQueryFilter:@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
DEPARTMENTpermission — either resolved against the ACL extension’s tables or through aPermissionEvaluatoryou register yourself.