How does the dao interface of mybatis relate to sql in the xml file?Step by step analysis

Keywords: Java SQL Mybatis xml Spring

Preface

Before you begin the body, explain how the Dao interface corresponds to the SQL in the XML file.

At the end of a sentence, mybatis parses these xml files, establishes a relationship with Dao through the namespace ins id e the xml file, and then associates each sql in the xml with an interface in dao.

The question then arises:'If I have two xml files that have established relationships with this dao, then that's not a conflict?"

With that in mind, we're going to start the next topic!

I. Initialization

First, we need to know that each MyBatis-based application is centered on an instance of SqlSessionFactory, which can be obtained through SqlSessionFactoryBuilder.

But SqlSessionFactory is an interface that actually has two methods: openSession, getConfiguration

The openSession method is to get a SqlSession object and complete the necessary database add-delete function.However, there are too few SqlSessionFactory attributes, so a combination of getConfiguration is required to configure properties such as mapper map files, SQL parameters, return value types, caches, and so on.

/**
 * Creates an {@link SqlSession} out of a connection or a DataSource
 * 
 * @author Clinton Begin
 */
public interface SqlSessionFactory {

  SqlSession openSession();

  SqlSession openSession(boolean autoCommit);
  SqlSession openSession(Connection connection);
  SqlSession openSession(TransactionIsolationLevel level);

  SqlSession openSession(ExecutorType execType);
  SqlSession openSession(ExecutorType execType, boolean autoCommit);
  SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
  SqlSession openSession(ExecutorType execType, Connection connection);

  Configuration getConfiguration();

}

You can see that getConfiguration is a method that belongs to the Configuration class.You can think of it as a configuration housekeeper.All configuration information for MyBatis is maintained in the Configuration object, and almost every object holds its reference.

But we use Mybatis with Spring in our daily development, so let Spring handle the instantiation.

So we can look at org.mybatis.spring.SqlSessionFactoryBean, which implements the InitializingBean interface.This means that afterPropertiesSet() is called after the class is instantiated.It only has one method

public void afterPropertiesSet() throws Exception {
    this.sqlSessionFactory = buildSqlSessionFactory();
}

This afterPropertiesSet method has only one action, buildSqlSessionFactory.It can be divided into two parts:

  • 1. Load various components from the property property of the configuration file and parse the configuration into the configuration
  • 2. Load the mapper file, parse the SQL statement, encapsulate it as a MappedStatement object, and configure it in the configuration.

2. How is the mapper interface method called?

There are roughly two ways:

  • API provided by Mybatis

Operate using the API provided by Mybatis, get the SqlSession object, and then manipulate the database based on the Statement Id and parameters.

String statement = "com.mmzsblog.business.dao.MemberMapper.getMemberList";
List<Member> result = sqlsession.selectList(statement);
  • mapper interface

Define the Mapper interface, where you define a series of business data manipulation methods.At the Service layer, database operations can be performed by injecting the mapper property and calling its methods.Like this

public interface MemberMapper { 
    List<Member> getMemberList();
}

@Service
public class MemberServiceImpl implements MemberService{
    @Resource
    private MemberMapper memberMapper;
    
    @Override
    public List<Member> getMemberList() {
        return memberMapper.getMemberList();
    }
}

Then, MemberMapper is just an interface and does not have any implementation classes.How does it ultimately execute into our SQL statement when we call it?

3. Proxy creation process of Mapper interface

3.1. First we will configure the basic package path that needs to be scanned

Configure by commenting:

@MapperScan({"com.mmzsblog.business.dao"})

Or configure it as xml:

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.mmzsblog.business.dao" />
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean>

3.2. Start scanning

When we come to the class org.mybatis.spring.mapper.MapperScannerConfigurer, we can see that it implements several interfaces.

The focus is on BeanDefinitionRegistryPostProcessor.It can dynamically register Bean information by postProcessBeanDefinitionRegistry().

    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        if (this.processPropertyPlaceHolders) {
            this.processPropertyPlaceHolders();
        }
        
        // Create a ClassPath scanner, set properties, and then invoke the scan method
        ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
        scanner.setAddToConfig(this.addToConfig);
        scanner.setAnnotationClass(this.annotationClass);
        scanner.setMarkerInterface(this.markerInterface);
        scanner.setSqlSessionFactory(this.sqlSessionFactory);
        scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
        scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
        scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
        scanner.setResourceLoader(this.applicationContext);
        scanner.setBeanNameGenerator(this.nameGenerator);
        // Create a ClassPath scanner, set properties, and then invoke the scan method
        scanner.registerFilters();
        scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));
    }

ClassPathMapperScanner inherits from ClassPathBeanDefinitionScanner in Spring, so its scan method calls the scan method of the parent ClassPathBeanDefinitionScanner.

public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider {
    ......
    public int scan(String... basePackages) {
        // 
        int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
        this.doScan(basePackages);
        if (this.includeAnnotationConfig) {
            AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
        }

        return this.registry.getBeanDefinitionCount() - beanCountAtScanStart;
    }
    ......
}    

In the scan method of the parent class, the doScan method overridden by the subclass ClassPathMapperScanner is called.

