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.