Generic procedure dispatch
When gdsmodule.sSaveProName (or sDeleteProName, sCalcProName,
sProcName, sSaveProNameBefore) is non-empty, the framework
invokes the named stored procedure instead of falling through to its
default Add/Update path. The same machinery handles button-press
calculations and on-demand custom logic.
The handler is GenericProcedureCallController in
xlyEntry/com/xly/web/businessweb/.
Endpoint shape
The frontend POSTs to a URL under /business/genericProcedureCall*
with:
- The proc name (often resolved from
gdsmoduleorgdsconfigformslave.sButtonParam). - The parameter values (frontend supplies them, the framework injects tenant identity).
The handler:
- Loads the proc's signature (parameter names and types) from
information_schema.PARAMETERS. - Composes
CALL <proc>(?, ?, …)with parameters bound positionally. - Augments the call with
sBrandsId,sSubsidiaryId,sLoginId,sLanguagefromRequestAddParamUtil. - Executes via MyBatis.
- Returns either the proc's result-set (for SELECTs inside the proc) or its OUT parameters (for save/calculate procs).
Why dynamic proc dispatch matters
With 1,687 stored procedures and ~177 functions in the schema, hard- wiring each one in Java would be untenable. Dispatch by name lets the framework call any proc the metadata names without a code change. The framework treats the proc as a black box: name in, parameters in, result out.
The downside: the runtime cannot statically know which procs exist or
what their effects are. A typo in gdsmodule.sSaveProName produces a
runtime "proc not found" error, not a compile error.
The conventions procs follow
xly's procs share a calling convention to make generic dispatch possible:
-
Common parameters in a fixed order: typically
(IN sLoginId, IN sCustomerId, IN sBrId, IN sSuId, …). Cross-cutting identity always comes first. -
Tenant-aware: every proc accepts
sBrId(=sBrandsId) andsSuId(=sSubsidiaryId) and uses them in every internal predicate to stay tenant-safe. -
Output via OUT params: save / calc procs return success / error
through
OUT sCode INT, OUT sReturn LONGTEXT— seexlyEntry/src/main/resources/templates/templesql/sSaveProName.sqlfor the canonical scaffold. -
Dynamic SQL inside: many procs build SQL strings with
CONCAT-
PREPARE+EXECUTE. This is xly's escape hatch for runtime shape variability and the reasonjSqlParseris in the dependency tree (the framework occasionally inspects these strings).
-
The companion file: templesql/
xlyEntry/src/main/resources/templates/templesql/ holds scaffolds
for the standard proc kinds:
sSaveProName.sqlsDeleteProName.sqlsProcName.sqlsSaveProNameBefore.sqlsButtonParam.sqlsProTempleByDb.sqlsp_btn_calc.sql
These are templates an engineer fills in to author a new proc — they are not used by the runtime to generate procs on the fly. See SQL templates for the loader and the placeholder syntax.
Failure modes to watch
-
Mismatched parameter order. Generic dispatch binds positionally;
a proc whose
INparameters are reordered is a runtime explosion. -
Missing tenant filter. A proc that forgets
sBrandsId/sSubsidiaryIdin any internal predicate is a multi-tenant leak. Procs are NOT auto-filtered by the framework — they have to do it themselves. -
Long-running procs blocking the request thread. Long
calculations should run on a separate worker (see
xlyErpTask, though it's currently disabled insettings.gradle).