[上一篇]:Mybatis源码解析之MapperProxy
上一篇我们知道了MyBatis通过JDK动态代理让我们只用写接口不用写实现,但是还是有一些细节需要我们去研究下。
问题
我们从MapperFactoryBean源码中看到调用getObject再调用getSqlSession().getMapper 这个getSqlSession()中的sqlSession是什么类型的,又是什么时候赋值的,是Spring注入还是实例化赋值
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
//SqlSessionDaoSupport中的方法
public SqlSession getSqlSession() {
return this.sqlSession;
}
揭秘源码
在《1》Mybatis源码解析之Spring获取Mapper过程中看到启动的时候调用processBeanDefinitions,而这方法里最后会通过definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
设置beanDefinition中按类型注入,看过《1》就知道这里的beanDefinition是MapperFactoryBean,主要是为了按类型自动注入SqlSessionFactory或者SqlSessionTemplate(也就是实例化MapperFactoryBean时会自动调用它的set方法)。我们接下来看下MapperFactoryBean有哪些set方法,通过源码看到MapperFactoryBean继承了SqlSessionDaoSupport,贴出SqlSessionDaoSupport源码
/**
* Convenient super class for MyBatis SqlSession data access objects.
* It gives you access to the template which can then be used to execute SQL methods.
*
* This class needs a SqlSessionTemplate or a SqlSessionFactory.
* If both are set the SqlSessionFactory will be ignored.
*
* {code Autowired} was removed from setSqlSessionTemplate and setSqlSessionFactory
* in version 1.2.0.
*
* @author Putthibong Boonbong
*
* @see #setSqlSessionFactory
* @see #setSqlSessionTemplate
* @see SqlSessionTemplate
* @version $Id$
*/
public abstract class SqlSessionDaoSupport extends DaoSupport {
private SqlSession sqlSession;
private boolean externalSqlSession;
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (!this.externalSqlSession) {
this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
}
}
public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
this.sqlSession = sqlSessionTemplate;
this.externalSqlSession = true;
}
/**
* Users should use this method to get a SqlSession to call its statement methods
* This is SqlSession is managed by spring. Users should not commit/rollback/close it
* because it will be automatically done.
*
* @return Spring managed thread safe SqlSession
*/
public SqlSession getSqlSession() {
return this.sqlSession;
}
/**
* {@inheritDoc}
*/
@Override
protected void checkDaoConfig() {
notNull(this.sqlSession, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");
}
}
可以看到有我们关心的setSqlSessionFactory与setSqlSessionTemplate,这个类上的注释告诉我们需要一个SqlSessionTemplate或者一个SqlSessionFactory,在本菜的MyBatisConfig配置中只配置了SqlSessionFactoryBean如下所示,
这样就会将配置的SqlSessionFactory注入到SqlSessionDaoSupport中
@Configuration
@EnableTransactionManagement(proxyTargetClass = true)
@PropertySource({"classpath:jdbc.properties"})
@MapperScan(basePackages = "me.ele.eliter.**.mapper")
public class MyBatisConfig {
private static final Log LOG = LogFactory.getLog(MyBatisConfig.class);
@Bean(destroyMethod = "close")
public DataSource dataSource(
@Value("${db.driver}") String driverClass,
@Value("${db.url}") String jdbcUrl,
@Value("${db.username}") String userName,
@Value("${db.password}") String passWord) throws Exception {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass(driverClass);
dataSource.setJdbcUrl(jdbcUrl);
dataSource.setUser(userName);
dataSource.setPassword(passWord);
return dataSource;
}
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(dataSource);
dataSourceTransactionManager.afterPropertiesSet();
return dataSourceTransactionManager;
}
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource, ApplicationContext applicationContext) throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
sessionFactory.setConfigLocation(applicationContext.getResource("classpath:mybatis-config.xml"));
sessionFactory.afterPropertiesSet();
sessionFactory.setPlugins(new Interceptor[] { new PagerInterceptor()});
return sessionFactory;
}
@Bean
public TransactionTemplate transactionTemplate(PlatformTransactionManager transactionManager) throws Exception {
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.afterPropertiesSet();
return transactionTemplate;
}
}
有人会问,你明明配置的是SqlSessionFactoryBean,为啥说会将这个以sqlSessionFactory注入到SqlSessionDaoSupport中,这是因为SqlSessionFactoryBean是个factoryBean,在获取它的实例时Spring会调用它的getObject方法,我们看下面它的getObject方法就知道返回的是SqlSessionFactory
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
将配置的SqlSessionFactory注入到SqlSessionDaoSupport中也就是调用 setSqlSessionFactory(SqlSessionFactory sqlSessionFactory)这个方法,单独拎出这个方法
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (!this.externalSqlSession) {
this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
}
}
我们看到直接创建了一个SqlSessionTemplate对象。接下下我们看下
new SqlSessionTemplate(sqlSessionFactory)干了些什么事,通过跟源码找到了最终调用的构造方法。
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;
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
}
/**
* Proxy needed to route MyBatis method calls to the proper SqlSession got
* from Spring's Transaction Manager
* It also unwraps exceptions thrown by {@code Method#invoke(Object, Object...)} to
* pass a {@code PersistenceException} to the {@code PersistenceExceptionTranslator}.
*/
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
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);
}
}
}
}
SqlSessionTemplate构造方法的最后一步创建了jdk的动态代理sqlSessionProxy,将代理对象作为sqlSession。查看SqlSessionInterceptor的源码看到第一行就是我们关心的getSqlSession,得到sqlSession后执行被代理的方法。我们继续跟踪getSqlSession
/**
* Gets an SqlSession from Spring Transaction Manager or creates a new one if needed.
* Tries to get a SqlSession out of current transaction. If there is not any, it creates a new one.
* Then, it synchronizes the SqlSession with the transaction if Spring TX is active and
* SpringManagedTransactionFactory
is configured as a transaction manager.
*
* @param sessionFactory a MyBatis {@code SqlSessionFactory} to create new sessions
* @param executorType The executor type of the SqlSession to create
* @param exceptionTranslator Optional. Translates SqlSession.commit() exceptions to Spring exceptions.
* @throws TransientDataAccessResourceException if a transaction is active and the
* {@code SqlSessionFactory} is not using a {@code SpringManagedTransactionFactory}
* @see SpringManagedTransactionFactory
*/
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Creating a new SqlSession");
}
session = sessionFactory.openSession(executorType);
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
getSqlSession方法首先从Spring当前事务中获取SqlSession,如果不存在就通过SqlSessionFactory创建一个新的SqlSession,再将sqlSession放入SqlSessionHolder里供当前事务下下一次使用。我们知道SqlSessionFactory是创建SqlSession的工厂,到这里就清楚了MyBatis是如何走到SqlSessionFactory创建SqlSession的
总结
通过本文知道了MapperFactoryBean(实际是SqlSessionDaoSupport)里的SqlSession是如何来的,主要是由于Mybatis设置了definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
通过类型注入,以及使用了Jdk动态代理。
如有错误,欢迎各位大佬斧正!