前面梳理了下MyBatis在单独使用时的工作流程和关键源码,现在看看MyBatis在和Spring整合的时候是怎么工作的
也先从使用开始
Spring整合MyBatis
1.引入依赖,除了MyBatis的依赖,还需要引入 mybatis-spring依赖
2.在spring的配置文件applicationContext.xml里配置SqlSessionFactoryBean,从名字可以看出我们是通过这个Bean来创建SqlSessionFactory
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="configLocation" value="classpath:mybatis-config.xml"></property>
<property name="mapperLocations" value="classpath:mapper/*.xml"></property>
<property name="dataSource" ref="dataSource"/>
</bean>
3.在applicationContext.xml配置扫描Mapper的路径
可以通过三种方式
1.配置MapperScannerConfigurer
<bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.XXX.XXX"/>
</bean>
2.配置<scan>标签
<context:component-scan base-package="com.XXX.XXX">
3.使用@MapperScan注解
原理分析
创建会话工厂
先看下前面配置的SqlSessionFactoryBean,其类图如下所示
其中的InitializingBean有一个待实现方法afterPropertiesSet,会在初始化Bean之后调用,这里就是在afterPropertiesSet实现方法里创建了SqlSessionFactory对象
//在Bean初始化之后调用
public void afterPropertiesSet() throws Exception {
Assert.notNull(this.dataSource, "Property 'dataSource' is required");
Assert.notNull(this.sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
Assert.state(this.configuration == null && this.configLocation == null || this.configuration == null || this.configLocation == null, "Property 'configuration' and 'configLocation' can not specified with together");
//创建SqlSessionFactory
this.sqlSessionFactory = this.buildSqlSessionFactory();
}
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
XMLConfigBuilder xmlConfigBuilder = null;
Configuration targetConfiguration;
//如果configuration已经存在,则把当前Bean的Properties属性也加入到Configuration
if (this.configuration != null) {
targetConfiguration = this.configuration;
if (targetConfiguration.getVariables() == null) {
targetConfiguration.setVariables(this.configurationProperties);
} else if (this.configurationProperties != null) {
targetConfiguration.getVariables().putAll(this.configurationProperties);
}
//如果configuration不存在,但是存在configLocation,则使用XmlConfigBuilder解析对应的配置文件
} else if (this.configLocation != null) {
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), (String)null, this.configurationProperties);
targetConfiguration = xmlConfigBuilder.getConfiguration();
} else {
//如果Configuration对象不存在,configLocation路径也没有,则使用默认的configurationProperties给configuration赋值
LOGGER.debug(() -> {
return "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration";
});
targetConfiguration = new Configuration();
Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);
}
//基于当前factoryBean里已有的属性,对targetConfiguration对象里面的属性进行赋值
Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);
Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);
Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);
String[] typeHandlersPackageArray;
if (StringUtils.hasLength(this.typeAliasesPackage)) {
typeHandlersPackageArray = StringUtils.tokenizeToStringArray(this.typeAliasesPackage, ",; \t\n");
Stream.of(typeHandlersPackageArray).forEach((packageToScan) -> {
targetConfiguration.getTypeAliasRegistry().registerAliases(packageToScan, this.typeAliasesSuperType == null ? Object.class : this.typeAliasesSuperType);
LOGGER.debug(() -> {
return "Scanned package: '" + packageToScan + "' for aliases";
});
});
}
if (!ObjectUtils.isEmpty(this.typeAliases)) {
Stream.of(this.typeAliases).forEach((typeAlias) -> {
targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
LOGGER.debug(() -> {
return "Registered type alias: '" + typeAlias + "'";
});
});
}
if (!ObjectUtils.isEmpty(this.plugins)) {
Stream.of(this.plugins).forEach((plugin) -> {
targetConfiguration.addInterceptor(plugin);
LOGGER.debug(() -> {
return "Registered plugin: '" + plugin + "'";
});
});
}
if (StringUtils.hasLength(this.typeHandlersPackage)) {
typeHandlersPackageArray = StringUtils.tokenizeToStringArray(this.typeHandlersPackage, ",; \t\n");
Stream.of(typeHandlersPackageArray).forEach((packageToScan) -> {
targetConfiguration.getTypeHandlerRegistry().register(packageToScan);
LOGGER.debug(() -> {
return "Scanned package: '" + packageToScan + "' for type handlers";
});
});
}
if (!ObjectUtils.isEmpty(this.typeHandlers)) {
Stream.of(this.typeHandlers).forEach((typeHandler) -> {
targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
LOGGER.debug(() -> {
return "Registered type handler: '" + typeHandler + "'";
});
});
}
if (this.databaseIdProvider != null) {
try {
targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
} catch (SQLException var23) {
throw new NestedIOException("Failed getting a databaseId", var23);
}
}
Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);
//如果XMLConfigBuilder不为空,就调用parse方法,这跟MyBatis里调用的一样
if (xmlConfigBuilder != null) {
try {
xmlConfigBuilder.parse();
LOGGER.debug(() -> {
return "Parsed configuration file: '" + this.configLocation + "'";
});
} catch (Exception var21) {
throw new NestedIOException("Failed to parse config resource: " + this.configLocation, var21);
} finally {
ErrorContext.instance().reset();
}
}
//创建environment,事务工厂(默认使用SpringManagedTransactionFactory)
targetConfiguration.setEnvironment(new Environment(this.environment, (TransactionFactory)(this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory), this.dataSource));
//根据配置的MapperLocation 使用XMLMapperBuilder解析mapper.xml,把接口和对应的MapperProxyFactory注册到MapperRegistry 中。
if (!ObjectUtils.isEmpty(this.mapperLocations)) {
Resource[] var24 = this.mapperLocations;
int var4 = var24.length;
for(int var5 = 0; var5 < var4; ++var5) {
Resource mapperLocation = var24[var5];
if (mapperLocation != null) {
try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (Exception var19) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", var19);
} finally {
ErrorContext.instance().reset();
}
LOGGER.debug(() -> {
return "Parsed mapper file: '" + mapperLocation + "'";
});
}
}
} else {
LOGGER.debug(() -> {
return "Property 'mapperLocations' was not specified or no matching resources found";
});
}
//使用SqlSessionFactoryBuilder的build方法构建SqlSessionFactory
return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}
创建SqlSession
Spring在创建SqlSession的时候不是直接使用MyBatis的DefaultSqlSession,而是自己封装了一个SqlSessionTemplate,这是因为DefaultSqlSession不是线程安全的,所以Spring特性封装了一个线程安全的SqlSessionTemplate(线程安全问题在web场景下不可避免)
SqlSessionTemplate里创建的SqlSession是使用的jdk动态代理,所有方法的调用实际都是通过这个proxy来调用的
private final SqlSessionFactory sqlSessionFactory;
private final ExecutorType executorType;
private final SqlSession sqlSessionProxy;
private final PersistenceExceptionTranslator exceptionTranslator;
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
}
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
this(sqlSessionFactory, executorType, new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));
}
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
Assert.notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
Assert.notNull(executorType, "Property 'executorType' is required");
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
//通过JDK动态代理创建代理对象
this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionTemplate.SqlSessionInterceptor());
}
//创建代理对象的InvocationHandler
private class SqlSessionInterceptor implements InvocationHandler {
private SqlSessionInterceptor() {
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//获得SqlSession对象,commit和close都是调用的该对象的方法,猜测SqlSessionTemplate线程安全也是因为这段代码
SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
Object unwrapped;
try {
//调用目标对象方法,实际调用了DefaultSqlSession的方法
Object result = method.invoke(sqlSession, args);
if (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
sqlSession.commit(true);
}
unwrapped = result;
} catch (Throwable var11) {
unwrapped = ExceptionUtil.unwrapThrowable(var11);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException)unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw (Throwable)unwrapped;
} finally {
if (sqlSession != null) {
SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
return unwrapped;
}
}
这一段分析线程安全的原因后面补充
知道Spring中是使用SqlSessionTemplate来保证线程安全的,那么我们怎么获取这个Template并使用呢?在spring的早期版本,想要使用SqlSessionTemplate,需要让我们的dao类去继承SqlSessionDaoSupport,它持有一个SqlSessionTemplate 对象,并提供了getSqlSession的方法
public abstract class SqlSessionDaoSupport extends DaoSupport {
private SqlSessionTemplate sqlSessionTemplate;
public SqlSessionDaoSupport() { }
//在继承SqlSessionDaoSupport的时候需要调用setSqlSessionFactory把SqlSessionFactory注入(也可以通过xml注入)
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {
this.sqlSessionTemplate = this.createSqlSessionTemplate(sqlSessionFactory);
}
}
protected SqlSessionTemplate createSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
public final SqlSessionFactory getSqlSessionFactory() {
return this.sqlSessionTemplate != null ? this.sqlSessionTemplate.getSqlSessionFactory() : null;
}
//对外提供的获取SqlSessionTemplate的方法(SqlSessionTemplate也实现了SqlSession接口)
public SqlSession getSqlSession() {
return this.sqlSessionTemplate;
}
...
}
但是这样一来,我们的DAO层的接口,如果要操作数据库,就都需要实现SqlSessionDaoSupport去拿到一个SqlSessionTemplate
目前的spring早已经优化了使用SqlSessionTemplate的方式,可以直接使用@Autowired 在Service层自动注入的 Mapper 接口
接口的扫描注册
这些Mapper就是在applicationContext.xml配置MapperScannerConfigurer时注册上的
<bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.XXX.XXX"/>
</bean>
MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor(该接口就是用于在BeanDefinition注册后做一些后置处理,比放说修改BeanDefinition等)接口的postProcessBeanDefinitionRegistry方法
//MapperScannerConfigurer
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
this.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();
//扫描包路径,注册Mapper
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));
}
-----scanner.scan最后会调用ClassPathMapperScanner的doScan方法
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
LOGGER.warn(() -> {
return "No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.";
});
} else {
//对BeanDefinitions进行处理的方法
this.processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
Iterator var3 = beanDefinitions.iterator();
while(var3.hasNext()) {
BeanDefinitionHolder holder = (BeanDefinitionHolder)var3.next();
GenericBeanDefinition definition = (GenericBeanDefinition)holder.getBeanDefinition();
String beanClassName = definition.getBeanClassName();
LOGGER.debug(() -> {
return "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName + "' mapperInterface";
});
//the mapper interface is the original class of the bean
//but the actual class of the bean is MapperFactoryBean
//构造方法参数添加为当前接口的类名 definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
definition.setBeanClass(this.mapperFactoryBean.getClass());
definition.getPropertyValues().add("addToConfig", this.addToConfig);
boolean explicitFactoryUsed = false;
//向BeanDefinition的属性加入SqlSessionFactory和sqlSessionTemplate
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) {
LOGGER.warn(() -> {
return "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) {
LOGGER.warn(() -> {
return "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.";
});
}
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
}
if (!explicitFactoryUsed) {
LOGGER.debug(() -> {
return "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.";
});
//表示当前bean会自动填充setter方法的属性,容器中配置了SqlSessionFactory的bean,所以会调用setSqlSessionFactory()方法来注入属性。
definition.setAutowireMode(2);
}
}
}
从上面可以看到,我们扫描到的Mapper会把其BeanDefinition的BeanClass设置成MapperFactoryBean,所以在实例化的时候实际生成的也是MapperFactoryBean对象,而我们的MapperFactoryBean就继承了SqlSessionDaoSupport,而前面我们又向BeanDefinition里添加了SqlSessionTemplate和SqlSessionFactory的属性,所以生成的MapperFactoryBean实际就是一个继承了SqlSessionDaoSupport并且注入了SqlSessionTemplate和SqlSessionFactory的对象
接口注入的使用
前面说了,目前spring在使用Mapper的时候,直接在加了Service 注解的类里面使用@Autowired注入Mapper接口就好了
@Service
public class EmployeeService {
@Autowired
EmployeeMapper employeeMapper;
//按id查询
public Employee getEmp(Integer id) {
Employee employee = employeeMapper.selectByPrimaryKey(id);
return employee;
}
}
spring在启动的时候会去实例化EmployeeService,而EmployeeService里又需要注入EmployeeMapper对象,
Spring会根据Mapper的名字从BeanFactory 中获取它的BeanDefination,再从BeanDefination 中 获 取 BeanClass ,此时其BeanClass已经被替换成MapperFactoryBean了
接下来就只需要创建MapperFactoryBean实例对象就可以了,因为MapperFactoryBean实现了FactoryBean接口,所以会通过getObject()来获取对应的实例对象
//实际调用的是SqlSession(这里的返回的对象是SqlSessionTemplate)的getMapper方法
public T getObject() throws Exception {
return this.getSqlSession().getMapper(this.mapperInterface);
}
----SqlSessionTemplate 这里调用了Configuration的getMapper
public <T> T getMapper(Class<T> type) {
return this.getConfiguration().getMapper(type, this);
}
----Configuration 最终是通过configuration的mapperRegistry获得对应的Mapper,实际拿到的是
通过工厂类MapperProxyFactory获得的用MapperProxy增强的代理对象
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return this.mapperRegistry.getMapper(type, sqlSession);
}
总结一下:
关键类 | 功能 |
SqlSessionTemplate | Spring 中 线程安全的SqlSession实现类,通过代理的方式调用 DefaultSqlSession 的方法 |
SqlSessionInterceptor(内部类) | 定义在在 SqlSessionTemplate ,用来增强代理 DefaultSqlSession |
SqlSessionDaoSupport | 用于获取 SqlSessionTemplate,需要继承它并且注入SqlSessionFactory |
MapperFactoryBean | 注册到 IOC 容器中的Mapper接口替换类,继承了 SqlSessionDaoSupport 可以用来获取SqlSessionTemplate,注入接口的时候,会调用用它的 getObject()方法 最终调用Configuration的getMapper方法 |
SqlSessionHolder | 控制 SqlSession 和事务 |