乐趣区

关于springboot:mybatis-springboot-整合源码分析-springboot实战电商项目mall4j

springboot 实战电商我的项目 mall4j(https://gitee.com/gz-yami/mall4j)

java 开源商城零碎

代码版本

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.2.0</version>
</dependency>

既然是 springboot 那么要想晓得怎么运行的,有从三个中央动手

  1. xxxAutoConfiguration
  2. yml 对应的配置文件(xxxProperties
  3. 配置的注解

咱们先来看三个类

1. MybatisAutoConfiguration 结构 SqlSession

这个类通过 dataSource 的配置,结构出 SqlSessionFactorySqlSessionTemplate。如果以前应用 spring 相干的 api 的话,应该会比拟相熟 jdbcTemplateredisTemplate 之类的。SqlSessionTemplate 这个命名,就会让人联想到这个也是相似的性能。而 session 这个词很显著就是与服务之间交互保留连贯状态的货色。Factory 是工厂模式。从而能够得出:SqlSessionFactory 是用来创立 SqlSession 的,SqlSession能够关上或敞开与数据库的连贯。SqlSessionTemplate 就是操作这些开关的要害。

@EnableConfigurationProperties(MybatisProperties.class)
public class MybatisAutoConfiguration implements InitializingBean {
  @Bean
  @ConditionalOnMissingBean
  public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {// 省略...}
  @Bean
  @ConditionalOnMissingBean
  public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {// 省略...}
}

2. MybatisProperties 读取配置信息

这个类次要是将配置文件外面的配置转成 bean 映射,配置文件相似这个样子

#mybatis 的相干配置
mybatis:
  #mapper 配置文件
  mapper-locations: classpath:mapper/*Mapper.xml
  type-aliases-package: com.frozen-watermelon.**.model
  #开启驼峰命名
  configuration:
    map-underscore-to-camel-case: true
@ConfigurationProperties(prefix = MybatisProperties.MYBATIS_PREFIX)
public class MybatisProperties {public static final String MYBATIS_PREFIX = "mybatis";}

3. @MapperScan 扫描 Mapper 接口

咱们通常为了确定被扫描的 mapper 所属的包,都会有这样一个配置

@Configuration
@MapperScan({"com.frozen-watermelon.**.mapper"})
public class MybatisConfig {}

这里就有这个注解@MapperScan,那么这个注解是如何在源码中使用的呢?

咱们先看下这个注解类

@Import(MapperScannerRegistrar.class)
public @interface MapperScan {String[] value() default {};
  String[] basePackages() default {};}

这外面 @ImportMapperScannerRegistrar.class,也就是说这个类被实例化了。这个类同时实现了ImportBeanDefinitionRegistrar 接口,也就是说 registerBeanDefinitions() 这个办法会被调用

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
  @Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    // 获取 @MapperScan 的配置信息
    AnnotationAttributes mapperScanAttrs = AnnotationAttributes
        .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    if (mapperScanAttrs != null) {
      registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
          generateBaseBeanName(importingClassMetadata, 0));
    }
  }

  void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
      BeanDefinitionRegistry registry, String beanName) {
    // 筹备构建 MapperScannerConfigurer
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
    // 省略...
    List<String> basePackages = new ArrayList<>();
    // 获取注解的配置的信息,basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));

    basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText)
        .collect(Collectors.toList()));
    builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
    // 构建对象
    registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
  }
}

从下面的代码能够看出最终构建了一个 MapperScannerConfigurer 对象。那么 MapperScannerConfigurer 到底是干嘛用的呢?MapperScannerConfigurer实现了 BeanDefinitionRegistryPostProcessor 接口,也就是说postProcessBeanDefinitionRegistry() 这个办法创立完 bean 之后会被调用

public class MapperScannerConfigurer
    implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
  @Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    // 省略...
    // 扫描 basePackage
    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }
}

4. 创立 mapper 代理对象

咱们来看下扫描干了啥

public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider {public int scan(String... basePackages) {int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
        // 真正的执行扫描 这里的 doScan 办法,调用的应该是 ClassPathMapperScanner 外面的扫描办法,下面 new 进去的
        doScan(basePackages);

        // Register annotation config processors, if necessary.
        if (this.includeAnnotationConfig) {AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
        }

        return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
    }
    
    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {Assert.notEmpty(basePackages, "At least one base package must be specified");
        Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
        for (String basePackage : basePackages) {Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
            for (BeanDefinition candidate : candidates) {ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
                candidate.setScope(scopeMetadata.getScopeName());
                String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
                if (candidate instanceof AbstractBeanDefinition) {postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
                }
                if (candidate instanceof AnnotatedBeanDefinition) {AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
                }
                if (checkCandidate(beanName, candidate)) {BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
                    definitionHolder =
                            AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
                    beanDefinitions.add(definitionHolder);
                    // 将 beanDefininition 进行 register 操作,前面就能够创立这个 bean 了
                    registerBeanDefinition(definitionHolder, this.registry);
                }
            }
        }
        return beanDefinitions;
    }
}
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
  private Class<? extends MapperFactoryBean> mapperFactoryBeanClass = MapperFactoryBean.class;
  @Override
  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    // 调用父类的构造方法 `ClassPathBeanDefinitionScanner` 这个是 spring 的办法,次要是用来结构一个 bean,此时是结构 basePackage 外面的各种 mapper 的定义信息
    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

    if (beanDefinitions.isEmpty()) {LOGGER.warn(() -> "No MyBatis mapper was found in'" + Arrays.toString(basePackages)
          + "'package. Please check your configuration.");
    } else {
      // 解决 bean 定义信息,将下面创立的 bean 定义信息传入
      processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
  }
  private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    AbstractBeanDefinition definition;
    BeanDefinitionRegistry registry = getRegistry();
    for (BeanDefinitionHolder holder : beanDefinitions) {definition = (AbstractBeanDefinition) holder.getBeanDefinition();

      // 这里的 mapperFactoryBeanClass 是 MapperFactoryBean,而原来的 definition 是 basePackage 外面的各种 mapper 的定义信息,神奇的操作
      // 能够看下 debugger 的截图就能够看到这微妙的信息,MapperFactoryBean 这个货色很重要
      definition.setBeanClass(this.mapperFactoryBeanClass);

      // 上面增加了一堆 sqlSessionFactory、sqlSessionTemplate,不过初始化的时候没有用到
      boolean explicitFactoryUsed = false;
      if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {definition.getPropertyValues().add("sqlSessionFactory",
            new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
        explicitFactoryUsed = true;
      } else if (this.sqlSessionFactory != null) {definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
        explicitFactoryUsed = true;
      }

      if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {if (explicitFactoryUsed) {
          LOGGER.warn(() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
        }
        definition.getPropertyValues().add("sqlSessionTemplate",
            new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
        explicitFactoryUsed = true;
      } else if (this.sqlSessionTemplate != null) {if (explicitFactoryUsed) {
          LOGGER.warn(() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
        }
        definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
        explicitFactoryUsed = true;
      }

    }
  }
}

将一般的 mapper 变成MapperFactoryBean

下面曾经将 bean 的定义信息进行了 registerBeanDefinition,而此时 register 的是MapperFactoryBean 这个 bean。而这个 bean 实现了 FactoryBean 的接口,那么咱们就来看下 MapperFactoryBean 这个信息,顺便看下 getObject() 办法

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
  @Override
  public T getObject() throws Exception {return getSqlSession().getMapper(this.mapperInterface);
  }
}

这个 getSqlSession() 就是用 SqlSessionTemple 啦,这里的 getMapper 又是什么呢?一路找上来就能发现,是 mybatisMapperProxyFactory应用 jdk 动静代理生成的代理 mapper

  // SqlSessionTemple
  public <T> T getMapper(Class<T> type) {return getConfiguration().getMapper(type, this);
  }
  // 往下找 
  // Configuration
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {return mapperRegistry.getMapper(type, sqlSession);
  }
  // 往下找 
  // MapperRegistry
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {throw new BindingException("Type" + type + "is not known to the MapperRegistry.");
    }
    try {return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {throw new BindingException("Error getting mapper instance. Cause:" + e, e);
    }
  }
  // 往下找
  // MapperProxyFactory
  protected T newInstance(MapperProxy<T> mapperProxy) {return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface}, mapperProxy);
  }

下面的一堆操作,通过 FactoryBeangetObject() 只是将代理过后的 mapper 交给了 spring 去治理,那么 mybatis 是怎么治理的呢?咱们持续回到 MapperFactoryBean,发现他也实现了InitializingBean 接口。所以还有 afterPropertiesSet() 会被调用。

public abstract class DaoSupport implements InitializingBean {
    @Override
    public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
        // Let abstract subclasses check their configuration.
        checkDaoConfig();

        // Let concrete implementations initialize themselves.
        try {initDao();
        }
        catch (Exception ex) {throw new BeanInitializationException("Initialization of DAO failed", ex);
        }
    }
}

public abstract class SqlSessionDaoSupport extends DaoSupport {
  @Override
  protected void checkDaoConfig() {notNull(this.sqlSessionTemplate, "Property'sqlSessionFactory'or'sqlSessionTemplate'are required");
  }
}

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
  @Override
  protected void checkDaoConfig() {super.checkDaoConfig();

    notNull(this.mapperInterface, "Property'mapperInterface'is required");

    Configuration configuration = getSqlSession().getConfiguration();
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
      try {
        // 代理的 mapper 放到 mybatis 的 Configuration 中
        configuration.addMapper(this.mapperInterface);
      } catch (Exception e) {logger.error("Error while adding the mapper'" + this.mapperInterface + "'to configuration.", e);
        throw new IllegalArgumentException(e);
      } finally {ErrorContext.instance().reset();}
    }
  }
}

下面通过configuration.addMapper(this.mapperInterface); 将代理的 mapper 放到 mybatis 的 Configuration 中。下面的所有,都是对@MapperScan 扫描进去的接口创立动静代理的操作。

持续看下 addMapper() 办法

  public <T> void addMapper(Class<T> type) {if (type.isInterface()) {if (hasMapper(type)) {throw new BindingException("Type" + type + "is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        // 缓存 mapper 代理
        knownMappers.put(type, new MapperProxyFactory<>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        // 解析
        parser.parse();
        loadCompleted = true;
      } finally {if (!loadCompleted) {knownMappers.remove(type);
        }
      }
    }
  }

最初看下解析的办法

  public void parse() {String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) {
      // 这外面就会去解析与 Mapper 接口雷同包名上面的 xml 文件
      loadXmlResource();
      configuration.addLoadedResource(resource);
      assistant.setCurrentNamespace(type.getName());
      parseCache();
      parseCacheRef();
      for (Method method : type.getMethods()) {if (!canHaveStatement(method)) {continue;}
        if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()
            && method.getAnnotation(ResultMap.class) == null) {parseResultMap(method);
        }
        try {
          // 这里回去解析接口办法上的 SQL 注解
          parseStatement(method);
        } catch (IncompleteElementException e) {configuration.addIncompleteMethod(new MethodResolver(this, method));
        }
      }
    }
    parsePendingMethods();}

5. MapperProxy 代理 mapper 接口的办法

咱们曾经晓得 mapper 的所有的接口都会被代理,这个代理类是谁呢?不言而喻的是MapperProxy

  protected T newInstance(MapperProxy<T> mapperProxy) {return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface}, mapperProxy);
  }

假如咱们有一个这样的 mapper:

public interface UserMapper {int getById(String userId);
}

这个时候咱们调用 userMapper.getById("1") 会产生什么呢?

因为被 MapperProxy 代理了,所以咱们要看下代理的 invoke() 办法

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {if (Object.class.equals(method.getDeclaringClass())) {return method.invoke(this, args);
      } else {
        // 缓存办法,并执行办法
        return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
      }
    } catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);
    }
  }
  private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
    try {
      // MapUtil.computeIfAbsent 如果存在就返回缓存对象,如果不存在再结构
      return MapUtil.computeIfAbsent(methodCache, method, m -> {if (m.isDefault()) {
          try {if (privateLookupInMethod == null) {return new DefaultMethodInvoker(getMethodHandleJava8(method));
            } else {return new DefaultMethodInvoker(getMethodHandleJava9(method));
            }
          } catch (IllegalAccessException | InstantiationException | InvocationTargetException
              | NoSuchMethodException e) {throw new RuntimeException(e);
          }
        } else {
          // 缓存的办法
          return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
        }
      });
    } catch (RuntimeException re) {Throwable cause = re.getCause();
      throw cause == null ? re : cause;
    }
  }

咱们持续看 PlainMethodInvoker 这个类的 invoke() 办法

    @Override
    public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {return mapperMethod.execute(sqlSession, args);
    }

通过办法是增删改查(比方 <select>标签判断出是查问),调用 SqlSession 增删改查对应的办法

  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {result = executeForCursor(sqlSession, args);
        } else {Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
          if (method.returnsOptional()
              && (result == null || !method.getReturnType().equals(result.getClass()))) {result = Optional.ofNullable(result);
          }
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for:" + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {throw new BindingException("Mapper method'" + command.getName()
          + "attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }

springboot 实战电商我的项目 mall4j(https://gitee.com/gz-yami/mall4j)

java 开源商城零碎

退出移动版