MyabtisPlus在启动阶段主要完成读取配置文件,解析xml文件,生成单表CURD的SQL,实例化Mapper等操作。
本篇含有大量SpringIoC的知识,对于不明白的记住结论即可。
关于druid数据库连接池,点击此处详细说明。
DataSource
yml配置:
datasource:
url: jdbc:mysql://localhost:3306/bbs?useUnicode=true&useSSL=false&allowMultiQueries=true&autoReconnect=true&serverTimezone=Asia/Shanghai
username: root
password: root
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
druid:
# druid连接池属性
......
实例化任意mapper类的时候,实例化的是MapperFactoryBean,其BeanDefinition的autowireMode属性为AUTOWIRE_BY_TYPE,需要实例化父类SqlSessionDaoSupport下的SqlSessionTemplate和SqlSessionFactory(关于为何Mapper类实例化的是MapperFactoryBean,可以点击此处:@MapperScan的作用)。
查看SqlSessionFactory的BeanDefinition,得知其定义类为MybatisPlusAutoConfiguration,它有如下注解:
类MybatisPlusAutoConfiguration注解
SqlSessionFactory、SqlSessionFactoryBean由Mybatis的jar包引入,@ConditionalOnSingleCandidate(DataSource.class)是条件注解,获取IoC容器中实现DataSource的bean,只有一个或多个时只有一个@Primary修饰的bean才算符合
@EnableConfigurationProperties(MybatisPlusProperties.class)将配置文件前缀为mybatis-plus的属性绑定到MybatisPlusProperties对象,@AutoConfigureAfter(DataSourceAutoConfiguration.class)说明它在DataSourceAutoConfiguration之后加载。
于是查看DataSourceAutoConfiguration,它有如下注解:
类DataSourceAutoConfiguratio注解
@EnableConfigurationProperties(DataSourceProperties.class)将配置文件前缀为spring.datasource的属性绑定到DataSourceProperties对象。
查看SqlSessionFactory,它的条件如下:
MybatisPlusAutoConfiguration.SqlSessionFactory注解
@ConditionalOnMissingBean说明我们没有配置SqlSessionFactory的时候,才会实例化。它的构造方法需要实例化过的DataSource类。
因为我们引入了druid的jar包,所以会自动加载DruidDataSourceAutoConfigure类,它定义了数据源的类型为DruidDataSourceWrapper,@Bean(initMethod = "init")在初始化的时候将会执行init()方法,点开它,发现它将配置文件前缀为spring.datasource.druid的属性绑定到自身。因为实现了InitializingBean接口,所以会执行afterPropertiesSet方法,将DataSourceProperties相关属性绑定到自身。
DruidDataSourceAutoConfigure.DataSource注解
DruidDataSource->init():
createAndLogThread();
// 创建一个守护线程,用于数据库资源连接
createAndStartCreatorThread();
// 创建一个守护线程,用于数据库资源回收
createAndStartDestroyThread();
// 等待上面两个线程执行countDown()方法
initedLatch.await();
// 省略处没有出错的话,将会在控制台打印{dataSource-1} inited
......
createAndStartCreatorThread()方法调用后,启动CreateConnectionThread线程,它用于MySQL连接,默认属性第一次进入empty.await()阻塞住,等待其他线程调用empty.signal()唤醒。主线程走完,DruidDataSourceWrapper实例化完成,实质上没有数据库连接。
DruidDataSource->CreateConnectionThread->run():
initedLatch.countDown();
for (;;) {
// 必须存在线程等待,才创建连接
if (poolingCount >= notEmptyWaitThreadCount
&& !(keepAlive && activeCount + poolingCount < minIdle)) {
// 走到这里,直接阻塞了
empty.await();
}
}
SqlSessionFactory与SqlSessionTemplate
DataSource实例化完毕,开始实例化SqlSessionFactory
MybatisPlusAutoConfiguration->sqlSessionFactory():
MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
// 设置数据源、配置类、xml文件、globalConfig等信息
// 如果使用了实现了自定义的注入填充器、主键生成器、sql注入器,则使用自定义的
......
// 创建SqlSessionFactory
return factory.getObject();
MybatisSqlSessionFactoryBean->getObject():
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
MybatisSqlSessionFactoryBean->afterPropertiesSet():
this.sqlSessionFactory = buildSqlSessionFactory();
MybatisSqlSessionFactoryBean->buildSqlSessionFactory():
// 遍历xml文件
for (Resource mapperLocation : this.mapperLocations) {
try {
// 读取文件流
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
// 首先解析xml里<mapper></mapper>标签里的内容,比如<select></select>、<update></update>等标签内容
// 然后解析mapper.java里使用四大注解标记的sql语句
// 最后使用sql注入器注入默认语句
xmlMapperBuilder.parse(); // --1
}
}
......
// 发现sqlSessionFactory就是DefaultSqlSessionFactory,它封装了configuration对象
final SqlSessionFactory sqlSessionFactory = this.sqlSessionFactoryBuilder.build(targetConfiguration);
if (globalConfig.isBanner()) {
System.out.println(" _ _ |_ _ _|_. ___ _ | _ ");
System.out.println("| | |\\/|_)(_| | |_\\ |_)||_|_\\ ");
System.out.println(" / | ");
System.out.println(" " + MybatisPlusVersion.getVersion() + " ");
}
return sqlSessionFactory;
XMLMapperBuilder->parse(): // --1
if (!configuration.isResourceLoaded(resource)) {
// 解析xml中<mapper></mapper>里的内容,将结果封装成MappedStatement赋到configuration的mappedStatements变量
configurationElement(parser.evalNode("/mapper"));
// 将resource放进configuration的loadedResources中
configuration.addLoadedResource(resource);
// 解析mapper
bindMapperForNamespace();
}
XMLMapperBuilder->bindMapperForNamespace():
// xml文件中<mapper>标签的namespace属性
String namespace = builderAssistant.getCurrentNamespace();
Class<?> boundType = null;
try {
// 使用反射加载
boundType = Resources.classForName(namespace);
}
// boundType是xml对应的mapper类
// configuration的mybatisMapperRegistry的knownMappers是否有mapper类,没有的话,会加入并且解析
if (!configuration.hasMapper(boundType)) {
configuration.addLoadedResource("namespace:" + namespace);
configuration.addMapper(boundType);
}
MybatisConfiguration->addMapper():
mybatisMapperRegistry.addMapper(type);
MybatisMapperRegistry->addMapper():
// knownMappers再一次判断,如果之前注入,直接返回
......
parser.parse();
MybatisMapperAnnotationBuilder->parse():
// 解析xml,因为之前解析过了,这里跳过
loadXmlResource();
......
Method[] methods = type.getMethods();
for (Method method : methods) {
// 遍历mapper类里的方法,如果有注解,将解析的信息封装成MappedStatement,并赋到configuration的mappedStatements变量
if (!method.isBridge()) {
// 只有@Select、@Insert、@Update、@Delete才会解析
parseStatement(method);
SqlParserHelper.initSqlParserInfoCache(typeName, method);
}
}
// 如果这个类是Mapper类的子类的话,会使用Sql注入器注入,这里选择的是默认的Sql注入器
if (GlobalConfigUtils.isSupperMapperChildren(configuration, type)) {
GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type);
}
AbstractSqlInjector->inspectInject():
Class<?> modelClass = extractModelClass(mapperClass);
// DefaultSqlInjector注入器加入了selectById,insert,deleteById等方法
List<AbstractMethod> methodList = this.getMethodList(mapperClass);
if (CollectionUtils.isNotEmpty(methodList)) {
// tableInfo记录了bean的反射类、表名、主键填充类型、主键字段、字段与数据库对应字段等信息
// fieldList是除了主键字段的其他字段
// 使用了相当多的注解,如@TableName、@TableId、@TableField、@TableLogic等
TableInfo tableInfo = TableInfoHelper.initTableInfo(builderAssistant, modelClass);
// 循环注入自定义方法,主要是将已经定义好的sql语句模板根据TableInfo拼接成sql语句
methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo));
}
SqlSessionFactory实例化完毕,开始实例化SqlSessionTemplate。
定义方法位于MybatisPlusAutoConfiguration->sqlSessionTemplate(),最终执行构造方法。
SqlSessionTemplate->SqlSessionTemplate():
// 可见,当执行SqlSession的方法时,会进入到SqlSessionInterceptor.invoke()方法中
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class }, new SqlSessionInterceptor());
MapperFactoryBean
依赖注入完毕后,在初始化的过程中,它的父类SqlSessionDaoSupport的父类DaoSupport实现了InitializingBean接口。
DaoSupport->afterPropertiesSet():
checkDaoConfig();
MapperFactoryBean->checkDaoConfig():
// 实例化SqlSessionFactory的时候,如果有xml,会顺便添加java类和自动注入的SQL语句
// 如果java类没有对应的.xml文件,在这边仍然会注入
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
configuration.addMapper(this.mapperInterface);
}
MapperFactoryBean是FactoryBean的实现类,所以实例化完毕后,会调用getObject()方法。
MapperFactoryBean->getObject():
return getSqlSession().getMapper(this.mapperInterface);
SqlSessionTemplate->getMapper():
return getConfiguration().getMapper(type, this);
MybatisConfiguration->getMapper():
return mybatisMapperRegistry.getMapper(type, sqlSession);
MybatisMapperRegistry->getMapper():
return mapperProxyFactory.newInstance(sqlSession);
MybatisMapperProxyFactory->newInstance():
final MybatisMapperProxy<T> mapperProxy = new MybatisMapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
MybatisMapperProxyFactory->newInstance():
// mapperProxy固定为MybatisMapperProxy
// 可见,实例化Mapper方法,返回的是动态代理,执行mapper方法时,会进入MybatisMapperProxy.invoke()方法中
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);