点击上方蓝字关注我们

1

01

前言

前阵子和朋友聊天,他说他们项目有个需求,要实现主键自动生成,不想每次新增的时候,都手动设置主键。于是我就问他,那你们数据库表设置主键自动递增不就得了。他的回答是他们项目目前的id都是采用雪花算法来生成,因此为了项目稳定性,不会切换id的生成方式。

朋友问我有没有什么实现思路,他们公司的orm框架是mybatis,我就建议他说,不然让你老大把mybatis切换成mybatis-plus。mybatis-plus就支持注解式的id自动生成,而且mybatis-plus只是对mybatis进行增强不做改变。朋友还是那句话,说为了项目稳定,之前项目组没有使用mybatis-plus的经验,贸然切换不知道会不会有什么坑。后面没招了,我就跟他说不然你用mybatis的拦截器实现一个吧。于是又有一篇吹水的创作题材出现。

1

02

前置知识

在介绍如何通过mybatis拦截器实现主键自动生成之前,我们先来梳理一些知识点

mybatis拦截器的作用

mybatis拦截器设计的初衷就是为了供用户在某些时候可以实现自己的逻辑而不必去动mybatis固有的逻辑

Interceptor拦截器

每个自定义拦截器都要实现

org.apache.ibatis.plugin.Interceptor

这个接口,并且自定义拦截器类上添加@Intercepts注解

拦截器能拦截哪些类型

Executor:拦截执行器的方法。ParameterHandler:拦截参数的处理。ResultHandler:拦截结果集的处理。StatementHandler:拦截Sql语法构建的处理。

拦截的顺序a、不同类型拦截器的执行顺序

Executor -> ParameterHandler -> StatementHandler -> ResultSetHandler

b、多个拦截器拦截同种类型同一个目标方法,执行顺序是后配置的拦截器先执行

比如在mybatis配置如下

<plugins>    <plugin interceptor="com.lybgeek.InterceptorA" />    <plugin interceptor="com.lybgeek.InterceptorB" />  plugins>

则InterceptorB先执行。

如果是和spring做了集成,先注入spring ioc容器的拦截器,则后执行。比如有个mybatisConfig,里面有如下拦截器bean配置

    @Bean    public InterceptorA interceptorA(){        return new InterceptorA();    }

    @Bean    public InterceptorB interceptorB(){        return new InterceptorB();    }

则InterceptorB先执行。当然如果你是直接用@Component注解这形式,则可以配合@Order注解来控制加载顺序

拦截器注解介绍

@Intercepts:标识该类是一个拦截器

@Signature:指明自定义拦截器需要拦截哪一个类型,哪一个方法。
@Signature注解属性中的type表示对应可以拦截四种类型(Executor、ParameterHandler、ResultHandler、StatementHandler)中的一种;method表示对应类型(Executor、ParameterHandler、ResultHandler、StatementHandler)中的哪类方法;args表示对应method中的参数类型

拦截器方法介绍a、 intercept方法

public Object intercept(Invocation invocation) throws Throwable

这个方法就是我们来执行我们自己想实现的业务逻辑,比如我们的主键自动生成逻辑就是在这边实现。

Invocation这个类中的成员属性target就是@Signature中的type;method就是@Signature中的method;args就是@Signature中的args参数类型的具体实例对象

b、 plugin方法

public Object plugin(Object target)

这个是用返回代理对象或者是原生代理对象,如果你要返回代理对象,则返回值可以设置为

Plugin.wrap(target, this);this为拦截器

如果返回是代理对象,则会执行拦截器的业务逻辑,如果直接返回target,就是没有拦截器的业务逻辑。说白了就是告诉mybatis是不是要进行拦截,如果要拦截,就生成代理对象,不拦截是生成原生对象

c、 setProperties方法

public void setProperties(Properties properties)

用于在Mybatis配置文件中指定一些属性

1

03

主键自动生成思路

定义一个拦截器

主要拦截

