In the previous articles, we mainly analyzed the separate use of Mybatis. In practice, most of the common project development uses mybatis in conjunction with Spring. After all, few projects are developed without Spring.This article describes how Mybatis works with Spring and how its source code is implemented.
Spring-Mybatis uses
Add maven dependencies
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>4.3.8.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.2</version> </dependency>
Add mybatis-config.xml file under src/main/resources
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <typeAliases> <typeAlias alias="User" type="com.chenhao.bean.User" /> </typeAliases> <plugins> <plugin interceptor="com.github.pagehelper.PageInterceptor"> <property name="helperDialect" value="mysql"/> </plugin> </plugins> </configuration>
Add User.xml under src/main/resources/mapper path
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.chenhao.mapper.UserMapper"> <select id="getUser" parameterType="int" resultType="com.chenhao.bean.User"> SELECT * FROM USER WHERE id = #{id} </select> </mapper>
Add beans.xml under src/main/resources/path
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> <property name="url" value="jdbc:mysql://127.0.0.1:3306/test"></property> <property name="username" value="root"></property> <property name="password" value="root"></property> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="configLocation" value="classpath:mybatis-config.xml"></property> <property name="dataSource" ref="dataSource" /> <property name="mapperLocations" value="classpath:mapper/*.xml" /> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.chenhao.mapper" /> </bean> </beans>
Ways of Annotation
- The above analysis was configured in spring's XML configuration file applicationContext.xml, and mybatis-spring also provides a comment-based way to configure sqlSessionFactory and Mapper interfaces.
- SqlSessionFactory is primarily configured in the @Configuration annotation's configuration class using the @Bean annotation's method named sqlSessionFactory;
- The Mapper interface specifies the packages that need to be scanned to obtain the mapper interface by combining the @MapperScan annotation with the @Configuration annotation's configuration class.
@Configuration @MapperScan("com.chenhao.mapper") public class AppConfig { @Bean public DataSource dataSource() { return new EmbeddedDatabaseBuilder() .addScript("schema.sql") .build(); } @Bean public DataSourceTransactionManager transactionManager() { return new DataSourceTransactionManager(dataSource()); } @Bean public SqlSessionFactory sqlSessionFactory() throws Exception { //Establish SqlSessionFactoryBean object SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); //set up data sources sessionFactory.setDataSource(dataSource()); //Set up Mapper.xml Route sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml")); // Set up MyBatis jPaginate PageInterceptor pageInterceptor = new PageInterceptor(); Properties properties = new Properties(); properties.setProperty("helperDialect", "mysql"); pageInterceptor.setProperties(properties); sessionFactory.setPlugins(new Interceptor[]{pageInterceptor}); return sessionFactory.getObject(); } }
How you compare Spring-Mybatis, that is, to the beans.xml file
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="configLocation" value="classpath:mybatis-config.xml"></property> <property name="dataSource" ref="dataSource" /> <property name="mapperLocations" value="classpath:mapper/*.xml" /> </bean>
This corresponds to the generation of the SqlSessionFactory, similar to the following code used by native Mybatis
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build( Resources.getResourceAsStream("mybatis-config.xml"));
The UserMapper proxy object is obtained by scanning, that is, the MapperScannerConfigurer class
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.chenhao.mapper" /> </bean>
The following code, which corresponds to the Mapper interface, is similar to the one used by native Mybatis:
SqlSession session = sqlSessionFactory.openSession(); UserMapper mapper = session.getMapper(UserMapper.class);
Then we can get it directly from Spring's BeanFactory in Service, as follows
So let's now focus on how the SqlSessionFactory and Mapper interfaces are generated in Spring
Design and Implementation of SqlSessionFactoryBean
General idea
- mybatis-spring manages mybatis related components as beans of spring's IOC container, and wraps mybatis related components using Spring's FactoryBean interface in order to achieve the integration of spring with mybatis.When spring's IOC container starts loading, if it finds a bean that implements the FactoryBean interface, it calls the bean's getObject method to get the actual bean object registered with the IOC container, where the FactoryBean interface provides a declaration of the getObject method, thereby unifying the behavior of spring's IOC container.
- SqlSessionFactory, as the startup component of mybatis, provides SqlSessionFactoryBean for wrapping in mybatis-spring, so integrating mybatis in a spring project requires first configuring SqlSessionFactoryBean in a spring configuration, such as the XML configuration file applicationContext.xml, to introduce the SqlSessionFactoryBean, which loads and creates the SqlSessionFac when the spring project startsThe Tory object is then registered with Spring's IOC container so that it can be injected directly into the application code or as an attribute into the bean object corresponding to other components of mybatis.The configuration in applicationContext.xml is as follows:
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> // data source <property name="dataSource" ref="dataSource" /> // The resource file for mapper.xml, also known as the SQL file <property name="mapperLocations" value="classpath:mybatis/mapper/**/*.xml" /> //mybatis Configuration mybatisConfig.xml Resource File <property name="configLocation" value="classpath:mybatis/mybitas-config.xml" /> </bean>
Interface Design and Implementation
The interface for SqlSessionFactory is designed as follows: three interfaces, FactoryBean provided by spring, InitializingBean and ApplicationListener, are implemented, and the related components of mybatis are encapsulated internally as internal properties, such as mybatisConfig.xml configuration resource file reference, mapper.xml configuration resource file reference, SqlSessionFactoryBuilder constructor and SqlSessionFactory reference.
// analysis mybatisConfig.xml Files and mapper.xml,Set up data sources and transaction management mechanisms used to encapsulate these into Configuration object // Use Configuration Object as construction parameter, create SqlSessionFactory Object, where SqlSessionFactory Is Single bean,Finally, the SqlSessionFactory Singleton object registered with spring Container. public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> { private static final Logger LOGGER = LoggerFactory.getLogger(SqlSessionFactoryBean.class); // mybatis To configure mybatisConfig.xml Resource Files private Resource configLocation; //Resolved mybatisConfig.xml Post-Generation Configuration object private Configuration configuration; // mapper.xml Resource Files private Resource[] mapperLocations; // data source private DataSource dataSource; // Transaction management, mybatis Access spring An important reason is that it can also be used directly spring Transaction management provided private TransactionFactory transactionFactory; private Properties configurationProperties; // mybatis Of SqlSessionFactoryBuidler and SqlSessionFactory private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); private SqlSessionFactory sqlSessionFactory; // Realization FactoryBean Of getObject Method @Override public SqlSessionFactory getObject() throws Exception { //... } // Realization InitializingBean Of @Override public void afterPropertiesSet() throws Exception { //... } // Is Single public boolean isSingleton() { return true; } }
We focus on the two interfaces, FactoryBean and InitializingBean. spring's IOC container calls InitializingBean's afterPropertiesSet method to initialize the bean object when it loads an instance of the bean object that creates the SqlSessionFactoryBean.
The afterPropertiesSet method of InitializingBean
It's a good idea to take a look at my previous article on Spring source code, which has a detailed source code analysis of Bean's life cycle. Now let's briefly review the call to the afterPropertiesSet of InitializingBean in the initializeBean method when getBean(), and in the previous step of populateBean, setting the properties of the bean object instance, the afterPropertiesSet of InitializingBeanDo some post processing.At this point, we should note that the populateBean method has already assigned the properties of the SqlSessionFactoryBean object, that is, the dataSource, mapperLocations, configLocation of the property configuration in the xml, which have already been assigned to the properties of the SqlSessionFactoryBean object and can then be used directly when calling the afterPropertiesSet.
// bean Core implementation of object instance creation protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException { // Create using a constructor or factory method bean Object Instance // Instantiate the bean. BeanWrapper instanceWrapper = null; if (mbd.isSingleton()) { instanceWrapper = this.factoryBeanInstanceCache.remove(beanName); } if (instanceWrapper == null) { instanceWrapper = createBeanInstance(beanName, mbd, args); } ... // Initialization bean Object instances, including attribute assignments, initialization methods, BeanPostProcessor Execution // Initialize the bean instance. Object exposedObject = bean; try { // 1. InstantiationAwareBeanPostProcessor implement: // (1). call InstantiationAwareBeanPostProcessor Of postProcessAfterInstantiation, // (2). call InstantiationAwareBeanPostProcessor Of postProcessProperties and postProcessPropertyValues // 2. bean Attribute assignment of objects populateBean(beanName, mbd, instanceWrapper); // 1. Aware Method Calls for Interfaces // 2. BeanPostProcess Execute: Call BeanPostProcessor Of postProcessBeforeInitialization // 3. call init-method: First InitializingBean Of afterPropertiesSet,Then apply the configured init-method // 4. BeanPostProcess Execute: Call BeanPostProcessor Of postProcessAfterInitialization exposedObject = initializeBean(beanName, exposedObject, mbd); } // Register bean as disposable. try { registerDisposableBeanIfNecessary(beanName, bean, mbd); } catch (BeanDefinitionValidationException ex) { throw new BeanCreationException( mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex); } return exposedObject; }
As mentioned above, in the populateBean phase, the three properties dataSource, mapperLocations, and configLocation have been assigned to the properties of the SqlSessionFactoryBean object, and the values of these configurations can be used directly when calling afterPropertiesSet.Let's move on to the afterPropertiesSet method
@Override public void afterPropertiesSet() throws Exception { notNull(dataSource, "Property 'dataSource' is required"); notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required"); state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null), "Property 'configuration' and 'configLocation' can not specified with together"); // Establish sqlSessionFactory this.sqlSessionFactory = buildSqlSessionFactory(); }
The SqlSessionFactoryBean's afterPropertiesSet method is implemented as follows: Call the buildSqlSessionFactory method to create the sqlSessionFactory object for the IOC container registered with spring.Let's move on to the buildSqlSessionFactory
protected SqlSessionFactory buildSqlSessionFactory() throws IOException { // Configuration Class Configuration configuration; // analysis mybatis-Config.xml Files, // Save configuration information to configuration XMLConfigBuilder xmlConfigBuilder = null; if (this.configuration != null) { configuration = this.configuration; if (configuration.getVariables() == null) { configuration.setVariables(this.configurationProperties); } else if (this.configurationProperties != null) { configuration.getVariables().putAll(this.configurationProperties); } //Resource file is not empty } else if (this.configLocation != null) { //according to configLocation Establish xmlConfigBuilder,XMLConfigBuilder Will be created in the constructor Configuration object xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties); //take XMLConfigBuilder Created in Constructor Configuration Object directly assigned to configuration attribute configuration = xmlConfigBuilder.getConfiguration(); } //slightly.... if (xmlConfigBuilder != null) { try { //analysis mybatis-Config.xml File and save the configuration information to configuration xmlConfigBuilder.parse(); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Parsed configuration file: '" + this.configLocation + "'"); } } catch (Exception ex) { throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex); } } if (this.transactionFactory == null) { //Transaction defaults to SpringManagedTransaction,This is very important, and I will buy you later to write a separate article explaining it Mybatis and Spring Transactional Relationships this.transactionFactory = new SpringManagedTransactionFactory(); } // by sqlSessionFactory Bind Transaction Manager and Data Source // such sqlSessionFactory In Creation sqlSession This transaction manager allows you to access the jdbc Connect to execute SQL configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource)); // analysis mapper.xml if (!isEmpty(this.mapperLocations)) { for (Resource mapperLocation : this.mapperLocations) { if (mapperLocation == null) { continue; } try { // analysis mapper.xml File and register with configuration Object's mapperRegistry XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), configuration, mapperLocation.toString(), configuration.getSqlFragments()); xmlMapperBuilder.parse(); } catch (Exception e) { throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e); } finally { ErrorContext.instance().reset(); } if (LOGGER.isDebugEnabled()) { LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'"); } } } else { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found"); } } // take Configuration Object instances as parameters, // call sqlSessionFactoryBuilder Establish sqlSessionFactory Object Instance return this.sqlSessionFactoryBuilder.build(configuration); }
Core logic of buildSqlSessionFactory: parse mybatis configuration file mybatisConfig.xml and mapper configuration file mapper.xml and encapsulate them in the Configuration object, and finally call mybatis's sqlSessionFactoryBuilder to create the SqlSessionFactory object.This is equivalent to the initialization of the native mybatis described earlier.In addition, it is important that mybatis-spring defaults to SpringManagedTransaction when no transaction is specified in the configuration, so be prepared.The SqlSessionFactory is now created and assigned to the sqlSessionFactory property of the SqlSessionFactoryBean.
The getObject method definition for FactoryBean
FactoryBean: The factory where object instances of a class are created.
Spring's IOC container checks whether the bean object implements the FactoryBean interface when it is started and created. If so, it calls the bean object's getObject method, implements the creation and return of the bean object instance that is actually needed in the getObject method, and then registers the bean object instance that is actually needed in the spring container; if not, it directly registers the beanN Object instances are registered with the spring container.
The getObject method of SqlSessionFactoryBean is implemented as follows:Since Spring has called the afterPropertiesSet method of InitializingBean to create the sqlSessionFactory object when creating the bean object of SqlSessionFactoryBean itself, it can return the sqlSessionFactory object directly to Spring's IOC container to complete the registration of sqlSessionFactory's bean object, and then it canDepends on injecting sqlSessionFactory objects directly when applying code injection or when spring creates other bean objects.
@Override public SqlSessionFactory getObject() throws Exception { if (this.sqlSessionFactory == null) { afterPropertiesSet(); } // Return directly sqlSessionFactory object // Single object, owned by all mapper Share return this.sqlSessionFactory; }
summary
From the above analysis, Spring completed the creation of sqlSessionFactory object instance by calling SqlSessionFactoryBean's afterPropertiesSet method while loading the bean object instance that created SqlSessionFactoryBean; when registering SqlSessionFactoryBean object instance with Spring's IOC container, it found that SqlSessionFactoryBean implemented the FactoryBean interface, so it is not SqlSessionFactoryBean object instances themselves need to be registered with the IOC container of spring. Instead, the object corresponding to the return value of SqlSessionFactoryBean's getObject method needs to be registered with the IOC container of spring, which is the SqlSessionFactory object, so the IOC container of spring has been completed to register sqlSessionFactory object instances.Creating a proxy object for Mapper