关于MyBatis
本身的Mapper
机制,请参考文档 MyBatis的Mapper机制
首先,在没有使用MyBatis-Spring
的情况下,我们这么去访问Mapper
public Student findStudentById(int studentId) {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
try {
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
Student student = studentMapper.queryByPrimaryKey(studentId);
return student;
} finally {
sqlSession.close();
}
}
每次都要调用SqlSessionFactory.openSession
方法打开SqlSession
会话。并且用完SqlSession
之后,就需要调用SqlSession.close
。
SqlSession.close
做了什么事情:
① 关闭数据库游标
② 关闭连接,注意:这边说的关闭连接只是调用connection.close
方法,但是如果用连接池,那么connection.close
一般是把连接放回连接池中,而不是直接close
掉。具体要看连接池对connection
的包装。
下面看看使用了MyBatis-Spring
之后的Mapper
是怎么样的。
我们一般会把某个Mapper
以成员变量的形式注入到业务类中。比如:
public class CityCacheServiceImpl implements CityCacheService {
@Autowired
private CityMapper cityMapper;
}
我们都知道这边注入的CityMapper
一般都是单例,是Spring Bean
工场产生的Singleton
。要知道为何CityMapper
能被注入进来,就得看下MyBatis-Spring
的配置了。
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<property name="mapperLocations">
<list>
<value>classpath:mapper/*Mapper.xml</value>
</list>
</property>
<property name="transactionFactory">
<bean class="org.apache.ibatis.transaction.managed.ManagedTransactionFactory"/>
</property>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.test.dao"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
从上面的配置可以看出,org.mybatis.spring.mapper.MapperScannerConfigurer
会在初始化的时候扫描basePackage
目录。知道org.mybatis.spring.mapper.MapperScannerConfigurer
中的postProcessBeanDefinitionRegistry
方法。
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
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));
}
看到使用了ClassPathMapperScanner
来做具体扫描的动作。追踪到ClassPathMapperScanner.processBeanDefinitions
。这个方法是用来针对Mapper
详细构建BeanDefinition
的。我在里面把关键的几个注释写上。
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
// 设置class。org.mybatis.spring.mapper.MapperFactoryBean
// 这个类是实现FactoryBean接口的。
// 在@Autowired时候,会调用FactoryBean.getObject方法来获取bean对象
// 可以参考MapperFactoryBean.getObject方法
// 下面会设置它的两个成员变量,分别是sqlSession和sqlSessionFactors
definition.setBeanClass(this.mapperFactoryBean.getClass());
definition.getPropertyValues().add("addToConfig", this.addToConfig);
boolean explicitFactoryUsed = false;
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionFactory != null) {
// 添加sqlSession工场
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
explicitFactoryUsed = true;
}
if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionTemplate != null) {
// 添加sqlSessionTemplate
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
}
if (!explicitFactoryUsed) {
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
}
以上的关键注释这边在重复下
// 设置class
。org.mybatis.spring.mapper.MapperFactoryBean
// 这个类是实现FactoryBean
接口的。
// 在@Autowired
时候,会调用FactoryBean.getObject
方法来获取bean
对象
// 可以参考MapperFactoryBean.getObject
方法
// 下面会设置它的两个成员变量,分别是sqlSession
和sqlSessionFactory
在@Autowired
时候,会调用SqlSessionTemplate.getMapper
方法来构建Mapper
代理对象。详细看下SqlSessionTemplate
的几个关键点。
public class SqlSessionTemplate implements SqlSession, DisposableBean {
// SqlSession的代理对象
private final SqlSession sqlSessionProxy;
// 省去了大量代码
// 动态代理类
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);
}
}
}
}
}
在未启用事务的情况下,用MyBatis-Spring
初始化的所有Mapper
在调用代理方法时,都会执行这边的invoke
方法。每次调用都会调用getSqlSession
方法来创建一个新的DefaultSqlSession
对象。执行完成之后又会调用closeSqlSession
来关闭SqlSession
总结
Spring
的这种兼容处理,是很巧妙的。也使得开发者调用方式比纯粹的MyBatis
又进一步简洁了。