Executor#update(MappedStatement ms, Object parameter)`}

这个方法。mybatis的insert、update、delete都是通过这个方法,因此我们通过拦截这个这方法,来实现主键自动生成。其代码块如下

@Intercepts(value={@Signature(type = Executor.class,method = "update",args = {MappedStatement.class,Object.class})})public class AutoIdInterceptor implements Interceptor {}

判断sql操作类型

Executor 提供的方法中,update 包含了 新增,修改和删除类型,无法直接区分,需要借助 MappedStatement 类的属性 SqlCommandType 来进行判断,该类包含了所有的操作类型

public enum SqlCommandType {  UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH;}

当SqlCommandType类型是insert我们才进行主键自增操作

填充主键值a、编写自动生成id注解

Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface AutoId {    /**     * 主键名     * @return     */    String primaryKey();

    /**     * 支持的主键算法类型     * @return     */    IdType type() default IdType.SNOWFLAKE;

    enum IdType{        SNOWFLAKE    }}

b、 雪花算法实现

我们可以直接拿hutool这个工具包提供的idUtil来直接实现算法。

<dependency>            <groupId>cn.hutoolgroupId>            <artifactId>hutool-allartifactId>        dependency>
Snowflake snowflake = IdUtil.createSnowflake(0,0);long value = snowflake.nextId();

c、填充主键值

其实现核心是利用反射。其核心代码片段如下

ReflectionUtils.doWithFields(entity.getClass(), field->{                    ReflectionUtils.makeAccessible(field);                    AutoId autoId = field.getAnnotation(AutoId.class);                    if(!ObjectUtils.isEmpty(autoId) && (field.getType().isAssignableFrom(Long.class))){                        switch (autoId.type()){                            case SNOWFLAKE:                                SnowFlakeAutoIdProcess snowFlakeAutoIdProcess = new SnowFlakeAutoIdProcess(field);                                snowFlakeAutoIdProcess.setPrimaryKey(autoId.primaryKey());                                finalIdProcesses.add(snowFlakeAutoIdProcess);                                break;                        }                    }                });
public class SnowFlakeAutoIdProcess extends BaseAutoIdProcess {

    private static Snowflake snowflake = IdUtil.createSnowflake(0,0);

    public SnowFlakeAutoIdProcess(Field field) {        super(field);    }

    @Override    void setFieldValue(Object entity) throws Exception{        long value = snowflake.nextId();        field.set(entity,value);    }}

如果项目中的mapper.xml已经的insert语句已经含有id,比如

