High-Performance, Feature-Rich MyBatis Enhancement Framework
Bus Mapper is an enterprise-level enhancement framework based on MyBatis. It is designed to enhance without changing the core MyBatis functionality, created to simplify development and improve efficiency.
- Non-Intrusive Enhancement: Seamless integration without affecting existing projects.
- Minimal Performance Overhead: Automatic CRUD injection upon startup with virtually no performance loss.
- Powerful CRUD: Built-in generic Mapper allows for complete single-table operations with minimal configuration.
- Lambda Type Safety: Uses the Fn<T, R> functional interface for compile-time safety checks.
- Flexible Condition Builder: Chain-style API supporting complex dynamic queries.
| Feature | Performance Gain | Description |
|---|---|---|
| Multi-Values Batch Insert | Inserts multiple records in a single SQL statement. | |
| Object Pooling Management |
StringBuilder reuse to reduce memory footprint. |
|
| Intelligent Caching | Multi-level caching for metadata and SQL. | |
| Lock-Free Concurrency | Lock-free design using ConcurrentHashMap. |
|
| Column Selection Optimization | Loads fields on demand, reducing data transfer. |
- Audit Log: Automatically records data changes, operator, SQL statement, and execution time.
- Multi-Tenancy Support: Column isolation, Schema isolation, and independent database modes.
- Permission Control: Data permission filtering to prevent unauthorized access.
- Slow SQL Monitoring: Automatic detection of slow queries, outputting execution time and SQL.
- Data Masking: Automatic sensitive data masking for protected fields.
Supports 18 mainstream and domestic databases:
Mainstream Databases: MySQL / MariaDB, PostgreSQL, Oracle, SQL Server, SQLite, H2, Hsqldb
Domestic Databases: Shenzhou General (Oscar), HandGo Database (CirroData), Xugu Database (Xugudb)
Enterprise Databases: DB2, Informix, AS400, Firebird, Herddb
<dependency>
<groupId>org.miaixz</groupId>
<artifactId>bus-mapper</artifactId>
<version>x.x.x</version>
</dependency><dependency>
<groupId>org.miaixz</groupId>
<artifactId>bus-starter</artifactId>
<version>x.x.x</version>
</dependency># application.yml
spring:
datasource:
name: com_deepparser
url: jdbc:postgresql://localhost:5432/miaixz?useSSL=false&useUnicode=true&characterEncoding=utf8&allowPublicKeyRetrieval=true
username: postgres
password: password
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: org.postgresql.Driver
# Bus Mapper Configuration
bus:
mapper:
basePackage:
ai.deepparser.nexus.mapper
mapperLocations: classpath:mapper/**/*.xml
autoDelimitKeywords: true
reasonable: false
supportMethodsArguments: false
params: count=countSql
configurationProperties:
provider:
useOnce: false
initSize: 1024
concurrency: 1000
dev_db:
table:
prefix: dp_
tenant:
column: tenant_id
ignore: tenant,token,user
test_db:
table:
prefix: dev_
tenant:
column: tenant_id
ignore: tenant,token,user@SpringBootApplication
@EnableMapper
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}@Data
@Table("user")
public class User {
@Id
@KeyType(KeyType.Type.AUTO)
private Long id;
@Column("user_name")
private String name;
private String email;
private Integer age;
@Version
private Integer version;
@TenantId
private String tenantId;
@CreateTime
private LocalDateTime createTime;
}@Repository
public interface UserMapper extends BasicMapper<User, Long> {
// Inheriting BasicMapper is sufficient; no methods need to be written.
}// Insert single record
User user = new User();
user.setName("John Doe");
userMapper.insert(user);
// Insert (non-null fields only)
userMapper.insertSelective(user);
// Batch Insert - High Performance
List<User> users = new ArrayList<>();
// ... Add data
userMapper.insertBatch(users); // 10,000 records only take 150-200ms// Query by Primary Key
User user = userMapper.selectById(1L);
// Query all
List<User> allUsers = userMapper.selectAll();
// Query by Entity Properties
User queryUser = new User();
queryUser.setAge(25);
List<User> users = userMapper.select(queryUser);
// Batch Query
List<User> users = userMapper.selectByIds(Arrays.asList(1L, 2L, 3L));// Update by Primary Key (all fields)
userMapper.updateByPrimaryKey(user);
// Update by Primary Key (non-null fields only)
userMapper.updateByPrimaryKeySelective(user);
// Batch Update
userMapper.updateBatch(users);// Delete by Primary Key
userMapper.deleteById(1L);
// Batch Delete
userMapper.deleteBatchByIds(Arrays.asList(1L, 2L, 3L));// Create condition wrapper
ConditionWrapper<User, Long> wrapper = mapper.wrapper();
// Basic Conditions
List<User> users = wrapper
.eq(User::getAge, 25)
.like(User::getName, "%John%")
.isNotNull(User::getEmail)
.orderBy(User::getCreateTime, Sort.ORDER.DESC)
.list();
// Complex Conditions
List<User> users = wrapper
.eq(User::getStatus, 1)
.like(User::getName, "%Jane%")
.between(User::getAge, 18, 65)
.in(User::getRegion, Arrays.asList("Beijing", "Shanghai", "Shenzhen"))
.orderBy(User::getCreateTime, Sort.ORDER.DESC)
.limit(100)
.list();
// Dynamic Conditions
if (StringKit.isNotBlank(name)) {
wrapper.like(User::getName, "%" + name + "%");
}
if (minAge != null) {
wrapper.ge(User::getAge, minAge);
}
List<User> users = wrapper.list();
// Column Selection (only query necessary fields)
List<User> users = wrapper
.select(User::getId, User::getName, User::getEmail)
.eq(User::getStatus, 1)
.list();
// Paging Query
Page<User> page = wrapper
.eq(User::getStatus, 1)
.orderBy(User::getCreateTime, Sort.ORDER.DESC)
.page(1, 20); // Page 1, 20 records per page
// Count
long count = wrapper
.eq(User::getStatus, 1)
.count();// Use a cursor to avoid loading into memory all at once
try (Cursor<User> cursor = userMapper.selectCursorByCondition(condition)) {
cursor.forEach(user -> processUser(user));
}
// Use Stream API
try (Stream<User> stream = userMapper.selectStreamByCondition(condition)) {
stream.filter(u -> u.getAge() > 18).forEach(System.out::println);
}@Table("user")
@TableAudit // Table-level audit
public class User {
@Audit // Field-level audit
private String email;
}@Configuration
public class MapperConfiguration {
@Bean
public AuditHandler auditHandler() {
// Custom Audit Log Recorder
AuditProvider customLogger = new AuditProvider() {
@Override
public void log(AuditRecord record) {
// Regular SQL record
System.out.println("SQL Execution: " + record.getSqlId());
}
@Override
public void logSlowSql(AuditRecord record) {
// Slow SQL warning
System.out.println("Slow SQL: " + record.getSqlId());
System.out.println("Elapsed Time: " + record.getElapsedTime() + "ms");
System.out.println("SQL: " + record.getSql());
}
@Override
public void logFailure(AuditRecord record) {
// SQL execution failure record
System.err.println("SQL Failure: " + record.getSqlId());
System.err.println("Exception: " + record.getException());
}
};
// Create Audit Configuration
org.miaixz.bus.mapper.support.audit.AuditConfig config =
org.miaixz.bus.mapper.support.audit.AuditConfig.builder()
.enabled(true)
.slowSqlThreshold(1000) // Slow SQL threshold: 1 second
.logParameters(true) // Record SQL parameters
.logResults(false) // Do not record query results
.logAllSql(false) // Only record slow SQL
.auditLogger(customLogger)
.build();
return new AuditHandler(config);
}
@Bean
public MybatisInterceptor mybatisInterceptor(AuditHandler auditHandler) {
MybatisInterceptor interceptor = new MybatisInterceptor();
interceptor.addHandler(auditHandler);
return interceptor;
}
}@Table("user")
public class User {
@TenantId
private String tenantId;
}
// Use TenantContext to set Tenant ID
TenantContext.setCurrentTenantId("tenant_001");
try {
// All queries automatically add tenant filtering
userMapper.selectAll();
} finally {
TenantContext.clear();
}
// Or use Lambda approach (recommended)
TenantContext.runWithTenant("tenant_001", () -> {
userMapper.selectAll();
});@Configuration
public class MapperConfiguration {
@Bean
public TenantHandler tenantHandler() {
// Method 1: Simplest - only need to provide logic to get tenant ID
TenantConfig config = TenantConfig.of(() ->
SecurityContextHolder.getTenantId()
);
return new TenantHandler(config);
}
@Bean
public MybatisInterceptor mybatisInterceptor(TenantHandler tenantHandler) {
MybatisInterceptor interceptor = new MybatisInterceptor();
interceptor.addHandler(tenantHandler);
return interceptor;
}
}@Configuration
public class MapperConfiguration {
@Bean
public TenantHandler tenantHandler() {
// Method 2: Complete Configuration
TenantConfig config = TenantConfig.builder()
.mode(TenantMode.COLUMN)
.column("tenant_id")
.ignoreTables("sys_config", "sys_dict", "sys_log")
.provider(() -> {
// Get Tenant ID from Spring Security
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null && auth.getPrincipal() instanceof UserDetails) {
return ((CustomUserDetails) auth.getPrincipal()).getTenantId();
}
return null;
})
.enabled(true)
.build();
return new TenantHandler(config);
}
// ... MybatisInterceptor setup remains the same
}To temporarily ignore tenant filtering (e.g., for administrator views):
// Temporarily ignore tenant filtering
TenantContext.runIgnoreTenant(() -> {
// Queries here will not include the tenant filter condition
List<User> allUsers = userMapper.selectAll();
});
// Or manually control
TenantContext.setIgnoreTenant(true);
try {
// Query data from all tenants
List<User> allUsers = userMapper.selectAll();
} finally {
TenantContext.setIgnoreTenant(false);
}bus:
mapper:
configurationProperties:
# Data Source 1 Configuration
dev_db:
table:
prefix: dp_
tenant:
column: tenant_id
ignore: sys_config,sys_dict,sys_log
# Data Source 2 Configuration
prod_db:
table:
prefix: prod_
tenant:
column: tenant_id
ignore: sys_config,sys_dict,sys_log| Annotation | Description | Example |
|---|---|---|
@Table |
Specifies the table name | @Table("user") |
@Id |
Marks the primary key field | @Id |
@KeyType |
Primary key generation strategy | @KeyType(KeyType.Type.AUTO) |
@Column |
Specifies the column name | @Column("user_name") |
@Version |
Optimistic locking version number | @Version |
@TenantId |
Tenant ID field | @TenantId |
@CreateTime |
Creation time auto-fill | @CreateTime |
@UpdateTime |
Update time auto-fill | @UpdateTime |
@Ignore |
Ignores the field | @Ignore |
@TableAudit |
Table-level auditing | @TableAudit |
@Audit |
Field-level auditing | @Audit |
public enum Type {
AUTO, // Database auto-increment
IDENTITY, // IDENTITY primary key
UUID, // UUID
SNOWFLAKE, // Snowflake algorithm
SEQUENCE // Sequence
}// ✅ Recommended: Type safe, supports refactoring
wrapper.eq(User::getName, "John Doe")
.gt(User::getAge, 18);
// ❌ Not Recommended: String hardcoding, error prone
wrapper.eq("name", "John Doe")
.gt("age", 18);// ✅ Recommended: High-performance batch insert
userMapper.insertBatch(users); // Single SQL statement, fast
// ❌ Not Recommended: Loop insertion
for (User user : users) {
userMapper.insert(user); // Multiple SQL statements, slow
}// ✅ Recommended: Reduces network transfer
wrapper.select(User::getId, User::getName)
.list();
// ❌ Not Recommended: Queries all fields
wrapper.list(); // SELECT *// ✅ Recommended: Uses cursor, low memory footprint
try (Cursor<User> cursor = userMapper.selectCursorByCondition(condition)) {
cursor.forEach(user -> process(user));
}
// ❌ Not Recommended: Loads all data into memory at once
List<User> users = userMapper.selectByCondition(condition); // Potential OOM// ✅ Recommended: Use Lambda for automatic management
TenantContext.runWithTenant("tenant_001", () -> {
userMapper.selectAll();
});
// ❌ Not Recommended: Manual management can lead to context leakage
TenantContext.setCurrentTenantId("tenant_001");
userMapper.selectAll();
// Easy to forget to call clear()@Configuration
public class KeyGeneratorConfig {
@Bean
public KeyGenerator customKeyGenerator() {
return new KeyGenerator() {
@Override
public Object generateKey() {
// Custom ID generation logic
return IdWorker.getId();
}
};
}
}@Configuration
public class MultiDataSourceConfig {
@Bean
@Primary
public TenantHandler primaryTenantHandler() {
return new TenantHandler(
TenantConfig.builder()
.column("tenant_id")
.provider(() -> getTenantId())
.build()
);
}
@Bean
public TenantHandler secondaryTenantHandler() {
return new TenantHandler(
TenantConfig.builder()
.column("org_id") // Different column name
.provider(() -> getOrgId())
.build()
);
}
}# application.yml
logging:
level:
org.miaixz.bus.mapper: DEBUG
# Or use audit log
bus:
mapper:
audit:
enabled: true
log-all-sql: true
print-console: true// Method 1: Using @KeyType(AUTO)
@Id
@KeyType(KeyType.Type.AUTO)
private Long id;
List<User> users = new ArrayList<>();
userMapper.insertBatch(users);
// The 'id' field in 'users' will be automatically populated.
// Method 2: Using a custom key generator
@Id
@KeyType(KeyType.Type.SNOWFLAKE)
private Long id;@Table("user")
public class User {
@Logic // Logical deletion field
private Integer deleted; // 0-Not deleted, 1-Deleted
}
// Configuration
@Configuration
public class LogicDeleteConfig {
@Bean
public LogicDeleteHandler logicDeleteHandler() {
return LogicDeleteHandler.builder()
.deletedValue(1)
.notDeletedValue(0)
.build();
}
}// Method 1: Throw an exception (strict mode)
TenantConfig config = TenantConfig.builder()
.provider(() -> {
String tenantId = getTenantId();
if (tenantId == null) {
throw new IllegalStateException("Tenant ID cannot be null");
}
return tenantId;
})
.build();
// Method 2: Return a default value
TenantConfig config = TenantConfig.builder()
.provider(() -> {
String tenantId = getTenantId();
return tenantId != null ? tenantId : "default";
})
.build();| Bus Mapper Version | MyBatis Version | Spring Boot Version | JDK Version |
|---|---|---|---|
| 8.x | 3.5.x+ | 3.x+ | 17+ |
| 7.x | 3.5.x+ | 2.x+ | 11+ |
bus:
mapper:
configurationProperties:
provider:
useOnce: false # Disable single-use, enable object reuse
initSize: 1024 # Initial pool size
concurrency: 1000 # Concurrency levelTenantConfig config = TenantConfig.builder()
.enableSqlCache(true) // Enable SQL caching
.build();// Recommended batch size for insertion is 500-1000 records
List<List<User>> batches = Lists.partition(users, 500);
for (List<User> batch : batches) {
userMapper.insertBatch(batch);
}// Only query necessary fields, can reduce network transfer by 50-90%
wrapper.select(User::getId, User::getName)
.list();Based on JMH benchmark results:
| Framework | Time Taken | Performance Increase |
|---|---|---|
| Traditional Loop | - | |
| MyBatis Flex | ||
| Bus Mapper |
| Framework | Average Latency | QPS |
|---|---|---|
| MyBatis Flex | ||
| Bus Mapper |
| Framework | Hit Rate | Time Saved |
|---|---|---|
| MyBatis Flex | ||
| Bus Mapper |
| Metric | Bus Mapper | MyBatis Flex |
|---|---|---|
| Full GC Count | ||
| Total GC Time |
Detailed Report: [可疑链接已删除]
int insert(T entity); // Insert (all fields)
int insertSelective(T entity); // Insert (non-null fields)
int insertBatch(List<T> entities); // Batch insertT selectById(I id); // Query by primary key
List<T> selectByIds(Collection<I> ids); // Batch query
List<T> selectAll(); // Query all
List<T> select(T entity); // Query by entity properties
List<T> selectByCondition(Condition<T> c); // Query by condition
long selectCount(T entity); // Count
Cursor<T> selectCursorByCondition(...); // Cursor queryint updateByPrimaryKey(T entity); // Update by primary key
int updateByPrimaryKeySelective(T entity); // Update by primary key (non-null)
int updateBatch(List<T> entities); // Batch updateint deleteById(I id); // Delete by primary key
int deleteBatchByIds(Collection<I> ids); // Batch delete
int delete(T entity); // Delete by entity properties.eq(User::getName, "John Doe") // Equals
.ne(User::getStatus, 0) // Not Equals
.gt(User::getAge, 18) // Greater Than
.like(User::getName, "%John%") // Like
.between(User::getAge, 18, 65) // Range Between
.in(User::getRegion, list) // In collection
.isNull(User::getEmail) // Is Null
.orderBy(User::getCreateTime, DESC) // Ordering
.select(User::getId, User::getName) // Column selection
.limit(10) // Limit
.list() // Query list
.one() // Query single record
.count() // Count
.page(1, 20) // Pagingmapper:
# Global configuration, effective for all databases
# Tenant Isolation
tenant:
column: tenant_id
ignore: sys_tenant,sys_config,sys_dict
# SQL Auditing
audit:
enabled: true
slow-sql-threshold: 500
log-parameters: true
print-console: true
# Data Population
populate:
created: true
modified: true
creator: true
modifier: true
# Data Visibility
visible:
enabled: true
ignore: sys_admin_table
# Table Prefix
table:
value: prod_
ignore: sys_log,sys_config
# Per-Database Configuration (overrides global)
configurationProperties:
com_deepparser:
table:
prefix: dp_
ignore: tenant,assets,license
tenant:
column: tenant_id
ignore: tenant,assets,license
# ......Would you like to search for a tutorial video on YouTube for getting started with Bus Mapper?