No-FK, semantic-FK reality
The xly schema has zero triggers and zero foreign-key constraints
on any xly-authored table (the gds*, ele*, mft*, quo*, sal*,
acc*, … families). The only FK constraints that exist sit on bundled
third-party schemas — Activiti's act_* tables and Quartz's qrtz_*
tables — neither of which the framework's own runtime joins through.
Every join the framework relies on for its own metadata or business
data is by convention only.
This is a deliberate design choice and an important one to understand before reading any further.
Why xly disabled FKs
Two reasons given by the architecture, both pragmatic:
- Bulk-write performance. Mass inserts (work-order calculation, month-end closures, batch imports) write hundreds of thousands of rows at a time. With FKs enabled, MySQL validates each row's references on insert — for the volumes xly handles, this is the limiting factor.
-
Schema-migration agility. xly evolves quickly: new modules,
new fields, new tables. With FKs, every schema change has to consider
the constraint graph; without them, a
CREATE TABLEorALTER TABLEis a local operation. The cost of that agility is borne at runtime by the application code.
What a "semantic FK" is
A semantic FK is a column that would be a foreign key if FKs were enabled, but isn't. The relationship is encoded in:
-
Column naming. A
sCustomerIdcolumn is understood to referenceeleCustomer.sId. AsParentIdon a slave table is understood to reference the master'ssId. -
Co-mention in stored procedures and MyBatis mappers. The runtime
joins
A.sParentId = B.sIdin many places; reading the proc body reveals which tables are paired. -
Documentation in column comments.
COLUMN_COMMENTis filled in for many columns and often names the referenced concept (e.g.,加工商ID). -
Metadata declarations. The framework's
gdsconfigformslave.sActiveId/sActiveKeycolumns explicitly name the form and field a dropdown-control points at — a metadata-encoded FK.
None of these are enforced. A row can be deleted from eleCustomer and
nothing in the database will stop you; orphan sCustomerId references
will simply hang around.
Recovering relationships
When the auto-catalog or wiki needs to know "what does column X reference", the lookup process is:
- Read the column comment (often the most helpful single line).
- Search the schema for matching ID columns (
greprecon/columns.tsvforXSomethingId). - Search the codebase for SQL fragments that JOIN on the column.
- Check
gdsconfigformslave.sActiveId— the framework records dropdowns and lookups there. - As a last resort, follow the master/slave naming convention.
This is the work the auto-catalog is meant to streamline: each generated table page lists the table's columns and (in a future enhancement) the procs that reference them, narrowing the search.
Failure modes
The cost of disabling FKs lives at runtime. Three failure modes recur:
-
Orphan rows. A
sCustomerIdwhose customer was deleted. The framework's queries left-join through these and either silently drop them or display blanks. -
Mismatched IDs across tenants. Without an FK, nothing prevents a
row from referencing an ID owned by a different
sBrandsId. The multi-tenant filter (Slice 2) is the only thing keeping this from exploding into cross-tenant data leakage. -
Procs that need to manually validate referential integrity. Many
of xly's stored procedures explicitly check
EXISTS (…)before insert. The integrity work that an FK would do automatically is now scattered across the proc body. Skip it once and the bug surfaces weeks later.
The wiki's job is to surface these relationships even though the database does not. Most of that surfacing happens in the auto-catalog and in slice cross-references; this page is the canonical "why aren't there FKs and what should I trust instead" reference.