insert into select 主键自增_springboot2结合mybatis拦截器实现主键自动生成
點擊上方藍字關注我們
1
01
前言
前陣子和朋友聊天,他說他們項目有個需求,要實現主鍵自動生成,不想每次新增的時候,都手動設置主鍵。于是我就問他,那你們數據庫表設置主鍵自動遞增不就得了。他的回答是他們項目目前的id都是采用雪花算法來生成,因此為了項目穩(wěn)定性,不會切換id的生成方式。
朋友問我有沒有什么實現思路,他們公司的orm框架是mybatis,我就建議他說,不然讓你老大把mybatis切換成mybatis-plus。mybatis-plus就支持注解式的id自動生成,而且mybatis-plus只是對mybatis進行增強不做改變。朋友還是那句話,說為了項目穩(wěn)定,之前項目組沒有使用mybatis-plus的經驗,貿然切換不知道會不會有什么坑。后面沒招了,我就跟他說不然你用mybatis的攔截器實現一個吧。于是又有一篇吹水的創(chuàng)作題材出現。
1
02
前置知識
在介紹如何通過mybatis攔截器實現主鍵自動生成之前,我們先來梳理一些知識點
mybatis攔截器的作用mybatis攔截器設計的初衷就是為了供用戶在某些時候可以實現自己的邏輯而不必去動mybatis固有的邏輯
Interceptor攔截器每個自定義攔截器都要實現
org.apache.ibatis.plugin.Interceptor這個接口,并且自定義攔截器類上添加@Intercepts注解
攔截器能攔截哪些類型Executor:攔截執(zhí)行器的方法。ParameterHandler:攔截參數的處理。ResultHandler:攔截結果集的處理。StatementHandler:攔截Sql語法構建的處理。
攔截的順序a、不同類型攔截器的執(zhí)行順序Executor?-> ParameterHandler -> StatementHandler -> ResultSetHandlerb、多個攔截器攔截同種類型同一個目標方法,執(zhí)行順序是后配置的攔截器先執(zhí)行比如在mybatis配置如下
<plugins>????<plugin?interceptor="com.lybgeek.InterceptorA"?/>
????<plugin?interceptor="com.lybgeek.InterceptorB"?/>
??plugins>
則InterceptorB先執(zhí)行。
如果是和spring做了集成,先注入spring ioc容器的攔截器,則后執(zhí)行。比如有個mybatisConfig,里面有如下攔截器bean配置
? ? @Bean????public?InterceptorA interceptorA(){
????????return?new?InterceptorA();
????}
????@Bean
????public?InterceptorB interceptorB(){
????????return?new?InterceptorB();
????}
則InterceptorB先執(zhí)行。當然如果你是直接用@Component注解這形式,則可以配合@Order注解來控制加載順序
攔截器注解介紹@Intercepts:標識該類是一個攔截器
@Signature:指明自定義攔截器需要攔截哪一個類型,哪一個方法。
@Signature注解屬性中的type表示對應可以攔截四種類型(Executor、ParameterHandler、ResultHandler、StatementHandler)中的一種;method表示對應類型(Executor、ParameterHandler、ResultHandler、StatementHandler)中的哪類方法;args表示對應method中的參數類型
這個方法就是我們來執(zhí)行我們自己想實現的業(yè)務邏輯,比如我們的主鍵自動生成邏輯就是在這邊實現。
Invocation這個類中的成員屬性target就是@Signature中的type;method就是@Signature中的method;args就是@Signature中的args參數類型的具體實例對象
b、?plugin方法public?Object?plugin(Object?target)這個是用返回代理對象或者是原生代理對象,如果你要返回代理對象,則返回值可以設置為
Plugin.wrap(target, this);this為攔截器
如果返回是代理對象,則會執(zhí)行攔截器的業(yè)務邏輯,如果直接返回target,就是沒有攔截器的業(yè)務邏輯。說白了就是告訴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 包含了 新增,修改和刪除類型,無法直接區(qū)分,需要借助 MappedStatement 類的屬性?SqlCommandType?來進行判斷,該類包含了所有的操作類型
public?enum?SqlCommandType {??UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH;
}
當SqlCommandType類型是insert我們才進行主鍵自增操作
填充主鍵值a、編寫自動生成id注解Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)
@Documented
public?@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其核心實現思路是創(chuàng)建一個新的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:?115: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:?115: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
總結
以上是生活随笔為你收集整理的insert into select 主键自增_springboot2结合mybatis拦截器实现主键自动生成的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python代码检查工具_基于Pytho
- 下一篇: python程序运行时间计算公式_Pyt