inrtintolect主键⾃增_mybatis拦截器实现主键⾃动⽣成前⾔
前阵⼦和朋友聊天,他说他们项⽬有个需求,要实现主键⾃动⽣成,不想每次新增的时候,都⼿动设置主键。于是我就问他,那你们数据库表设置主键⾃动递增不就得了。他的回答是他们项⽬⽬前的id都是采⽤雪花算法来⽣成,因此为了项⽬稳定性,不会切换id的⽣成⽅式。
朋友问我有没有什么实现思路,他们公司的orm框架是mybatis,我就建议他说,不然让你⽼⼤把mybatis切换成mybatis-plus。
mybatis-plus就⽀持注解式的id⾃动⽣成,⽽且mybatis-plus只是对mybatis进⾏增强不做改变。朋友还是那句话,说为了项⽬稳定,之前项⽬组没有使⽤mybatis-plus的经验,贸然切换不知道会不会有什么坑。后⾯没招了,我就跟他说不然你⽤mybatis的拦截器实现⼀个吧。于是⼜有⼀篇吹⽔的创作题材出现。
前置知识
在介绍如何通过mybatis拦截器实现主键⾃动⽣成之前,我们先来梳理⼀些知识点
1、mybatis拦截器的作⽤
mybatis拦截器设计的初衷就是为了供⽤户在某些时候可以实现⾃⼰的逻辑⽽不必去动mybatis固有的逻辑
2、Interceptor拦截器
每个⾃定义拦截器都要实现
org.apache.ibatis.plugin.Interceptor
这个接⼝,并且⾃定义拦截器类上添加@Intercepts注解
3、拦截器能拦截哪些类型
Executor:拦截执⾏器的⽅法。ParameterHandler:拦截参数的处理。ResultHandler:拦截结果集的处理。StatementHandler:拦截Sql语法构建的处理。
4、拦截的顺序
a、不同类型拦截器的执⾏顺序
Executor -> ParameterHandler -> StatementHandler -> ResultSetHandler
b、多个拦截器拦截同种类型同⼀个⽬标⽅法,执⾏顺序是后配置的拦截器先执⾏
⽐如在mybatis配置如下
则InterceptorB先执⾏。
如果是和spring做了集成,先注⼊spring ioc容器的拦截器,则后执⾏。⽐如有个mybatisConfig,⾥⾯有如下拦截器bean配置
@Bean public InterceptorA interceptorA(){ return new InterceptorA(); } @Bean public InterceptorB interceptorB(){ return new InterceptorB(); }则InterceptorB先执⾏。当然如果你是直接⽤@Component注解这形式,则可以配合@Order注解来控制加载顺序
5、拦截器注解介绍
@Intercepts:标识该类是⼀个拦截器
@Signature:指明⾃定义拦截器需要拦截哪⼀个类型,哪⼀个⽅法。
@Signature注解属性中的type表⽰对应可以拦截四种类型(Executor、ParameterHandler、ResultHandler、StatementHandler)中的
⼀种;method表⽰对应类型(Executor、ParameterHandler、ResultHandler、StatementHandler)中的哪类⽅法;args表⽰对应
method中的参数类型
6、拦截器⽅法介绍
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是不是要
进⾏拦截,如果要拦截,就⽣成代理对象,不拦截是⽣成原⽣对象5根手指
c、 tProperties⽅法
public void tProperties(Properties properties)
⽤于在Mybatis配置⽂件中指定⼀些属性
主键⾃动⽣成思路
1、定义⼀个拦截器
主要拦截
玉林旅游景点
`Executor#update(MappedStatement ms, Object parameter)`}
这个⽅法。mybatis的inrt、update、delete都是通过这个⽅法,因此我们通过拦截这个这⽅法,来实现主键⾃动⽣成。其代码块如下
@Intercepts(value={@Signature(type = Executor.class,method = "update",args = {MappedStatement.class,Object.class})})public class AutoIdInterceptor impleme
2、判断sql操作类型
Executor 提供的⽅法中,update 包含了 新增,修改和删除类型,⽆法直接区分,需要借助 MappedStatement 类的属性
SqlCommandType 来进⾏判断,该类包含了所有的操作类型
public enum SqlCommandType { UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH;}
当SqlCommandType类型是inrt我们才进⾏主键⾃增操作
3、填充主键值
3.1、编写⾃动⽣成id注解
Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface AutoId { /** * 主键名 * @return */ String primaryKe
3.2、 雪花算法实现
我们可以直接拿hutool这个⼯具包提供的idUtil来直接实现算法。
引⼊
cn.hutool hutool-all
Snowflake snowflake = ateSnowflake(0,0);long value = Id();
3.3、填充主键值
其实现核⼼是利⽤反射。其核⼼代码⽚段如下
ReflectionUtils.Class(), field->{ ReflectionUtils.makeAccessible(field); AutoId autoId = Annotation(AutoId.c public
class SnowFlakeAutoIdProcess extends BaAutoIdProcess { private static Snowflake snowflake = ateSnowflake(0,0); public SnowFlakeAutoId 如果项⽬中的l已经的inrt语句已经含有id,⽐如
inrt into sys_test( `id`,`type`, `url`,`menu_type`,`gmt_create`)values( #{id},#{type}, #{url},#{menuType},#{gmtCreate})
则只需到填充id值这⼀步。拦截器的任务就完成。如果l的inrt不含id,形如
inrt into sys_test( `type`, `url`,`menu_type`,`gmt_create`)values( #{type}, #{url},#{menuType},#{gmtCreate})
则还需重写inrt语句以及新增id参数
4、重写inrt语句以及新增id参数(可选)
4.1 重写inrt语句
⽅法⼀:
从 MappedStatement 对象中获取 SqlSource 对象,再从从 SqlSource 对象中获取获取 BoundSql 对象,通过 BoundSql#getSql ⽅
法获取原始的sql,最后在原始sql的基础上追加id
⽅法⼆:
引⼊
com.alibabadruid${druid.version}
通过
com.alibaba.druid.sql.parr.MySqlStatementParr
获取相应的表名、需要inrt的字段名。然后重新拼凑出新的inrt语句
4.2 把新的sql重置给Invocation
其核⼼实现思路是创建⼀个新的MappedStatement,新的MappedStatement绑定新sql,再把新的MappedStatement赋值给
Invocation的args[0],代码⽚段如下
private void retSql2Invocation(Invocation invocation, BoundSqlHelper boundSqlHelper,Object entity) throws SQLException { final Object[] args = invocation
党啊亲爱的妈妈的歌词4.3 新增id参数
其核⼼是利⽤
org.apache.ibatis.mapping.ParameterMapping
核⼼代码⽚段如下
private void tPrimaryKeyParaterMapping(String primaryKey) { ParameterMapping parameterMapping = new ParameterMapping.Builder(boundSqlHelpe
5、将mybatis拦截器注⼊到spring容器
可以直接在拦截器上加
@org.springframework.stereotype.Component
注解。也可以通过
@Bean public AutoIdInterceptor autoIdInterceptor(){ return new AutoIdInterceptor(); }
6、在需要实现⾃增主键的实体字段上加如下注解
@AutoId(primaryKey = "id")private Long id;
测试
1、对应的测试实体以及单元测试代码如下
@Datapublic class TestDO implements Serializable {private static final long rialVersionUID = 1L;@AutoId(primaryKey = "id")private Long id;private Integer type; @Autowired private TestService testService; @Test public void testAdd(){ TestDO testDO = new TestDO(); testDO.tType(1); testDO.tMen
2、当mapper的inrt语句中含有id,形如下
inrt into sys_test(`id`,`type`, `url`,`menu_type`,`gmt_create`)values( #{id},#{type}, #{url},#{menuType},#{gmtCreate})
以及批量插⼊sql
inrt into sys_test( `id`,`gmt_create`,`type`,`url`,`menu_type`)values( #{test.id},#{Create},#{pe}, #{test.url},#{uType})
查看控制台sql打印语句
宋城15:52:04 [main] DEBUG com.lybgeek.dao.TestDao.save - ==> Preparing: inrt into sys_test(`id`,`type`, `url`,`menu_type`,`gmt_create`) values( ?,?, ?,?,? ) 15:5 15:52:04 [main] lybgeek.dao.TestDao.saveBatch - ==> Preparing: inrt into sys_test( `id`,`gmt_create`,`type`,`url`,`menu_type`) values ( ?,?,?, ?, ?)查看数据库
打印机显示已暂停
3、当mapper的inrt语句中不含id,形如下
inrt into sys_test(`type`, `url`,`menu_type`,`gmt_create`)values(#{type}, #{url},#{menuType},#{gmtCreate})
鼻炎怎么治好
以及批量插⼊sql