Use when working on persistence in mediask-backend with MyBatis-Plus, especially when adding or reviewing DO/Mapper/RepositoryAdapter code, deciding whether manual SQL is justified, preserving optimistic locking and audit fields, or avoiding N+1 query regressions.
Use this skill for persistence changes in mediask-backend that touch mediask-infra DOs, mappers, repository adapters, or query adapters.
mediask-infra implements ports, mapper stays as the MyBatis boundary, and upper layers do not call mappers directly.mediask-infra, not controller/use case/domain code.BaseDO; if yes, preserve , , and soft-delete semantics.versionupdatedAtUse selectById, selectOne, selectList, insert, updateById, and deleteById when the operation is a straightforward single-table CRUD operation.
Prefer lambda wrappers such as Wrappers.lambdaQuery(...) and Wrappers.lambdaUpdate(...) over string column names when a wrapper is needed.
Do not handwrite SQL or XML just because a query has a few predicates. Ordinary filtered CRUD should stay in MyBatis-Plus.
Custom mapper SQL is justified when at least one of these is true:
If none of those are true, stay with MyBatis-Plus CRUD.
BaseDOBaseDO carries @Version, createdAt, updatedAt, and deletedAt. In this repo, that means updates on BaseDO descendants must preserve:
versionupdatedAt fill via MetaObjectHandlerdeletedAtFor updates to a specific existing row:
updateById(...).id and current version into the update entity.Do not use mapper.update(null, UpdateWrapper...) on BaseDO entities for normal business updates. That pattern can bypass the version plugin and audit fill.
If an update affects 0 rows after loading an existing row, treat it as a possible optimistic-lock conflict and map it to an explicit business conflict instead of a generic system error.
NULLMyBatis-Plus updateById(...) will typically skip null properties under the default field strategy. That means "user cleared this optional field" can silently fail unless the write path is designed for it.
When a business field must support clearing to NULL:
@TableField(updateStrategy = FieldStrategy.ALWAYS) on the specific nullable column, not a global strategy change.updateById(...) with entity id and version so optimistic locking and updatedAt fill still work.mapper.update(null, wrapper) just to force NULL writes on BaseDO entities.updateById(...) and asserts the cleared field is null.Important repo pitfall:
blankToNull(...) does not make the database column clearable by itself.null, the corresponding DO field still needs @TableField(updateStrategy = FieldStrategy.ALWAYS).null reaching updateById(...) while the database row still keeps the old value, because MyBatis-Plus drops that column from the generated UPDATE.Use this only for fields that are intentionally clearable. Do not blanket-apply ALWAYS to unrelated columns.
For write paths:
Do not collapse both into SYSTEM_ERROR.
When reading or updating active business data, include the repo’s effective-row predicates, typically:
deletedAt IS NULLstatus = ACTIVE where the adapter already treats that as the effective rowMatch the existing read adapter semantics. Write adapters should not silently broaden them.
Watch for code shaped like:
That is acceptable only for single-record reads where the cardinality is known to stay tiny and the code path is not a list/batch API.
For list or batch flows, replace N+1 with one of:
IN (...) fetch plus in-memory assemblyWhen reviewing, treat “works for now” as insufficient if the query shape is structurally N+1.
selectOne(Wrappers.lambdaQuery(...))deletedAt IS NULL*_NOT_FOUND business error if missingid, version, and changed fieldsupdateById(...)0, throw an explicit conflict errorupdate(null, wrapper) on a DO that extends BaseDOmediask-infra/src/main/java/me/jianwen/mediask/infra/persistence/base/BaseDO.javamediask-infra/src/main/java/me/jianwen/mediask/infra/persistence/base/MybatisPlusConfig.javamediask-infra/src/main/java/me/jianwen/mediask/infra/persistence/base/AuditFieldMetaObjectHandler.javadocs/docs/02-CODE_STANDARDS.mddocs/docs/03A-JAVA_CONFIG.mdWhen using this skill, explain persistence decisions in concrete terms:
updatedAt are preserved