springboot2结合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配置如下
<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注解来控制加载顺序
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是不是要进⾏拦截,如果要拦截,就⽣成代理对象,不拦截是⽣成原⽣对象
c、 setProperties⽅法
public void setProperties(Properties properties)
⽤于在Mybatis配置⽂件中指定⼀些属性
主键⾃动⽣成思路
1、定义⼀个
主要拦截
`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 {}
2、判断sql操作类型
Executor 提供的⽅法中,update 包含了 新增,修改和删除类型,⽆法直接区分,需要借助 MappedStatement 类的属性SqlCommandType 来进⾏判断,该类包含了所有的操作类型
public enum SqlCommandType {
UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH;
}
当SqlCommandType类型是insert我们才进⾏主键⾃增操作
3、填充主键值
3.1、编写⾃动⽣成id注解
Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AutoId {
/**
* 主键名
* @return
*/
String primaryKey();
/**
* ⽀持的主键算法类型
* @return
*/
IdType type()default IdType.SNOWFLAKE;
enum IdType{
SNOWFLAKE
}
}
3.2、 雪花算法实现
我们可以直接拿hutool这个⼯具包提供的idUtil来直接实现算法。
引⼊
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
Snowflake snowflake = ateSnowflake(0,0);
long value = Id();
3.3、填充主键值
其实现核⼼是利⽤反射。其核⼼代码⽚段如下
ReflectionUtils.Class(), field->{
ReflectionUtils.makeAccessible(field);
AutoId autoId = Annotation(AutoId.class);
if(!ObjectUtils.isEmpty(autoId)&&(Type().isAssignableFrom(Long.class))){
pe()){
case SNOWFLAKE:
SnowFlakeAutoIdProcess snowFlakeAutoIdProcess =new SnowFlakeAutoIdProcess(field);
snowFlakeAutoIdProcess.setPrimaryKey(autoId.primaryKey());
finalIdProcesses.add(snowFlakeAutoIdProcess);
break;
}
}
});
public class SnowFlakeAutoIdProcess extends BaseAutoIdProcess {
private static Snowflake snowflake = ateSnowflake(0,0);
public SnowFlakeAutoIdProcess(Field field){
super(field);
}
@Override
void setFieldValue(Object entity)throws Exception{
long value = Id();
field.set(entity,value);spring ioc注解
}
}
如果项⽬中的l已经的insert语句已经含有id,⽐如
insert into sys_test( `id`,`type`, `url`,`menu_type`,`gmt_create`)values( #{id},#{type}, #{url},#{menuType},#{gmtCreate})
则只需到填充id值这⼀步。的任务就完成。如果l的insert不含id,形如
insert into sys_test( `type`, `url`,`menu_type`,`gmt_create`)values( #{type}, #{url},#{menuType},#{gmtCreate})
则还需重写insert语句以及新增id参数
4、重写insert语句以及新增id参数(可选)
4.1 重写insert语句
⽅法⼀:
从 MappedStatement 对象中获取 SqlSource 对象,再从从 SqlSource 对象中获取获取 BoundSql 对
象,通过 BoundSql#getSql ⽅法获取原始的sql,最后在原始sql的基础上追加id
⽅法⼆:
引⼊
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
通过
com.alibaba.druid.sql.parser.MySqlStatementParser
获取相应的表名、需要insert的字段名。然后重新拼凑出新的insert语句
4.2 把新的sql重置给Invocation
其核⼼实现思路是创建⼀个新的MappedStatement,新的MappedStatement绑定新sql,再把新的MappedStatement赋值给Invocation的args[0],代码⽚段如下
private void resetSql2Invocation(Invocation invocation, BoundSqlHelper boundSqlHelper,Object entity)throws SQLException { final Object[] args = Args();
MappedStatement statement =(MappedStatement) args[0];
MappedStatement newStatement =newMappedStatement(statement,new BoundSqlSqlSource(boundSqlHelper));
MetaObject msObject = MetaObject.forObject(newStatement,new DefaultObjectFactory(),new DefaultObjectWrapperFactory(),new DefaultReflector Factory());
msObject.setValue("sqlSource.boundSqlHelper.boundSql.sql", Sql());
args[0]= newStatement;
}
4.3 新增id参数
其核⼼是利⽤
org.apache.ibatis.mapping.ParameterMapping
核⼼代码⽚段如下
private void setPrimaryKeyParaterMapping(String primaryKey){
ParameterMapping parameterMapping =new ParameterMapping.Configuration(),Type Handler()).build();
}
5、将mybatis注⼊到spring容器
可以直接在上加
@org.springframework.stereotype.Component
注解。也可以通过
@Bean
public AutoIdInterceptor autoIdInterceptor(){
return new AutoIdInterceptor();
}
6、在需要实现⾃增主键的实体字段上加如下注解
@AutoId(primaryKey ="id")
private Long id;
测试
1、对应的测试实体以及单元测试代码如下
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论