原生的Mybatis框架是没有ID自增器,但例如国产的Mybatis Plus却是支持,不过,Mybatis Plus却是缺少了自定属性的填充;例如:我们需要自定义填充一些属性,updateDate、createDate等,这时Mybatis Plus自带的ID自增器就无法满足需求;这种时候我们就需要自定义的ID增加器,可以自定义ID增长策略同时还得支持更多的属性自定义扩展,当然,最好能做成插件形式为其他项目或者模块提供引入那就更新好了。
在开始之前我们先确定和分析主要的需求,了解了需求才能更好的制作
首先我们得确定主要的宗旨那就是实现ID自增长器,同时,还等保证该增长的灵活性和扩展性,让其他项目引入之后可以很灵活的更改增长策略,随意替换ID增长的实现。主要需求为下列几点:
- 自定义的ID增长策略,同时保证该特性灵活性可以随意替换
- 支持自定义的附带属性扩展增强
- 保证其他的项目引起使用时的绝对简单,最好能像Spring Boot自动装配模块一样保证简单
确定需求之后我们现在开始根据需求来功能了,我们由外到内、由粗到细的去实现整个模块。
先确定该模块的外体特征,先看第3点
保证其他的项目引起使用时的绝对简单,最好能像Spring Boot自动装配模块一样保证简单
要保证该模块的引用使用简单那么就必须使用Spring Boot的特性->自动配置,实现自定义场景装配器,利用该场景装配才能保证模块可以自动配置完成启动之初的初始化。
我们先来新建Maven模块
这里的模块命名最好遵循Spring Boot官方的建议,第三方的模块命名由模块名称+spring-boot;官方模块由spring-boot+模块名称
建好模块之后我们来整理下目录
这里我们把多余的目录、文件删除,这里使用了Maven的初始化模板,会自动生成一些模板文件;但是,该操作的主要目的只是获得一个Maven结构的项目
接下来确定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启动器,本质是在Mybatis的基础上进行扩展的,必须引入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>
到这里整个模块的创建已经完成了,接下来我们的仔细分析下该如何实现自定义的增长器
插件要使用方便那么就意味着要抛弃繁杂的配置,同时对必要的信息注入配置时应该采用注解的方式来保持简洁;
既然是ID增长器那就必须的确定哪个属性为ID,并且确定ID属性的类的全限定名,在这里我们定义两个注解
1、这里定义了@Key注解,该注解的主要目的是为了标识出ORM实体类映射数据库表中的主键,插件最终的主要注入的属性
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、创建一个Spring Boot 的AutoConfiguration用来自动完成插件的装配,包括初始化上下文环境、扫描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实现BeanFactoryPostProcessor.postProcessBeanFactory接口方法,其目的是在工厂创建并扫描完Beandefition后触发实体类的扫描
GeneratorInterceptor实现了Mybatis的拦截器,其拦截活动为update/insert,在Mybatis触发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中取出已经构建的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); // 根据找到的实体类的包路径扫描实体类存放的地方,并且将下面所有的实体类扫描出来 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方法实现,其目的是触发实体类的@Key注解扫描 134 * 并将扫描结果保存至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); // 调用解析BeanDefinition方法 140 MapperRegisterStore mapperRegisterStore = beanFactory.getBean(MapperRegisterStore.class); // 从BeanFactory容器中获取MapperRegisterStore实例 141 mapperRegisterStore.putAll(fieldMap); 142 } 143 }
实体类扫描流程逐一分析
1、Spring BeanFactory生命周期:
触发时机->postProcessBeanFactory,了解Spring启动流程的都知道,Spring在启动过程中定义了很多Hook,同时也为整个Bean的创建到消费定义了生命周期,那么BeanFactory工厂呢?
当然,BeanFactory也生命周期,而我们恰恰就是在这个BeanFactory生命周期的触发点上定义了实现
BeanFactoryPostProcessor 该定义实现最终会在Spring启动流程中Refresh()下的
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
调用触发,我们可以在这个节点上定义扫描实体类的实现;因为,这个节点刚好是BeanFactory创建完成BeanDefinition构建成功、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方法中触发;注意该接口中的唯一实现方法入参为BeanDefinitionRegistry ,我们可以利用BeanDefinitionRegistry往Spring IOC中注入自定义的BeanDefinition,然后再由Spring完成实例和初始化。
我们的实现代码中就实现了改接口,并且往里注入一个名为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结构,K为类的类型、V为类的字段属性;这里我们用来存储扫描的实体类的类型并且对应的@Key注解的字段,后续利用该字段可以通过反射的方式注入自增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的实现
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、扫描实体类路径:
分析完了触发点,接下来我们接着分析parseBeanDefinitionCandidate方法;如下列代码所示,该方法主要调用了checkConfigrationClassCandidate来提取由@SpringBootApplication注解的主类,并且从主类上获取由@KeyGenerator注解定义的实体类包路径;通过包路径再去通过scanTableInfo方法扫描出来所有的实体类Class,这里代码实现时利用了MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory()实现,该类在ResourceLoader的基础上增强了缓存,让已经加载过的资源不用重复加载;最终再由ScannedGenericBeanDefinition 将转为的元数据转为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中文章来源:https://www.toymoban.com/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中取出已经构建的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); // 根据找到的实体类的包路径扫描实体类存放的地方,并且将下面所有的实体类扫描出来 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 }
至此,实体类与数据表的的主键映射的流程解析完毕,接下来将解析Mybatis下的自定义的拦截器,这个拦截器也是实现自定义ID自增的关键
5、Mybatis拦截器:
熟悉Mybatis都应该都听过Myabtis的自定义拦截器,该拦截器允许在Executor、ParameterHandler、ResultSetHandler、StatementHandler处进行拦截,实现原理是利用JDK的动态代理将所有Interceptor实现类对前面四个接口下的
任意方法进行代理,最后会产生一个代理后的包装调用链,这个链的最尾部就是实际Executor、ParameterHandler、ResultSetHandler、StatementHandler中的任意一个需要拦截的方法,那么之前的Interceptor接口的实现类则会对传入
的参数或结果进行拦截处理;我们利用这个特性在update或insert方法真正执行之前先执行我们自定义的拦截器,并对这个拦截中传入的参数进行处理,传入我们需要自增长的ID的值。
拦截器的实现首先对传入的参数进行提取,取出主要的插入对象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; // 注入自定义增长器的具体实现 16 this.mapperRegisterStore = mapperRegisterStore; // 注入存储实体类主键的数据结构类 17 } 18 19 /** 20 * 实现拦截器方法 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代理对象没有传入的参数!"); 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()); // 调用ID自增长器的nextId方法,用来获取自定义的ID,这里使用的是Twitter的雪花 37 } 38 39 SqlCommandType commandType = mappedStatement.getSqlCommandType(); 40 boolean existsInsert = false; 41 switch (commandType) { // 根据Mybatis原先解析出的sql命令类型确定是插入还是更新 42 case INSERT: 43 existsInsert = true; 44 break; 45 case UPDATE: 46 existsInsert = false; 47 break; 48 default: 49 throw new BindingException("参数绑定,发生未知错误!"); 50 } 51 52 // 这里调用自定义增长器实现额外的属性添加,existsInsert标记当前的操作时插入还是更新 53 generator.propertyValues(obj, existsInsert); 54 return invocation.proceed(); 55 } 56 }
6、Generator接口:
Generator接口主要为引入的插件自定义实现自增长的策略实现,这里演示使用雪花算法
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的实现文章来源地址https://www.toymoban.com/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 }
实现自定义的ID增长器的方式有很多种,这里只是本身的一种思路,我觉得如何去实现的并不是太重要,重要的是如何开拓实现的思路,实现一种变通,增强思维能力和举一反三的思维模式才是最主要的。
源码地址:https://gitee.com/luxsong/mybatis-generator-starter
到了这里,关于利用Mybatis拦截器实现自定义的ID增长器的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!