Official site: https://xbatis.cn
DeepWiki documentation: https://deepwiki.com/xbatis/xbatis
Knowledge pack for AI agents working with the Xbatis framework. It distills the official Chinese materials into a self-contained explanation so that automated assistants can understand the framework capabilities, common patterns, and key APIs.
English | 简体中文
- Positioning: Xbatis is built on MyBatis and delivers a highly ORM-like database experience, emphasizing "less SQL, fluent DSL, cross-database compatibility".
- Primary advantages:
- Multi-table joins, subqueries, fluent pagination, automatic SQL optimization (auto-removal of redundant
LEFT JOIN/ORDER BY, smarterCOUNT). - Built-in
RETURNINGsupport, batch insert/update chains, native function wrappers, SQL templates. - Single mapper mode that allows one
BasicMapperto cover every entity. - Database function library, database-specific
dbAdapt, dynamic datasource routing. - Full annotation ecosystem: logical delete, multi-tenancy, optimistic locking, result mapping, condition mapping, dynamic default value injection, etc.
- Multi-table joins, subqueries, fluent pagination, automatic SQL optimization (auto-removal of redundant
- Core characteristics (from the official highlights):
- Extremely lightweight: only wraps MyBatis without invasive modification.
- High performance: keeps execution efficiency close to handwritten SQL.
- Flexible and easy to use: fluent APIs read like natural language.
- Highly available: covers more than 90% of common SQL scenarios.
- Reliable and safe: concise design yet feature-rich, hardened by extensive testing.
- Pagination optimization: automatically tunes
JOIN,COUNT,ORDER BY, and ships with a built-in pager.
| Module | Typical package | Core types | Purpose |
|---|---|---|---|
| Core Mapper | cn.xbatis.core.mybatis.mapper |
MybatisMapper<T>, BasicMapper |
Provides base CRUD plus single-mapper capabilities |
| Fluent DSL | cn.xbatis.core.chain |
QueryChain, InsertChain, UpdateChain, DeleteChain |
Build complex SQL, batch ops, returning clauses |
| Global config | cn.xbatis.core.config |
XbatisGlobalConfig |
Unified naming rules, interceptors, dynamic values, paging |
| Annotation suite | cn.xbatis.db.annotations |
@Table, @TableId, @TableField, @LogicDelete,@LogicDeleteTime, @TenantId, @Version, @Condition, @Fetch, etc. |
Entity mapping, injection rules, condition objects, result shaping |
| Database functions | db.sql.api.impl.cmd |
Methods |
Cross-database functions, SQL templates, fluent wrappers |
| Multi-tenant | cn.xbatis.core.tenant |
TenantContext, TenantId |
Register and propagate tenant IDs globally |
| Dynamic datasource | cn.xbatis.datasource.routing |
@DS, JdbcConfigDecryptor |
Runtime datasource switching, encrypted configs, grouped routing |
| Logical delete | cn.xbatis.core.logic |
LogicDeleteSwitch, LogicDeleteUtil |
Toggle logical delete, easy overrides |
| Dynamic values | cn.xbatis.core.dynamic |
XbatisGlobalConfig#setDynamicValue |
Define tokens like {NOW}, {TODAY} |
| Code generation | cn.xbatis.codegen |
GeneratorConfig and sub modules |
One-stop generation for entities, mapper, service skeletons, etc. |
<dependencyManagement>
<dependencies>
<dependency>
<groupId>cn.xbatis</groupId>
<artifactId>xbatis-spring-boot-parent</artifactId>
<version>1.10.6</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>cn.xbatis</groupId>
<artifactId>xbatis-spring-boot3-starter</artifactId>
</dependency>
</dependencies>spring:
datasource:
url: jdbc:mysql://localhost:3306/dbName
username: dbusername
password: dbpassword@SpringBootApplication
@MapperScan("com.xx.xxx.mapper")
public class XbatisApplication {
public static void main(String[] args) {
SpringApplication.run(XbatisApplication.class, args);
}
}- Maven dependencies (using
xbatis-spring-boot-starter):<dependencyManagement> <dependencies> <dependency> <groupId>cn.xbatis</groupId> <artifactId>xbatis-spring-boot-parent</artifactId> <version>1.10.6</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>cn.xbatis</groupId> <artifactId>xbatis-spring-boot-starter</artifactId> </dependency> </dependencies>
- Datasource & mapper scanning: same as 3.2/3.3; Spring Boot 2 continues to use
application.ymland@MapperScan. - Bootstrap class: identical to 3.3, ensure the starter version matches Spring Boot 2.
- Maven dependencies:
<!-- Order matters: register Xbatis plugin before mybatis-solon-plugin --> <dependency> <groupId>cn.xbatis</groupId> <artifactId>xbatis-solon-plugin</artifactId> <version>1.10.6</version> </dependency> <dependency> <groupId>org.noear</groupId> <artifactId>mybatis-solon-plugin</artifactId> <version>${mybatis.solon.version}</version> </dependency>
- Configuration example:
# solon.yml ds: schema: demo # recommend matching the database name jdbcUrl: jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf8 driverClassName: com.mysql.cj.jdbc.Driver username: root password: 123456 # Rule: mybatis.<datasource bean name> mybatis.master: mappers: - "com.demo.mapper" # package scan - "classpath:mapper/**/*.xml" # XML scan
- Datasource bean:
@Configuration public class MybatisConfig { @Bean(name = "master", typed = true) public DataSource dataSource(@Inject("${ds}") HikariDataSource ds) { return ds; } }
- Business usage:
@Controller public class DemoController { @Db // omit name for single datasource; specify @Db("master") for multi-datasource UserMapper mapper; @Get @Mapping("/test") public List<User> test() { return QueryChain.of(mapper) .like(User::getName, "abc") .list(); } }
- Further configuration aligns with
mybatis-solon-plugin; chaining DSL, sharding, and multi-tenancy remain available. Entity, mapper, and service examples:
@Data
@Table
public class SysUser {
@TableId
private Integer id;
private String userName;
private String password;
private Integer roleId;
private LocalDateTime createTime;
}
public interface SysUserMapper extends MybatisMapper<SysUser> {}
@Service
public class TestService {
@Autowired
private SysUserMapper sysUserMapper;
public Pager<SysUser> demo() {
return QueryChain.of(sysUserMapper)
.eq(SysUser::getId, 1)
.like(SysUser::getUserName, "xxx")
.paging(Pager.of(1, 10));
}
}
> Pager resides in `cn.xbatis.core.mybatis.mapper.context.Pager` and implements `cn.xbatis.page.IPager`. Common usage:
> - Static factories: `Pager.of(size)`, `Pager.of(number, size)`.
> - `setExecuteCount(boolean)`: control whether to run the count query.
> - `paging(Pager)`: pass into chain APIs and read `getResults()`, `getTotal()`, `getNumber()`, `getSize()`, `getTotalPage()`, etc.
> - `PagerGetSetUtil` enables dynamic read/write of extension fields by `PagerField`.- Configure ahead of time via Spring
ConfigurationCustomizeror@PostConstruct. - Common methods:
setSingleMapperClass(MybatisBasicMapper.class): enable single-mapper mode withBasicMapper.setTableUnderline(boolean)/setColumnUnderline(boolean): control table/column naming.setDatabaseCaseRule(DatabaseCaseRule rule)orsetDatabaseCaseRule(DbType, rule): adjust casing strategy.setDynamicValue("{KEY}", (clazz, type) -> {...})andgetDynamicValue(clazz, type, key): register and use dynamic placeholders.setLogicDeleteInterceptor((entity, update) -> {...})andsetLogicDeleteSwitch(boolean): standardize logical delete metadata & toggles.addMapperMethodInterceptor(new MyInterceptor()),enableInterceptOfficialMapperMethod(): register custom mapper interceptors, optionally extending official methods.setPagingProcessor(DbType, PagingProcessor): customize pagination for each database.
| Property | Nullable | Default | Description |
|---|---|---|---|
value |
Yes | – | Table name; defaults to camelCase-to-snake-case of entity name |
schema |
Yes | – | Database schema |
columnNameRule |
Yes | columnNameRule.IGNORE |
Rule to derive column names when not specified |
databaseCaseRule |
Yes | DatabaseCaseRule.DEFAULT |
Controls casing; works with XbatisGlobalConfig.setDatabaseCaseRule(...) |
Example:
@Table(databaseCaseRule = DatabaseCaseRule.UPPERCASE)
public class SysUser { }| Property | Nullable | Default | Description |
|---|---|---|---|
value |
Yes | IdAutoType.AUTO |
Primary key strategy: AUTO, NONE, SQL, GENERATOR |
dbType |
Yes | – | Target database type for differentiating strategies |
sql |
Yes | – | Required when value = SQL; custom SQL for ID retrieval |
generator |
Yes | – | Required when value = GENERATOR; refers to registered ID generator |
Repeatable to accommodate multiple database strategies.
| Property | Nullable | Default | Description |
|---|---|---|---|
value |
Yes | – | Column name; can be omitted when camelCase-to-snake-case applies |
select |
Yes | true |
Whether to participate when calling select(Entity.class) |
insert |
Yes | true |
Whether to write during save |
update |
Yes | true |
Does it update when updating? It will update when forced |
neverUpdate |
Yes | false |
Never update, unless specified with Updating Chain, it will only be updated, and it will not be forced to update either |
jdbcType |
Yes | – | Explicit JDBC type |
typeHandler |
Yes | – | Custom type handler |
defaultValue |
Yes | – | Insert default; supports static values and dynamic tokens like {NOW} |
updateDefaultValue |
Yes | – | Default during updates |
defaultValueFillAlways |
Yes | false |
Always apply defaultValue on insert |
updateDefaultValueFillAlways |
Yes | false |
Always apply updateDefaultValue |
exists |
Yes | true |
Whether the field physically exists (false for transient fields) |
Logical delete annotation.
| Property | Nullable | Default | Description |
|---|---|---|---|
beforeValue |
Yes | – | Value before deletion; null treated as NULL |
afterValue |
No | – | Value after deletion; supports dynamic tokens {NOW}, etc. |
Combine with XbatisGlobalConfig.setLogicDeleteInterceptor to fill deleter metadata.
Field name for logic delete timestamp; supports LocalDateTime, Date, Long (ms), Integer (seconds)
No extra properties. Marks tenant ID fields; automatically filled on save/update/delete and appended to query
conditions. Register tenant suppliers through TenantContext.registerTenantGetter (
cn.xbatis.core.tenant.TenantContext).
No extra properties. Marks optimistic-lock version fields. save sets it to 0; update/delete append
WHERE version = ? and successful updates increment it automatically.
cn.xbatis.db.annotations.OnUpdate)
| Annotation | Property | Nullable | Default | Description |
|---|---|---|---|---|
@OnInsert |
value |
Yes | – | Assign pre-insert listener implementing OnInsertListener<T> |
@OnUpdate |
value |
Yes | – | Assign pre-update listener implementing OnUpdateListener<T> |
Besides local listeners, register global handlers via XbatisGlobalConfig.setGlobalOnInsertListener /
setGlobalOnUpdateListener.
5.8 Result-mapping annotations (@ResultEntity / @ResultField / @Fetch, etc. under cn.xbatis.db.annotations)
-
@ResultEntity: declare VO ↔ entity mapping and auto-completeSELECTcolumns. -
@ResultField:| Property | Nullable | Default | Description | | --- | --- | --- | --- | |
value| Yes | – | Column(s) mapped to the field; supports multi-column inputs | |jdbcType| Yes | – | JDBC type | |typeHandler| Yes | – | Custom type handler | -
@Fetch:| Property | Nullable | Default | Description | | --- | --- | --- | --- | |
column| Yes | – | Matching column; alternate toproperty, takes precedence | |property| No | – | Source entity property | |source| No | – | Source entity type | |storey| Yes |-1| Source hierarchy; autodetect by default | |target| No | – | Target entity (to be fetched) | |targetProperty| No | – | Target entity join column | |targetSelectProperty| Yes | – | Selected columns or expressions | |middle| No | – | Middle entity (join table) | |middleSourceProperty| No | – | Middle entity column bound to source | |middleTargetProperty| No | – | Middle entity column bound to target | |orderBy| Yes | – | Order clause, e.g.,"[{xx} desc]"or"field desc"| |multiValueErrorIgnore| Yes |false| Ignore multi-row result when expecting 1-to-1 | |limit| Yes |0| Limit result size; 0 = unlimited | |memoryLimit| Yes |false| Apply limit in-memory with IN to reduce queries | |nullFillValue| Yes |null| Default when result is empty | |otherConditions| Yes | – | Extra conditions, e.g.,[{Type} = 2]| -
AI generation tip: prefer using
@ResultEntityand the related annotations (@ResultField,@NestedResultEntity,@NestedResultEntityField,@ResultCalcField,@Fetch,@PutEnumValue,@PutValue, etc.) on VO/DTOs for direct return types, eliminating manual transformation. Do not annotate entities with these; keep@TypeHandler,@Put***on result objects only to avoid polluting entity models. -
@NestedResultEntity(cn.xbatis.db.annotations.NestedResultEntity):| Property | Nullable | Default | Description | | --- | --- | --- | --- | |
target| Yes | – | Target entity; defaults to@ResultEntitytarget | |storey| Yes |1| Storage level; differentiate layers in self-joins | Declaring VO fields as nested objects automatically fills nested results recursively. -
@NestedResultEntityField(cn.xbatis.db.annotations.NestedResultEntityField):| Property | Nullable | Default | Description | | --- | --- | --- | --- | |
value| Yes | – | Field name inside the nested entity | Use when VO field names differ from entity fields. Works well with Lombok@FieldNameConstants. -
@ResultCalcField(cn.xbatis.db.annotations.ResultCalcField):| Property | Nullable | Default | Description | | --- | --- | --- | --- | |
value| No | – | Calculation SQL, e.g.,count(1),sum({id})| |target| Yes | – | Target entity; defaults to@ResultEntitytarget | |storey| Yes |1| Storage level | Whenselect(VO.class), automatically produces aggregate columns. Use{field}placeholders for dynamic columns. -
@PutEnumValue(cn.xbatis.db.annotations.PutEnumValue):| Property | Nullable | Default | Description | | --- | --- | --- | --- | |
source| No | – | Source entity | |property| No | – | Field storing enum code | |storey| Yes |1| Storage level | |target| No | – | Enum class | |code| No |code| Field name representing the code | |value| No |name| Field name representing display value | |required| No |false| Throw if enum not found | |defaultValue| Yes | – | Fallback when enum missing | Suitable for auto-converting status codes to labels; supports nested mappings and multi-column matching. -
@PutValue(cn.xbatis.db.annotations.PutValue):| Property | Nullable | Default | Description | | --- | --- | --- | --- | |
source| No | – | Source entity | |property| No | – | Source field | |storey| Yes |1| Storage level | |factory| No | – | Factory class providing static method | |method| No | – | Method name inside factory | |required| No |false| Throw if value missing | |defaultValue| Yes | – | Default value | Supports injecting dynamic values based on multiple fields. Results are cached per session usingfactory+method+args.
Combine these annotations: use @ResultEntity for overall mapping, @NestedResultEntity for nested structure,
@ResultCalcField for aggregates, and @PutEnumValue/@PutValue for extra data—delivering near-entity auto-wiring for
VOs.
-
@SplitTable:| Property | Nullable | Default | Description | | --- | --- | --- | --- | |
value| No | – |TableSplitterimplementation used to compute actual table name | -
@SplitTableKey: marks the sharding key field; supports a single column and takes sharding input at runtime.
TableSplitter interface methods:
boolean support(Class<?> type);
String split(String sourceTableName, Object splitValue);support declares supported key types; split returns actual table names like sys_user_0 ~ sys_user_9.
Queries/updates must include the shard key so Xbatis can locate the table.
| Property | Nullable | Default | Description |
|---|---|---|---|
value |
No | – | Target entity; defaults to current class if omitted |
logic |
Yes | Logic.AND |
Default top-level logic; set to Logic.OR for OR combinations |
| Property | Nullable | Default | Description |
|---|---|---|---|
value |
Yes | Condition.Type.EQ |
Condition type, e.g., LIKE, GT, BETWEEN, EXISTS |
target |
Yes | – | Target entity; defaults to @ConditionTarget or current class |
property |
Yes | – | Target property name; defaults to field name |
storey |
Yes | – | Source storage level for nested objects |
likeMode |
Yes | – | LEFT, RIGHT, BOTH, etc. |
toEndDayTime |
Yes | false |
Auto-extend to end of day for LTE or second parameter of BETWEEN |
defaultValue |
Yes | – | Static value or dynamic key {NOW}, {TODAY}, custom entries |
| Property | Nullable | Default | Description |
|---|---|---|---|
value |
Yes | – | Multiple @Condition elements, often for keyword multi-column search |
logic |
Yes | Logic.OR |
Logic among combined conditions |
| Property | Nullable | Default | Description |
|---|---|---|---|
value |
Yes | – | Grouped fields |
logic |
Yes | Logic.AND |
Group logic; change to Logic.OR as needed |
If DTO implements ObjectConditionLifeCycle (cn.xbatis.core.sql.ObjectConditionLifeCycle), it can preprocess input in
beforeBuildCondition() (e.g., derive past 7 days based on timeType) and cooperate with the annotations above for
deep nesting.
MybatisMapper<T> (cn.xbatis.core.mybatis.mapper.MybatisMapper) ships with:
-
Read:
get,list,listAll,listByIds,cursor,exists,count,mapWithKey,page, etc., accepting lambdaWherebuilders. -
Create:
save,saveOrUpdate,saveBatch,saveModel,saveModelBatch,saveBatch(list, fields), supporting entity andModel. -
Update:
update,updateBatch,update(model, where),update(list, forceFields),saveOrUpdate, plus forced update fields and batchCASE WHENupdates. -
Delete:
deleteById,deleteByIds,delete(entity),delete(where),deleteAll, andDeleteChain. -
Single-mapper mode (for large entity counts or unified data-access layers):
- Define your own MybatisBasicMapper interface extending BasicMapper
public interface MybatisBasicMapper extends BasicMapper { }
BasicMapperlives incn.xbatis.core.mybatis.mapper.BasicMapperand wraps all general CRUD. - Configure scanning (Spring Boot example)
Generates a single mapper bean instead of one per entity.
@MapperScan(basePackageClasses = MybatisBasicMapper.class, markerInterface = BasicMapper.class)
- Register the global mapper
Call
XbatisGlobalConfig.setSingleMapperClass(MybatisBasicMapper.class);during startup to designate the default entry. - Usage pattern
Combine with
@Autowired private MybatisBasicMapper mybatisBasicMapper; public void demo() { // Entity CRUD mybatisBasicMapper.save(new SysUser()); mybatisBasicMapper.deleteById(SysUser.class, 1); // Fluent DSL: explicitly pass entity class QueryChain.of(mybatisBasicMapper, SysUser.class) .eq(SysUser::getId, 1) .list(); UpdateChain.of(mybatisBasicMapper, SysUser.class) .set(SysUser::getUserName, "basic") .eq(SysUser::getId, 1) .execute(); }
BasicDaoImpl(cn.xbatis.core.mvc.impl.BasicDaoImpl) to keep a classic three-layer architecture. - Handling complex SQL
Single mapper still cooperates with XML using namespace
xxx.MybatisBasicMapperandwithSqlSession:List<SysRole> roles = mybatisBasicMapper.withSqlSession( SysRole.class, "selectByIds", params, (statement, p, sqlSession) -> sqlSession.selectList(statement, p) );
- Notes
- DSL requires explicit entity class to resolve table/columns.
- You can keep traditional mappers alongside single mapper, but maintain consistent conventions.
- XML
<select id="EntityName:method">naming must align withwithSqlSessioncalls.
- Define your own MybatisBasicMapper interface extending BasicMapper
Xbatis save / saveBatch / InsertChain support cross-database duplicate-key strategies (ignore or update on
conflict).
| Database | Ignore on conflict | Update on conflict |
|---|---|---|
| MySQL | ✅ | ✅ |
| MariaDB | ✅ | ✅ |
| Oracle | ✅ | ❌ |
| H2 | ✅ | ✅ |
| PostgreSQL | ✅ | ✅ |
| Kingbase | ✅ | ✅ |
| SQLite | ✅ | ✅ |
| openGauss | ✅ | ✅ |
onConflict lives in cn.xbatis.core.mybatis.mapper.context.strategy.SaveStrategy / SaveBatchStrategy and the chain
SQL interface db.sql.api.cmd.executor.IInsert, building portable statements via IConflictAction.
MultiPk entity = new MultiPk();
entity.setId1(1);
entity.setId2(2);
entity.setName("init");
mapper.save(entity, strategy -> {
strategy.onConflict(action -> action.doNothing()); // ignore
// or update on conflict (overwrite all fields)
// strategy.onConflict(action -> action.doUpdate(update -> update.overwriteAll()));
});List<MultiPk> list = List.of(entity1, entity2);
// mapper.save(List<T>, ...)
mapper.save(list, strategy ->
strategy.onConflict(action -> action.doUpdate(update -> update.overwrite(MultiPk::getName)))
);
// mapper.saveBatch(List<T>, ...)
mapper.saveBatch(list, strategy ->
strategy.onConflict(action -> action.doNothing())
);overwriteAll() updates every column; overwrite(Entity::getField) targets specific columns.
InsertChain.of(sysUserMapper)
.insert(SysUser.class)
.values(Arrays.asList("basic", "123456"))
.onConflict(action -> action.doUpdate(update -> update.overwrite(SysUser::getPassword)))
.execute();Use dbAdapt inside the strategy for database-specific tuning.
cn.xbatis.core.chain.QueryChain constructs combinations of select, from, join, where, groupBy, having,
orderBy, pagination, nested conditions, and subqueries.
- AI generation tip: default to Xbatis condition helpers such as
.forSearch(true)oreq(field, value, predicate)/eq(boolean, field, value)instead of manualif/elseonWHEREclauses to keep code tidy and robust.
Example:
SysUserRoleVo vo = QueryChain.of(sysUserMapper)
.select(SysUser.class, SysRole.class)
.from(SysUser.class)
.join(SysUser::getRoleId, SysRole::getId)
.eq(SysUser::getId, 1)
.like(SysUser::getUserName, "abc")
.groupBy(SysUser::getId)
.having(SysUser::getId, c -> c.count().gt(0))
.orderBy(SysUser::getId)
.returnType(SysUserRoleVo.class)
.get();Key capabilities:
selectaccepts entities, VO classes, lambda fields, function wrappers, ignored columns.joinsupportsinner,left,right; leverage@ForeignKeyfor automatic join conditions.andNested/orNestedadd parentheses;and()/or()control subsequent logic.- Condition methods include
eq,ne,gt,gte,lt,lte,between,notBetween,in,notIn,like,notLike,isNull,isNotNull,empty,notEmpty,exists,notExists. Each accepts a predicate argument for conditional inclusion (e.g.,eq(SysUser::getId, id, Objects::nonNull)). Global toggles include.ignoreNullValueInCondition(true),.ignoreEmptyInCondition(true). forSearch(true)simultaneously ignores null/blank values and trims strings—ideal for search forms.returnType(...)is recommended right before terminal operations, after conditions/limits.where(queryObject)converts annotated DTO fields into conditions.dbAdapt(selector -> ...)builds DB-specific behavior.- Terminal APIs:
get,list,count,exists,paging(Pager),cursor,mapWithKey.
cn.xbatis.core.chain.InsertChain supports INSERT ... VALUES and INSERT ... SELECT combos:
InsertChain.of(sysUserMapper)
.insert(SysUser.class)
.fields(SysUser::getUserName, SysUser::getRoleId)
.fromSelect(Query.create()
.select(SysUser2::getUserName, SysUser2::getRoleId)
.from(SysUser2.class)
)
.execute();Add values repeatedly for batch inserts.
cn.xbatis.core.chain.UpdateChain enables dynamic set, arithmetic updates, RETURNING, nested conditions:
SysUser user = UpdateChain.of(sysUserMapper)
.update(SysUser.class)
.set(SysUser::getUserName, "new name")
.set(SysUser::getVersion, c -> c.plus(1))
.eq(SysUser::getId, 1)
.returning(SysUser.class)
.returnType(SysUser.class)
.executeAndReturning();cn.xbatis.core.chain.DeleteChain builds delete conditions and can return affected entities:
List<SysUser> removed = DeleteChain.of(sysUserMapper)
.in(SysUser::getId, 1, 2)
.returning(SysUser.class)
.returnType(SysUser.class)
.executeAndReturningList();- Mark DTOs with
@ConditionTarget. - Use
@Conditionfor EQ, NE, GT, LT, BETWEEN, EXISTS, LIKE, etc., specifying target property, defaults,toEndDayTime,likeMode. @Conditionsmaps a single field to multiple columns with AND/OR control.@ConditionGroupgroups and nests logic.- Implement
ObjectConditionLifeCyclefor preprocessing (e.g., deriving date ranges from enums) before building conditions. - Sorting uses similar patterns via condition annotations and helper utilities.
Example:
@Data
@ConditionTarget(SysUser.class)
public class QueryREQ {
private Integer id;
@Condition(value = Condition.Type.LIKE)
private String userName;
@Conditions(
logic = Logic.OR,
value = {
@Condition(property = SysUser.Fields.userName, value = Condition.Type.LIKE),
@Condition(property = SysUser.Fields.password, value = Condition.Type.LIKE)
}
)
private String keyword;
}- Import
import static db.sql.api.impl.cmd.Methods.*;for unified functions. - Supported functions include
count,sum,avg,min,max,abs,ceil,floor,rand,sign,pi,truncate,round,pow,sqrt,exp,mod,log,sin,cos,tan,charLength,concat,upper,lower,substring,currentDate,dateDiff,dateAdd,inetAton, plus MySQL-specificfindInSet,md5,jsonExtract,groupConcat, etc. - SQL template system:
Methods.tpl– general SQL template with placeholders{0},{1}.Methods.fTpl– function template, chainable with other functions.Methods.cTpl– condition template for complexWHEREfragments.- Templates auto-escape single quotes; use
Methods.cTpl(true, "...", ...)for automatic'handling.
Example:
QueryChain.of(sysUserMapper)
.select(SysUser::getRoleId, c -> Methods.tpl("count({0})+{1}", c, "1"))
.and(GetterFields.of(SysUser::getId, SysUser::getId),
cs -> Methods.cTpl("{0}+{1}={2}", cs[0], cs[1], 2));QueryChain,UpdateChain,DeleteChain,InsertChainall supportdbAdapt:
QueryChain.of(sysUserMapper)
.select(SysUser::getId)
.dbAdapt((query, selector) -> selector
.when(DbType.H2, db -> query.eq(SysUser::getId, 3))
.when(DbType.MYSQL, db -> query.eq(SysUser::getId, 2))
.otherwise(db -> query.eq(SysUser::getId, 1))
)
.get();- You can also call
mapper.dbAdapt(selector -> {...})for entirely different flows.
- Add
cn.xbatis:xbatis-datasource-routing. - Configure
spring.ds.routing.*for multiple datasources, master-slave, replica groups, and pool properties (supports Hikari, Druid, custom). - Annotate classes/methods with
@DS("master"),@DS("slave")to switch datasource. - Notes:
- Transaction propagation: use
@Transactional(propagation = Propagation.NOT_SUPPORTED)orREQUIRES_NEWwhen switching datasources mid-call. - For self-invocation within same class, use
AopContext.currentProxy()or split the class. spring.ds.jdbc-config-decrypt=truewithJdbcConfigDecryptorenables encrypted JDBC configs.- Supports distributed transaction frameworks like Seata (
spring.ds.seata=true).
- Transaction propagation: use
- Annotate entity fields with
@TenantId; automatically fills tenant ID on write and appends conditions for query/update/delete. - Register tenant suppliers via
TenantContext.registerTenantGetter(() -> tenantId)(cn.xbatis.core.tenant.TenantContext), returning a single ID orTenantId(multi-tenant set). - Return
nullfrom supplier to temporarily disable tenant enforcement. - ThreadLocal usage: wrap helper like
TenantTLUtilto set/clear tenants in filters/interceptors and return viaTenantContext.
@LogicDelete(beforeValue = "0", afterValue = "1")controls before/after markers and timestamp.@LogicDeleteTimecontrols logic delete time markersXbatisGlobalConfig.setLogicDeleteInterceptorcan fill deleter info automatically.- Toggle globally via
setLogicDeleteSwitch(true/false); locally via:
try (LogicDeleteSwitch ignored = LogicDeleteSwitch.with(false)) {
mapper.getById(1);
}
// or
LogicDeleteUtil.execute(false, () -> mapper.getById(1));- Note:
DeleteChainperforms physical delete; only mapper delete methods respect logical delete.
@Versionfields default to 0 onsave.update/deleteappendWHERE version=?; updates increment version on success.
- Annotate entity with
@SplitTable(TableSplitterClass.class);valuepoints toTableSplitterimplementation. - Annotate sharding key with
@SplitTableKey; only single-column sharding is supported. TableSplitter#support(Class<?>)declares supported key types;split(String, Object)returns real table name (e.g.,sys_user_3).- Chains behave like regular entities, but queries must include sharding key conditions to resolve actual tables.
- Works seamlessly with multi-db, tenant, logical delete, etc.
- Built-in tokens:
{BLANK},{NOW}(supportsLocalDateTime,LocalDate,Date,Long,Integer,String),{TODAY}(date range). - Custom dynamic example:
XbatisGlobalConfig.setDynamicValue("{day7}", (clazz, type) -> new LocalDate[]{
LocalDate.now().minusDays(7), LocalDate.now()
});- Fetch dynamic value inside condition objects via
XbatisGlobalConfig.getDynamicValue(clazz, LocalDate[].class, "{day7}"). - Global listeners:
XbatisGlobalConfig.setGlobalOnInsertListener(entity -> {
// fill creator, etc.
});
XbatisGlobalConfig.setGlobalOnUpdateListener(entity -> {
// fill updater, etc.
});- Template mechanism enables reusable SQL fragments, function wrappers, condition composition without string splicing.
- Fully compatible with traditional MyBatis XML; use naming conventions in single-mapper mode:
<mapper namespace="xxx.MybatisBasicMapper">
<select id="SysRole:selectByIds" resultType="com.xbatis.core.test.DO.SysRole">
select * from t_sys_role
where id >= #{id} and id <= #{id2}
order by id asc
</select>
</mapper>Invocation:
List<SysRole> roleList = mybatisBasicMapper.withSqlSession(
SysRole.class, "selectByIds", params,
(statement, p, sqlSession) -> sqlSession.selectList(statement, p)
);- XML works alongside chain DSL: call templates within chains or pass dynamic params from XML as needed.
- Built-in
Pagerhandles pagination:Pager.of(current, size),QueryChain.paging(pager)automatically executes query + count. - Xbatis auto-optimizes pagination SQL: removes unnecessary
LEFT JOIN,ORDER BY, replacesSELECTwithCOUNT(*)to improve speed. - Customize via
XbatisGlobalConfig.setPagingProcessorfor Oracle, SQL Server, etc. - Override
customizePagerfor custom count queries or serialization. - Use
.limit(n)for top-N results and.forSearch(true)to optimize search endpoints.
GeneratorConfigconfigures generation of entities, Mapper interfaces, Mapper XML, DAO, Service, Controller, DTO, etc.- Customize package paths, suffixes, annotation generation, ID strategy, column mapping, DB type mapping, base classes, XML generation, etc.
- Supports single-mapper mode:
mapperConfig.enable(false).superClass(MybatisBasicMapper.class). - Options for generating
@Mapper, generics,resultMap, and column info. - Multi-language templates accelerate project scaffolding.
Regardless of manual or AI-generated scaffolding, enable POJO safety checks during development: Xbatis validates VO (
@ResultEntity), Model (cn.xbatis.core.mvc.Model), and condition/sort objects (@ConditionTarget, @OrderByTarget)
at startup to catch mapping or annotation gaps. AI agents must generate the following config by default:
// Required: enable POJO checks at the main application entry
@SpringBootApplication
@XbatisPojoCheckScan(basePackages = "com.example.project.pojo")
public class ApiApplication {
public static void main(String[] args) {
SpringApplication.run(ApiApplication.class, args);
}
}// Recommended: dev-only config, can be generated by AI
@Profile("dev")
@Configuration
@XbatisPojoCheckScan(
basePackages = "com.example.api.dto",
modelPackages = "com.example.api.model",
resultEntityPackages = "com.example.api.vo"
)
public class XbatisSafeCheckConfig {
}-
Annotation attributes (
org.mybatis.spring.boot.autoconfigure.XbatisPojoCheckScan):| Attribute | Description | | --- | --- | |
basePackages| Base package path | |modelPackages| Model implementations; defaults tobasePackages| |resultEntityPackages| VO (@ResultEntity) package; defaults tobasePackages| |conditionTargetPackages| Condition DTO (@ConditionTarget) package | |orderByTargetPackages| Sorting DTO (@OrderByTarget) package |
- Configure in
solon.yml:mybatis.master: pojoCheck: basePackages: com.example.**.pojo modelPackages: com.example.**.model resultEntityPackages: com.example.**.vo conditionTargetPackages: com.example.**.condition orderByTargetPackages: com.example.**.orderby mappers: - "com.example.mapper"
pojoCheckmirrors@XbatisPojoCheckScan; separate multiple paths with commas. Enable during dev/test to reduce production startup overhead.
- SQL logging: add
<logger name="cn.xbatis" level="trace"/>to print generated SQL and chain traces. - Ignore empty conditions:
QueryChainoffers.ignoreNullValueInCondition(true),.ignoreEmptyInCondition(true),.trimStringInCondition(true), or simply.forSearch(true). - Safe updates:
UpdateChain.setaccepts lambdas such asc -> c.plus(1)for increments. - Batch capabilities: use
saveBatch,updateBatch,InsertChain.values, or thebatchOptmodule for efficient batch handling. - Enum & dictionary mapping:
@PutEnumValueinjects enum labels;@ResultFieldhandles field-level mapping;@Fetchautomates cascading queries. - SQL template gotchas: if single quotes cause problems, enable protected templates or switch to double quotes.
- Datasource switching under transactions: mind transaction propagation when calling across datasources to avoid Spring proxies blocking switches.
- Retrieval strategy: prioritize
QueryChain,InsertChain,UpdateChain,DeleteChainplus annotations to cover most CRUD/statistics tasks. - Code generation tips:
- Always use method references (e.g.,
SysUser::getId) instead of hard-coded column names. - Search endpoints should call
.forSearch(true)or rely on annotated DTOs to avoid manual null checks. - For multi-database scenarios, add
dbAdapt; multi-tenant scenarios should wireTenantContextautomatically. - When updates/deletes must return values, use
.executeAndReturning()or.executeAndReturningList().
- Always use method references (e.g.,
- Project structure tips:
- Single-mapper mode suits large projects; combine
BasicMapperwith DSL for unified data access. - Document dynamic datasource, tenant, and logical delete switches explicitly in generated code.
- Use
GeneratorConfigfor initial scaffolding, then have the agent generate incremental chains/service logic.
- Single-mapper mode suits large projects; combine
- Security & auditing: leverage
@OnInsert,@OnUpdate, logical delete interceptors, tenant context to auto-fill creator/update/tenant fields and satisfy audit requirements.
Set databaseId explicitly to avoid detection overhead:
mybatis:
configuration:
databaseId: MYSQL # see db-support.md for available IDs- When query,
from, andreturnTypeall target the mapper entity, omit them:
SysUser user = QueryChain.of(sysUserMapper)
.eq(SysUser::getId, 1)
.get();-
If
select(VO.class)equalsreturnType(VO.class), just callreturnType; the framework auto-selects columns based on VO annotations. -
Bulk ignore:
.forSearch(true)enables ignore-null/blank and auto-trim; use it by default. -
Precise ignore:
QueryChain.of(sysUserMapper)
.eq(SysUser::getId, id, Objects::nonNull)
.like(SysUser::getUserName, userName, StringUtils::isNotBlank)
.get();- Combine
@ResultEntity+@NestedResultEntityfor nested structures; omit attributes when VO fields match entity fields. - Recommend using Lombok
@FieldNameConstantsand referencingX.Fields.xxxin annotations to avoid string literals.
connect exposes the chain itself for correlated subqueries:
QueryChain.of(sysUserMapper)
.select(SysUser::getId, SysUser::getUserName)
.connect(query -> query.exists(SubQuery.create()
.select1()
.from(SysUser.class)
.eq(SysUser::getId, query.$(SysUser::getId))
.isNotNull(SysUser::getPassword)
.limit(1)))
.list();Static-import db.sql.api.impl.cmd.Methods.* for concise chains:
Integer id = QueryChain.of(sysUserMapper)
.select(SysUser::getId, c -> sum(add(c, 1)))
.returnType(Integer.TYPE)
.get();These convenience patterns greatly enhance productivity and should be default assumptions when designing AI generation strategies.
Bundle this knowledge base with project source code and dependency API docs to build a vector store so AI agents can generate Xbatis-compliant code and configuration accurately.***