How it works
One generic design serves all 25 master tables. Read this page once, then open any feature and expand Learn at the bottom of its page.
1. A request, step by step (saving a currency)
- The browser posts the form to
POST /m/currencies/save. MasterController.save()asksMasterRegistryfor the definition"currencies".- It loads the entity (or creates a new
Currency) and binds the request withServletRequestDataBinder- only the fields listed in the definition. - Bean Validation runs the annotations on
Currency.java(@NotBlank,@Pattern...). - The definition's validator runs the rules that need the database
(
CurrencyService.validateCurrency: only one base currency). MasterService.save()(@Transactional) calls the hooks, thenrepository.saveAndFlush()β Hibernate sendsINSERT/UPDATE ... WHERE row_version = ?.- It publishes
MasterChangedEvent;MasterListenerswrites the audit log and clears caches - in the same transaction. - A SQL Server constraint error (
uq_currency_code) is turned into a friendly message byDbErrors. - Success β redirect to the list with a flash message (Post/Redirect/Get).
2. Project layout
src/main/java/com/study/masterdata ββ domain/ JPA entities (BaseEntity + 30 tables) ββ repo/ Spring Data repositories ββ master/ the generic part β ββ FieldSpec, MasterDefinition describe a master β ββ MasterRegistry all 25 definitions β ββ MasterService CRUD + search (Specification) β ββ MasterListeners audit, caches, config history ββ feature/ business logic per feature (UnitService ...) ββ tool/ "Try it" panels (strategy pattern) ββ web/ MVC controllers, REST API, view helpers src/main/resources ββ db/migration/V1__schema.sql tables (Flyway) ββ db/migration/V2__seed_data.sql starter data ββ templates/ Thymeleaf pages + tools/*.html
3. Ideas worth noticing
- Soft delete vs hard delete - βΈ sets
active = 0; π really deletes and fails while a foreign key still points at the row. - Optimistic locking - open the same record in two tabs, save both: the second one is refused (
@Version row_version). - Pessimistic locking - running numbers lock the counter row (
UPDLOCK) so 20 parallel requests get 20 numbers. - Effective dating - tax rates and exchange rates are looked up "as of" a date.
- Caching -
@Cacheableon lookups, parameters and config, cleared by an event listener. - Self-reference & cascade - categories (tree) and province β district β subdistrict.
- Flyway - the schema is versioned SQL; Hibernate only validates it (
ddl-auto=validate).
4. Add your own master (exercise)
- Create
V3__department.sqlwith a table that has the 4 audit columns (copy one from V1). - Create
Department extends BaseEntitywith Bean Validation annotations andlabel(). - Create
DepartmentRepository extends MasterRepository<Department>. - Add one
MasterDefinition.of("departments", ...)inMasterRegistry. - Restart: list, form, search, CSV, audit log and
/api/masters/departmentsall work.
All masters
key β table β entity| Feature | Master | Table | Entity | Try-it panel | API |
|---|---|---|---|---|---|
| 286 | ποΈ Categories | category | Category | category-tree | /api/masters/categories |
| 287 | π·οΈ Tags | tag | Tag | tag-cloud | /api/masters/tags |
| 288 | π Units of Measure | unit | Unit | unit-converter | /api/masters/units |
| 289 | π¦ Statuses | status_def | StatusDef | workflow | /api/masters/statuses |
| 289 | π Transitions | status_transition | StatusTransition | workflow | /api/masters/status-transitions |
| 302 | π Lookup Types | lookup_type | LookupType | lookup-overview | /api/masters/lookup-types |
| 302 | π Lookup Values | lookup_value | LookupValue | lookup-overview | /api/masters/lookup-values |
| 290 | πΊοΈ Provinces | province | Province | address-cascade | /api/masters/provinces |
| 290 | ποΈ Districts | district | District | address-cascade | /api/masters/districts |
| 290 | ποΈ Subdistricts | subdistrict | Subdistrict | address-cascade | /api/masters/subdistricts |
| 305 | π Countries | country | Country | country-summary | /api/masters/countries |
| 291 | π¦ Banks | bank | Bank | bank-check | /api/masters/banks |
| 292 | π± Currencies | currency | Currency | currency-format | /api/masters/currencies |
| 301 | π Exchange Rates | exchange_rate | ExchangeRate | fx-convert | /api/masters/exchange-rates |
| 300 | π§Ύ Tax Rates | tax_rate | TaxRate | tax-calc | /api/masters/tax-rates |
| 304 | π€ Payment Terms | payment_term | PaymentTerm | due-date | /api/masters/payment-terms |
| 293 | π Holidays | holiday | Holiday | holiday-calendar | /api/masters/holidays |
| 294 | π Fiscal Years | fiscal_year | FiscalYear | fiscal-periods | /api/masters/fiscal-years |
| 295 | π’ Running Numbers | running_number | RunningNumber | running-number | /api/masters/running-numbers |
| 303 | π€ Document Prefix / Suffix | doc_prefix | DocPrefix | prefix-preview | /api/masters/doc-prefixes |
| 296 | π Document Templates | doc_template | DocTemplate | template-preview | /api/masters/templates |
| 297 | ποΈ System Parameters | system_parameter | SystemParameter | parameter-read | /api/masters/parameters |
| 298 | βοΈ System Config | system_config | SystemConfig | config-resolve | /api/masters/config |
| 299 | π£οΈ Languages | app_language | AppLanguage | i18n-matrix | /api/masters/languages |
| 299 | π€ Translations | translation | Translation | i18n-matrix | /api/masters/translations |