# mybatis-jet
**Repository Path**: bitcarton/mybatis-jet
## Basic Information
- **Project Name**: mybatis-jet
- **Description**: 一个简单的,高性能的,易学的mybatis增强框架
- **Primary Language**: Java
- **License**: Apache-2.0
- **Default Branch**: springboot3
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 30
- **Forks**: 9
- **Created**: 2019-06-13
- **Last Updated**: 2026-10-02
## Categories & Tags
**Categories**: database-dev
**Tags**: MyBatis, mybatis-plugs, mybatis增强工具, 简化mybatis增删改查, 数据库持久化框架
## README
mybatis-jet
✈️ 给 MyBatis 装上喷气引擎 —— MyBatis, at jet speed.
更快 · 更安全 · 开箱即用的 MyBatis 增强
👉 https://gitee.com/bitcarton/mybatis-jet 👈
👉 mybatis-jet 👈
[**🌎English Documentation**](README.en.md)
### 📚简介
mybatis-jet是基于mybatis开发的一款增强的持久层框架。
只需在springboot项目中引入依赖mybatis-jet-spring-boot-starter
即可实现mybatis的CRUD简化操作,不用再书写基本的增删改查sql
jet 的名字就是产品的承诺:MyBatis 的每一次 CRUD,都是一次满油门起飞。
## 🍺目的
mybatis-jet为简化代码,提高效率而生!
## 🐮特性
- 不对mybatis做任何修改 只做mybatis的扩展增强。
- 代码自动生成,根据表名可快速生成xxxMapper.java、xxxService.java、xxxServiceImpl.java、xxxController.java、xxxMapper.xml。
- 依赖少,仅仅依赖 mybatis-jet-spring-boot-starter
- 自动填充创建时间,更新时间,创建人等信息。
- 内置基于Mybatis物理分页的分页插件。
- 自动记录sql执行的时间,方便定位慢查询。
- 代码简洁,方便理解。
- 内置接口拦截器,通过注解确定是否拦截请求。
- 支持通过注解实现逻辑删除。
- 内置主键自动生成:支持雪花算法、UUID、数据库号段(Leaf 双 Buffer)模式,插入时自动填充,无需手工赋值。
### ⚡ 性能(基准测试数据)
jet 的速度感不靠形容词,靠仪表盘读数——以下数字全部来自开源可复现的基准测试:
测试环境:H2 内存库、1 万行数据、JDK 17(bench 代码随仓库开源,环境与复现命令详见报告):
| 场景 | mybatis-jet | 对比项 |
| --- | --- | --- |
| Lambda 条件解析 | **8 ns** | 约 90 倍于同类框架(734 ns) |
| 物理分页(20 行 + total) | **5.2 μs** | 约 38 倍于同类框架(199 μs) |
| 批量更新(100 行) | **827 μs** | 约 1.8 倍于同类框架(1528 μs) |
> 说明:以上为单线程内存库基准,用于量级对比;真实数据库 + 网络环境下差距会收敛。
### 🛡️ 安全
- 所有基于字符串的列名入口均通过 **meta-info 白名单校验**(`SqlUtil.checkColumn`),拒绝拼接式 SQL 注入;
- lambda 条件 / 排序字段全部经由 `TableMeta` 元数据层解析,从架构上切断外部输入进入 SQL 文本的路径。
## 📦快速开始
我们将通过一个简单的 Demo 来阐述 mybatis-jet 的强大功能,在此之前,我们假设您已经:
a.拥有 Java 开发环境、相应 IDE以及mysql数据库
b.熟悉 Spring Boot
c.熟悉 Maven
如果从零开始用 mybatis-jet来实现该表的增删改查我们需要做什么呢?
👉 1.创建表
现有一张 sys_user 表,其对应的数据库 Sql 脚本如下:
```
CREATE TABLE sys_user (
id bigint NOT NULL COMMENT '主键ID',
name varchar(30) COMMENT '姓名',
age int COMMENT '年龄',
phone varchar(11) COMMENT '电话',
PRIMARY KEY (id)
)
```
👉 2.创建springboot项目
使用 idea 创建SpringBoot项目
第一步:

第二步:

第三步:选择LomBok插件

