Analysis of mapper interface method call of Mybatis

1, Question

The top layer of Mybatis architecture is the interface layer, which defines the way to interact with the database. There are two ways:
  • API provided by Mybatis
Use the API provided by Mybatis to obtain the SqlSession object, and then operate the database according to the Statement Id and parameters.
String statement = "com.viewscenes.netsupervisor.dao.UserMapper.getUserList";
List<User> result = sqlsession.selectList(statement);
  • mapper interface
Define mapper interface, which defines a series of business data operation methods. In the Service layer, you can perform database operations by injecting the mapper attribute and calling its methods. As follows:
public interface UserMapper {   
    List<User> getUserList();
}

@Service
public class UserServiceImpl implements UserService{
    @Autowired
    UserMapper userDao;
    @Override
    public List<User> getUserList() {
        return userDao.getUserList();
    }
} 
UserMapper is just an interface without any implementation class. How do you execute SQL statements when calling it?

2, Scanning

1. Configuration information

Speaking of this, it depends on another Bean in the configuration file. By specifying the path of the basic package, Mybatis can scan the following classes through Spring and register them as BeanDefinition objects.
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.viewscenes.netsupervisor.dao" />
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean> 
Or some friends have an annotationClass attribute in their project, that is
<property name="annotationClass" value="org.springframework.stereotype.Repository" /> 
Its function is to filter the defined annotation class when scanning the package. If there is this annotation, it will be scanned and usually identified by @ Repository on the class. However, its function is only for filtering. We can also customize this annotation. For example:
@MyDao
public interface UserMapper {}
<property name="annotationClass" value="com.viewscenes.netsupervisor.util.MyDao" />

Of course, if it is determined that all classes under the basic package path should be registered, there is no need to configure annotationClass.

2. Scan basic package

Go to the org.mybatis.spring.mapper.mappercannerconfigurer class, and you can see that it implements several interfaces. The focus is BeanDefinitionRegistryPostProcessor. It can dynamically register Bean information with the method of postProcessBeanDefinitionRegistry().
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    //establish ClassPath Scanners, set properties, and then call scan methods.
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    scanner.setAnnotationClass(this.annotationClass);
    scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
    //If configured annotationClass,Add it to includeFilters
    scanner.registerFilters();
    scanner.scan(this.basePackage);
} 
Classpathmappercanner inherits from the ClassPathBeanDefinitionScanner in Spring, so the scan method will call the scan method of the parent class, and the doScan method of the child class will be called in the scan method of the parent class.
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
    public Set<BeanDefinitionHolder> doScan(String... basePackages) {
        //call Spring of scan method. Is to register the classes under the basic package as BeanDefinition
        Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
        processBeanDefinitions(beanDefinitions);
        return beanDefinitions;
    }
} 
Super. Doscan (base packages) is a method in Spring. It mainly depends on the collection of BeanDefinition returned.

3. Configure BeanDefinition

All Mapper interfaces have been scanned and registered as BeanDefinition objects. Next, call processBeanDefinitions() to configure these BeanDefinition objects.
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {

    private MapperFactoryBean<?> mapperFactoryBean = new MapperFactoryBean<Object>();
    
    private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
        GenericBeanDefinition definition;
        for (BeanDefinitionHolder holder : beanDefinitions) {
            definition = (GenericBeanDefinition) holder.getBeanDefinition();
            
            //take mapper The name of the interface is added to the construction parameter
            definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
            //set up BeanDefinition of class
            definition.setBeanClass(this.mapperFactoryBean.getClass());
            //Add attribute addToConfig
            definition.getPropertyValues().add("addToConfig", this.addToConfig);
            //Add attribute sqlSessionFactory
            definition.getPropertyValues().add("sqlSessionFactory", 
new RuntimeBeanReference(this.sqlSessionFactoryBeanName)); ...... } } 
The process is very simple, that is, some properties are set in the BeanDefinition object. Focus on two.
  • Set beanClass
Set the beanclass of the BeanDefinition object to MapperFactoryBean. What does that mean? Taking usermapper as an example means that the current mapper interface is in the Spring container. beanName is usermapper and beanclass is MapperFactoryBean.class. Then, during IOC initialization, the instantiated object is the MapperFactoryBean object.
  • Set sqlSessionFactory property
Adding the property sqlSessionFactory to the BeanDefinition object means that setSqlSessionFactory() will be called when setting the PropertyValue for the BeanDefinition object.

3, Create proxy for SqlSession

As mentioned above, when setting PropertyValue for BeanDefinition object, its setSqlSessionFactory will be called. Let's take a look at this method. First, the BeanDefinition object here is the MapperFactoryBean object whose beanClass is MapperFactoryBean.class. Locating this class, we found that it inherits from org.mybatis.spring.support.SqlSessionDaoSupport.
public abstract class SqlSessionDaoSupport extends DaoSupport {
    
    private SqlSession sqlSession;
    
