If you find the content helpful, give it a thumbs up and encourage you to update 😂.

In this paper, continuously updated, the latest version please click: source reading | Mybatis and Spring is how to integrate

preface

Note: Basic Spring and SpringBoot knowledge is required to read this article

First go to Mybatis-Spring official website link, open a SSM integration case project to eat this article, the effect is better oh. The website makes it clear that to use MyBatis with Spring, you need to define at least two things in the Spring application context: an SqlSessionFactory and at least one data mapper class. In MyBatis-Spring, SqlSessionFactory is created using SqlSessionFactoryBean.

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource" />
</bean>
Copy the code

How SqlSessionFactoryBean is created and executed in SpringBoot will not be explained. Interested can go to the org. Mybatis. Spring. The boot. Autoconfigure. MybatisAutoConfiguration# sqlSessionFactory method, it is the use of simple SpringBoot autowire mechanism

How to create SqlSessionFactory for SqlSessionFactoryBean

SqlSessionFactory is an important object in MyBatis. It is used to create THE SqlSession object, which is used to manipulate the database. We can see from the inheritance of SqlSessionFactoryBean that it doesFactoryBean and InitialzingBeanI won’t go into the role of these two classes

Let’s jump right into the important code

public SqlSessionFactory getObject(a) throws Exception {
    if (this.sqlSessionFactory == null) {
      // The afterPropertiesSet method is called during the lifetime of the Bean
      // Perhaps to prevent getObject from being called prematurely, or perhaps to accommodate the difference between SpringBoot and Spring integration
      afterPropertiesSet();
    }
    return this.sqlSessionFactory;
}

public void afterPropertiesSet(a) throws Exception {
    notNull(dataSource, "Property 'dataSource' is required");
    notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
    // Go all the way down to the build method
    this.sqlSessionFactory = buildSqlSessionFactory();
}
public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
}
Copy the code

Org. Mybatis. Spring. SqlSessionFactoryBean# buildSqlSessionFactory go buildSqlSessionFactory method, Finally, build the DefaultSqlSessionFactory with SqlSessionFactoryBuilder#build

What exactly does @mapperscan annotation do?

MapperScan is responsible for mapper scanning and registration when Spring and Mybatis integrate

@Import(MapperScannerRegistrar.class)
Copy the code

The @import annotation is also provided by the Spring Framework to register common Javabeans into the container. The application imports the MapperScannerRegistrar class to implement the application

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar.ResourceLoaderAware
Copy the code

It implements the ImportBeanDefinitionRegistrar, Spring manually registered bean in two ways:

  1. implementationImportBeanDefinitionRegistrar
  2. implementationBeanDefinitionRegistryPostProcessor

It is important to note that if a Bean implementation BeanDefinitionRegistryPostProcessor or ImportBeanDefinitionRegistrar interface, that we use in this class the @autowired or @ Value annotation, We’re going to find that it doesn’t work. The reason is that the Spring container does not yet parse the @Autowired or @Value annotations when it executes the interface’s methods. If we want to use the get configuration file Properties, we can read the configuration file in the raw way directly with IO, then get the Properties object, and then get the configuration value.

Who is MapperScannerRegistrar?

Here will call registerBeanDefinitions method ImportBeanDefinitionRegistrar during ConfigurationClassPostProcessor processing Configuration class is called, Used to generate the BeanDefinitions required by the Configuration class. And ConfigurationClassPostProcessor is realized BeanDefinitionRegistryPostProcessor interface (registered into bean, so support mapper and injected into the spring container).

  @Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    AnnotationAttributes mapperScanAttrs = AnnotationAttributes
        .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    if(mapperScanAttrs ! =null) { registerBeanDefinitions(mapperScanAttrs, registry); }}void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry) {

    ClassPathMapperScanner scanner = newClassPathMapperScanner(registry); ./** * This will scan all the mapper under basePackages as BDS and instantiate them as beans */ note that all registered beans are actually MapPerFactoryBeans [proxy] */
    scanner.doScan(StringUtils.toStringArray(basePackages));
  }