项目基本结构

👉 3.配置Maven
在项目的pom.xml文件中加入mybatis-jet的Maven依赖:mybatis-jet-spring-boot-starter
最新版本:
```xml
com.bitcarton
mybatis-jet-spring-boot-starter
1.0.0
```
👉4.配置yml
修改 application.properties为application.yml
新增 开发环境: application-dev.yml
新增 测试环境: application-test.yml
新增 生产环境: application-pro.yml
小说明
1.开发环境为我们进行开发所使用的配置
2.测试环境为测试人员进行软件测试所使用的配置
3.生产环境为上线部署所使用的配置
application.yml中配置为:
```
spring:
profiles:
active: dev
server:
port: 8080
```
tips:
```
spring:
profiles:
active: dev
```
指定所使用的环境
```
server:
port: 8080
```
指定项目所启动的端口为8080端口
application-dev.yml内容如下:
```
mybatis:
mapper-locations: classpath:/mapping/*.xml
type-aliases-package: com.panpan.housesale.entity
configuration:
map-underscore-to-camel-case: true
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://127.0.0.1:3306/test_db?useUnicode=true&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
username: root
password: 111111
driver-class-name: com.mysql.cj.jdbc.Driver
filters: stat,wall
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
#配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
#如果允许时报错 java.lang.ClassNotFoundException: org.apache.log4j.Priority
#则导入 log4j 依赖即可,Maven 地址: https://mvnrepository.com/artifact/log4j/log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
```
👉5.修改启动类
在启动类上加上Mapper扫描的注解
```
@MapperScan("com.xxxxx.xxxxx.mapper")
```
## 🍊代码生成器
我们通过数据库表可以快速生成entity,controller,mapper,service,serviceImpl,mapping.xml
生成代码需要连接数据库,所以我们需要进行数据库的连接,只需要通过配置数据库基本信息,利用mybatis-jet的代码生成类 CodeBuilder 即可
👉1.配置代码生成器
在启动类的同级目录下,建立生成器类 CodeGenerate
```
import com.bitcarton.mybatisjet.core.generate.CodeBuilder;
import com.bitcarton.mybatisjet.core.generate.template.GeneratorTemplate;
public static void main(String[] args) {
String url="jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai";
String user="root";
String psd="123456";
String driver="com.mysql.cj.jdbc.Driver";
String filePath="D://generate_code//";
String basePackName="org.example";
GeneratorTemplate build = new CodeBuilder(url,driver).
setBasePackName(basePackName).
setUser(user).
setPassword(psd).
setFilePath(filePath)
.build();
build.generate(CodeTest.class);
}
```
注释:
url: 数据库连接url
root: 数据库用户名
psd: 数据库密码
filePath: java代码生成位置(防止代码覆盖所以我们不会自动生成到对应的代码位置)
CodeGenerator2: mybatis-jet代码核心生成器
👉2.使用方法
1.执行main方法
2.在控制台输入作者以及要生成代码的表

3.输入完成之后点击回车
4.当输出以下信息表示代码生成成功

5.去代码生成的位置查看生成的代码

👉3.建立代码包
建立代码包:entity、controller、mapper、service、impl以及xml的文件夹

👉4.复制代码
将生成的代码复制到对应的文件夹

