一个轻量级的 MyBatis 增强工具库,免写 XML、免写 Mapper 接口,一个
DbUtil静态类搞定动态 SQL 查询、实体对象 CRUD、分页、事务管控,并提供编译期类型安全的 SQL DSL 构建器。
| 特性 | 描述 |
|---|---|
| 免 XML 动态 SQL | 通过 DbUtil 直接传入 SQL 字符串或 DSL 构建器执行查询,无需编写 Mapper XML |
| SelectBuilder DSL | 完整的面向对象链式 SQL 构建器,支持子查询、多表 JOIN、EXISTS、OR 嵌套等复杂场景 |
| 强类型元模型 (APT) | 编译期自动生成 Q 开头的表元模型类,媲美 QueryDSL/jOOQ 的类型安全体验 |
| 实体对象 CRUD | 通过 JPA 注解(@Table、@Column、@Id、@Transient)自动映射,免写 SQL |
| 主键生成策略 | @KeySql 注解支持 IDENTITY 自增、UUID、SQL 序列、自定义生成器,插入后自动回填 |
| 分页查询 | 自动检测数据库方言,内置 MySQL、PostgreSQL、Oracle、SQL Server、达梦等分页支持 |
| 事务管控 | Spring 环境自动参与 @Transactional;非 Spring 环境提供 executeInTransaction 手动事务 |
| Spring Boot 自动配置 | 引入依赖即生效,零配置自动初始化 |
| 批量插入 | 基于 JDBC Batch 的高性能批量插入,支持事务参与和主键回填 |
- Java 8+
- MyBatis 3.5+
引入 Starter 即可零配置自动生效,自动注入 SqlSessionFactory 并初始化 DbUtil,所有 SQL 操作自动参与 Spring @Transactional 事务管理。
<dependency>
<groupId>io.github.juoliii</groupId>
<artifactId>mybatis-helper-springboot-starter</artifactId>
<version>1.0.3</version>
</dependency>仅引入核心库,需手动初始化:
<dependency>
<groupId>io.github.juoliii</groupId>
<artifactId>mybatis-helper-core</artifactId>
<version>1.0.3</version>
</dependency>// 手动初始化
DbUtil.init(sqlSessionFactory);无需编写 Mapper 接口或 XML 文件,直接通过 DbUtil 静态方法执行 SQL。所有查询均支持 MyBatis #{...} 占位符防止 SQL 注入。
// 查询列表 — 返回 Map
List<Map<String, Object>> list = DbUtil.selectList("SELECT * FROM sys_user WHERE status = #{status}", params);
// 查询列表 — 映射到实体类
List<SysUser> users = DbUtil.selectList("SELECT * FROM sys_user WHERE status = 1", SysUser.class);
// 查询单条
SysUser user = DbUtil.selectOne("SELECT * FROM sys_user WHERE id = #{id}", params, SysUser.class);
// 插入
int rows = DbUtil.insert("INSERT INTO sys_user(name) VALUES(#{name})", params);
// 更新
DbUtil.update("UPDATE sys_user SET status = 1 WHERE id = #{id}", params);
// 删除
DbUtil.delete("DELETE FROM sys_user WHERE status = -1");QueryWrapper 提供链式 API 构建 WHERE 条件,所有参数自动使用预编译占位符防止 SQL 注入。
QueryWrapper wrapper = new QueryWrapper()
.eq("status", 1) // status = ?
.like("name", "test") // name LIKE '%test%'
.in("type", Arrays.asList(1, 2, 3)) // type IN (?, ?, ?)
.between("create_time", startDate, endDate) // create_time BETWEEN ? AND ?
.isNotNull("email") // email IS NOT NULL
.orderByDesc("create_time"); // ORDER BY create_time DESC
List<SysUser> users = DbUtil.selectByQuery("SELECT * FROM sys_user", wrapper, SysUser.class);| 方法 | 说明 | 示例 |
|---|---|---|
eq(column, value) |
等于 | eq("status", 1) |
ne(column, value) |
不等于 | ne("status", -1) |
gt(column, value) |
大于 | gt("age", 18) |
ge(column, value) |
大于等于 | ge("score", 60) |
lt(column, value) |
小于 | lt("price", 100) |
le(column, value) |
小于等于 | le("amount", 999) |
like(column, value) |
模糊匹配(自动添加 %) |
like("name", "test") |
likeLeft(column, value) |
左模糊 %value |
likeLeft("name", "test") |
likeRight(column, value) |
右模糊 value% |
likeRight("name", "test") |
in(column, values) |
IN 查询 | in("type", list) |
notIn(column, values) |
NOT IN | notIn("type", list) |
between(column, start, end) |
范围 | between("age", 18, 30) |
notBetween(column, start, end) |
不在范围 | notBetween("age", 18, 30) |
isNull(column) |
IS NULL | isNull("email") |
isNotNull(column) |
IS NOT NULL | isNotNull("email") |
apply(sql) |
原生 SQL 片段 | apply("DATE(create_time) = CURDATE()") |
所有条件方法均支持第三个 boolean condition 参数,用于动态决定是否拼接该条件:
QueryWrapper wrapper = new QueryWrapper()
.eq("status", status)
.like("name", keyword, keyword != null && !keyword.isEmpty()) // 仅当 keyword 非空时才拼接
.ge("create_time", startDate, startDate != null);QueryWrapper wrapper = new QueryWrapper()
.eq("status", 1)
.or(sub -> sub.eq("type", 1).like("name", "vip")) // ... AND (type = ? AND name LIKE ?) OR ...
.and(sub -> sub.gt("age", 18).lt("age", 60)); // ... AND (age > ? AND age < ?)QueryWrapper wrapper = new QueryWrapper()
.exists(new SelectBuilder().select("1").from("sys_role r").where(w -> w.apply("r.user_id = u.id")))
.inSql("dept_id", new SelectBuilder().select("id").from("sys_dept").where(w -> w.eq("status", 1)));SelectBuilder 提供完整的链式 API 构建 SELECT 查询,支持多表 JOIN、子查询嵌套、EXISTS、GROUP BY / HAVING、ORDER BY 等所有标准 SQL 特性。
String keyword = "test";
SelectBuilder sb = new SelectBuilder()
.select("u.id", "u.name", "d.name AS deptName")
// SELECT 子查询
.select(new SelectBuilder()
.select("COUNT(*)").from("sys_role r")
.where(sub -> sub.apply("r.user_id = u.id"))
.as("roleCount"))
// FROM 子查询
.from(new SelectBuilder()
.select("id", "name").from("sys_user")
.where(w -> w.gt("id", 1))
.as("u"))
// LEFT JOIN 子查询
.leftJoin(new SelectBuilder()
.select("id", "status").from("sys_dept")
.as("d"))
.on(w -> w.apply("u.dept_id = d.id").eq("d.status", 1))
// WHERE 条件
.where(w -> w
.eq("u.status", 1)
.like("u.name", keyword, keyword != null)
.in("u.type", Arrays.asList(1, 2, 3))
.exists(new SelectBuilder()
.select("1").from("sys_role r")
.where(sub -> sub.apply("r.user_id = u.id").eq("r.role_name", "admin")))
.or(sub -> sub.eq("u.type", 1).like("u.name", "vip")))
.groupBy("u.dept_id")
.having(w -> w.apply("COUNT(*) > 1").or(sub -> sub.apply("MAX(u.status) = 2")))
.orderByDesc("u.create_time");
// 执行查询
List<Map<String, Object>> list = DbUtil.selectList(sb);
List<SysUser> users = DbUtil.selectList(sb, SysUser.class);SelectBuilder.n() 可以作为 new SelectBuilder() 的简写:
SelectBuilder sb = SelectBuilder.n()
.select("*").from("sys_user")
.where(w -> w.eq("status", 1));| 方法 | 说明 |
|---|---|
leftJoin(table).on(...) |
LEFT JOIN |
rightJoin(table).on(...) |
RIGHT JOIN |
innerJoin(table).on(...) |
INNER JOIN |
crossJoin(table) |
CROSS JOIN |
以上所有 JOIN 方法均支持三种参数类型:String 表名、BtTable 强类型表、SelectBuilder 子查询。
本项目内建了 Annotation Processor (APT),在编译期自动拦截 @Table 注解的实体类,为其生成以 Q 开头的强类型元模型类(如 QSysUser),让你的 SQL 构建具备 100% 的编译期类型检查和 IDE 代码补全。
在使用方项目的 pom.xml 中添加编译器插件配置:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
<annotationProcessorPaths>
<path>
<groupId>io.github.juoliii</groupId>
<artifactId>mybatis-helper-core</artifactId>
<version>1.0.3</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>@Table(name = "sys_user")
public class SysUser {
@Id
private Long id;
@Column(name = "user_name")
private String userName;
private Long deptId; // 自动映射为 dept_id
private Integer status;
private String username;
private Date createTime; // 自动映射为 create_time
}编译后自动生成 QSysUser.java:
public class QSysUser extends BtTable {
public static final QSysUser SYS_USER = new QSysUser("sys_user");
public static QSysUser as(String alias) {
return new QSysUser(alias);
}
public QSysUser(String alias) {
super("sys_user", alias);
}
public final BtColumn ID = createColumn("id");
public final BtColumn USER_NAME = createColumn("user_name");
public final BtColumn DEPT_ID = createColumn("dept_id");
public final BtColumn STATUS = createColumn("status");
public final BtColumn USERNAME = createColumn("username");
public final BtColumn CREATE_TIME = createColumn("create_time");
public final BtColumn[] ALL = new BtColumn[] { ID, USER_NAME, DEPT_ID, STATUS, USERNAME, CREATE_TIME };
}QSysUser u = new QSysUser("u");
QSysDept d = new QSysDept("d");
SelectBuilder sb = new SelectBuilder()
// u.ALL 可选取该表所有列
.select(u.ALL)
.select(d.NAME.as("deptName"))
.from(u)
// 两个 BtColumn 比较时,自动生成 u.dept_id = d.id(无占位符)
.leftJoin(d).on(w -> w.eq(u.DEPT_ID, d.ID))
// 与普通值比较时,自动生成 u.status = #{param}(有占位符)
.where(w -> w.eq(u.STATUS, 1).like(u.USERNAME, "vip"))
.groupBy(u.DEPT_ID)
.orderByDesc(u.CREATE_TIME);
List<Map<String, Object>> list = DbUtil.selectList(sb);核心优势:列名不再是字符串硬编码,重命名字段后编译器自动报错,IDE 全程代码补全。
通过 JPA 注解自动解析表名、列名和主键,直接传入实体对象进行增删改操作。
| 注解 | 位置 | 说明 |
|---|---|---|
@Table(name = "...") |
类 | 指定表名,缺省则将类名转为下划线命名 |
@Id |
字段 | 标记主键字段 |
@Column(name = "...") |
字段 | 指定列名,缺省则将字段名转为下划线命名 |
@Transient |
字段 | 标记非数据库字段,不参与任何 SQL 操作 |
@KeySql(...) |
字段 | 指定主键生成策略(配合 @Id 使用) |
@Table(name = "sys_user")
public class SysUser {
@Id
@KeySql(type = IdGenerationType.IDENTITY)
private Long id;
@Column(name = "user_name")
private String userName;
private String email; // 自动映射为 email 列
@Transient
private String tempField; // 不参与数据库操作
}SysUser user = new SysUser();
user.setUserName("test");
user.setEmail("test@example.com");
// 插入所有字段(包括 null 值的字段)
DbUtil.insert(user);
// 仅插入非 null 字段(Selective)
DbUtil.insertSelective(user);基于 JDBC ExecutorType.BATCH 高性能批量插入,而非逐条循环。支持参与外部事务、支持主键回填。
List<SysUser> users = new ArrayList<>();
users.add(user1);
users.add(user2);
users.add(user3);
// 批量插入(自动处理主键生成策略)
int rows = DbUtil.insert(users);SysUser user = new SysUser();
user.setId(1L);
user.setEmail("new@example.com");
// 按主键更新所有字段(null 值的字段也会被 SET 为 null)
DbUtil.update(user);
// 按主键仅更新非 null 的字段(Selective,推荐)
DbUtil.updateSelective(user);SysUser user = new SysUser();
user.setId(1L);
// 按主键删除
DbUtil.delete(user);// 按条件更新(不依赖主键,WHERE 由 QueryWrapper 指定)
SysUser updateEntity = new SysUser();
updateEntity.setStatus(0);
QueryWrapper wrapper = new QueryWrapper();
wrapper.eq("dept_id", 5);
DbUtil.updateByQuery(updateEntity, wrapper);
// 生成:UPDATE sys_user SET status = 0 WHERE dept_id = 5
// 按条件删除
DbUtil.deleteByQuery(SysUser.class, new QueryWrapper().lt("create_time", "2024-01-01"));
// 生成:DELETE FROM sys_user WHERE create_time < '2024-01-01'通过 @KeySql 注解指定主键生成方式,插入后自动将生成的主键值回填到实体对象。
| 策略 | 说明 | 适用场景 |
|---|---|---|
NONE |
不自动生成,用户手动赋值(默认) | 业务主键 |
IDENTITY |
数据库自增,插入后回填 | MySQL AUTO_INCREMENT、PostgreSQL SERIAL |
UUID |
应用层生成 32 位无分隔符 UUID | 分布式系统、无序主键 |
SQL |
执行指定 SQL 获取主键值 | Oracle 序列、PostgreSQL 序列 |
CUSTOM |
自定义生成器 | 雪花算法、自定义编号规则 |
@Id
@KeySql(type = IdGenerationType.IDENTITY)
private Long id;
// 使用
SysUser user = new SysUser();
user.setUserName("test");
DbUtil.insert(user);
System.out.println(user.getId()); // 输出数据库生成的自增 ID,如:1001IDENTITY 策略下,
id列不会出现在 INSERT 语句的列列表中,由数据库自增生成,插入完成后自动回填到实体对象。
@Id
@KeySql(type = IdGenerationType.UUID)
private String id;
// 使用
SysUser user = new SysUser();
user.setUserName("test");
DbUtil.insert(user);
System.out.println(user.getId()); // 输出如:a1b2c3d4e5f6...(32位)适用于 Oracle 序列等场景,插入前先执行指定 SQL 获取主键值:
// Oracle 序列
@Id
@KeySql(type = IdGenerationType.SQL, sql = "SELECT sys_user_seq.nextval FROM DUAL")
private Long id;
// PostgreSQL 序列
@Id
@KeySql(type = IdGenerationType.SQL, sql = "SELECT nextval('sys_user_seq')")
private Long id;实现 GenId 接口编写自定义生成逻辑:
// 1. 实现 GenId 接口
public class SnowflakeGenId implements GenId<Long> {
@Override
public Long genId() {
return SnowflakeIdWorker.nextId(); // 你的雪花算法实现
}
}
// 2. 在实体类中使用
@Id
@KeySql(type = IdGenerationType.CUSTOM, genId = SnowflakeGenId.class)
private Long id;自定义生成器类必须提供无参构造函数。
内置基于数据库方言的分页查询支持,通过 selectPage 系列方法实现,返回 PageResult 对象。
自动方言检测:无需手动配置,初始化时通过 JDBC DatabaseMetaData 自动识别数据库类型并匹配对应的分页方言。分页 COUNT 查询由 CountSqlOptimizer(基于 JSqlParser)智能优化——自动移除 ORDER BY / LIMIT,简单查询直接替换 SELECT 为 COUNT(*),复杂查询(DISTINCT / GROUP BY / UNION)包装为子查询。
| 数据库 | 分页语法 |
|---|---|
| MySQL / MariaDB / SQLite / H2 | LIMIT ... OFFSET ... |
| PostgreSQL / KingbaseES / openGauss | LIMIT ... OFFSET ... |
| Oracle(12c+) | OFFSET ... ROWS FETCH NEXT ... ROWS ONLY |
| SQL Server(2012+) | OFFSET ... ROWS FETCH NEXT ... ROWS ONLY |
| 达梦(DM) | LIMIT ... OFFSET ... |
// 直接 SQL 分页,pageNumber 从 1 开始
PageResult<SysUser> page = DbUtil.selectPage(
"SELECT * FROM sys_user WHERE status = 1",
SysUser.class, 1, 10 // 第1页,每页10条
);
// 带参数的分页查询
Map<String, Object> params = new HashMap<>();
params.put("status", 1);
PageResult<Map<String, Object>> page = DbUtil.selectPage(
"SELECT * FROM sys_user WHERE status = #{status}",
params, 1, 10
);SelectBuilder sb = SelectBuilder.n()
.select("*")
.from("sys_user")
.where(w -> w.eq("status", 1))
.orderByDesc("create_time");
PageResult<SysUser> page = DbUtil.selectPage(sb, SysUser.class, 2, 20);QueryWrapper wrapper = new QueryWrapper();
wrapper.eq("status", 1).like("name", "test");
PageResult<SysUser> page = DbUtil.selectPageByQuery(
"SELECT * FROM sys_user", wrapper, SysUser.class, 1, 10
);| 字段 | 类型 | 说明 |
|---|---|---|
list |
List<T> |
当前页数据列表 |
total |
long |
总记录数 |
pageNumber |
int |
当前页码(从 1 开始) |
pageSize |
int |
每页条数 |
pages |
int |
总页数 |
PageResult<SysUser> page = DbUtil.selectPage(sql, SysUser.class, 1, 10);
List<SysUser> users = page.getList(); // 当前页数据
long total = page.getTotal(); // 总记录数
int pages = page.getPages(); // 总页数
boolean hasNext = page.hasNext(); // 是否有下一页
boolean hasPrev = page.hasPrevious(); // 是否有上一页如果使用的数据库不在内置支持列表中,可以通过以下方式扩展:
// 方式一:注册自定义方言(按数据库产品名匹配)
DialectRegistry.registerDialect("gbase", new DialectFactory() {
@Override
public String buildPaginationSql(String sql, int offset, int limit) {
return sql + " LIMIT " + limit + " OFFSET " + offset;
}
@Override
public String buildCountSql(String sql) {
return "SELECT COUNT(*) FROM (" + sql + ") _t";
}
});
// 方式二:直接指定方言(跳过自动检测)
DialectRegistry.setDialect(new MySqlDialect());在 Spring Boot 环境中,DbUtil 自动参与 Spring 的 @Transactional 事务管理。你只需像往常一样使用 Spring 事务注解即可。
默认每次调用 CRUD 方法都会自动开启新的 Session 并自动提交。如果需要在同一个事务中执行多次操作,使用 DbUtil.executeInTransaction:
// 无返回值的事务操作
DbUtil.executeInTransaction(() -> {
// 以下操作在同一个底层 SqlSession 事务中执行
DbUtil.insert(user1);
DbUtil.insert(user2);
DbUtil.update(user3);
// 如果发生异常,事务自动回滚;全部成功则自动 commit
});
// 带返回值的事务操作
boolean success = DbUtil.executeInTransaction(() -> {
DbUtil.insert(user1);
return true;
});注意:如果在 Spring 环境中调用
executeInTransaction,该方法会安全降级,直接执行传入的逻辑,事务管理交由 Spring@Transactional统一接管。executeInTransaction也支持嵌套调用。
mybatis-helper
├── com.bitian.db.mybatis_helper
│ ├── apt/ # APT 编译期处理器
│ │ └── BtMetaProcessor # 自动生成 Q 开头的强类型元模型类
│ ├── dialect/ # 数据库分页方言
│ │ ├── DialectFactory # 方言接口
│ │ ├── DialectRegistry # 方言注册中心 & 自动检测
│ │ ├── CountSqlOptimizer # 智能 COUNT SQL 优化(基于 JSqlParser)
│ │ ├── MySqlDialect # MySQL / MariaDB / SQLite / H2
│ │ ├── PostgreSqlDialect # PostgreSQL / KingbaseES / openGauss
│ │ ├── OracleDialect # Oracle 12c+
│ │ ├── SqlServerDialect # SQL Server 2012+
│ │ └── DmDialect # 达梦 DM
│ ├── mapper/ # MyBatis Mapper
│ │ └── DbMapper # 内部动态 SQL 执行 Mapper
│ ├── meta/ # 元数据模型
│ │ ├── BtTable / BtColumn # 强类型 DSL 核心元模型
│ │ ├── EntityResolver # JPA 注解实体类元数据解析器
│ │ ├── EntityMetadata # 实体类元数据封装
│ │ ├── ColumnInfo # 列信息
│ │ ├── KeySql # 主键生成策略注解
│ │ ├── IdGenerationType # 主键生成策略枚举
│ │ ├── GenId # 自定义主键生成器接口
│ │ ├── UUIDGenId # 内置 UUID 生成器
│ │ └── PageResult # 分页结果封装
│ ├── provider/ # MyBatis SQL Provider
│ │ └── DbProvider # 动态 SQL 提供器
│ ├── spring/ # Spring Boot 集成
│ │ └── DbUtilAutoConfiguration # 自动配置类
│ └── util/ # 核心工具
│ ├── DbUtil # 核心入口(查询、CRUD、分页、事务)
│ ├── SelectBuilder # 完整 SQL DSL 构建器
│ └── QueryWrapper # WHERE 条件构建器
| 方法 | 说明 |
|---|---|
selectList(sql, params, clazz) |
SQL 查询,返回实体类列表 |
selectList(sql, params) |
SQL 查询,返回 Map 列表 |
selectList(builder, clazz) |
SelectBuilder 查询,返回实体类列表 |
selectList(builder) |
SelectBuilder 查询,返回 Map 列表 |
selectOne(sql, params, clazz) |
SQL 查询,返回单个实体 |
selectOne(sql, params) |
SQL 查询,返回单个 Map |
selectByQuery(baseSql, wrapper, clazz) |
QueryWrapper 查询,返回实体类列表 |
selectOneByQuery(baseSql, wrapper, clazz) |
QueryWrapper 查询,返回单个实体 |
| 方法 | 说明 |
|---|---|
selectPage(sql, params, clazz, pageNumber, pageSize) |
SQL 分页查询,返回实体类 |
selectPage(sql, params, pageNumber, pageSize) |
SQL 分页查询,返回 Map |
selectPage(builder, clazz, pageNumber, pageSize) |
SelectBuilder 分页查询 |
selectPageByQuery(baseSql, wrapper, clazz, pageNumber, pageSize) |
QueryWrapper 分页查询 |
| 方法 | 说明 |
|---|---|
insert(entity) |
插入实体对象(所有字段) |
insertSelective(entity) |
插入实体对象(仅非 null 字段) |
insert(list) |
批量插入(JDBC Batch) |
update(entity) |
按主键更新(所有字段) |
updateSelective(entity) |
按主键更新(仅非 null 字段) |
updateByQuery(entity, wrapper) |
按 QueryWrapper 条件更新 |
delete(entity) |
按主键删除 |
deleteByQuery(clazz, wrapper) |
按 QueryWrapper 条件删除 |
| 方法 | 说明 |
|---|---|
executeInTransaction(Runnable) |
在事务中执行(无返回值) |
executeInTransaction(Supplier<R>) |
在事务中执行(有返回值) |