@CrudMapping#
com.palmyralabs.palmyra.base.annotations.CrudMapping
Binds a handler component to one or more URL paths and the entity type it operates on — Palmyra’s equivalent of Spring’s @RequestMapping for CRUD surfaces. Target: TYPE. Retention: RUNTIME.
Attributes#
| Attribute | Signature |
|---|---|
mapping |
String[] mapping() — collection-level path; consumed by QueryHandler, CreateHandler, SaveHandler |
secondaryMapping |
String secondaryMapping() default "" — single-record path (usually {id}-templated); consumed by UpdateHandler, DeleteHandler |
type |
Class<?> type() — entity type handled |
queryType |
Class<?> queryType() default Object.class — type used for query projections |
Which handler hits which URL#
Palmyra routes HTTP methods to handler interfaces based on which mapping the request matched:
| Handler | HTTP method | Matches |
|---|---|---|
QueryHandler |
GET |
mapping (collection list / filter / paginate) |
ReadHandler |
GET |
mapping |
CreateHandler |
POST |
mapping |
SaveHandler |
POST |
mapping (upsert by body keys) |
UpdateHandler |
POST (or PUT) |
secondaryMapping (targets a single row by {id}) |
DeleteHandler |
DELETE |
secondaryMapping |
Keep mapping for collection-level concerns (list, create, upsert) and secondaryMapping for per-record concerns (update, delete).
Example#
Following the clinic-sample convention:
@Component
@CrudMapping(
mapping = "/masterdata/industrySector",
type = IndustrySectorModel.class,
secondaryMapping = "/masterdata/industrySector/{id}"
)
public class IndustrySectorHandler extends AbstractHandler
implements QueryHandler, ReadHandler, SaveHandler, UpdateHandler, DeleteHandler {
}That single handler answers:
GET /api/masterdata/industrySector?...→QueryHandlerGET /api/masterdata/industrySector/{id}→ReadHandlerPOST /api/masterdata/industrySector→SaveHandler(orCreateHandlerif you compose it instead)POST /api/masterdata/industrySector/{id}→UpdateHandlerDELETE /api/masterdata/industrySector/{id}→DeleteHandler
The {id} placeholder#
{id} in a secondaryMapping URL resolves to the primary key of the mapped table — the table Palmyra derived from the class declared in type (via @PalmyraType and the primary-key field on the POJO, flagged with @PalmyraField(primaryKey = true)).
So for the IndustrySectorHandler above, {id} is the primary key of the IndustrySectorModel / industry_sector table. A request to DELETE /api/masterdata/industrySector/42 deletes the row where the primary key equals 42; POST /api/masterdata/industrySector/42 updates it.
If the primary key is composite or spans multiple fields, use a multi-segment path template (e.g. /masterdata/industrySector/{tenantId}/{id}) and Palmyra will match each placeholder against the corresponding primary-key component in declaration order.
When secondaryMapping is omitted#
If you don’t declare secondaryMapping, update and delete routes are not published — the handler will only accept the collection-level verbs. A read-only list endpoint looks like:
@Component
@CrudMapping(mapping = "/reports/daily-sales", type = DailySalesModel.class)
public class DailySalesHandler implements QueryHandler { }