insert into sys_test( `id`,`type`, `url`,`menu_type`,`gmt_create`)values( #{id},#{type}, #{url},#{menuType},#{gmtCreate})

则只需到填充id值这一步。拦截器的任务就完成。如果mapper.xml的insert不含id,形如

insert into sys_test( `type`, `url`,`menu_type`,`gmt_create`)values( #{type}, #{url},#{menuType},#{gmtCreate})

则还需重写insert语句以及新增id参数

重写insert语句以及新增id参数(可选)a、重写insert语句

方法一:
从 MappedStatement 对象中获取 SqlSource 对象,再从从 SqlSource 对象中获取获取 BoundSql 对象,通过 BoundSql#getSql 方法获取原始的sql,最后在原始sql的基础上追加id

方法二:

引入

<dependency>      <groupId>com.alibabagroupId>      <artifactId>druidartifactId>      <version>${druid.version}version>    dependency>

通过

com.alibaba.druid.sql.dialect.mysql.parser.MySqlStatementParser

获取相应的表名、需要insert的字段名。然后重新拼凑出新的insert语句

b、把新的sql重置给Invocation

其核心实现思路是创建一个新的MappedStatement,新的MappedStatement绑定新sql,再把新的MappedStatement赋值给Invocation的args[0],代码片段如下

private void resetSql2Invocation(Invocation invocation, BoundSqlHelper boundSqlHelper,Object entity) throws SQLException {        final Object[] args = invocation.getArgs();        MappedStatement statement = (MappedStatement) args[0];        MappedStatement newStatement = newMappedStatement(statement, new BoundSqlSqlSource(boundSqlHelper));        MetaObject msObject = MetaObject.forObject(newStatement, new DefaultObjectFactory(), new DefaultObjectWrapperFactory(),new DefaultReflectorFactory());        msObject.setValue("sqlSource.boundSqlHelper.boundSql.sql", boundSqlHelper.getSql());

            args[0] = newStatement;

    }

c、新增id参数

其核心是利用

org.apache.ibatis.mapping.ParameterMapping

核心代码片段如下

private void setPrimaryKeyParaterMapping(String primaryKey) {           ParameterMapping parameterMapping = new ParameterMapping.Builder(boundSqlHelper.getConfiguration(),primaryKey,boundSqlHelper.getTypeHandler()).build();           boundSqlHelper.getBoundSql().getParameterMappings().add(parameterMapping);       }

d、将mybatis拦截器注入到spring容器

可以直接在拦截器上加

@org.springframework.stereotype.Component

注解。也可以通过

    @Bean    public AutoIdInterceptor autoIdInterceptor(){        return new AutoIdInterceptor();    }

e、在需要实现自增主键的实体字段上加如下注解

 @AutoId(primaryKey = "id")  private Long id;

1

04

测试

对应的测试实体以及单元测试代码如下

@Datapublic class TestDO implements Serializable {  private static final long serialVersionUID = 1L;

  @AutoId(primaryKey = "id")  private Long id;  private Integer type;  private String url;  private Date gmtCreate;  private String menuType;}
@Autowired    private TestService testService;

    @Test    public void testAdd(){        TestDO testDO = new TestDO();        testDO.setType(1);        testDO.setMenuType("1");        testDO.setUrl("www.test.com");        testDO.setGmtCreate(new Date());        testService.save(testDO);        testService.get(110L);    }

    @Test    public void testBatch(){        List testDOList = new ArrayList<>();for (int i = 0; i < 3; i++) {            TestDO testDO = new TestDO();            testDO.setType(i);            testDO.setMenuType(i+"");            testDO.setUrl("www.test"+i+".com");            testDO.setGmtCreate(new Date());            testDOList.add(testDO);        }        testService.saveBatch(testDOList);    }

当mapper的insert语句中含有id

形如下

<insert id="save" parameterType="com.lybgeek.TestDO" useGeneratedKeys="true" keyProperty="id">    insert into sys_test(`id`,`type`, `url`,`menu_type`,`gmt_create`)    values( #{id},#{type}, #{url},#{menuType},#{gmtCreate})  insert>

以及批量插入sql

<insert id="saveBatch"  parameterType="java.util.List" useGeneratedKeys="false">    insert into sys_test( `id`,`gmt_create`,`type`,`url`,`menu_type`)    values    "list" item="test" index="index" separator=",">      ( #{test.id},#{test.gmtCreate},#{test.type}, #{test.url},      #{test.menuType})  insert>

查看控制台sql打印语句

15:52:04 [main] DEBUG com.lybgeek.dao.TestDao.save - ==> Preparing: insert into sys_test(`id`,`type`, `url`,`menu_type`,`gmt_create`) values( ?,?, ?,?,? )15:52:04 [main] DEBUG com.lybgeek.dao.TestDao.save - ==> Parameters: 356829258376544258(Long), 1(Integer), www.test.com(String), 1(String), 2020-09-11 15:52:04.738(Timestamp)15:52:04 [main] DEBUG com.lybgeek.dao.TestDao.save - <== Updates: 1
15:52:04 [main] DEBUG c.n.lybgeek.dao.TestDao.saveBatch - ==> Preparing: insert into sys_test( `id`,`gmt_create`,`type`,`url`,`menu_type`) values ( ?,?,?, ?, ?) , ( ?,?,?, ?, ?) , ( ?,?,?, ?, ?)15:52:04 [main] DEBUG c.n.lybgeek.dao.TestDao.saveBatch - ==> Parameters: 356829258896637961(Long), 2020-09-11 15:52:04.847(Timestamp), 0(Integer), www.test0.com(String), 0(String), 356829258896637960(Long), 2020-09-11 15:52:04.847(Timestamp), 1(Integer), www.test1.com(String), 1(String), 356829258896637962(Long), 2020-09-11 15:52:04.847(Timestamp), 2(Integer), www.test2.com(String), 2(String)15:52:04 [main] DEBUG c.n.lybgeek.dao.TestDao.saveBatch - <== Updates: 3

查看数据库

当mapper的insert语句中不含id

形如下

<insert id="save" parameterType="com.lybgeek.TestDO" useGeneratedKeys="true" keyProperty="id">    insert into sys_test(`type`, `url`,`menu_type`,`gmt_create`)    values(#{type}, #{url},#{menuType},#{gmtCreate})  insert>

以及批量插入sql

<insert id="saveBatch"  parameterType="java.util.List" useGeneratedKeys="false">    insert into sys_test(`gmt_create`,`type`,`url`,`menu_type`)    values    "list" item="test" index="index" separator=",">      (#{test.gmtCreate},#{test.type}, #{test.url},      #{test.menuType})  insert>

查看控制台sql打印语句

15:59:46 [main] DEBUG com.lybgeek.dao.TestDao.save - ==> Preparing: insert into sys_test(`type`,`url`,`menu_type`,`gmt_create`,id) values (?,?,?,?,?)15:59:46 [main] DEBUG com.lybgeek.dao.TestDao.save - ==> Parameters: 1(Integer), www.test.com(String), 1(String), 2020-09-11 15:59:46.741(Timestamp), 356831196144992264(Long)15:59:46 [main] DEBUG com.lybgeek.dao.TestDao.save - <== Updates: 1
15:59:46 [main] DEBUG c.n.lybgeek.dao.TestDao.saveBatch - ==> Preparing: insert into sys_test(`gmt_create`,`type`,`url`,`menu_type`,id) values (?,?,?,?,?),(?,?,?,?,?),(?,?,?,?,?)15:59:46 [main] DEBUG c.n.lybgeek.dao.TestDao.saveBatch - ==> Parameters: 2020-09-11 15:59:46.845(Timestamp), 0(Integer), www.test0.com(String), 0(String), 356831196635725829(Long), 2020-09-11 15:59:46.845(Timestamp), 1(Integer), www.test1.com(String), 1(String), 356831196635725828(Long), 2020-09-11 15:59:46.845(Timestamp), 2(Integer), www.test2.com(String), 2(String), 356831196635725830(Long)15:59:46 [main] DEBUG c.n.lybgeek.dao.TestDao.saveBatch - <== Updates: 3

从控制台我们可以看出,当mapper.xml没有配置id字段时,则拦截器会自动帮我们追加id字段

查看数据库

1

05

总结

本文虽然是介绍mybatis拦截器实现主键自动生成,但文中更多讲解如何实现一个拦截器以及主键生成思路,并没把intercept实现主键方法贴出来。其原因主要是主键自动生成在mybatis-plus里面就有实现,其次是有思路后,大家就可以自己实现了。最后对具体实现感兴趣的朋友,可以查看文末中demo链接

1

06

参考文档

https://www.cnblogs.com/chenchen127/p/12111159.html

https://blog.csdn.net/hncaoyuqi/article/details/103187983

https://blog.csdn.net/zsj777/article/details/81986096

1

07

demo链接

https://github.com/lyb-geek/springboot-learning/tree/master/springboot-mybatis-autoId

by mybatis 自定义order_springboot2结合mybatis拦截器实现主键自动生成相关推荐

  1. insert into select 主键自增_springboot2结合mybatis拦截器实现主键自动生成

    点击上方蓝字关注我们 1 01 前言 前阵子和朋友聊天,他说他们项目有个需求,要实现主键自动生成,不想每次新增的时候,都手动设置主键.于是我就问他,那你们数据库表设置主键自动递增不就得了.他的回答是他 ...

  2. java kafka 设置分区_Java kafka如何实现自定义分区类和拦截器

    Java kafka如何实现自定义分区类和拦截器 2.producer配置文件指定,具体的分区类 // 具体的分区类 props.put(ProducerConfig.PARTITIONER_CLAS ...

  3. java kafka 分区_Java kafka如何实现自定义分区类和拦截器

    生产者发送到对应的分区有以下几种方式: (1)指定了patition,则直接使用:(可以查阅对应的java api, 有多种参数) (2)未指定patition但指定key,通过对key的value进 ...

  4. kafka 自定义Interceptor(通过拦截器对消息进行定制化处理)

    文章目录 1. 说明 1.1 configure(configs) 1.2 onSend(ProducerRecord) 1.3 onAcknowledgement(RecordMetadata, E ...

  5. Spring 拦截器和过滤器中自动注入为 null 的原因及解决方案

    起因 开发过程中在过滤器(filter)中注入Bean出现空指针异常,通过查找资料了解空指针的原因,特此记录. 问题分析 由于其他bean在service,controller层注入一点问题也没有,开 ...

  6. MyBatis在Oracle中插入数据并返回主键的问题解决

    前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家.点击跳转到教程. 前言:我早期用过这个方法,但是返回的依旧是影响行数,不是主键. 只是这种写法可以达到我要的效果: 用 ...

  7. spring mvc +cookie+拦截器功能 实现系统自动登陆

    先看看我遇到的问题: @ResponseBody@RequestMapping("/logout")public Json logout(HttpSession session,H ...

  8. mybatis 增删改查、批量插入和删除以及自动生成uuid主键和分页

    Mapper接口: public int update(Admin admin); public Admin selectByUserName(String account); public List ...

  9. mybatis中(Oracle)关于insert时主键自动加1的使用方法

    <insert id="insert" paramType="User"> <selecKey keyProperty="id&qu ...

最新文章

  1. c语言的适当大小的子集,编译原理上机实验报告
  2. git-flow 流程 备忘清单
  3. Django - app
  4. Conda官方下载安装步骤及conda用法详细介绍
  5. Leecode15. 三数之和——Leecode大厂热题100道系列
  6. 《程序员面试金典》输出单层节点
  7. 【Blog.Core开源】完成升级.NET 6.0
  8. 带有DIY的Openshift上的Spring Boot / Java 8 / Tomcat 8
  9. Linux加密框架 crypto RC4
  10. polymer web componets 大前端
  11. visio画图中怎么添加①
  12. androidstudio 日历视图怎么显示农历_ipad自带的日历程序
  13. 第 24 章 状态模式
  14. SQL中删除重复数据问题
  15. VMware虚拟机中Windows内存扩展
  16. 计算机安全模式怎么消除计,大神为你解说win7系统解除word安全模式的妙计
  17. 运筹学4个人完成三项工作_【用人话讲运筹学】十一、指派问题
  18. 中国软件企业排名(不是绝对的)
  19. 基于PHP的班级分数量化管理系统
  20. oracle启用amm,【内存管理】Oracle AMM自动内存管理详解

热门文章

  1. jsf初学解决GlassFish Server 无法启动
  2. DEEPIN搭建J2EE时遇到This program requires DISPLAY en...
  3. 组策略批量更改固定IP为自动获取
  4. [eZ publish] fetch_alias() and fetch()
  5. 同林多域共用一台Exchange
  6. oracle数据库基础知识总结,oracle知识点总结(一)
  7. 保护模式下的80386及其编程02:机器状态和存储寻址
  8. 堆和栈区别 java_JAVA中堆和栈的区别
  9. WinAPI-01GetModuleHandle
  10. 移动组件到指定坐标_《我的世界》传送石碑组件 史蒂夫表示跑路的日子终于结束了...