I use mybatis-spring process has been a doubt in Mybatis source (a) Overview I mentioned, SqlSession
and Mapper object lifecycle is a method level, that is, each request SqlSession
and Mapper objects are not the same , Bean is a non-single embodiment. But after Spring integration, why we can directly inject Mapper objects, by direct injection, then if Mapper has become the object of a singleton?
With questions we look at how Mybatis-Spring is achieved.
Initialization SqlSessionFactory
We are through SqlSessionFactoryBean
to complete integration with Spring Mybatis, class diagram as follows:
We have found through the class diagram SqlSessionFactoryBean
implements FactoryBean
an interface, then the Spring Bean is instantiated when calls FactoryBean
the getObject()
method. Therefore Mybatis with Spring inlet is integrated org.mybatis.spring.SqlSessionFactoryBean#getObject()
method, the following source code:
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
By discovering with source code, in the afterPropertiesSet();
completion of the process sqlSessionFactory
initialization.
And Mybatis source (b) Mybatis initialization described in the same, or through XMLConfigBuilder
, XMLMapperBuilder
and XMLStatementBuilder
three builders to complete the parsing of Mybatis XML file.
Spring loaded into the container mapper
After Mybatis and Spring integration, there are three ways to Examples Mapper Spring loaded into the container, as follows:
- Use
<mybatis:scan/>
elements - Use
@MapperScan
annotations - Spring XML configuration file a registration
MapperScannerConfigurer
Here we introduce MapperScannerConfigurer
.
MapperScannerConfigurer
We have found through the class diagram, MapperScannerConfigurer
realized BeanDefinitionRegistryPostProcessor
, it executes BeanDefinitionRegistryPostProcessor
the postProcessBeanDefinitionRegistry
method to complete the loading of Bean.
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
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);
scanner.registerFilters();
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
We can see by scanning package, it will scan all the Mapper class, and then register to define the Bean Spring container.
Mapper is an interface class, however, can not be directly instantiated, so ClassPathMapperScanner
, it will all Mapper object BeanDefinition
to change, and the interfaces to all object points Mapper MapperFactoryBean
plant Bean, so the interface in Spring Mybatis corresponding to all Mapper class is MapperFactoryBean
, source code is as follows:
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
for (BeanDefinitionHolder holder : beanDefinitions) {
GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();
definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
definition.setBeanClass(MapperFactoryBean.class);
definition.getPropertyValues().add("addToConfig", this.addToConfig);
...
// 设置按类型注入属性,这里主要是注入sqlSessionFactory和sqlSessionTemplate
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
return beanDefinitions;
}
From source we see ClassPathMapperScanner
major following BeanDefinition
:
- The Class point
MapperFactoryBean
- Modify the attribute value to be injected, such
addToConfig
as:sqlSessionFactory
, ,sqlSessionFactory
- Modified injection method
AbstractBeanDefinition.AUTOWIRE_BY_TYPE
By the above-described examples may be modified so that the interface Mapper object and placed into Spring container.
Folders Factory Bean
We can see from FIG class that it is a FactoryBean
so instantiated call back to its getObject()
method completes loading the Bean, the following source code:
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
It is worth to say about that, getSqlSession()
get to that SqlSessionTemplate
object, Mapper is a case of a single example of how to ensure that each access to the database Sqlsession
is not the same, that is SqlSessionTemplate
implemented.
MapperFactoryBean
It also implements InitializingBean
the interface, using the InitializingBean
feature that the interface will Mapper Mybatis into the Configuration
subject, the following source code:
@Override
public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
// Let abstract subclasses check their configuration.
checkDaoConfig();
...
}
protected void checkDaoConfig() {
super.checkDaoConfig();
notNull(this.mapperInterface, "Property 'mapperInterface' is required");
Configuration configuration = getSqlSession().getConfiguration();
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
// 将Mapper放到Mybatis的Configuration对象中
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();
}
}
}
Mapper Spring container may be implanted into an interface equivalent to a configuration, this looks better understood:
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean"> <property name="mapperInterface" value="org.mybatis.spring.sample.mapper.UserMapper" /> <property name="sqlSessionFactory" ref="sqlSessionFactory" /> </bean>
- Through the above, we can find the source to load objects Mapper Spring container fact, Mapper proxy object corresponding to the interface
MapperProxy
, and it is a single vessel embodiment. - Mapper is a singleton really is no problem, because Mapper itself is not shared variable, it is a thread-safe class, we only need to ensure that the database used for each request
Sqlsession
is not a single example on the line. In order to achieve this,MapperProxy
it isSqlSession
not used directlyDefaultSqlSession
, but the useSqlSessionTemplate
.
SqlSessionTemplate
SqlSessionTemplate
+ Proxy mode using dynamic static proxy mode, SqlSession enhanced, the new database for each request SqlSession
put enhancer SqlSessionInterceptor
which is achieved.
public class SqlSessionTemplate implements SqlSession, DisposableBean {
private final SqlSessionFactory sqlSessionFactory;
private final ExecutorType executorType;
private final SqlSession sqlSessionProxy;
private final PersistenceExceptionTranslator exceptionTranslator;
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
notNull(executorType, "Property 'executorType' is required");
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
// 使用动态代理模式,对SqlSession进行增强
this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
new Class[]{SqlSession.class}, new SqlSessionInterceptor());
}
/**
* {@inheritDoc}
*/
@Override
public <T> T selectOne(String statement) {
return this.sqlSessionProxy.selectOne(statement);
}
}
SqlSessionInterceptor
This is one of the core principles to achieve Mybatis-Spring, it will create a new process for each request in the database SqlSession
, source code as follows:
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 每次获取新的SqlSession
SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
try {
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
// 事务提交
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
// 异常处理
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
// release the connection to avoid a deadlock if the translator is no loaded. See issue #22
// 资源释放
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator
.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
if (sqlSession != null) {
// 资源释放
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
In this enhancer which implements the newly created each request Sqlsession
, and also made uniform resource release, transaction processing, which makes us in the Spring and integration, without concern for resource release and other operations, will focus on their business .
to sum up
Achieve Mybatis-Spring has two core points:
- By
MapperFactoryBean
clever Mapper proxy object corresponding to the interfaceMapperProxy
is loaded into the Spring container. - By
SqlSessionTemplate
using a static dynamic proxy + proxy mode, cleverly realized are used to access the database each time a newSqlsession
object.
Mybatis source Chinese comments
https://github.com/xiaolyuh/mybatis
Experience
Here Mybatis source series is finished, compared with the Spring source code, I suggest that you go to the next Mybatis source, for the following reasons:
- Mybatis source code is very neat, packet structure, the structure of the code are worth learning.
- Mybatis use many design patterns, most of the design patterns can be found in its shadow here, it can be seen as best practice design patterns.
- Mybatis overall design is very clever, scalability is very strong.