MyBatis提供了整合到 Spring Boot 的方案 mybatis-spring-boot-starter,能够让你快速的在 Spring Boot 上面使用 MyBatis,那么我们来看看这个 mybatis-spring-boot-starter 是如何将 MyBatis 集成到 Spring Boot中的。
引入mybatis-spring-boot-starter包。
org.mybatis.spring.boot
mybatis-spring-boot-starter
2.0.0
引入这个包后,spring就会自动将mybatis相关配置注册到容器,然后,我们就可以直接使用mybatis操作数据库了。在此之前我们还需要做以下操作:
在使用之前,需要配置一个数据源,用于连接数据库,这个版本的数据源默认用的是Hikari,在org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration这个自动装配类里面,内置了3种数据源:tomcat-jdbc、Hikari和Dbcp2,以Hikari为例来看下源码:
/**
* Hikari DataSource configuration.
*/
@ConditionalOnClass(HikariDataSource.class)
@ConditionalOnMissingBean(DataSource.class)
@ConditionalOnProperty(name = "spring.datasource.type", havingValue = "com.zaxxer.hikari.HikariDataSource", matchIfMissing = true)
static class Hikari {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.hikari")
public HikariDataSource dataSource(DataSourceProperties properties) {
HikariDataSource dataSource = createDataSource(properties,
HikariDataSource.class);
if (StringUtils.hasText(properties.getName())) {
dataSource.setPoolName(properties.getName());
}
return dataSource;
}
}
满足以上三个条件,就会创建数据源,@ConditionalOnClass(HikariDataSource.class)这个是说类路径下需要有HikariDataSource类,也就是需要引HikariCP包,看下依赖关系:
可以看到mybatis已经把这个包引进来了,所以这个条件是满足的,再看@ConditionalOnMissingBean(DataSource.class)这个条件,没啥好说的,意思就是有了就不创建了,也意味着项目下,只会有一个数据源,那么问题来了,多数据源怎么搞呢?这个后面再说。再看第三个条件@ConditionalOnProperty(name = "spring.datasource.type", havingValue = "com.zaxxer.hikari.HikariDataSource", matchIfMissing = true),这个是说配置文件的数据源类型是com.zaxxer.hikari.HikariDataSource,主要看matchIfMissing=true这个属性,意思就是配置文件没有指定数据源类型,哪就是满足条件,那就是说,你没有指定用哪种数据源的话,哪就默认这个了。
所以,经过以上过程,就可以创建数据源了,还要稍等下,数据库的用户名和密码啥的还没设置,这个是省不了的,数据库相关的属性,都在org.springframework.boot.autoconfigure.jdbc.DataSourceProperties这个类里面,常用的配置如下:
spring:
datasource:
url: "jdbc:mysql://192.168.43.61:3306/cib"
username: icbc
password: icbc
只需要指定这三个就可以了,像driverClassName这些,都可以不指定,spring会分析url,从url里面把mysql解析出来,就自动匹配到mysql驱动了,所以说,spring boot还是做了很多事情,尽量让我们省事,说到mysql,这里还少个mysql驱动的包,需要引入下:
mysql
mysql-connector-java
这里没有写版本,使用的是spring boot指定的版本,需要在pom里面指定spring boot为父pom:
org.springframework.boot
spring-boot-starter-parent
2.1.3.RELEASE
@Mapper
public interface CityMapper {
@Select("SELECT * FROM CITY WHERE state = #{state}")
City findByState(@Param("state") String state);
}
@SpringBootApplication
public class SampleMybatisApplication implements CommandLineRunner {
private final CityMapper cityMapper;
public SampleMybatisApplication(CityMapper cityMapper) {
this.cityMapper = cityMapper;
}
public static void main(String[] args) {
SpringApplication.run(SampleMybatisApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
System.out.println(this.cityMapper.findByState("CA"));
}
}
这就是最简洁的方式了。以上都是通过注解的方式,其中Mapper用注解的方式,在对于稍微复杂的语句就会力不从心并且会显得混乱。所以,如果你需要做很复杂的事情,那么最好使用 XML 来映射语句。还有一点,就是Mapper很多的情况下,每个接口上都写注解,也挺啰嗦,所以也可以指定扫描的包。
这样就需要增加mybatis的一些配置,就不全使用mybatis的默认配置了。
mybatis:
type-aliases-package: com.jverson.dao.entity #类型别名包
mapper-locations: classpath*:mybatis/mapper/*.xml,classpath*:com.jverson.dao/log/*.xml #Mapper的xml文件
config-locations: classpath:mybatis/mybatis-config.xml #mybatis的配置文件
包扫描,可以在启动程序那块,加一句这个注解:@MapperScan("com.jverson.mapper")。
spring boot注册starter包中的bean的机制是读取starter包的spring.factories这个文件,这个文件会指定starter包的自动装配的类:
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration这个就是mybatis的自动装配的类,这个类是一个配置类,并声明了SqlSessionFactory和SqlSessionTemplate这两个bean,从这个类开始,便进入了spring的加载bean过程,我们看下spring的加载过程。
第一步:从xml文件或者注解获取bean的定义信息,并注册到BeanFactory(容器)
第二步:执行BeanFactory的后置处理器,在这一步可以操作bean的定义信息
第三步:注册Bean的后置处理器BeanPostProcessor,这个BeanPostProcessor有两个方法:postProcessBeforeInitialization和postProcessAfterInitialization,这两个方法会分别在每个bean实例初始化(afterPropertiesSet或init方法)前和后调用,Spring容器通过BeanPostProcessor给了我们一个机会对Spring管理的bean进行再加工。可以修改bean的属性,可以给bean生成一个动态代理实例等等。一些Spring AOP的底层处理也是通过实现BeanPostProcessor来执行代理包装逻辑的。
第四步:创建所有的 singletons bean(lazy-init 的除外),包括bean实例化、填充属性和初始化
以上是大概步骤,具体详细会有很多细节,可以参考
spring-mvc源码-bean定义加载_matt8的专栏-CSDN博客https://blog.csdn.net/matt8/article/details/106352083?spm=1001.2014.3001.5501
先看下这两个bean是干嘛的。
SqlSessionFactory是单个数据库映射关系经过编译后的内存镜像,主要作用是创建SqlSession。SqlSessionFactory是线程安全的,一旦被创建,在整个应用程序执行期间都会存在。创建SqlSessionFactory很消耗数据库资源,如果多次创建同一数据库的SqlSessionFactory,此数据库的资源很容易被耗尽。尽量使一个数据库只对应一个SqlSessionFactory,构建SqlSessionFactory时,通常使用单例模式。
SqlSessionTemplate是用来操作数据库的,提供了增删改查方法,它依赖于SqlSessionFactory,它相当于是一次和数据库的请求,它从SqlSessionFactory里面获取数据库连接。
SqlSessionFactory的创建很简单,SqlSessionTemplate这个创建有个地方要注意下,是这个构造方法创建的。
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());
}
主要看sqlSessionProxy这个字段,这个是数据库的连接对象,所有的增删改查操作都是通过它完成的,这里返回的是一个代理对象,对SqlSession接口一个实现,看下代理的增强逻辑SqlSessionInterceptor()的实现。
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);
}
}
}
}
1、先获取了SqlSession对象,这个对象实际上返回的是DefaultSqlSession对象
2、通过Object result = method.invoke(sqlSession, args)反射执行sqlSession的方法,其实就是执行DefaultSqlSession的方法
3、如果有事务,就执行sqlSession.commit(true),其实也是执行DefaultSqlSession的commit方法
4、关闭数据库连接
其实,这个代理逻辑就是对DefaultSqlSession对象方法的一个包装,最终执行数据库操作的还是DefaultSqlSession这个对象,org.mybatis.spring.SqlSessionTemplate这个类提供了数据库操作的方法,都是通过sqlSessionProxy这个代理对象完成的,这个类也是mapper执行数据库调用的方法,大概是这么一个调用关系。
mapper -> SqlSessionTemplate -> sqlSessionProxy -> DefaultSqlSession
spring容器中有了SqlSessionFactory和SqlSessionTemplate这两个bean后,就可以操作数据库了,可以直接注入SqlSessionTemplate这个bean来操作数据库,但是我们一般都是通过mapper来操作的数据库,SqlSessionTemplate的注入是mybatis帮我们处理了。
mapper都是接口,自然就能想到,mybatis肯定是通过代理的方式,给mapper接口生成了代理对象,实际的操作都是代理对象执行的,也的确如此,这个代理对象的类就是MapperFactoryBean。
mapper装配的入口有两个,一个是在org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration.MapperScannerRegistrarNotFoundConfiguration这里,这个是用于装配@Mapper这个注解的mapper。另一个是用于装配@MapperScan这个注解,这个注解引入了一个mapper扫描注册类,用于注册bean信息到容器。
@org.springframework.context.annotation.Configuration
@Import({ AutoConfiguredMapperScannerRegistrar.class })
@ConditionalOnMissingBean(MapperFactoryBean.class)
public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {
@Override
public void afterPropertiesSet() {
logger.debug("No {} found.", MapperFactoryBean.class.getName());
}
}
看@Import({ AutoConfiguredMapperScannerRegistrar.class }),引入了一个mapper扫描注册类,主要逻辑也在AutoConfiguredMapperScannerRegistrar里。
这个类实现了ImportBeanDefinitionRegistrar这个接口,并实现了其的registerBeanDefinitions方法,这个方法就是用于注册bean信息的,这里注册的是mapper的信息,主要逻辑就是通过org.mybatis.spring.mapper.ClassPathMapperScanner#doScan这个方法扫描项目包下的使用@Mapper的类或接口,然后进行bean信息的注册,核心逻辑在org.mybatis.spring.mapper.ClassPathMapperScanner#processBeanDefinitions在这个方法里,这里说下@MapperScan也是走的这个逻辑。
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
String beanClassName = definition.getBeanClassName();
LOGGER.debug(() -> "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
//设置构造函数的参数,这里的参数值beanClassName为原始类,也就是mapper接口,这里设置了构造函数的参数值,那么后面实例化的时候,会调用带参数的构造函数来new对象,也就是MapperFactoryBean(Class mapperInterface)这个方法,因为最终使用的是代理类,所以最终new的也是代理类
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
//设置当前bean的类为MapperFactoryBean类,我们知道当前其实是一个mapper接口,这么设置后,后面实例化的就是MapperFactoryBean
definition.setBeanClass(this.mapperFactoryBean.getClass());
definition.getPropertyValues().add("addToConfig", this.addToConfig);
boolean explicitFactoryUsed = false;
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
//设置sqlSessionFactory,@MapperScan会走到这个逻辑,类似@MapperScan(basePackages = {"com.dao.mapper"},sqlSessionFactoryRef = "dbSqlSessionFactory"),这里的sqlSessionFactoryBeanName就是sqlSessionFactoryRef的值,因为当前是注册bean信息阶段,所以还没有sqlSessionFactory实例,这里只是设置了名字,通过RuntimeBeanReference来告诉后续实例化时,使用sqlSessionFactoryBeanName这个名字来注入sqlSessionFactory
definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionFactory != null) {
//目前还没看到哪里能走到这个逻辑
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
explicitFactoryUsed = true;
}
//设置sqlSessionTemplate,逻辑与sqlSessionFactory类似
if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
if (explicitFactoryUsed) {
LOGGER.warn(() -> "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(() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
}
if (!explicitFactoryUsed) {
LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
//自动装配的情况下会走到这里,因为前面指定sqlSessionFactory或sqlSessionTemplate的逻辑都不会走,这时候会设置注入的方式为类型注入,注意,这个和Spring注解(@Autowire)注入的方式不是一回事,MapperFactoryBean本身也没有使用任何注解,我们知道MapperFactoryBean有一个sqlSessionTemplate属性需要注入,后续实例化MapperFactoryBean时,在注入ssqlSessionTemplate时,会根据这里设置的类型注入的方式进行注入,这种注入方式会调用setter方法进行注入,也就是会调用org.mybatis.spring.support.SqlSessionDaoSupport#setSqlSessionTemplate这个方法注入,我们知道sqlSessionTemplate在自动配置的情况下,已经默认创建了,所以根据类型找到的就是这个
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
上面的逻辑,里面做了详细的注释,里面提到一点注入方式,这里说下:
spring种5中注入模型
1、AUTOWIRE_NO = 0;(不使用自动注入,spring默认的值,一般使用的 @AutoWired ,@Resource 都是属于No 模式)
2、AUTOWIRE_BY_NAME = 1;(通过名字自动注入)
3、AUTOWIRE_BY_TYPE = 2;(通过类型自动注入)
4、AUTOWIRE_CONSTRUCTOR = 3;(通过构造函数自动注入)
5、AUTOWIRE_AUTODETECT = 4;(已经被标注过时)
注入模型,可以理解为bean注入的一种配置,设置之后,这个bean的所有依赖,都使用这种模式注入,要想指定模式,需要实现ImportBeanDefinitionRegistrar这个接口,和上面mybatis用法一样。
留一个问题,如果既设置了模式,又设置了@AutoWired注解,最后以谁为准?
那么mapper信息,是在spring加载bean的那个环节注册进来的,因为是通过@Import({ AutoConfiguredMapperScannerRegistrar.class })这个注解引进来的,所以也是在spring解析@Import这个注解的时候执行的,那么这个@Import注解是什么时候解析的,是在解析配置类的时候,也就是@Configuration这个注解解析的时候,@Configuration注解是ConfigurationClassPostProcessor解析的。
ConfigurationClassPostProcessor实现了 BeanDefinitionRegistryPostProcessor接口,而 BeanDefinitionRegistryPostProcessor 接口继承了BeanFactoryPostProcessor接口,所以这是一个BeanFactory的后置处理器,BeanFactory的后置处理器是在spring注册完bean信息之后,也就是前面说的spring加载bean的第二步,如果从spring启动类说起的话,整个链路是这样的:
org.springframework.context.support.AbstractApplicationContext#refresh --> org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors --> org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry -->org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions
在这个processConfigBeanDefinitions方法里有一行代码:
this.reader.loadBeanDefinitions(configClasses);
这一句里面继续调org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForConfigurationClass --> org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsFromRegistrars
这个就是最终执行扫描mapper的方法了,看下:
private void loadBeanDefinitionsFromRegistrars(Map registrars) {
registrars.forEach((registrar, metadata) ->
registrar.registerBeanDefinitions(metadata, this.registry));
}
这里执行的就是org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration.AutoConfiguredMapperScannerRegistrar#registerBeanDefinitions这个方法了,这个方法的代码前面已经说了。
spring通过BeanFactory后置处理器的方式,来让第三方可以定制bean信息的注册。
看下@MapperScan注解的定义。
这里也有一个@Import(MapperScannerRegistrar.class)注解,所以和@Mapper注解的机制是一样,执行时机也是一样的, MapperScannerRegistrar这个类同样是实现了ImportBeanDefinitionRegistrar接口,主要看下org.mybatis.spring.annotation.MapperScannerRegistrar#registerBeanDefinitions这个方法,这个方法的主要逻辑在私有的org.mybatis.spring.annotation.MapperScannerRegistrar#registerBeanDefinitions这个方法里。
void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry) {
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
// this check is needed in Spring 3.1
Optional.ofNullable(resourceLoader).ifPresent(scanner::setResourceLoader);
Class extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
if (!Annotation.class.equals(annotationClass)) {
scanner.setAnnotationClass(annotationClass);
}
Class> markerInterface = annoAttrs.getClass("markerInterface");
if (!Class.class.equals(markerInterface)) {
scanner.setMarkerInterface(markerInterface);
}
Class extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
if (!BeanNameGenerator.class.equals(generatorClass)) {
scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
}
Class extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass));
}
scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));
List basePackages = new ArrayList<>();
basePackages.addAll(
Arrays.stream(annoAttrs.getStringArray("value"))
.filter(StringUtils::hasText)
.collect(Collectors.toList()));
basePackages.addAll(
Arrays.stream(annoAttrs.getStringArray("basePackages"))
.filter(StringUtils::hasText)
.collect(Collectors.toList()));
basePackages.addAll(
Arrays.stream(annoAttrs.getClassArray("basePackageClasses"))
.map(ClassUtils::getPackageName)
.collect(Collectors.toList()));
scanner.registerFilters();
scanner.doScan(StringUtils.toStringArray(basePackages));
}
主要就是给扫描器设置一些信息,主要看下:
scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));
这两个信息,我们如下使用@MapperScan注解时,
@MapperScan(basePackages = {"com.dao.mapper"},sqlSessionTemplateRef = "db1sqlSessionTemplate",sqlSessionFactoryRef = "db1SqlSessionFactory")
sqlSessionTemplate和sqlSessionFactory会被设置成我们给的值,后续会把这两个值注入到mapper中,一般用于多数据源的情况。后续扫描器的处理就和@Mapper的注解处理是同一个方法了org.mybatis.spring.mapper.ClassPathMapperScanner#processBeanDefinitions。
@Mapper的注册实现类:org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration.AutoConfiguredMapperScannerRegistrar
@MapperScan的注册实现类:org.mybatis.spring.annotation.MapperScannerRegistrar
这两个实现类都是属于mybatis的,通过实现spring的ImportBeanDefinitionRegistrar的接口(当然还实现了其他接口),与spring整合到一起,融入到spring的管理体系中,这也是spring的精髓所在,spring本身支持在bean生命周期的各个阶段进行扩展和自定义。
通过前一阶段,所有的mapper信息都已经注册到了容器,下一步,就是根据注册的信息实例化mapper了。也就是执行spring加载bean流程的org.springframework.context.support.AbstractApplicationContext#finishBeanFactoryInitialization这个方法,这个方法会调org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons这个方法,对容器中的所有单例的bean进行创建,创建的bean分两种情况,一种是FactoryBean类型的bean,一种是普通bean,因为mapper的代理类MapperFactoryBean实现了FactoryBean接口,所以是FactoryBean类型的bean,核心源码如下:
// Trigger initialization of all non-lazy singleton beans...
for (String beanName : beanNames) {
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
if (isFactoryBean(beanName)) {
// getBean(beanName)获取的是FactoryBean创建的bean实例
// getBean("&"+beanName)获取FactoryBean本身,所以这里获取的是FactoryBean本身
Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
if (bean instanceof FactoryBean) {
final FactoryBean> factory = (FactoryBean>) bean;
boolean isEagerInit;
// 判断是否有代码运行权限
if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
isEagerInit = AccessController.doPrivileged((PrivilegedAction)
((SmartFactoryBean>) factory)::isEagerInit,
getAccessControlContext());
}
else {
// 默认情况 FactoryBean 延迟创建bean
// 但如果是 SmartFactoryBean,而且设置其 eagerInit 值为 true
// 那么就进行提前创建
isEagerInit = (factory instanceof SmartFactoryBean &&
((SmartFactoryBean>) factory).isEagerInit());
}
if (isEagerInit) {
// 进行提前创建
getBean(beanName);
}
}
}
else {
getBean(beanName);
}
}
}
如果是mapper的话,根据代码逻辑,这里不会提前创建,但是FactoryBean本身是会创建的,也就是mapper的代理类MapperFactoryBean是会创建的。创建逻辑就在org.springframework.beans.factory.support.AbstractBeanFactory#getBean这个方法里,最终一路下去会调到org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean,这个是创建bean的核心逻辑,代码比较多,和我们有关就是这一段:
// Create bean instance.
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
destroySingleton(beanName);
throw ex;
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
最核心的是createBean(beanName, mbd, args)是这个方法,bean的实例化、属性填充和初始化等等都是在这里处理的。
实例化:通过有参的构造函数实例化MapperFactoryBean,参数就是mapper接口
属性填充:就是将依赖注入,这里会将sqlSessionTemplate和sqlSessionFactory注入,@MapperScan的情况下,指定了名称,会根据名称注入,默认自动配置的情况下,会根据类型注入
初始化:MapperFactoryBean有自己的逻辑,下面详细说下
看下MapperFactoryBean的继承体系。
MapperFactoryBean的父类DaoSupport实现了InitializingBean接口,那么在MapperFactoryBean创建后会,在初始化阶段会调用org.springframework.dao.support.DaoSupport#afterPropertiesSet这个方法。
public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
this.checkDaoConfig();
try {
this.initDao();
} catch (Exception var2) {
throw new BeanInitializationException("Initialization of DAO failed", var2);
}
}
checkDaoConfig()是一个抽象方法,MapperFactoryBean做了实现:
@Override
protected void checkDaoConfig() {
//检查sqlSessionTemplate不能为空,这里肯定不会为空,在属性填充阶段已经注入了
super.checkDaoConfig();
//检查mapper接口不能为空,这里肯定不会为空,在实例化阶段已经通过有参构造函数传进来了
notNull(this.mapperInterface, "Property 'mapperInterface' is required");
Configuration configuration = getSqlSession().getConfiguration();
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
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();
}
}
}
主要看下configuration.addMapper(this.mapperInterface)这个方法:
public void addMapper(Class type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
//将mapper接口包装成MapperProxyFactory对象保存至map缓存起来,后面mapper注入service时,会从这里获取
knownMappers.put(type, new MapperProxyFactory<>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
再继续看parser.parse()这个方法:
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
// 先加载xml资源,如:UserMapper.xml,parse方法先解析配置文件,然后再解析接口里的注解配置,且注解里的配置会覆盖配置文件里的配置,也就是说注解的优先级高于配置文件
loadXmlResource();
// 添加解析过的映射,下次判断有就不在解析
configuration.addLoadedResource(resource);
// 命名空间
assistant.setCurrentNamespace(type.getName());
// 二级缓存的处理,处理注解@CacheNamespace与@CacheNamespaceRef
parseCache();
parseCacheRef();
//获取mapper的所有方法
Method[] methods = type.getMethods();
for (Method method : methods) {
try {
// issue #237
if (!method.isBridge()) {
// 解析方法,对方法上各种注解的解析,并把解析完的信息,添加到Configuration的mappedStatements中去,在后面执行sql语句时根据方法名取出来调用
parseStatement(method);
}
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
}
至此,MapperFactoryBean的初始化也完事了。回到org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean这个方法,在这个方法里的创建单例bean后,会调用org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#getObjectForBeanInstance,接着调用org.springframework.beans.factory.support.AbstractBeanFactory#getObjectForBeanInstance,这个方法会决定返回MapperFactoryBean本身还是它创建的bean。
protected Object getObjectForBeanInstance(
Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) {
// Don't let calling code try to dereference the factory if the bean isn't a factory.
if (BeanFactoryUtils.isFactoryDereference(name)) {//是否是FactoryBean名字的前缀&
if (beanInstance instanceof NullBean) {
return beanInstance;
}
if (!(beanInstance instanceof FactoryBean)) {//不是FactoryBean的话名字有&会报异常
throw new BeanIsNotAFactoryException(beanName, beanInstance.getClass());
}
if (mbd != null) {
mbd.isFactoryBean = true;
}
//要找的就是FactoryBean自身,直接返回
return beanInstance;
}
// Now we have the bean instance, which may be a normal bean or a FactoryBean.
// If it's a FactoryBean, we use it to create a bean instance, unless the
// caller actually wants a reference to the factory.
//不是FactoryBean就直接返回,也就是普通的bean
if (!(beanInstance instanceof FactoryBean)) {
return beanInstance;
}
//除了以上逻辑,那就剩下最后一种情况了,就是获取的是FactoryBean创建的bean
Object object = null;
if (mbd != null) {
mbd.isFactoryBean = true;
}
else {
//从FactoryBean的缓存中获取
object = getCachedObjectForFactoryBean(beanName);
}
if (object == null) {
// Return bean instance from factory.
FactoryBean> factory = (FactoryBean>) beanInstance;
// Caches object obtained from FactoryBean if it is a singleton.
//mbd没定义,但是FactoryBean是有定义的,获取mbd
if (mbd == null && containsBeanDefinition(beanName)) {
mbd = getMergedLocalBeanDefinition(beanName);
}
boolean synthetic = (mbd != null && mbd.isSynthetic());
object = getObjectFromFactoryBean(factory, beanName, !synthetic);
}
return object;
}
第一段是处理FactoryBean的情况,也就是创建MapperFactoryBean走的逻辑,中间是普通bean的情况,最后一段是获取FactoryBean创建的bean,也就是MapperFactoryBean创建的bean,这个是在需要注入mapper时触发的,下一节说。
一般在sercie里使用mapper:
@Autowired
private MyMapper myMapper;
当创建service时,在属性填充阶段会去获取依赖的mapper进行注入,也就是org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean这个方法里:
// Initialize the bean instance.
Object exposedObject = bean;
try {
populateBean(beanName, mbd, instanceWrapper);
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
catch (Throwable ex) {
if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
throw (BeanCreationException) ex;
}
else {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
}
}
以上截取了这个方法的中间代码段,主要就是在创建完bean后,对bean进行属性填充和初始化,如果当前的bean是依赖mapper的service,那么mapper就是在这个时候通过属性填充这个方法注入进来的,具体方法就是org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean这个方法,逻辑并不复杂,如果属性是bean,最终还是调org.springframework.beans.factory.support.AbstractBeanFactory#getBean方法获取bean,也就是获取mapper,在上一节创建bean的时候,最后一段逻辑是获取FactoryBean创建的bean,也就是mapper的获取属于这种方式,最终调的是object = getObjectFromFactoryBean(factory, beanName, !synthetic);这个方法获取的bean,里面接着调org.springframework.beans.factory.support.FactoryBeanRegistrySupport#doGetObjectFromFactoryBean这个方法,这个方法有这样一句代码:
object = AccessController.doPrivileged((PrivilegedExceptionAction
主要是factory::getObject这个方法,这里的factory就是当前mapper的MapperFactoryBean,也就是调MapperFactoryBean的getObject的方法获取。
链路如下:
org.mybatis.spring.mapper.MapperFactoryBean#getObject -->
org.mybatis.spring.SqlSessionTemplate#getMapper -->
org.apache.ibatis.session.Configuration#getMapper -->
org.apache.ibatis.binding.MapperRegistry#getMapper
看下这个方法的代码:
public T getMapper(Class type, SqlSession sqlSession) {
final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
从knownMappers里面获取MapperProxyFactory,这个是前面创建MapperFactoryBean时设置的,所以这里是一定存在的,不存在会抛出异常。然后调用org.apache.ibatis.binding.MapperProxyFactory#newInstance的这个方法,
protected T newInstance(MapperProxy mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
这里调的是newInstance(SqlSession sqlSession)这个方法,内部又调了newInstance(MapperProxy
有了mapper对象后,就可以执行方法了,像这样:
Food food = myMapper.getById(7L);
因为这里的myMapper是一个代理对象,也就是MapperProxy,当执行getById方法的时候,其实执行的是代理对象的org.apache.ibatis.binding.MapperProxy#invoke方法。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
最终执行的mapperMethod.execute(sqlSession, args)这个方法,其中mapperMethod的也就是方法上的或者配置文件中的sql语句及相关注解之类的信息,这些信息在MapperFactoryBean初始化阶段已经解析好了,都放在了org.apache.ibatis.session.Configuration里了,这里可以直接拿来使用,最终执行mapperMethod.execute(sqlSession, args)这个方法:
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional() &&
(result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
这个方法就是具体执行数据库的操作了,逻辑也并不难,拿查询说吧,查询单条的会执行
result = sqlSession.selectOne(command.getName(), param);
这一句,这里的sqlSession就是SqlSessionTemplate,我们知道,在创建SqlSessionTemplate时,所有的方法执行,也是代理执行的,上面这句,最终会执行到:
@Override
public T selectOne(String statement, Object parameter) {
return this.sqlSessionProxy.selectOne(statement, parameter);
}
这个在说SqlSessionTemplate的时候,也提到这一点了,再往里就是具体数据库操作了,这里就点到为止了。
上面啰嗦了这么多,主要就是从spring的角度来看看,mybatis是如何融入进来的,现在就来做个架构的总结吧。