Mybatis springboot integration source code analysis – springboot actual e-commerce project mall4j

Time:2022-1-3

Springboot e-commerce project mall4j( https://gitee.com/gz-yami/mall4j )

Java open source mall system

Code version

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

Since it is a spring boot, there are three places to start if you want to know how to run it

  1. xxxAutoConfiguration
  2. Configuration file corresponding to YML(xxxProperties
  3. Notes for configuration

Let’s look at three classes first

1. Construct sqlsession from mybatisautoconfiguration

This class passesdataSourceThe configuration ofSqlSessionFactoryAndSqlSessionTemplate。 If you used spring related APIs before, you should be familiar with themjdbcTemplateAndredisTemplateOr something.SqlSessionTemplateThis naming will remind people of this similar function. andsessionThis word is obviously something that interacts with services to save the connection state. Factory is the factory mode. It can be concluded that:SqlSessionFactoryIs used to createSqlSessionofSqlSessionYou can open or close a connection to the database.SqlSessionTemplateIs the key to operating these switches.

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

2. Mybatisproperties reads configuration information

This class is mainly used to convert the configuration in the configuration file into bean mapping. The configuration file is similar to this

#Configuration of mybatis
mybatis:
  #Mapper profile
  mapper-locations: classpath:mapper/*Mapper.xml
  type-aliases-package: com.frozen-watermelon.**.model
  #Open hump naming
  configuration:
    map-underscore-to-camel-case: true
@ConfigurationProperties(prefix = MybatisProperties.MYBATIS_PREFIX)
public class MybatisProperties {
  public static final String MYBATIS_PREFIX = "mybatis";
}

3. @ mapperscan scan mapper interface

We usually have such a configuration to determine the package of the scanned mapper

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

Here is the annotation@MapperScanSo how is this annotation used in the source code?

Let’s take a look at this annotation class first

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

In here@ImportYesMapperScannerRegistrar.classThat is, the class is instantiated. This class also implementsImportBeanDefinitionRegistrarInterface, that isregisterBeanDefinitions()This method will be called

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
  @Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    //Get the configuration information of @ 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) {
    //Preparing to build mappercannerconfigurer
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
    //Omit
    List<String> basePackages = new ArrayList<>();
    //Get the configuration information of the annotation,
    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));
    //Build object
    registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
  }
}

As can be seen from the above code, aMapperScannerConfigurerObject. thatMapperScannerConfigurerWhat the hell is it for?MapperScannerConfigurerRealizedBeanDefinitionRegistryPostProcessorInterface, that ispostProcessBeanDefinitionRegistry()This method will be called after the bean is created

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

4. Create mapper proxy object

Let’s see what the scan did

public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider {
    public int scan(String... basePackages) {
        int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
        //To perform scanning, the doscan method here should call the scanning method in classpathmapperscanner, which comes from new above
        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);
                    //Register beandefinition, and then you can create this 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) {
    //Call the parent class's construction method 'classpathbeandefinitionscanner'. This is a spring method, which is mainly used to construct a bean. At this time, it is the definition information of various mappers in the basepackage
    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 {
      //Process the bean definition information and pass in the bean definition information created above
      processBeanDefinitions(beanDefinitions);
    }

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

      //Maperfactorybeanclass here is mapperfactorybean, and the original definition is the definition information of various mappers in the basepackage, which is a magical operation
      //You can see this wonderful information by looking at the screenshot of debugger. Mapperfactorybean is very important
      definition.setBeanClass(this.mapperFactoryBeanClass);

      //A bunch of sqlsessionfactory and sqlsessiontemplate are added below, but they are not used during initialization
      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;
      }

    }
  }
}

Turn ordinary mapper intoMapperFactoryBean

Mybatis springboot integration source code analysis - springboot actual e-commerce project mall4j

Mybatis springboot integration source code analysis - springboot actual e-commerce project mall4j

The bean definition information has been described aboveregisterBeanDefinition, and the of register isMapperFactoryBeanThis bean. And this bean implementsFactoryBeanSo let’s take a lookMapperFactoryBeanThis information, by the waygetObject()method

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

thisgetSqlSession()Just useSqlSessionTempleAll right, heregetMapperWhat is it? You can find it all the way down, rightmybatisstayMapperProxyFactoryProxy mapper generated using JDK dynamic proxy

// SqlSessionTemple
  public <T> T getMapper(Class<T> type) {
    return getConfiguration().getMapper(type, this);
  }
  //Look down 
  // Configuration
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }
  //Look down 
  // 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);
    }
  }
  //Look down
  // MapperProxyFactory
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

A bunch of operations above, throughFactoryBeanofgetObject()Just leave the mapper after the agent to spring to manage, thenmybatisHow is it managed? Let’s go back toMapperFactoryBean, I found that he realized itInitializingBeanInterface. So there areafterPropertiesSet()Will be called.

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 {
        //The agent's mapper is placed in the configuration of mybatis
        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();
      }
    }
  }
}

Above throughconfiguration.addMapper(this.mapperInterface);Put the agent’s mapper into the configuration of mybatis. Everything above is right@MapperScanThe operation of creating a dynamic agent for the scanned interface.

Keep lookingaddMapper()method

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 {
        //Cache mapper proxy
        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);
        //Analysis
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

Finally, let’s look at the analytical method

public void parse() {
    String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) {
      //This will parse the XML file under the same package name as the mapper interface
      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 {
          //Here, go back to parsing the SQL annotation on the interface method
          parseStatement(method);
        } catch (IncompleteElementException e) {
          configuration.addIncompleteMethod(new MethodResolver(this, method));
        }
      }
    }
    parsePendingMethods();
  }

5. Mapperproxy proxy mapper interface method

We already know that all interfaces of mapper will be proxied. Who is the proxy class? ObviouslyMapperProxy

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

Suppose we have a mapper like this:

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

This time we calluserMapper.getById("1")What will happen?

Due to beingMapperProxyAgent, so we need to look at the agent’sinvoke()method

@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 {
        //Cache method and execute method
        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 returns the cache object if it exists, and reconstructs if it does not exist
      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 {
          //Cache method
          return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
        }
      });
    } catch (RuntimeException re) {
      Throwable cause = re.getCause();
      throw cause == null ? re : cause;
    }
  }

Let’s keep watchingPlainMethodInvokerOf this classinvoke()method

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

The method is to add, delete, modify and query (for example<select>The tag determines whether it is a query) and callsSqlSessionCorresponding methods of adding, deleting, modifying and querying

  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 e-commerce project mall4j( https://gitee.com/gz-yami/mall4j )

Java open source mall system

Recommended Today

Vue2 technology finishing 3 – Advanced chapter – update completed

3. Advanced chapter preface Links to basic chapters:https://www.cnblogs.com/xiegongzi/p/15782921.html Link to component development:https://www.cnblogs.com/xiegongzi/p/15823605.html 3.1. Custom events of components 3.1.1. Binding custom events There are two implementation methods here: one is to use v-on with vuecomponent$ Emit implementation [PS: this method is a little similar to passing from child to parent]; The other is to use ref […]