Spring整合Mybaits的步骤
引入依赖
在Spring整合Mybaits的时候需要引入一个中间依赖包mybatis-spring
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.5</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.2</version>
</dependency>
添加spring配置
为了整合mybatis,需要在spring的配置文件中引入mybtis的配置,这些配置通过两个类实现,SqlSessionFactoryBean以及MapperFactoryBean;SqlSessionFactoryBean负责加载mybatis-config文件以及注入数据源,MapperFactoryBean负责加载mapper接口
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
">
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="username" value="root"></property>
<property name="password" value="husj0423"></property>
<property name="url" value="jdbc:mysql://localhost:3306/test"></property>
<property name="initialSize" value="1"></property>
<property name="maxTotal" value="20"></property>
<property name="maxIdle" value="5"></property>
<property name="minIdle" value="2"></property>
</bean>
<bean id="userService" class="com.handerh.spring.test.aop.db.jdbc.UserServiceImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="configLocation" value="sqlmap/mybatis-config.xml"></property>
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="mapper.UserMapper"></property>
<property name="sqlSessionFactory" ref="sqlSessionFactory"></property>
</bean>
</beans>
添加Mybatis配置文件
<?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">
<!-- mybatis的主配置文件 -->
<configuration>
<!-- 指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件 -->
<mappers>
<mapper resource="sqlmap/UserMapper.xml"/>
</mappers>
</configuration>
测试
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring-mabatis.xml");
UserMapper userMapper = (UserMapper) applicationContext.getBean("userMapper");
User user = userMapper.selectByPrimaryKey(6);
}
Spring整合Mybaits源码分析
在Spring与Myabtis整合的配置文件中,配置了两个重要的beanSqlSessionFactoryBean
以及MapperFactoryBean
,接下来主要分析这两个类是如何将mybatis整合到Spring中
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="configLocation" value="sqlmap/mybatis-config.xml"></property>
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="mapper.UserMapper"></property>
<property name="sqlSessionFactory" ref="sqlSessionFactory"></property>
</bean>
SqlSessionFactoryBean
可以看到它实现了两个非常重要的接口:
-
InitializingBean:实现此接口的bean会在初始化时调用afterPropertiesSet方法进行bean的逻辑初始化
-
FactoryBean:一旦某个Bean实现此接口,那么通过getBean获取bean时实际上获取的是此类的getObject返回的实例
SqlSessionFactoryBean的初始化
在实例化SqlSessionFactoryBean会调用afterPropertiesSet方法,在该方法最终会调用buildSqlSessionFactory函数来创建SqlSessionFactory;根据spring配置文件中注入的属性configLocation
构造XMLConfigBuilder并且进行解析。Spring不仅可以通过configLocation的方式整合Mybatis的配置,还可以将Mybatis的配置直接整合到Spring配置文件中进行属性注入,并通过targetConfiguration来承载属性,最终使用sqlSessionFactoryBuilder实例根据解析的configuration创建SqlSessionFactory
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
final Configuration targetConfiguration;
XMLConfigBuilder xmlConfigBuilder = null;
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);
}
} else if (this.configLocation != null) {
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
targetConfiguration = xmlConfigBuilder.getConfiguration();
} else {
targetConfiguration = new Configuration();
Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);
}
Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);
Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);
Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);
// 类别名
if (hasLength(this.typeAliasesPackage)) {
scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream()
.filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface())
.filter(clazz -> !clazz.isMemberClass()).forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);
}
if (!isEmpty(this.typeAliases)) {
Stream.of(this.typeAliases).forEach(typeAlias -> {
targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
});
}
//Mybatis插件,可以修改Mybatis内部的允许规则
if (!isEmpty(this.plugins)) {
Stream.of(this.plugins).forEach(plugin -> {
targetConfiguration.addInterceptor(plugin);
LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
});
}
//定义类型处理器
if (hasLength(this.typeHandlersPackage)) {
scanClasses(this.typeHandlersPackage, TypeHandler.class).stream().filter(clazz -> !clazz.isAnonymousClass())
.filter(clazz -> !clazz.isInterface()).filter(clazz -> !Modifier.isAbstract(clazz.getModifiers()))
.filter(clazz -> ClassUtils.getConstructorIfAvailable(clazz) != null)
.forEach(targetConfiguration.getTypeHandlerRegistry()::register);
}
if (!isEmpty(this.typeHandlers)) {
Stream.of(this.typeHandlers).forEach(typeHandler -> {
targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'");
});
}
if (!isEmpty(this.scriptingLanguageDrivers)) {
Stream.of(this.scriptingLanguageDrivers).forEach(languageDriver -> {
targetConfiguration.getLanguageRegistry().register(languageDriver);
LOGGER.debug(() -> "Registered scripting language driver: '" + languageDriver + "'");
});
}
Optional.ofNullable(this.defaultScriptingLanguageDriver)
.ifPresent(targetConfiguration::setDefaultScriptingLanguage);
if (this.databaseIdProvider != null) {// fix #64 set databaseId before parse mapper xmls
try {
targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
} catch (SQLException e) {
throw new NestedIOException("Failed getting a databaseId", e);
}
}
Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);
if (xmlConfigBuilder != null) {
try {
xmlConfigBuilder.parse();
LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'");
} catch (Exception ex) {
throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
} finally {
ErrorContext.instance().reset();
}
}
// SpringManagedTransactionFactory 事务工厂
targetConfiguration.setEnvironment(new Environment(this.environment,
this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
this.dataSource));
// mapper文件解析
if (this.mapperLocations != null) {
if (this.mapperLocations.length == 0) {
LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
} else {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
}
}
} else {
LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
}
return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}
获取SqlSessionFactoryBean的实例
当通过getBean方法获取对应SqlSessionFactoryBean实例时,其实获取到的是getObject返回的初始化后的SqlSessionFactory
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
MapperFactoryBean
MapperFactoryBean的继承体系如下:
可以看到MapperFactoryBean也实现了InitializingBean以及FactoryBean接口,同样的在通过getBean初始化bean实例的前也会调用afterPropertiesSet方法
afterPropertiesSet校验
public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
// Let abstract subclasses check their configuration.
checkDaoConfig();
// Let concrete implementations initialize themselves.
try {
initDao();
}
catch (Exception ex) {
throw new BeanInitializationException("Initialization of DAO failed", ex);
}
}
这里的checkDaoConfig()方法主要时做一个验证,校验mapperInterface这个属性是否有值,同时将mapperInterface放入到Mybatis的MapperRegistry中。
getObject获取mapper实例
afterPropertiesSet方法主要是做了一个校验逻辑,真正获取mapper实例的逻辑在getObject方法中
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
public SqlSession getSqlSession() {
return this.sqlSessionTemplate;
}
看到这里其实就是通过SqlSession来创建Mapper实例了,但是这个SqlSession不是独立使用时的DefaultSqlSession
,而是SqlSessionTemplate
,这个SqlSessionTemplate是什么时候注入的呢,看上面的类图发现SqlSessionTemplate继承了qlSessionDaoSupport
,这个类中有一个属性SqlSessionTemplate,还包含一个set方法setSqlSessionFactory
,这个方法完成了SqlSessionTemplate的创建,如下:
public abstract class SqlSessionDaoSupport extends DaoSupport {
private SqlSessionTemplate sqlSessionTemplate;
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {
this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory);
}
}
}
而我们在Spring配置文件中刚好配置为MapperFactoryBean注入了SqlSessionFactory,通过SqlSessionFactory创建SqlSessionTemplate从而完成mapper实例的创建
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="mapper.UserMapper"></property>
<property name="sqlSessionFactory" ref="sqlSessionFactory"></property>
</bean>
SqlSessionTemplate获取mapper实例
public <T> T getMapper(Class<T> type) {
return getConfiguration().getMapper(type, this);
}
通过获取Mybatis配置来创建mapper实例,最终调用的是MapperRegistry.getMapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
//动态代理创建mapper对象,这里的sqlSession是sqlSessionTemplate
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
}
}
}
mapper代理对象的执行
获取到mapper代理对象之后,就可以执行方法调用了。看一下SqlSessionTemplate中的方法:
public <T> T selectOne(String statement) {
return this.sqlSessionProxy.selectOne(statement);
}
public <T> T selectOne(String statement, Object parameter) {
return this.sqlSessionProxy.selectOne(statement, parameter);
}
public int insert(String statement, Object parameter) {
return this.sqlSessionProxy.insert(statement, parameter);
}
public int update(String statement) {
return this.sqlSessionProxy.update(statement);
}
发现所有的增删改方法都是通过sqlSessionProxy来实现的,这个对象则是在初始化SqlSessionTemplate时生成的一个代理对象:
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class }, new SqlSessionInterceptor());
}
对于JDK动态代理生成的对象方法的调用都说在Invocationhandler中完成的,SqlSessionInterceptor实现了Invocationhandler接口:
private class SqlSessionInterceptor implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 1.获取SqlSession
SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
try {
// 2. 执行目标方法
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
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 {
//3.关闭sqlSession
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
在SqlSessionInterceptor有一个获取SqlSession的操作,来看一下:
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
//获取SqlSessionHolder,如果是同一个事物,Spring会保证在改事物中获取到的SqlSession是同一个
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
//从SqlSession中获取SqlSession
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}
//通过SqlSessionFactory创建DefaultSqlSession
LOGGER.debug(() -> "Creating a new SqlSession");
session = sessionFactory.openSession(executorType);
//如果开启了事物会将该SqlSession注册到SessionHolder中
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
这段代码的主要逻辑就是创建SqlSession来完成mapper的执行,如果加入了Spring的事物管理,则需要保证在同一个事物中获取到的SqlSession是同一个(事物的提交以及回滚),到这里Mybatis与Spring整合的核心逻辑就已经完成了,另外,Spring为了简化Mapper接口的注册,加入了包扫描,减少在Spring配置文件中的mapper配置。
MapperScannerConfigurer
在Spring配置文件加入如下配置,就不需要为每一个Mapper接口再配置MapperFactoryBean了
<bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="mapper"></property>
</bean>
我们来了解一下MapperScannerConfigurer是如何实现的。先看一下它的继承结构:
发现MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor,InitializingBean两个接口,不过MapperScannerConfigurer的afterPropertiesSet方法中没有去做任何的逻辑处理,主要逻辑都是在postProcessBeanDefinitionRegistry方法中实现的(实现了BeanDefinitionRegistryPostProcessor接口),这个接口在容器启动的时候(AbstractApplicationContext.refresh())会被调用:
invokeBeanFactoryPostProcessors(beanFactory);
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
}
看一下MapperScannerConfigurer中postProcessBeanDefinitionRegistry的处理逻辑:
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
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.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
if (StringUtils.hasText(lazyInitialization)) {
scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
}
scanner.registerFilters();
// 对指定路径完成扫描
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
doScan扫描
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
for (String basePackage : basePackages) {
// 扫描basePackage下的java文件
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
// 解析该bean是否包含scope注解 默认为single
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
// 生成bean名称 默认首字母小写
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
//检测常用注解:Primary Lazy
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
// 检测该bean是否已经注册
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
// 如果当前bean需要被代理 则需要进一步处理
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
// bean的注册
beanDefinitions.add(definitionHolder);
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
processBeanDefinitions构造MapperFactoryBean类型的bean
关键代码就下面两行,生成一个MapperFactoryBean类型的Bean,其中的mapperInterface属性,通过构造函数时将代理的beanClassName传入,这样通过包扫描就可以生成许多的MapperFactoryBean类型的Bean,简化Mapper接口的注册
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
String beanClassName = definition.getBeanClassName();
// 构造函数时使用Mapper自身的类,因为MapperFactoryBean中的属性mapperInterface通过构造函数传入,以便进行代理
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
// 实际上该Bean类型为MapperFactoryBean
definition.setBeanClass(this.mapperFactoryBeanClass);
}
}
总结
spring整合mybatis分为两个方面,一个是加载配置以及mapper实例的初始化,这一块主要通过SqlSessionFactoryBean以及MapperFactoryBean来实现,另一方面是执行流程,通过SqlSessionTemplate以及SqlSessionInterceptor实现
- SqlSessionFactoryBean:实现了InitiallizeBean,初始化时执行afterProperties方法,根据spring配置文件中的dataSource以及configLocations创建SqlSessionFactory
- MapperFactoryBean:实现了FactoryBean,在获取bean对象时实际上走的是getObject方法,通过sqlSessionTemplate获取mapper实例
- SqlSessionTemplate:通过实现SqlSessionDaoSupport接口注入SqlSessionFactory对象时,完成SqlSessionTemplate对象的初始化;它在创建mapper对象时还是通过mybatis原生的配置类来完成的。
- SqlSessionInterceptor:mapper实例在执行增删改时最终会调用到SqlSession的增删改方法,也就是SqlSessionTemplate的一些方法,这些方法都是通过sqlSessionProxy的一个代理对象来完成的,这个代理对象通过SqlSessionInterceptor创建,在执行具体的方法时会走到SqlSessionInterceptor的invoke方法中,这个方法中才会创建真正的SqlSession负责执行增删改方法,同时将SqlSession与事务关联,如果是同一个事物,Spring会保证在改事物中获取到的SqlSession是同一个。