👉5.启动项目
启动项目 访问 http://localhost:8080/v1/sys_user/list 查询所有用户列表
## 🐮CRUD 接口
说明:通过封装mybatis的 BaseService接口即可快速实现数据库的CRUD操作
泛型 T 为任意实体对象;参数 Serializable 为任意类型主键 mybatis-jet 不推荐使用复合主键约定每一张表都有自己的唯一 id 主键;对象 Wrapper 为 条件构造器。
1.插入接口
1.1单条插入
```
/**
* 插入一条记录
* @param entity 传入的插入对象
* @return T
*/
T insert(T entity);
```
使用方法:在serviceImpl层写代码示例如下
```
SysUser user = new SysUser();
user.setName("张三");
user.setAge(32);
user.setPhone("13615425135");
super.insert(user);
```
1.2批量插入
```
/**
* 批量插入
* @param entityList
* @return
*/
int saveBatch(List entityList);
```
使用方法:在serviceImpl层写代码示例如下
```
List sysUserList = new ArrayList<>();
for (int i = 0; i <10 ; i++) {
SysUser sysUser = new SysUser();
sysUser.setName("张三"+i);
sysUser.setAge(30+i);
sysUser.setPhone("1361542513"+i);
sysUserList.add(sysUser);
}
super.saveBatch(sysUserList);
```
2.删除接口
2.1单个删除
```
/**
* 根据id删除一条数据
* @param id 传入的查询ID
* @return 删除条数
*/
int deleteById(Serializable id);
```
使用方式:在serviceImpl层写代码示例如下
```
super.deleteById(12000012);
```
2.2批量删除
```
/**
* 批量删除
* @param queryWrapper
* @return
*/
int delete(Wrapper queryWrapper);
```
使用方式:在serviceImpl层写代码示例如下
```
Wrapper queryWrapper = new Wrapper();
queryWrapper.eq("age",30);
queryWrapper.like("name","张三");
super.delete(queryWrapper);
```
说明:关于条件构造器会专门讲解。
3 修改接口
3.1单个修改
```
/**
* 根据id更新一条数据
* @param bean 传入的更新对象
* @return 返回更新条数
*/
int updateById(T bean);
```
使用方式:在serviceImpl层写代码示例如下
```
SysUser sysUser = new SysUser();
sysUser.setId(1234561114L);
sysUser.setPhone("13615425135");
sysUser.setAge(36);
super.updateById(sysUser);
```
3.2批量修改
```
/**
* 批量修改
* @param entityList
* @return
*/
int updateBatchById(List entityList);
```
使用方式:在serviceImpl层写代码示例如下
```
List sysUserList = new ArrayList<>();
SysUser sysUser = new SysUser();
sysUser.setId(111100001101L);
sysUser.setAge(35);
sysUserList.add(sysUser);
SysUser sysUser2 = new SysUser();
sysUser2.setId(111100001102L);
sysUser2.setAge(32);
sysUserList.add(sysUser);
super.updateBatchById(sysUserList);
```
4.列表查询接口
```
/**
* 查询列表数据
* @param queryWrapper 查询条件
* @return 集合
*/
List list(Wrapper queryWrapper);
```
使用方式:在serviceImpl层写代码示例如下
```
Wrapper queryWrapper = new Wrapper();
queryWrapper.like("name","张三");
List list = super.list(queryWrapper);
```
5.分页查询
```
/**
* 分页查询
* @param page
* @param queryWrapper
* @return
*/
Page page(Page page, Wrapper queryWrapper);
```
使用方式:在controller层写代码示例如下
```
@PostMapping("/page")
public ResultReturn> getPage(@RequestBody SysUser user){
return success(sysUserService.page( getPage(),new Wrapper(user)));
}
```
说明:controller需要继承BaseController
6查询单条
```
/**
* 根据id获取
* @param id 对象ID
* @return 对象
*/
T getById(Serializable id);
```
使用方式:在controller层写代码示例如下
```
@GetMapping("/{id}")
public ResultReturn get(@PathVariable("id") Long id) {
return success(sysUserService.getById(id));
}
```
7.查询数量
```
/**
* 查询count
* @param queryWrapper 查询条件
* @return 数量
*/
int selectCount(Wrapper queryWrapper);
```
使用方式:在serviceImpl层写代码示例如下
```
Wrapper queryWrapper = new Wrapper();
queryWrapper.like("name","张三");
int count = super.selectCount(queryWrapper);
```
## 🐮 条件构造器Wrapper
1 eq构造
如果我们想快速查询数据库表sys_user 的姓名为 “Tom” 如何进行操作呢?
回答:只需service调用list 传入 条件构造器Wrapper Wrapper调用 eq 方法,下面的方法即为 查询姓名是Tom的SysUser列表
```
sysUserService.list(new Wrapper<>(sysUser).eq("name","Tom"));
```
2 ne构造
如果我们想快速查询数据库表sys_user 的姓名不是 “Tom” 如何进行操作呢?
回答:只需service调用list 传入 条件构造器Wrapper Wrapper调用 ne 方法,下面的方法即为 查询姓名不是Tom的SysUser列表
```
sysUserService.list(new Wrapper<>(sysUser).ne("name","Tom"));
```
3 like构造
如果我们想根据姓名模糊查询,怎么操作呢?
回答:只需service调用list 传入 条件构造器Wrapper Wrapper调用 like 方法,下面的方法即为 根据姓名“Tom”模糊查询
```
sysUserService.list(new Wrapper<>(sysUser).like("name","Tom"));
```
4 in构造
如果我们想查询姓名是“Tom”,“Jack”,“June”怎么操作呢?
回答:只需service调用list 传入 条件构造器Wrapper Wrapper调用 in 方法,传入ArrayList即可
```
List arrayList=new ArrayList<>();
arrayList.add("Tom");
arrayList.add("Jack");
arrayList.add("June");
sysUserService.list(new Wrapper<>(sysUser).in("name",arrayList));
```
扩展:
以上只列出部分条件构造器的方法,我们还有notNull(非空查询)、isNull(空值查询)、setSqlSelect(固定列查询)等等, 更多请查看 mybatis-jet
## 🐮 主键自动生成
mybatis-jet 支持在插入时自动为主键赋值,只需在实体类的主键字段上通过 `@TableId` 注解声明 `idType`,单条插入(`insert`)与批量插入(`saveBatch`)均会自动填充,无需手工赋值。
### 1. 主键策略总览
`idType` 有六种取值(枚举 `com.bitcarton.mybatisjet.core.annotation.IdType`):
| idType | 值 | 说明 | 是否自动填充 |
| --- | --- | --- | --- |
| `AUTO` | 0 | 自增,交给数据库自增列 | 否 |
| `SNOW` | 1 | 雪花算法,单机自包含,无外部依赖 | 是 |
| `UUID` | 2 | UUID(数字类型场景) | 是 |
| `UUID_STR` | 3 | UUID 字符串 | 是 |
| `SEGMENT` | 4 | **数据库号段模式**(本节重点) | 是 |
| `NONE` | 5 | 无需填充,主键由调用方自己赋值 | 否 |
### 2. 号段模式快速使用
号段模式(Segment)借鉴美团 Leaf 的双 Buffer 方案:每次从数据库取一段连续 ID 缓存在内存里,用完再取下一段,**既保证全局唯一,又把数据库压力降到"每 step 次 insert 才访问一次号段表"**。
👉 1. 创建号段表
在业务库执行以下 DDL,并为每个业务初始化一行:
```sql
CREATE TABLE leaf_alloc (
biz_tag varchar(64) NOT NULL COMMENT '业务标识(与 @TableId.bizType 一致)',
max_id bigint NOT NULL DEFAULT 1 COMMENT '当前已分配到的最大ID',
step int NOT NULL DEFAULT 1000 COMMENT '每次取号段的步长',
description varchar(200) DEFAULT NULL COMMENT '业务描述',
update_time varchar(32) DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (biz_tag)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '号段分配表';
-- 为某个业务初始化一行,例如订单业务:
INSERT INTO leaf_alloc (biz_tag, max_id, step, description)
VALUES ('order', 1, 1000, '订单主键号段');
```
👉 2. 实体类声明注解
在主键字段上标注 `@TableId(idType = IdType.SEGMENT, bizType = "业务标识")`:
```java
import com.bitcarton.mybatisjet.core.annotation.IdType;
import com.bitcarton.mybatisjet.core.annotation.Table;
import com.bitcarton.mybatisjet.core.annotation.TableColumn;
import com.bitcarton.mybatisjet.core.annotation.TableId;
@Table(name = "t_order")
public class Order {
/**
* bizType 必须与 leaf_alloc.biz_tag 一致
* 主键字段类型建议为 Long / long
*/
@TableId(idType = IdType.SEGMENT, bizType = "order")
@TableColumn(name = "id")
private Long id;
@TableColumn(name = "order_no")
private String orderNo;
// getter / setter ...
}
```
⚠️ 注意:使用号段模式时 **`bizType` 必填**。不指定时框架会直接抛出异常:
`使用号段Id生成时必须指定bizTag`。同样,`leaf_alloc` 里不存在对应 `biz_tag` 的行时,
取号会抛出 `biz_tag不存在`。
👉 3. 正常插入,主键自动填充
```java
// 单条插入
Order order = new Order();
order.setOrderNo("SO20260925001");
super.insert(order); // order.getId() 已被自动填充为号段 ID
// 批量插入同样自动逐条填充
List orders = new ArrayList<>();
for (int i = 0; i < 100; i++) { ... }
super.saveBatch(orders);
```
👉 4. 也可以直接取号使用
如果某些场景需要脱离实体注解直接拿 ID(如生成单号前缀),可直接注入号段生成器:
```java
@Autowired
private SegmentIdGenerator segmentIdGenerator;
long id = segmentIdGenerator.nextId("order");
```
### 3. 号段模式方案详解(实现原理)
号段模式的核心矛盾是:**数据库行锁能保证唯一,但每次 insert 都去数据库取 ID 的话,号段表会成为整个系统的写入瓶颈**。mybatis-jet 的方案是把"取 ID"从"每次 insert 一次 DB"变成"每 step 次 insert 一次 DB",同时用双 Buffer 把取号段的耗时从请求路径上挪走。
#### 3.1 一次取号在数据库侧发生了什么
取号动作由 `JdbcSegmentDbLoader.updateMaxIdAndSelect(bizTag)` 完成,全程在一个手动事务里:
```sql
-- 第 1 步:原子推进(InnoDB 对该行加排他锁 X 锁)
UPDATE leaf_alloc SET max_id = (max_id + step) WHERE biz_tag = ?;
-- 第 2 步:同一事务内读回推进后的 max_id
SELECT max_id, step FROM leaf_alloc WHERE biz_tag = ?;
-- 第 3 步:业务组装成功后 commit;任何异常 rollback
```
为什么这样能保证全局唯一?
- 多实例(多 JVM、多机器)并发取号时,`UPDATE` 的行锁把并发请求**串行化**——
谁先拿到锁谁先推进 `max_id`,后到的在锁上等待,读到的是自己推进后的值;
- `UPDATE` 和 `SELECT` 在同一事务内,读到的 `max_id` 一定是本次推进后的值,
不存在"读到别人推进的旧值"的竞态;
- 事务异常时 `rollback` 并恢复连接的 `autoCommit=true`(连接池复用的最佳实践),
不会出现"推进了 max_id 却没拿到号段"的空洞。
拿到 `[max_id - step + 1, max_id]` 这个闭区间后,这一段 ID 就归属了当前实例,
数据库里的记录已经推进,其它实例永远不会再发这一段。
#### 3.2 双 Buffer:把取号耗时挪出请求路径
如果等当前号段**用完了**才去数据库取下一段,那么每隔 step 次插入就会出现一次
"取号段的 DB 往返"卡在业务请求里(一次 UPDATE + 一次 SELECT + 一次网络往返,
通常 1~10ms)。号段越大这个毛刺越稀但单次停顿后浪费越大,号段越小毛刺越密。
双 Buffer 的做法是:**永远准备两个号段,当前段消耗到只剩 10% 时,后台线程就提前把下一段拉回来备着**。
mybatis-jet 的实现(`SegmentIdGenerator`)结构如下:
```
ConcurrentHashMap>
│
▼
┌────────────────────────────────────┐
│ Segment[0] 当前段 [startId,endId] │ ← AtomicLong getAndIncrement 发号
│ Segment[1] 备用段 (提前拉好/为null)│
└────────────────────────────────────┘
```
取号主流程(`nextId(bizTag)`):
1. **首次访问**:`computeIfAbsent` 为该 bizTag 初始化双 Buffer,同步从 DB 加载第一个号段作为当前段,备用段为 `null`;
2. **正常发号**:当前段 `AtomicLong.getAndIncrement()` 原子自增,落在 `[startId, endId]` 内直接返回;
3. **提前预载**:当前段剩余 ≤ 10%(`Segment.needPreLoad()`)且备用段为空时,向后台线程池提交异步任务去 DB 拉下一段,拉到后用 **CAS** 塞进 `Segment[1]` 备用位——主线程完全无感知,不阻塞;
4. **耗尽切换**:当前段发完(`nextId()` 返回 `-1`)时,若备用段已就绪,用 **CAS** 把备用段提升为当前段,回到第 2 步——切换本身只是换了个数组的引用,纳秒级;
5. **兜底同步**:如果当前段耗尽时备用段还没拉回来(比如 DB 刚好抖了一下),当前线程**同步**去 DB 拉一个新号段顶上。这是兜底路径,宁可这一次请求慢一点,也不能发不出 ID。
后台预加载线程池:核心 4 线程、最大 8 线程、队列 200、拒绝策略 `CallerRunsPolicy`(队列满时由调用线程自己执行,**宁可慢不可丢**——丢了备用段就意味着后面的请求要走同步兜底)。
#### 3.3 每个组件在整条链路里的位置
从你写下 `super.insert(order)` 到主键落进 SQL,完整链路是:
```
service.insert(order)
└─ InsertProviderHelper.getInsertSql(t) (单条)/ getSaveBatchSql(批量)
└─ HelperFactory.setTableId(tableId, field, entity) (识别 @TableId 的 idType)
└─ case SEGMENT → new SegmentIdHelper(bizType) (bizType 必填校验在这里)
└─ SegmentIdHelper.nextId()
└─ SpringContextHolder.getBean(SegmentIdGenerator.class) (取 Spring 单例)
└─ SegmentIdGenerator.nextId(bizTag) (双 Buffer 发号)
└─ JdbcSegmentDbLoader.updateMaxIdAndSelect() (DB 事务取段,仅预载/兜底时触发)
```
说明:
- `SegmentIdGenerator` 是 Spring 单例(`@Component`),**所有 bizTag 的双 Buffer 都缓存在它内部**,
同一个 bizTag 无论调用多少次 `insert`,用的都是同一个缓冲区,ID 全局严格不重复;
- `SegmentIdHelper` 是轻量包装对象(每次插入 new 一个,开销可忽略),真正发号的是单例生成器;
- 号段模式下要求运行在 Spring 环境(starter 引入后自动具备),因为需要从容器里取 `SegmentIdGenerator`
和 `DataSource` 两个 Bean。
#### 3.4 step(步长)怎么定
step 是号段模式唯一的权衡旋钮,它同时决定三件事:
| step 越大 | step 越小 |
| --- | --- |
| 取号段的 DB 访问越少(每 step 次 insert 才一次) | DB 访问越频繁 |
| 实例宕机浪费的 ID 段越大(当前段+备用段全部作废) | 浪费越少 |
| 单实例号段独占时间越长,多实例 ID 段间跳跃感越明显 | 段间更连续 |
经验公式:`step ≈ 峰值QPS × 期望DB访问间隔(秒)`。例如峰值 2000 QPS、
希望号段表每分钟最多被访问一次,step 取 `2000 × 60 = 120000`。
不同业务用不同的 bizTag + 独立的 step,互不干扰。
#### 3.5 号段模式的边界与注意事项
| 事项 | 说明 |
| --- | --- |
| 全局唯一 | ✅ 有保证。DB 行锁串行化发号,任何实例任何时刻拿到的区间两两不相交 |
| 趋势递增 | ✅ 大致满足。单实例内严格递增;多实例之间是段级递增(段与段之间跳跃) |
| 连续无空洞 | ❌ 不保证。实例重启/宕机会丢弃当前段与备用段中未发完的部分,ID 会有空洞——**这正好避免了把发号量暴露给外部** |
| 严格全局递增 | ❌ 不保证。两个实例各自持有号段时,先后插入的记录 ID 可能交叉 |
| 字段类型 | 主键字段建议 `Long`/`long`(号段发的是 long);UUID 等其它类型请用对应 idType |
| 依赖环境 | 需要 Spring 容器(starter)+ 可用的 `DataSource`;`leaf_alloc` 必须提前建表并初始化 biz_tag |
| 安全性 | 号段表只被框架访问,业务 SQL 不触碰;bizTag 建议按业务域命名(order / device / user 等)隔离 |
#### 3.6 与其它主键策略怎么选
| 场景 | 推荐 |
| --- | --- |
| 单库单表、能接受自增 | `AUTO`,零成本 |
| 分布式、要 ID 趋势递增、不想加表 | `SNOW`(雪花,注意时钟回拨配置) |
| 分布式、步长可调、要极致发号性能(本地内存发号,无每次网络开销) | `SEGMENT` 号段 |
| 主键无业务含义要求、字符串存储 | `UUID_STR` |
| 主键由调用方/其它系统赋值 | `NONE` |
## 🐮 登录认证
mybatis-jet内部封装了JWT token认证,做登录功能时可直接进行使用。
1.创建JWT
mybatis-jet提供了JwtUtil工具类来进行token的创建,具体的方法来介绍下:
```
/**
* 前三个参数为自己用户token的一些信息比如id,权限,名称等。不要将隐私信息放入(大家都可以获取到)
* @param map 需要加密的信息
* @param security 加密字符串
* @param expire 失效时间 毫秒
* @return
*/
public static String createJwt(Map map,String security,long expire) {
//添加构成JWT的参数
JwtBuilder builder = Jwts.builder()
.setHeaderParam("typ", "JWT")
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis()+expire))
.setClaims(map)
.signWith(generalKey(security),SignatureAlgorithm.HS256);
//生成JWT
return builder.compact();
}
```
如果不传失效时间会默认24小时,我们提供了一个重载的方法
```
/**
* 创建JWT 默认失效时间是24小时
* @param map 需要加密的信息
* @param base64Security 加密字符串
* @return
*/
public static String createJwt(Map map, String base64Security) {
return createJwt(map,base64Security,EXPIRE);
}
```
具体如何使用?请参照一下代码:
```
//获取登录用户名
String username =user.getUsername();
String password= MD5Util.generate(user.getPassword());
List list=list(new Wrapper().eq("username",username).eq("password",password));
Optional first= list.stream().filter(dbUser->dbUser.getUsername().equals(username)&&dbUser.getPassword().equals(password)).findFirst();
if (first.isPresent()){
user.setUsername(username);
SysUser sysUser= first.get();
Map userMap = new HashMap<>();
userMap.put("userId",sysUser.getId()+"");
userMap.put("name",sysUser.getName());
userMap.put("companyId",sysUser.getCompanyId());
userMap.put("username",sysUser.getUsername());
userMap.put("effective", System.currentTimeMillis()+(120*60*1000));
String token= JwtUtil.createJwt(userMap, JwtUtil.LOGIN_BASE);
response.addHeader("authorization",token);
Cookie cookie = new Cookie("authorization",token);
cookie.setDomain(domain);
cookie.setPath("/");
response.addCookie(cookie);
SysUser user1= first.get();
SysUserVO vo = new SysUserVO();
BeanUtils.copyProperties(user1,vo);
vo.setAuthorization(token);
vo.setImgHead(sysUser.getAvatarUrl());
return ResultReturn.success(vo);
}
```
2.解析JWT
mybatis-jet提供了JwtUtil工具类来进行token的解析,具体的方法来介绍下:
```
/**
* 解密
* @param jsonWebToken token字符串
* @param security 加密字符串
* @return
*/
public static Claims parseJwt(String jsonWebToken, String security) {
try {
Jws claimsJws = Jwts.parserBuilder()
.setSigningKey(Keys.hmacShaKeyFor(security.getBytes(StandardCharsets.UTF_8)))
.build().parseClaimsJws(jsonWebToken);
return claimsJws.getBody();
} catch (Exception ex) {
ex.printStackTrace();
return null;
}
}
```
3.封装登录实体
登录成功之后,可以用token获取到登录实体Account,用来获取登录用户,具体方法如下:
```
public static Account account(String token,String security){
Claims claims= JwtUtil.parseJwt(token,security);
String userId= (String)claims.get("userId");
String username= (String)claims.get("username");
Integer companyId= (Integer)claims.get("companyId");
Account account = new Account();
account.setId(Long.parseLong(userId));
account.setUsername(username);
if(null!=companyId){
account.setCompanyId(Long.parseLong(Integer.toString(companyId)));
}
return account;
}
```
其中token是jwt生成的token,security为加密字符串,token可以从head或者cookie里面获取。
具体的使用方式见如下代码:
```
Account account = JwtUtil.account(request.getHeader("Authorization"),"xxxx");
```
## 🐮 拦截器
1.介绍
mybatis-jet的拦截器有记录请求方法执行时间、过滤非登录用户请求的功能,在controller里面使用注解即可拦截未登录请求。
使用方法:自定义拦截器并继承类:com.bitcarton.mybatisjet.commons.Interceptor.Interceptor
实例代码如下:
```
@Component
public class ReqInterceptor extends Interceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception{
return super.preHandle(request,response,handler);
}
}
```
接下来是注册拦截器,新建类WebConfigurer 并实现WebMvcConfigurer接口,代码如下:
```
@Configuration
public class WebConfigurer implements WebMvcConfigurer {
@Autowired
private ReqInterceptor interceptor;
/**
* @Description 这个方法是用来配置静态资源的,比如html,js,css,等等
* @Param [registry]
* @return void
**/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
}
/**
* @Description 这个方法用来注册拦截器,我们自己写好的拦截器需要通过这里添加注册才能生效
* @Date 9:34 2022/2/7
* @Param [registry]
* @return void
**/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// addPathPatterns("/**") 表示拦截所有的请求,
registry.addInterceptor(interceptor).addPathPatterns("/**");
}
}
```
配置完成即可实现mybatis-jet拦截器的功能。
2.拦截请求
Controller方法不加任何注解或者加注解 @Login(handler = Handler.INTERCEPT)即可起到拦截请求的功能,如果用户未登录就访问接口,系统会抛出未登录的信息。
3.放行请求
有一部分请求是不需要登录就直接能访问的,比如说注册接口,登录接口等等,这时候使用注解@Login(handler = Handler.PASS)即可放行请求,即不需要登录即可访问,代码示例如下:
注册接口:
```
@Login(handler = Handler.PASS)
@PostMapping("/register")
public ResultReturn registerUser(@RequestBody SysUserVO sysUser){
sysUser.setCompanyId(0L);
return sysUserService.registerUser(sysUser);
}
```
登录接口:
```
@Login(handler = Handler.PASS)
@PostMapping("/login")
public ResultReturn login(@RequestBody SysUserVO vo){
return sysUserService.login(vo,request,response);
}
```
## 🐮 枚举转换
有时候数据库里面存的是数字类型,但是在实体类里面我们要使用枚举类,这种情况mybatis-jet提供了比较方便快捷的方式来实现需求。
1.定义枚举类
先自定义枚举类并实现com.bitcarton.mybatisjet.core.enums.BaseEnum接口,代码如下:
```
import com.bitcarton.mybatisjet.core.enums.BaseEnum;
public enum StatusEnum implements BaseEnum {
ORDERED(0,"已下单"),
PAYED(1,"已付款"),
DELIVER(2,"代发货"),
DELIVERED(3,"已发货"),
RECEIVED(4,"已收货"),
OK(5,"订单完成");
private final Integer value;
private final String description;
StatusEnum(Integer value, String description) {
this.value = value;
this.description = description;
}
public StatusEnum getEnumByValue(Long value) {
StatusEnum[] enums = StatusEnum.values();
for (StatusEnum e : enums) {
if (e.getValue().equals(value)) {
return e;
}
}
return null;
}
@Override
public Integer getValue() {
return value;
}
@Override
public String getDescription() {
return description;
}
}
```
2.实体类引用枚举
需要在实体类里面将字段更改为枚举类型,如下:
```
/**
* 订单状态(
*/
private StatusEnum status;
```
3.增加配置文件
需要在配置文件里面添加对枚举转换的类:com.bitcarton.mybatisjet.core.plugin.parser
.AutoEnumTypeHandler
代码如下:
```
mybatis:
mapper-locations: classpath:/mapping/*.xml
type-aliases-package: com.panpan.housesale.entity
configuration:
map-underscore-to-camel-case: true
#log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
default-enum-type-handler: com.bitcarton.mybatisjet.core.plugin.parser.AutoEnumTypeHandler
```
通过这三个步骤的操作,就可以实现,数据库字段类型与枚举类型的匹配。
## 🍺捐赠
如果觉得还不错,请作者喝杯咖啡吧
微信支持

支付宝扫一扫支持
