MapperScan添加动态配置(占位符)
在对Mybatis自动扫描配置中,使用注解配置时,@MapperScan中的配置,通常配置如下:
@MapperScan(basePackages = {"com.aa.**.mapper","com.bb.**.mapper"}, sqlSessionFactoryRef = "sqlSessionFactory")
不支持在yml或properties文件中动态配置。因为MapperScan注解功能的实现类MapperScannerRegistrar实现的是ImportBeanDefinitionRegistrar。在对@Configuration注解标记的类配置时,实现占位符功能的PropertyPlaceholderAutoConfiguration还没有开始加载。
MapperScannerRegistrard的核心代码如下
ListbasePackages = new ArrayList(); basePackages.addAll((Collection)Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList())); basePackages.addAll((Collection)Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText).collect(Collectors.toList())); basePackages.addAll((Collection)Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName).collect(Collectors.toList())); scanner.registerFilters(); scanner.doScan(StringUtils.toStringArray(basePackages));
从上面代码中可以看出
为了拓展支持占位符动态配置,只需把basePackages的加载改写即可。
1、参考MapperScannerRegistrard,实现 ImportBeanDefinitionRegistrar和ResourceLoaderAware两个接口
2、为了动态读取配置文件信息,需要引入Environment,所以实现EnvironmentAware接口
3、代码
MapperScannerRegistrar.java
import org.mybatis.spring.mapper.ClassPathMapperScanner; import org.mybatis.spring.mapper.MapperFactoryBean; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.context.EnvironmentAware; import org.springframework.context.ResourceLoaderAware; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.env.Environment; import org.springframework.core.io.ResourceLoader; import org.springframework.core.type.AnnotationMetadata; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; import java.lang.annotation.Annotation; import java.util.*; import java.util.stream.Collectors; public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware,EnvironmentAware { private Environment environment; private ResourceLoader resourceLoader; private static final Logger logger = LoggerFactory.getLogger(MapperScannerRegistrar. class); @Override public void setEnvironment(Environment environment) { this.environment = environment; } @Override public void setResourceLoader(ResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; } @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { AnnotationAttributes mapperScanAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScanner.class.getName())); if (mapperScanAttrs != null) { this.registerBeanDefinitions(mapperScanAttrs, registry); } } void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry) { ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); Optional var10000 = Optional.ofNullable(this.resourceLoader); Objects.requireNonNull(scanner); Class extends Annotation> annotationClass = annoAttrs.getClass("annotationClass"); if (!Annotation.class.equals(annotationClass)) { scanner.setAnnotationClass(annotationClass); } Class> markerInterface = annoAttrs.getClass("markerInterface"); if (!Class.class.equals(markerInterface)) { scanner.setMarkerInterface(markerInterface); } Class extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator"); if (!BeanNameGenerator.class.equals(generatorClass)) { scanner.setBeanNameGenerator((BeanNameGenerator) BeanUtils.instantiateClass(generatorClass)); } Class extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean"); if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) { scanner.setMapperFactoryBeanClass(mapperFactoryBeanClass); } scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef")); scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef")); ListbasePackages = new ArrayList (); basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList())); for (String pkg : annoAttrs.getStringArray("basePackages")) { if (StringUtils.hasText(pkg)) { String value = parsePlaceHolder(pkg); if(StringUtils.hasText(value)){ List valueList = Arrays.asList(value.split(",")); for(String base : valueList){ basePackages.add(base); } } } } basePackages.addAll(Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName).collect(Collectors.toList())); scanner.registerFilters(); scanner.doScan(StringUtils.toStringArray(basePackages)); } private String parsePlaceHolder(String pro) { if (StringUtils.hasText(pro) && pro.contains(PropertySourcesPlaceholderConfigurer.DEFAULT_PLACEHOLDER_PREFIX)) { String value = environment.getProperty(pro.substring(2, pro.length() - 1)); if (logger.isDebugEnabled()) { logger.debug("find placeholder value " + value + " for key " + pro); } if (null == value) { throw new IllegalArgumentException("property " + pro + " can not find!!!"); } return value; } return pro; } }
MapperScanner.java
import org.mybatis.spring.mapper.MapperFactoryBean; import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.context.annotation.Import; import java.lang.annotation.*; @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Documented @Import({MapperScannerRegistrar.class}) public @interface MapperScanner { String[] value() default {}; String[] basePackages() default {}; Class>[] basePackageClasses() default {}; Class extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class; Class extends Annotation> annotationClass() default Annotation.class; Class> markerInterface() default Class.class; String sqlSessionTemplateRef() default ""; String sqlSessionFactoryRef() default ""; Class extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class; }
4、使用MapperScanner,占位符扫描配置
@MapperScanner(basePackages = { "${mybatis.mapperScanner.basePackage}" }, sqlSessionFactoryRef = "sqlSessionFactory")
mybatis: mapperScanner: basePackage: com.aa.**.mapper,com.bb.**.mapper
关于@MapperScan配置
问题
org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): top.yangzefeng.usercenter.user.service.UserService.getById
解决方案
常用配置:
@MapperScan("top.yangzefeng.usercenter.**.mapper")
@MapperScan("com.demo.mapper")
:扫描指定包中的接口@MapperScan("com.demo.*.mapper")
:一个代表任意字符串,但只代表一级包,比如可以扫到com.demo.aaa.mapper,不能扫到com.demo.aaa.bbb.mapper@MapperScan("com.demo.**.mapper")
:两个代表任意个包,比如可以扫到com.demo.aaa.mapper,也可以扫到com.demo.aaa.bbb.mapper
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。