Copy the code

Let’s move on to the details of the doScan implementation:

 @Override
  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    Mybatis mapper = mybatis mapper = mybatis mapper = mybatis mapper
    Beanclass = usermapper.class
    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

    if (beanDefinitions.isEmpty()) {
	  ...
    } else {
      // Handle bd
      processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
  }

  private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    for(BeanDefinitionHolder holder : beanDefinitions) { definition = (GenericBeanDefinition) holder.getBeanDefinition(); String beanClassName = definition.getBeanClassName(); . definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);// The actual class of the bean is MapperFactoryBean
      definition.setBeanClass(this.mapperFactoryBean.getClass()); .if(! explicitFactoryUsed) { LOGGER.debug(() ->"Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'."); definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); }}}Copy the code

Special attention should be paid to the last line here setAutowireMode (AbstractBeanDefinition. AUTOWIRE_BY_TYPE); Spring’s default autowage level is AUTOWIRE_NO, which needs to be changed to AUTOWIRE_BY_TYPE injection by type. Spring then uses the set method to inject properties based on the bean type, such as sqlSessionFactory and sqlSessionTemplate in MapperFactoryBean.

There are four types of autowiring in Spring

// No injection by default, also the default level = 0
public static final int AUTOWIRE_NO = AutowireCapableBeanFactory.AUTOWIRE_NO;
// Press name = 1
public static final int AUTOWIRE_BY_NAME = AutowireCapableBeanFactory.AUTOWIRE_BY_NAME;
// Type = 2
public static final int AUTOWIRE_BY_TYPE = AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE;
// Press constructor = 3
public static final int AUTOWIRE_CONSTRUCTOR = AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR;

// The other one is no longer recommended, so the details of auto-assembly will not be repeated here
Copy the code

Why doesn’t Mybatis use @autowired? Decoupled, you can compile without relying on Spring, and with Spring annotations, you have strong coupling.

At this point, the Mapper, which we can think of as bd, is added to Spring, so the next step is instantiation, so we’re going to look at the MapperFactoryBean

MapperFactoryBean

MapperFactoryBean extends SqlSessionDaoSupport extends DaoSupport, DaoSupport implements InitializingBean So afterPropertiesSet is executed after MFB instantiation

	@Override
	public final void afterPropertiesSet(a) 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); }}Copy the code

The afterPropertiesSet checkDaoConfig() method is important. Methods and SQL are bound when they are instantiated and placed in Map

mappedStatements, not when you call them.
,>

  protected void checkDaoConfig(a) {
    super.checkDaoConfig();

    Configuration configuration = getSqlSession().getConfiguration();
    if (this.addToConfig && ! configuration.hasMapper(this.mapperInterface)) {
      try {
        // At the end of this step, a parse is called to bind all the methods to the SQL statement
        // Put it in a map, so when you execute a mapper method, the bottom line is actually to get in a map
        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(); }}}Copy the code

This. MapperInterface is actually the current class. For example, when UserMapper is instantiated, the mapperInterface in MFB is itself, because MFB implements FactoryBean. So when you inject a Mapper with annotations, the mapper is generated by the getObject() method

  @Override
  public T getObject(a) throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }

   // org.mybatis.spring.support.SqlSessionDaoSupport#getSqlSession
   // This SqlSession is managed by Spring and is automatically injected
   public SqlSession getSqlSession(a) {
      return this.sqlSessionTemplate;
   }
Copy the code

GetSqlSession () always gets the SqlSessionTemplate in its integration with Spring

  // org.mybatis.spring.support.SqlSessionDaoSupport
  // Normally, the injection of sqlSessionTemplate is done in the setSqlSessionFactory method
  public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
    if (this.sqlSessionTemplate == null|| sqlSessionFactory ! =this.sqlSessionTemplate.getSqlSessionFactory()) {
      this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory); }}Copy the code

GetMapper gets the proxy class