public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
    ......
    public Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
        if (beanDefinitions.isEmpty()) {
            this.logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
        } else {
            this.processBeanDefinitions(beanDefinitions);
        }

        return beanDefinitions;
    }
    ......
}    

Here super.doScan(basePackages) is the method in Spring, so you can turn over the source code yourself if you want to know more.

3.3. bean registration completed and sqlSession proxy created

By following these steps, all Mapper interfaces have been scanned and registered as BeanDefinition objects.The processBeanDefinitions method in the doScan method above is used for registration.

public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
    ......
    // Set beanClass
    private MapperFactoryBean<?> mapperFactoryBean = new MapperFactoryBean();
    ......
    
    private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
        Iterator var3 = beanDefinitions.iterator();

        while(var3.hasNext()) {
            BeanDefinitionHolder holder = (BeanDefinitionHolder)var3.next();
            GenericBeanDefinition definition = (GenericBeanDefinition)holder.getBeanDefinition();
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + definition.getBeanClassName() + "' mapperInterface");
            }
            // Add the name of the mapper interface to the construction parameter
            definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
            // Set BeanDefinition's class
            definition.setBeanClass(this.mapperFactoryBean.getClass());
            // Add Attribute addToConfig
            definition.getPropertyValues().add("addToConfig", this.addToConfig);
            boolean explicitFactoryUsed = false;
            // Add attribute sqlSessionFactory
            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) {
                    this.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) {
                    this.logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
                }

                definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
                explicitFactoryUsed = true;
            }

            if (!explicitFactoryUsed) {
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
                }

                definition.setAutowireMode(2);
            }
        }
    }
    ......
}    

The process is relatively simple, with some properties set to the BeanDefinition object.For example:

  • Set beanClass

Set the BeanClass of the BeanDefinition object to MapperFactoryBean<?>.This is equivalent to registering with MemberMapper: the current mapper interface is in the Spring container, the beanName is the memberMapper, and the beanClass is the MapperFactoryBean.class.So when Spring's IOC is initialized, the instantiated object is the MapperFactoryBean object.

  • Set sqlSessionFactory property

Adding the property sqlSessionFactory to the BeanDefinition object is a convenient call to setSqlSessionFactory() when setting PropertyValue on the BeanDefinition object.

3.4. Create sqlSession proxy class

Finally, in the setSqlSessionFactory method, sqlSession gets the SqlSessionTemplate instance.In the SqlSessionTemplate object, there are mainly sqlSessionFactory and sqlSessionProxy, while sqlSessionProxy is actually a proxy object for the SqlSession interface.The invoke method of the proxy class is actually called.

public class MapperProxy<T> implements InvocationHandler, Serializable {
  ......
  @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 if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }
  ......
}  

3.5, Summary

The proxy creation process for the Mapper interface is roughly as follows:

  • 1. Scan all objects under the basic package path of the mapper interface and register them as BeanDefinition objects
  • 2. Set the beanClass and sqlSessionFactory properties of the BeanDefinition object (and when it gets the BeanDefinition object, call its factory method getObject to return the proxy class of the mapper interface)
  • 3. When setting the sqlSessionFactory property, the construction method of SqlSessionTemplate is called to create a proxy class for the SqlSession interface

Finally, we are at the Service level, through

@Resource 
private MemberMapper memberDao;

When an attribute is injected, the proxy class is returned.When the memberDao method is executed, the invoke method of the proxy class is also actually invoked.

4. Answer the first questions

When Mybatis initializes the SqlSessionFactoryBean, it finds the basic package path that the configuration needs to scan to parse all the XML files inside.The focus is on the following two areas:

1. Create SqlSource

Mybatis encapsulates each SQL tag as a SqlSource object.Then, according to the different SQL statements, it can be divided into dynamic SQL and static SQL.Static SQL contains a String-type SQL statement, while dynamic SQL consists of one SqlNode.

2. Create MappedStatement

Each SQL tag in an XML file corresponds to a MappedStatement object, where two properties are important.

  • id

ID consisting of fully qualified class name + method name.

  • sqlSource

The SqlSource object corresponding to the current SQL tag.
When the MappedStatement object is created, it is cached in Configuration#mappedStatements.

The Configuration object mentioned in the previous initialization, which we know is the Configuration Manager in Mybatis, maintains all the basic configuration information here.

For example, here is a piece of code:

<!-- namespace The value of is the fully qualified class name -->
<mapper namespace="com.java.mmzsblog.dao.MemberMapper">
    ......
    <!-- select In Label id The value of is the method name, which corresponds to the method name in the fully qualified class -->
    <select id="getMemberById" resultType="com.java.mmzsblog.entity.member">
        select * from member
        <where>
            <if test="memberId!=null">
                and member_id=#{memberId}
            </if>
        </where>
    </select>
    ......
</mapper>    

Once all the XML is parsed, Configuration contains all the SQL information.Then parse the finished XML like this:

If you're smart enough to see the above illustration, you probably know it.When we execute the Mybatis method, we find the MappedStatement object by fully qualified class name + method name, then parse the SQL content inside, and execute.

Posted by mike_at_hull on Mon, 30 Dec 2019 17:47:17 -0800