# 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 👈

Maven Central


[**🌎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项目 第一步: ![Image text](p1.png) 第二步: ![Image text](p2.png) 第三步:选择LomBok插件 ![Image text](p3.png) 项目基本结构 ![Image text](p4.png) 👉 3.配置Maven 在项目的pom.xml文件中加入mybatis-jet的Maven依赖:mybatis-jet-spring-boot-starter 最新版本:Maven Central ```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.在控制台输入作者以及要生成代码的表 ![Image text](pp1.png) 3.输入完成之后点击回车 4.当输出以下信息表示代码生成成功 ![Image text](pp2.png) 5.去代码生成的位置查看生成的代码 ![Image text](pp3.png) 👉3.建立代码包 建立代码包:entity、controller、mapper、service、impl以及xml的文件夹 ![Image text](pp4.png) 👉4.复制代码 将生成的代码复制到对应的文件夹 ![Image text](pp5.png) 👉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 ``` 通过这三个步骤的操作,就可以实现,数据库字段类型与枚举类型的匹配。 ## 🍺捐赠 如果觉得还不错,请作者喝杯咖啡吧 微信支持 ![Image text](f1.jpg) 支付宝扫一扫支持 ![Image text](f2.jpg)