Finally, getMapper of SqlSession is used to obtain the proxy class

// The underlying call is the getMapper method of 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 the proxy class
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: "+ e, e); }}Copy the code

At this point, we have a general idea of how the MYBatis DAO interface becomes a proxy class and is put into the Spring container.

Where does the data in knownMappers come from?

As MyBatis parses the nodes of a configuration file, it goes to the XMLMapperBuilder class, which has two important methods

  • parse
  • bindMapperForNamespace
  public void parse(a) {
    if(! configuration.isResourceLoaded(resource)) {// Parse the mapper node
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      // Bind the Mapper interface to the namespace
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }

  /** * Mapper interface binding process */
  private void bindMapperForNamespace(a) {
    String namespace = builderAssistant.getCurrentNamespace();
    if(namespace ! =null) { Class<? > boundType =null;
      try {
        boundType = Resources.classForName(namespace);
      } catch (ClassNotFoundException e) {
        //ignore, bound type is not required
      }
      if(boundType ! =null) {
        // Check whether the current mapper class is bound
        if(! configuration.hasMapper(boundType)) {// Spring may not know the real resource name so we set a flag
          // to prevent loading again this resource from the mapper interface
          // look at MapperAnnotationBuilder#loadXmlResource
          configuration.addLoadedResource("namespace:" + namespace);
          // Bind the mapper classconfiguration.addMapper(boundType); }}}}Copy the code

The addMapper method in the interface binding process calls MapperRegistry’s addMapper method to deposit the mapping of Class to MapperProxyFactory objects into knownMappers.

The proxy class performs the logic

Once put into the Spring container, we can inject the proxy object for database operations. Since interface methods are intercepted by the MapperProxy proxy logic, let’s focus on the proxy logic to see what the proxy logic does.

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        } else if (isDefaultMethod(method)) {
            returninvokeDefaultMethod(proxy, method, args); }}catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
    }
    // Get a MapperMethod object from the cache. If the cache fails, create a MapperMethod object
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    // sqlSession is different in different cases, such as defaultSqlSession and sqlSessionManger
    // The sqlSession method is still called, but sqlSessionManger resolves automatic shutdown and thread safety issues
    // Call the execute method to execute SQL
    return mapperMethod.execute(sqlSession, args);
}

private MapperMethod cachedMapperMethod(Method method) {
    return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}


/** * Create the mapping method */
public MapperMethod(Class
        mapperInterface, Method method, Configuration config) {
    // Create an SqlCommand object that contains some SQL-related information
    this.command = new SqlCommand(config, mapperInterface, method);
    // Create a MethodSignature object, which, by the class name, contains some information about the intercepted method
    this.method = new MethodSignature(config, mapperInterface, method);
}
 
/** * This object contains some SQL related information * it can be used to find MappedStatement * we can see the familiar Invalid Bound Statement (not found) exception */
public SqlCommand(Configuration configuration, Class
        mapperInterface, Method method) {
    final String methodName = method.getName();
    finalClass<? > declaringClass = method.getDeclaringClass();/ / parsing MappedStatement
    MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
                                                configuration);
    // Check whether the current method has a corresponding MappedStatement
    if (ms == null) {
        if(method.getAnnotation(Flush.class) ! =null){
            name = null;
            type = SqlCommandType.FLUSH;
        } else {
            // Familiar exception
            throw new BindingException("Invalid bound statement (not found): "
                                       + mapperInterface.getName() + "."+ methodName); }}else {
        name = ms.getId();
        type = ms.getSqlCommandType();
        if (type == SqlCommandType.UNKNOWN) {
            throw new BindingException("Unknown execution method for: "+ name); }}}Copy the code

The MapperMethod initialization process has been analyzed. Now the MapperMethod is created. Mybatis = Mybatis = Mybatis = Mybatis = Mybatis = Mybatis = Mybatis = Mybatis = Mybatis = Mybatis = Mybatis

This is the end of this article, if you like it, give a like + favorites + follow it! 🤓 have what want to see welcome message!!