原生的Mybatis框架是沒有ID自增器,但例如國產(chǎn)的Mybatis Plus卻是支持,不過,Mybatis Plus卻是缺少了自定屬性的填充;例如:我們需要自定義填充一些屬性,updateDate、createDate等,這時Mybatis Plus自帶的ID自增器就無法滿足需求;這種時候我們就需要自定義的ID增加器,可以自定義ID增長策略同時還得支持更多的屬性自定義擴展,當然,最好能做成插件形式為其他項目或者模塊提供引入那就更新好了。
?
在開始之前我們先確定和分析主要的需求,了解了需求才能更好的制作
首先我們得確定主要的宗旨那就是實現(xiàn)ID自增長器,同時,還等保證該增長的靈活性和擴展性,讓其他項目引入之后可以很靈活的更改增長策略,隨意替換ID增長的實現(xiàn)。主要需求為下列幾點:
- 自定義的ID增長策略,同時保證該特性靈活性可以隨意替換
- 支持自定義的附帶屬性擴展增強
- 保證其他的項目引起使用時的絕對簡單,最好能像Spring Boot自動裝配模塊一樣保證簡單
確定需求之后我們現(xiàn)在開始根據(jù)需求來功能了,我們由外到內(nèi)、由粗到細的去實現(xiàn)整個模塊。
先確定該模塊的外體特征,先看第3點
保證其他的項目引起使用時的絕對簡單,最好能像Spring Boot自動裝配模塊一樣保證簡單
要保證該模塊的引用使用簡單那么就必須使用Spring Boot的特性->自動配置,實現(xiàn)自定義場景裝配器,利用該場景裝配才能保證模塊可以自動配置完成啟動之初的初始化。
我們先來新建Maven模塊
這里的模塊命名最好遵循Spring Boot官方的建議,第三方的模塊命名由模塊名稱+spring-boot;官方模塊由spring-boot+模塊名稱
建好模塊之后我們來整理下目錄
這里我們把多余的目錄、文件刪除,這里使用了Maven的初始化模板,會自動生成一些模板文件;但是,該操作的主要目的只是獲得一個Maven結(jié)構(gòu)的項目
接下來確定POM文件的引用
1 <!-- springboot自動配置--> 2 <dependency> 3 <groupId>org.springframework.boot</groupId> 4 <artifactId>spring-boot-autoconfigure</artifactId> 5 </dependency> 6 7 <!-- springboot自動配置處理器--> 8 <dependency> 9 <groupId>org.springframework.boot</groupId> 10 <artifactId>spring-boot-configuration-processor</artifactId> 11 <optional>true</optional> 12 </dependency> 13 14 <!-- mybatis啟動器,本質(zhì)是在Mybatis的基礎(chǔ)上進行擴展的,必須引入Mybatis的支持--> 15 <dependency> 16 <groupId>org.mybatis.spring.boot</groupId> 17 <artifactId>mybatis-spring-boot-starter</artifactId> 18 <version>2.2.2</version> 19 </dependency>
到這里整個模塊的創(chuàng)建已經(jīng)完成了,接下來我們的仔細分析下該如何實現(xiàn)自定義的增長器
?插件要使用方便那么就意味著要拋棄繁雜的配置,同時對必要的信息注入配置時應(yīng)該采用注解的方式來保持簡潔;
既然是ID增長器那就必須的確定哪個屬性為ID,并且確定ID屬性的類的全限定名,在這里我們定義兩個注解
1、這里定義了@Key注解,該注解的主要目的是為了標識出ORM實體類映射數(shù)據(jù)庫表中的主鍵,插件最終的主要注入的屬性
1 /** 2 * 主鍵注解標記 3 * @Author: Song L.Lu 4 * @Since: 2024-01-18 11:20 5 **/ 6 7 @Documented 8 @Retention(RetentionPolicy.RUNTIME) 9 @Target({ElementType.FIELD}) 10 public @interface Key { 11 String value() default ""; 12 }
2、@KeyGenerator 注解,用來標記當前的實體類所在的包路徑,該路徑為插件提供查找實體類的路徑;注意該注解標記于Spring Boot啟動主類上
格式@KeyGenerator ("xxx.xx.xx.entity")
1 /** 2 * 標記當前實體類所在的包路徑 3 * @Author: Song L.Lu 4 * @Since: 2024-01-18 11:19 5 **/ 6 7 @Documented 8 @Retention(RetentionPolicy.RUNTIME) 9 @Target({ElementType.TYPE}) 10 public @interface KeyGenerator { 11 String value(); 12 }
3、創(chuàng)建一個Spring Boot 的AutoConfiguration用來自動完成插件的裝配,包括初始化上下文環(huán)境、掃描Entity實體類、注入Mybatis攔截器
GeneratorAutoConfiguration類由MATE-INFO/spring。factories注入,該配置文件在Spring Boot啟動時會自動掃描各模塊下的resource/MATE-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.ycg.tab.cloud.mybatis.generator.config.autoconfigure.GeneratorAutoConfiguration
?這里往Spring Context中注入了兩個實例對象
ParseMapperFactoryProcessor實現(xiàn)BeanFactoryPostProcessor.postProcessBeanFactory接口方法,其目的是在工廠創(chuàng)建并掃描完Beandefition后觸發(fā)實體類的掃描
GeneratorInterceptor實現(xiàn)了Mybatis的攔截器,其攔截活動為update/insert,在Mybatis觸發(fā)update/insert操作時進行ID增長和自定義的屬性添加效果
1 /** 2 * 自動裝配 3 * @Author: Song L.Lu 4 * @Since: 2024-01-18 11:19 5 **/ 6 7 @ConditionalOnBean({MybatisAutoConfiguration.class}) 8 @AutoConfigureAfter({MybatisAutoConfiguration.class, SnowflakeIdGenerator.class}) 9 public class GeneratorAutoConfiguration { 10 11 /** 12 * 注入實體類掃描處理器 13 * 主要在BeanFactoryPostProcessor.postProcessBeanFactory期間完成實體類的掃描 14 * @return 15 */ 16 @Bean 17 public BeanDefinitionRegistryPostProcessor postProcessor() { 18 return new ParseMapperFactoryProcessor(); 19 } 20 21 /** 22 * 向Spring context注入Mybatis攔截器 23 * @param generator 24 * @param mapperRegisterStore 25 * @return 26 */ 27 @Bean 28 public Interceptor interceptor(Generator<?> generator, MapperRegisterStore mapperRegisterStore) { 29 return new GeneratorInterceptor(generator, mapperRegisterStore); 30 } 31 }
4、ParseMapperFactoryProcessor類
1 /** 2 * 實體類掃描處理器 3 * 主要工作完成@Key注解的掃描,確定標記的主鍵 4 * @Author: Song L.Lu 5 * @Since: 2024-01-18 11:17 6 **/ 7 public class ParseMapperFactoryProcessorimplements BeanDefinitionRegistryPostProcessor { 8 static final String DEFAULT_RESOURCE_PATTERN = "**/*.class"; 9 10 private final MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(); 11 12 private final String resourcePattern = "**/*.class"; 13 14 private Environment environment; 15 16 private ResourcePatternResolver resourcePatternResolver; 17 18 public static boolean checkConfigurationClassCandidate(BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) { 19 AnnotationMetadata metadata; 20 String className = beanDef.getBeanClassName(); 21 if (className == null || beanDef.getFactoryMethodName() != null) 22 return false; 23 if (beanDef instanceof AnnotatedBeanDefinition && className 24 .equals(((AnnotatedBeanDefinition)beanDef).getMetadata().getClassName())) { 25 metadata = ((AnnotatedBeanDefinition)beanDef).getMetadata(); 26 } else { 27 try { 28 MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(className); 29 metadata = metadataReader.getAnnotationMetadata(); 30 } catch (IOException ex) { 31 return false; 32 } 33 } 34 return metadata.hasAnnotation("org.springframework.boot.autoconfigure.SpringBootApplication"); 35 } 36 37 public final Environment getEnvironment() { 38 if (this.environment == null) 39 this.environment = new StandardEnvironment(); 40 return this.environment; 41 } 42 43 private ResourcePatternResolver getResourcePatternResolver() { 44 if (this.resourcePatternResolver == null) 45 this.resourcePatternResolver = new PathMatchingResourcePatternResolver(); 46 return this.resourcePatternResolver; 47 } 48 49 protected String resolveBasePackage(String basePackage) { 50 return ClassUtils.convertClassNameToResourcePath(getEnvironment().resolveRequiredPlaceholders(basePackage)); 51 } 52 53 public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { 54 BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.rootBeanDefinition(MapperRegisterStore.class); 55 beanDefinitionBuilder.setScope("singleton"); 56 AbstractBeanDefinition abstractBeanDefinition = beanDefinitionBuilder.getBeanDefinition(); 57 registry.registerBeanDefinition(Objects.requireNonNull(abstractBeanDefinition.getBeanClassName()), abstractBeanDefinition); 58 } 59 60 /** 61 * 掃描被@Key注解標識的實體類 62 * @param beanFactory 63 * @return 64 */ 65 public Map<Class<?>, Field> parseBeanDefinitionCandidate(ConfigurableListableBeanFactory beanFactory) { 66 Map<Class<?>, Field> tableClassz = new HashMap<>(); 67 List<BeanDefinitionHolder> candidates = new ArrayList<>(); 68 String[] candidateNames = beanFactory.getBeanDefinitionNames(); // 從BeanFactory中取出已經(jīng)構(gòu)建的BeanDefinition 69 for (String name : candidateNames) { 70 BeanDefinition beanDef = beanFactory.getMergedBeanDefinition(name); 71 if (checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) // 找出@SpringBoot注解的主類,主類上使用@Key@KeyGenerator("com.xxx.xx.xx.system.notice.domain.entity") 72 candidates.add(new BeanDefinitionHolder(beanDef, name)); 73 } 74 String basePackage = parseCandidateBasePackages(candidates); // 獲取主類上的@KeyGenerator注解中的值,該值為實體類的包路徑 75 Set<BeanDefinition> bfs = scanTableInfo(basePackage); // 根據(jù)找到的實體類的包路徑掃描實體類存放的地方,并且將下面所有的實體類掃描出來 76 for (BeanDefinition bd : bfs) { // 遍歷所有實體類 77 try { 78 Class<?> clz = Class.forName(bd.getBeanClassName()); 79 ReflectionUtils.doWithFields(clz, ff -> { 80 Annotation[] annotations = ff.getAnnotations(); 81 for (Annotation annotation : annotations) { 82 if (annotation instanceof Key) // 判斷實體類上的字段是否帶有@Key注解,如果帶有那么該字段便是由@Key注解標記的實體主鍵 83 tableClassz.put(clz, ff); 84 } 85 }); 86 } catch (Throwable e) { 87 throw new BeanDefinitionStoreException("Failed to parse entity class [" + bd 88 .getBeanClassName() + "]", e); 89 } 90 } 91 return tableClassz; 92 } 93 94 private Set<BeanDefinition> scanTableInfo(String basePackage) { 95 Set<BeanDefinition> candidates = new LinkedHashSet<>(); 96 try { 97 String packageSearchPath = "classpath*:" + resolveBasePackage(basePackage) + '/' + "**/*.class"; 98 Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath); 99 for (Resource resource : resources) { 100 try { 101 MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource); 102 ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader); 103 sbd.setSource(resource); 104 candidates.add(sbd); 105 } catch (Throwable ex) { 106 throw new BeanDefinitionStoreException("Failed to read candidate entity class: " + resource, ex); 107 } 108 } 109 } catch (IOException ex) { 110 throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex); 111 } 112 return candidates; 113 } 114 115 public String parseCandidateBasePackages(List<BeanDefinitionHolder> candidates) { 116 for (BeanDefinitionHolder holder : candidates) { 117 BeanDefinition bd = holder.getBeanDefinition(); 118 try { 119 KeyGenerator annotation = AnnotationUtils.findAnnotation(Class.forName(bd.getBeanClassName()), KeyGenerator.class); 120 if (Objects.nonNull(annotation)) 121 return annotation.value(); 122 } catch (BeanDefinitionStoreException ex) { 123 throw ex; 124 } catch (Throwable ex) { 125 throw new BeanDefinitionStoreException("Failed to parse configuration class [" + bd 126 .getBeanClassName() + "]", ex); 127 } 128 } 129 return null; 130 } 131 132 /** 133 * BeanFactoryPostProcessor.postProcessBeanFactory方法實現(xiàn),其目的是觸發(fā)實體類的@Key注解掃描 134 * 并將掃描結(jié)果保存至MapperRegisterStore中,MapperRegisterStore中儲存了類的類型+@Key注解的主鍵字段 135 * @param beanFactory the bean factory used by the application context 136 * @throws BeansException 137 */ 138 public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { 139 Map<Class<?>, Field> fieldMap = parseBeanDefinitionCandidate(beanFactory); // 調(diào)用解析BeanDefinition方法 140 MapperRegisterStore mapperRegisterStore = beanFactory.getBean(MapperRegisterStore.class); // 從BeanFactory容器中獲取MapperRegisterStore實例 141 mapperRegisterStore.putAll(fieldMap); 142 } 143 }
實體類掃描流程逐一分析
1、Spring BeanFactory生命周期:
觸發(fā)時機->postProcessBeanFactory,了解Spring啟動流程的都知道,Spring在啟動過程中定義了很多Hook,同時也為整個Bean的創(chuàng)建到消費定義了生命周期,那么BeanFactory工廠呢?
當然,BeanFactory也生命周期,而我們恰恰就是在這個BeanFactory生命周期的觸發(fā)點上定義了實現(xiàn)
BeanFactoryPostProcessor 該定義實現(xiàn)最終會在Spring啟動流程中Refresh()下的
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
調(diào)用觸發(fā),我們可以在這個節(jié)點上定義掃描實體類的實現(xiàn);因為,這個節(jié)點剛好是BeanFactory創(chuàng)建完成BeanDefinition構(gòu)建成功、Bean未實例化前
1 @FunctionalInterface 2 public interface BeanFactoryPostProcessor { 3 4 /** 5 * Modify the application context's internal bean factory after its standard 6 * initialization. All bean definitions will have been loaded, but no beans 7 * will have been instantiated yet. This allows for overriding or adding 8 * properties even to eager-initializing beans. 9 * @param beanFactory the bean factory used by the application context 10 * @throws org.springframework.beans.BeansException in case of errors 11 */ 12 void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException; 13 14 }
2、BeanDefinitionRegistryPostProcessor 接口:
該接口繼承于上面的接口,同樣也是在postProcessBeanFactory方法中觸發(fā);注意該接口中的唯一實現(xiàn)方法入?yún)锽eanDefinitionRegistry ,我們可以利用BeanDefinitionRegistry往Spring IOC中注入自定義的BeanDefinition,然后再由Spring完成實例和初始化。
我們的實現(xiàn)代碼中就實現(xiàn)了改接口,并且往里注入一個名為MapperRegisterStore的BeanDefinition,讓我們一起來看看這個MapperRegisterStore的定義
?
1 public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor { 2 3 /** 4 * Modify the application context's internal bean definition registry after its 5 * standard initialization. All regular bean definitions will have been loaded, 6 * but no beans will have been instantiated yet. This allows for adding further 7 * bean definitions before the next post-processing phase kicks in. 8 * @param registry the bean definition registry used by the application context 9 * @throws org.springframework.beans.BeansException in case of errors 10 */ 11 void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException; 12 13 }
該定義非常的簡單,我們繼承了HashMap,并指定了該泛型;由該定義存儲了K,V結(jié)構(gòu),K為類的類型、V為類的字段屬性;這里我們用來存儲掃描的實體類的類型并且對應(yīng)的@Key注解的字段,后續(xù)利用該字段可以通過反射的方式注入自增ID的屬性
1 /** 2 * @Author: Song L.Lu 3 * @Since: 2023-05-30 15:15 4 **/ 5 public class MapperRegisterStore extends HashMap<Class<?>, Field> { 6 private static final long serialVersionUID = -3863847035136313223L; 7 public Field put(Class<?> k, Field v) { 8 if (Objects.nonNull(k) && Objects.nonNull(v)) { 9 return put(k, v); 10 } 11 return null; 12 } 13 }
下面是自定義注入MapperRegisterStore的實現(xiàn)
1 public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { 2 BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.rootBeanDefinition(MapperRegisterStore.class); 3 beanDefinitionBuilder.setScope("singleton"); 4 AbstractBeanDefinition abstractBeanDefinition = beanDefinitionBuilder.getBeanDefinition(); 5 registry.registerBeanDefinition(Objects.requireNonNull(abstractBeanDefinition.getBeanClassName()), abstractBeanDefinition); 6 }
3、掃描實體類路徑:
分析完了觸發(fā)點,接下來我們接著分析parseBeanDefinitionCandidate方法;如下列代碼所示,該方法主要調(diào)用了checkConfigrationClassCandidate來提取由@SpringBootApplication注解的主類,并且從主類上獲取由@KeyGenerator注解定義的實體類包路徑;通過包路徑再去通過scanTableInfo方法掃描出來所有的實體類Class,這里代碼實現(xiàn)時利用了MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory()實現(xiàn),該類在ResourceLoader的基礎(chǔ)上增強了緩存,讓已經(jīng)加載過的資源不用重復(fù)加載;最終再由ScannedGenericBeanDefinition 將轉(zhuǎn)為的元數(shù)據(jù)轉(zhuǎn)為BeanDefinition
1 private Set<BeanDefinition> scanTableInfo(String basePackage) { 2 Set<BeanDefinition> candidates = new LinkedHashSet<>(); 3 try { 4 String packageSearchPath = "classpath*:" + resolveBasePackage(basePackage) + '/' + "**/*.class"; 5 Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath); 6 for (Resource resource : resources) { 7 try { 8 MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource); 9 ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader); 10 sbd.setSource(resource); 11 candidates.add(sbd); 12 } catch (Throwable ex) { 13 throw new BeanDefinitionStoreException("Failed to read candidate entity class: " + resource, ex); 14 } 15 } 16 } catch (IOException ex) { 17 throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex); 18 } 19 return candidates; 20 }
4、找到@Key注解的字段:
由上面的scanTableInfo方法我們得到了所有實體類BeanDefinition集合,接下來通過遍歷這些類的字段就可以得到由@Key注解的字段,并且將字段put到MapperRegisterStore中文章來源:http://www.zghlxwxcb.cn/news/detail-825180.html
1 for (BeanDefinition bd : bfs) { // 遍歷所有實體類 2 try { 3 Class<?> clz = Class.forName(bd.getBeanClassName()); 4 ReflectionUtils.doWithFields(clz, ff -> { 5 Annotation[] annotations = ff.getAnnotations(); 6 for (Annotation annotation : annotations) { 7 if (annotation instanceof Key) // 判斷實體類上的字段是否帶有@Key注解,如果帶有那么該字段便是由@Key注解標記的實體主鍵 8 tableClassz.put(clz, ff); 9 } 10 }); 11 } catch (Throwable e) { 12 throw new BeanDefinitionStoreException("Failed to parse entity class [" + bd 13 .getBeanClassName() + "]", e); 14 } 15 }
1 /** 2 * 掃描被@Key注解標識的實體類 3 * @param beanFactory 4 * @return 5 */ 6 public Map<Class<?>, Field> parseBeanDefinitionCandidate(ConfigurableListableBeanFactory beanFactory) { 7 Map<Class<?>, Field> tableClassz = new HashMap<>(); 8 List<BeanDefinitionHolder> candidates = new ArrayList<>(); 9 String[] candidateNames = beanFactory.getBeanDefinitionNames(); // 從BeanFactory中取出已經(jīng)構(gòu)建的BeanDefinition 10 for (String name : candidateNames) { 11 BeanDefinition beanDef = beanFactory.getMergedBeanDefinition(name); 12 if (checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) // 找出@SpringBoot注解的主類,主類上使用@Key@KeyGenerator("com.xxx.xx.xx.system.notice.domain.entity") 13 candidates.add(new BeanDefinitionHolder(beanDef, name)); 14 } 15 String basePackage = parseCandidateBasePackages(candidates); // 獲取主類上的@KeyGenerator注解中的值,該值為實體類的包路徑 16 Set<BeanDefinition> bfs = scanTableInfo(basePackage); // 根據(jù)找到的實體類的包路徑掃描實體類存放的地方,并且將下面所有的實體類掃描出來 17 for (BeanDefinition bd : bfs) { // 遍歷所有實體類 18 try { 19 Class<?> clz = Class.forName(bd.getBeanClassName()); 20 ReflectionUtils.doWithFields(clz, ff -> { 21 Annotation[] annotations = ff.getAnnotations(); 22 for (Annotation annotation : annotations) { 23 if (annotation instanceof Key) // 判斷實體類上的字段是否帶有@Key注解,如果帶有那么該字段便是由@Key注解標記的實體主鍵 24 tableClassz.put(clz, ff); 25 } 26 }); 27 } catch (Throwable e) { 28 throw new BeanDefinitionStoreException("Failed to parse entity class [" + bd 29 .getBeanClassName() + "]", e); 30 } 31 } 32 return tableClassz; 33 }
至此,實體類與數(shù)據(jù)表的的主鍵映射的流程解析完畢,接下來將解析Mybatis下的自定義的攔截器,這個攔截器也是實現(xiàn)自定義ID自增的關(guān)鍵
5、Mybatis攔截器:
熟悉Mybatis都應(yīng)該都聽過Myabtis的自定義攔截器,該攔截器允許在Executor、ParameterHandler、ResultSetHandler、StatementHandler處進行攔截,實現(xiàn)原理是利用JDK的動態(tài)代理將所有Interceptor實現(xiàn)類對前面四個接口下的
任意方法進行代理,最后會產(chǎn)生一個代理后的包裝調(diào)用鏈,這個鏈的最尾部就是實際Executor、ParameterHandler、ResultSetHandler、StatementHandler中的任意一個需要攔截的方法,那么之前的Interceptor接口的實現(xiàn)類則會對傳入
的參數(shù)或結(jié)果進行攔截處理;我們利用這個特性在update或insert方法真正執(zhí)行之前先執(zhí)行我們自定義的攔截器,并對這個攔截中傳入的參數(shù)進行處理,傳入我們需要自增長的ID的值。
攔截器的實現(xiàn)首先對傳入的參數(shù)進行提取,取出主要的插入對象obj,利用之前在Spring啟動時由@Key掃描到的實體類主鍵字段。通過反射的方式將自定義的ID生成策略賦值給實體類的主鍵ID
1 /** 2 * @Author: Song L.Lu 3 * @Since: 2024-01-18 11:15 4 **/ 5 @Intercepts({@Signature( 6 type = Executor.class, 7 method = "update", 8 args = {MappedStatement.class, Object.class} 9 )}) 10 public class GeneratorInterceptor implements Interceptor { 11 private Generator generator; 12 private MapperRegisterStore mapperRegisterStore; 13 14 public GeneratorInterceptor(Generator generator, MapperRegisterStore mapperRegisterStore) { 15 this.generator = generator; // 注入自定義增長器的具體實現(xiàn) 16 this.mapperRegisterStore = mapperRegisterStore; // 注入存儲實體類主鍵的數(shù)據(jù)結(jié)構(gòu)類 17 } 18 19 /** 20 * 實現(xiàn)攔截器方法 21 * @param invocation 22 * @return 23 * @throws Throwable 24 */ 25 public Object intercept(Invocation invocation) throws Throwable { 26 Object[] args = invocation.getArgs(); 27 if (args.length == 0) { 28 throw new BindingException("Mapper代理對象沒有傳入的參數(shù)!"); 29 } else { 30 MappedStatement mappedStatement = (MappedStatement)invocation.getArgs()[0]; 31 Object obj = invocation.getArgs()[1]; 32 Class<?> clz = obj.getClass(); 33 Field id = this.mapperRegisterStore.get(clz); 34 if (Objects.nonNull(id)) { 35 ReflectionUtils.makeAccessible(id); 36 ReflectionUtils.setField(id, obj, this.generator.nextId()); // 調(diào)用ID自增長器的nextId方法,用來獲取自定義的ID,這里使用的是Twitter的雪花 37 } 38 39 SqlCommandType commandType = mappedStatement.getSqlCommandType(); 40 boolean existsInsert = false; 41 switch (commandType) { // 根據(jù)Mybatis原先解析出的sql命令類型確定是插入還是更新 42 case INSERT: 43 existsInsert = true; 44 break; 45 case UPDATE: 46 existsInsert = false; 47 break; 48 default: 49 throw new BindingException("參數(shù)綁定,發(fā)生未知錯誤!"); 50 } 51 52 // 這里調(diào)用自定義增長器實現(xiàn)額外的屬性添加,existsInsert標記當前的操作時插入還是更新 53 generator.propertyValues(obj, existsInsert); 54 return invocation.proceed(); 55 } 56 }
6、Generator接口:
Generator接口主要為引入的插件自定義實現(xiàn)自增長的策略實現(xiàn),這里演示使用雪花算法
1 /** 2 * @Author: Song L.Lu 3 * @Since: 2023-05-31 09:51 4 **/ 5 public interface Generator<T> { 6 T nextId(); 7 void propertyValues(Object c, boolean existsInsert); 8 }
7、Generator的實現(xiàn)文章來源地址http://www.zghlxwxcb.cn/news/detail-825180.html
1 /** 2 * @Author: Song L.Lu 3 * @Since: 2023-06-01 09:32 4 **/ 5 @Component 6 public class SnowflakeIdGenerator implements Generator<Long> { 7 private SnowflakeIdWorker snowflakeIdWorker; 8 9 @Autowired 10 public void setSnowflakeIdWorker(SnowflakeIdWorker snowflakeIdWorker) { 11 this.snowflakeIdWorker = snowflakeIdWorker; // 注入雪花算法的生成器 12 } 13 14 @Override 15 public Long nextId() { 16 return snowflakeIdWorker.nextId(); 17 } 18 19 @Override 20 public void propertyValues(Object c, boolean existsInsert) { 21 if(c instanceof DbBaseEntity){ 22 ((DbBaseEntity) c).populate(existsInsert); // 這里定義了額外屬性的賦值 23 } 24 } 25 26 }
實現(xiàn)自定義的ID增長器的方式有很多種,這里只是本身的一種思路,我覺得如何去實現(xiàn)的并不是太重要,重要的是如何開拓實現(xiàn)的思路,實現(xiàn)一種變通,增強思維能力和舉一反三的思維模式才是最主要的。
源碼地址:https://gitee.com/luxsong/mybatis-generator-starter
到了這里,關(guān)于利用Mybatis攔截器實現(xiàn)自定義的ID增長器的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!