    public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
        if (!this.externalSqlSession) {
            this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
        }
    }
} 
In its setSqlSessionFactory method, the final call is new SqlSessionTemplate(). Therefore, the object of sqlSession is actually an instance of SqlSessionTemplate. Let's look at its constructor.
public class SqlSessionTemplate implements SqlSession, DisposableBean {
    public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
                        PersistenceExceptionTranslator exceptionTranslator) {
        
        //set up sqlSessionFactory
        this.sqlSessionFactory = sqlSessionFactory;
        //Sets the type of actuator
        this.executorType = executorType;
        //Exception related processing class
        this.exceptionTranslator = exceptionTranslator;
        //sqlSession Agent for
        this.sqlSessionProxy = (SqlSession) newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class },
        new SqlSessionInterceptor());
    }
} 
Friends familiar with JDK dynamic agent will see newProxyInstance first. It creates a proxy class for the sqlSession interface. The processor of this proxy class is SqlSessionInterceptor(). Needless to say, SqlSessionInterceptor must implement the InvocationHandler interface. This means that when sqlSession is called, its proxy class actually executed will call the invoke() method of the processor program.
private class SqlSessionInterceptor implements InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args){
        //Skip the content first
    }
} 
Finally, in the setSqlSessionFactory method, sqlsession obtains the SqlSessionTemplate instance. The SqlSessionTemplate object mainly contains sqlSessionFactory and sqlSessionProxy, which is actually the proxy object of the sqlsession interface.
 

4, Create proxy for Mapper interface

As mentioned above, MapperFactoryBean inherits from SqlSessionDaoSupport. What's more, it also implements the FactoryBean interface. This shows that MapperFactoryBean is not a pure person. Ah, no, not a pure Bean, but a factory Bean. If you want to declare a Bean as a factory Bean, it needs to implement the FactoryBean interface, which has three methods.
public interface FactoryBean<T> {
    //Returns an instance of the object
    T getObject() throws Exception;
    //Returns the type of the object instance
    Class<?> getObjectType();
    //Is it a single instance
    boolean isSingleton();
} 
Since MapperFactoryBean is a factory Bean, it returns not the object itself, but the instance returned by the getObjectType method of the object. Why? When executing getBean in Spring, after creating Bean object and completing dependency injection, call bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);. This method will determine whether the current Bean is a FactoryBean. If not, it will not be executed. If so, it will eventually call its getObject() method.
protected Object getObjectForBeanInstance(
            Object beanInstance, String name, String beanName, RootBeanDefinition mbd) {

    if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) {
        return beanInstance;
    }
    //getObjectFromFactoryBean The final call is getObject
    Object object = getObjectFromFactoryBean(factory, beanName, !synthetic);
    return object;
} 
So, what object will getObject return?

1,getObject()

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
    public T getObject() throws Exception { 
        //mapperInterface yes mapper Object of interface
        return getSqlSession().getMapper(this.mapperInterface);
    }
} 
We have analyzed getSqlSession(), which returns an instance of the SqlSessionTemplate object. Therefore, we mainly look at getMapper().

2,getMapper

public class MapperRegistry {
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        final MapperProxyFactory<T> mapperProxyFactory = knownMappers.get(type);
        return mapperProxyFactory.newInstance(sqlSession);   
    }
} 
As we can see, its implementation is relatively simple. But the question is, where did knownMappers come from? Why can it get the MapperProxyFactory instance according to the type interface? Remember, when scanning annotated SQL statements, it calls the addMapper method, which is actually this class.
public class MapperRegistry {
    public <T> void addMapper(Class<T> type) {
        if (type.isInterface()) {
            try {
                /injection type Mapping of interfaces
                knownMappers.put(type, new MapperProxyFactory<T>(type));
                //Scan annotation
                MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
                parser.parse();
            }
        }
    }
} 
This explains why knownMappers.get(type) can get the instance of MapperProxyFactory. Let's see what objects are created and returned inside it.

3,newInstance

During the creation process, a proxy class is actually returned, that is, the proxy class of the mapper interface.
public class MapperProxyFactory<T> {

    public T newInstance(SqlSession sqlSession) {
        //mapperProxy Is a caller processor,Obviously it has to be realized InvocationHandler Interface
        final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
        
        //JDK Dynamic agent, generated is mapperInterface Proxy class for interface
        //mapperInterface It's ours mapper Interface
        //such as com.viewscenes.netsupervisor.dao.UserMapper
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), 
                new Class[] { mapperInterface }, mapperProxy);
    }
}
public class MapperProxy<T> implements InvocationHandler {

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //Skip the specific process first....
        return method.invoke(this, args);
    }
}
Seeing here, we all understand. The getObject method returns the proxy class of the mapper interface. In other words, each mapper interface corresponds to its own interface proxy. Then, when the mapper method is actually called, mapperproxy. Invoke (object proxy, method, object [] args) of the calling program processor will be called.

5, Summary

This chapter mainly describes the agent creation process of Mapper interface. Simply sort out the following process:
  • Scan the mapper interface basic package and register it as a BeanDefinition object.
  • Set the beanClass and sqlSessionFactory properties of the BeanDefinition object.
  • When setting the sqlSessionFactory property, call the construction method of SqlSessionTemplate to create the proxy class of SqlSession interface.
  • When obtaining the BeanDefinition object, call its factory method getObject to return the proxy class of the mapper interface.
Finally, in the Service layer, when we inject properties through @ Autowired UserMapper userDao, the proxy class is returned. When the userDao method is executed, the invoke method of the proxy class is actually called. Finally, let's take a look at what the proxy class looks like.

Added by zrosen88 on Mon, 06 Dec 2021 06:33:57 +0200