利用Mybatis拦截器实现自定义的ID增长器

这篇具有很好参考价值的文章主要介绍了利用Mybatis拦截器实现自定义的ID增长器。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

  原生的Mybatis框架是没有ID自增器,但例如国产的Mybatis Plus却是支持,不过,Mybatis Plus却是缺少了自定属性的填充;例如:我们需要自定义填充一些属性,updateDate、createDate等,这时Mybatis Plus自带的ID自增器就无法满足需求;这种时候我们就需要自定义的ID增加器,可以自定义ID增长策略同时还得支持更多的属性自定义扩展,当然,最好能做成插件形式为其他项目或者模块提供引入那就更新好了。

  利用Mybatis拦截器实现自定义的ID增长器

 

  在开始之前我们先确定和分析主要的需求,了解了需求才能更好的制作

  首先我们得确定主要的宗旨那就是实现ID自增长器,同时,还等保证该增长的灵活性和扩展性,让其他项目引入之后可以很灵活的更改增长策略,随意替换ID增长的实现。主要需求为下列几点:

  1. 自定义的ID增长策略,同时保证该特性灵活性可以随意替换
  2. 支持自定义的附带属性扩展增强
  3. 保证其他的项目引起使用时的绝对简单,最好能像Spring Boot自动装配模块一样保证简单

  确定需求之后我们现在开始根据需求来功能了,我们由外到内、由粗到细的去实现整个模块。

  先确定该模块的外体特征,先看第3点

保证其他的项目引起使用时的绝对简单,最好能像Spring Boot自动装配模块一样保证简单

要保证该模块的引用使用简单那么就必须使用Spring Boot的特性->自动配置,实现自定义场景装配器,利用该场景装配才能保证模块可以自动配置完成启动之初的初始化。

我们先来新建Maven模块

这里的模块命名最好遵循Spring Boot官方的建议,第三方的模块命名由模块名称+spring-boot;官方模块由spring-boot+模块名称

利用Mybatis拦截器实现自定义的ID增长器

建好模块之后我们来整理下目录

这里我们把多余的目录、文件删除,这里使用了Maven的初始化模板,会自动生成一些模板文件;但是,该操作的主要目的只是获得一个Maven结构的项目

利用Mybatis拦截器实现自定义的ID增长器

接下来确定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中

 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模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • MyBatis拦截器优雅实现数据脱敏

    现代网络环境中,敏感数据的处理是至关重要的。敏感数据包括个人身份信息、银行账号、手机号码等,泄露这些数据可能导致用户隐私泄露、财产损失等严重后果。因此,对敏感数据进行脱敏处理是一种必要的安全措施。 比如页面上常见的敏感数据都是加*遮挡处理过的,

    2024年02月06日
    浏览(43)
  • MyBatis Plus 拦截器实现数据权限控制

    上篇文章介绍的MyBatis Plus 插件实际上就是用拦截器实现的,MyBatis Plus拦截器对MyBatis的拦截器进行了包装处理,操作起来更加方便 2.1、InnerInterceptor MyBatis Plus提供的InnerInterceptor接口提供了如下方法,主要包括:在查询之前执行,在更新之前执行,在SQL准备之前执行 2.2、编写简

    2024年01月17日
    浏览(44)
  • 自定义拦截器实现

    在 Spring MVC 框架中, 拦截器作为一种机制, 用于对请求进行拦截. 拦截器可以在请求进入处理器之前、处理器返回处理之后、视图渲染之前等各个环节进行拦截. 拦截器通常用于实现一下功能 : 鉴权和身份认证 日志记录和统计 请求参数和校验和过滤 缓存和性能优化 路径重定向

    2024年02月09日
    浏览(56)
  • 自定义注解与拦截器实现不规范sql拦截(自定义注解填充插件篇)

    在自定义注解与拦截器实现不规范sql拦截(拦截器实现篇)中提到过,写了一个idea插件来辅助对Mapper接口中的方法添加自定义注解,这边记录一下插件的实现。 在上一篇中,定义了一个自定义注解对需要经过where判断的Mapper sql方法进行修饰。那么,现在想使用一个idea插件来

    2024年01月23日
    浏览(51)
  • 基于Mybatis-Plus拦截器实现MySQL数据加解密

    用户的一些敏感数据,例如手机号、邮箱、身份证等信息,在数据库以明文存储时会存在数据泄露的风险,因此需要进行加密, 但存储数据再被取出时,需要进行解密,因此加密算法需要使用对称加密算法。 常用的对称加密算法有AES、DES、RC、BASE64等等,各算法的区别与优劣

    2024年02月16日
    浏览(39)
  • SpringBoot定义拦截器+自定义注解+Redis实现接口防刷(限流)

    在拦截器Interceptor中拦截请求 通过地址+请求uri作为调用者访问接口的区分在Redis中进行计数达到限流目的 定义参数 访问周期 最大访问次数 禁用时长 代码实现 定义拦截器:实现HandlerInterceptor接口,重写preHandle()方法 注册拦截器:配置类实现WebMvcConfigurer接口,重写addIntercep

    2024年02月05日
    浏览(59)
  • MyBatis 拦截器介绍

    MyBatis 提供了一种插件 (plugin) 的功能,虽然叫做插件,但其实这是拦截器功能。那么拦截器拦截 MyBatis 中的哪些内容呢? 我们进入官网看一看: MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括: Executor

    2024年02月15日
    浏览(47)
  • Spring Boot 3自定义注解+拦截器+Redis实现高并发接口限流

    在当今互联网应用开发中,高并发访问是一个常见的挑战。为了保障系统的稳定性和可靠性,我们需要对接口进行限流,防止因过多的请求导致系统崩溃。 本文将介绍如何利用Spring Boot 3中的自定义注解、拦截器和Redis实现高并发接口限流,帮助程序员解决这一挑战。 1. 自定

    2024年04月28日
    浏览(49)
  • SpringBoot中接口幂等性实现方案-自定义注解+Redis+拦截器实现防止订单重复提交

    SpringBoot+Redis+自定义注解实现接口防刷(限制不同接口单位时间内最大请求次数): SpringBoot+Redis+自定义注解实现接口防刷(限制不同接口单位时间内最大请求次数)_redis防刷_霸道流氓气质的博客-CSDN博客 以下接口幂等性的实现方式与上面博客类似,可参考。 什么是幂等性? 幂等

    2024年02月15日
    浏览(55)
  • flume自定义拦截器

    要自定义 Flume 拦截器,你需要编写一个实现 org.apache.flume.interceptor.Interceptor 接口的自定义拦截器类。以下是一个简单的示例: 在上面的示例中,我们实现了 initialize() 、 intercept() 、 intercept(ListEvent events) 、 close() 方法来定义自定义拦截器的行为。你可以根据需要在这些方法中

    2024年01月23日
    浏览(46)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包