/**
* <pre>
* 总结 非Spring环境下,配置mybatis-config配置文件
* <mappers>
* <package name="luck.mybatis.mapper"/>
* <mapper class="luck.mybatis.mapper.UsersMapper"/>
* <mapper resource="mapper/PermissionsMapper.xml"/>
* </mappers>
*
*
* 逻辑1. 当配置为class/package: 则将指定Mapper接口或者指定包名下的Mapper接口进行解析
* 1.1. 最终会调用到{@link Configuration#addMapper(Class)}注册Mapper
* 1.1.1. 首先会将Mapper接口中的所有方法进行一一解析,因为Mapper接口中,可以使用注解,也可以使用mapper.xml进行SQL映射
* 会通过{@link MapperAnnotationBuilder}来解析Mapper接口,只处理可以书写{@link MapperAnnotationBuilder#statementAnnotationTypes}SQL相关的注解
*
* 1.1.2. 再通过{@link MapperAnnotationBuilder#parse()}对Mapper接口开始解析,加载Mapper接口的同时会标记该接口已经解析,防止重复加载,因为下面逻辑又会触发Mapper接口加载
* 接着又会同时加载该Mapper接口路径下+.xml的映射文件,如果存在该mapper.xml文件,会通过{@link XMLMapperBuilder}这个MapperXML解析器来解析该Mapper.xml文件
* 最终调用{@link XMLMapperBuilder#parse()}解析Mapper.xml,在这个过程中,会获取Mapper.xml中的命名空间进行分析
* 如果Mapper.xml中写的命名空间是一个全限定名并且是接口,该接口会当做Mapper接口来处理(又回到了1.1这一步),如果不符合这个条件,则会忽略.
* 在解析完.xml之后,会添加一个标识,该xml已经被解析
*
* 1.1.3. 这样一来我们可以发现,在注册Mapper的时候,处理Mapper接口的时候,会自动处理Mapper接口路径下的Mapper.xml映射文件
* 在解析Mapper.xml的时候,又会通过Mapper.xml中的命名空间来注册Mapper接口,完成了相互注册逻辑
*
*
* 逻辑2 当配置的是resource,url: 则将指定的mapper.xml文件进行解析
* 2.1. 会调用{@link XMLMapperBuilder#parse()}解析Mapper.xml,在这个过程中,会添加一个标识,标识该xml已经被解析,同时会获取Mapper.xml中的命名空间进行分析
* 如果Mapper.xml中写的命名空间是一个全限定名并且是接口,该接口会当做Mapper接口来处理(调用到{@link Configuration#addMapper(Class)}注册Mapper)
* 接下来的逻辑就和{@see 1.1}完全一样
*
*
* Spring-SpringBoot环境下
* 可以通过{@link Demo#sqlSessionFactory}来配置Mapper.xml的路径(如果不配置,那么就是按照Mybatis的固定规则,解析Mapper接口下的Mapper.xml文件,我们只需要扫描Mapper接口就行
* 我们可以通过PathMatchingResourcePatternResolver来对我们自己设置的mapper的路径进行解析,会解析到所有的Mapper.xml文件
* 解析到mapper.xml文件之后,就回到了Mybatis原生的XML解析步骤{@see 逻辑2}
*
* </pre>
*/
public class Demo {
@Bean
public SqlSessionFactoryBean sqlSessionFactory() throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
// 通过解析器,解析该路径下的所有资源文件
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
sessionFactory.setMapperLocations(resolver.getResources("classpath*:mapper/*.xml"));
return sessionFactory
}
}
// Spring环境
// 保留Mapper和Mapper接口是如何解析的关键步骤
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ContextRefreshedEvent> {
// mybatis全局配置文件
private Resource configLocation;
// 所有mapper.xml的路径
private Resource[] mapperLocations;
@Override
public void afterPropertiesSet() throws Exception {
// 初始化sqlSessionFactory
this.sqlSessionFactory = this.buildSqlSessionFactory();
}
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
// mybatis的核心配置类对象,因为创建Configuration的方式有很多
final Configuration targetConfiguration;
XMLConfigBuilder xmlConfigBuilder = null;
// 设置了自定义的Configuration对象
if (this.configuration != null) {
targetConfiguration = this.configuration;
}
// 指定了mybatis全局配置文件,通过配置文件生成Configuration对象
else if (this.configLocation != null) {
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
targetConfiguration = xmlConfigBuilder.getConfiguration();
}
// 其他情况,直接创建Configuration对象
else {
targetConfiguration = new Configuration();
}
// 如果使用的是Mybatis的配置文件创建Configuration对象
if (xmlConfigBuilder != null) {
// 解析XML配置文件
xmlConfigBuilder.parse();
}
// 如果指定了mapper.xml的路径
if (this.mapperLocations != null) {
// 遍历所有的mapper.xml文件
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
// 使用Xml的mapper解析器对该xml进行解析
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
xmlMapperBuilder.parse();
}
return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}
}
}
// Mapper.xml构建器,解析器
public class XMLMapperBuilder extends BaseBuilder {
// 配置类信息
public final Configuration configuration;
// XML解析器
public final XPathParser parser;
// Mapper.xml/Mapper接口构造者的辅助类
public final MapperBuilderAssistant builderAssistant;
// SQL片段
public final Map<String, XNode> sqlFragments;
// 该Mapper的资源文件
public final String resource;
// 解析该mapper.xml
public void parse() {
// 配置类中没有加载过该资源文件
if (!configuration.isResourceLoaded(resource)) {
// 解析xml中的所有标签,在这里会解析mapper文件中的所有<insert,select>等等标签,解析为statement信息
configurationElement(parser.evalNode("/mapper"));
// 标识该mapper.xml已经被解析过
configuration.addLoadedResource(resource);
// 通过xml中写的命名空间来注册Mapper接口(如果存在的命名空间,并且该命名空间是Mapper接口的情况)
this.bindMapperForNamespace();
}
}
// 解析mapper文件中的所有标签
public void configurationElement(XNode context) {
try {
// 获取命名空间
String namespace = context.getStringAttribute("namespace");
// 不存在抛出异常
if (namespace == null || namespace.isEmpty()) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
// 设置命名空间
builderAssistant.setCurrentNamespace(namespace);
// 解析cacheRef标签
cacheRefElement(context.evalNode("cache-ref"));
// 解析cache标签
cacheElement(context.evalNode("cache"));
// 解析parameterMap标签
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
// 解析resultMap标签
resultMapElements(context.evalNodes("/mapper/resultMap"));
// 解析sql标签
sqlElement(context.evalNodes("/mapper/sql"));
// 根据<insert,select,delete,update>标签构建对应的statement对象,保存到Configuration对象中
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
// 根据<insert,select,delete,update>标签构建对应的statement对象,并保存到Configuration类中
public void buildStatementFromContext(List<XNode> list) {
// 如果设置了使用指定类型的数据库
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
// 创建<insert,select,delete,update>元素的映射语句信息对应的Statement对象
public void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
// 遍历所有的select|insert|delete|update标签
for (XNode context : list) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
// 解析该select|insert|delete|update标签标签,并将statement保存到Configuration对象中
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
// 如果出现异常,先将该标签保存起来,标记该标签未解析完成
configuration.addIncompleteStatement(statementParser);
}
}
}
// 通过xml中写的命名空间来注册Mapper接口(如果存在的命名空间,并且该命名空间是Mapper接口的情况)
public void bindMapperForNamespace() {
// 获取当前命名空间
String namespace = builderAssistant.getCurrentNamespace();
// 如果存在命名空间
if (namespace != null) {
Class<?> boundType = null;
try {
// 获取命名空间指定的Mapper接口
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
// 如果不是Mapper接口,忽略
}
// 如果是Mapper接口,并且配置类中没有注册该Mapper
if (boundType != null && !configuration.hasMapper(boundType)) {
// 将这个命名空间进行标记,标记为已经加载过了,相同的命名空间不需要重复加载
configuration.addLoadedResource("namespace:" + namespace);
/**
* 注册该Mapper接口,内部同时也会做一件事,就是按照mybatis的固定规则,使用Mapper接口的类名+.xml来找该Mapper接口的XML映射文件
* 如果找到了,则会加载该XML配置文件,将该配置文件中的所有<insert,select>标签解析为Statement
* {@link MapperAnnotationBuilder#parse()}
* {@link MapperAnnotationBuilder#loadXmlResource()}
*/
configuration.addMapper(boundType);
}
}
}
}
public class Configuration {
// 添加Mapper接口,同时会解析Mappe接口中的注解信息,因为@Select...和<select>...效果一样,都要保存该方法对应的statement
public <T> void addMapper(Class<T> type) {
// 只处理接口
if (type.isInterface()) {
// 如果已经存在,抛出异常
if (this.hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// 注册mapper以及给定生成该mapper代理对象的工厂
knownMappers.put(type, new MapperProxyFactory<>(type));
// 解析该接口的所有方法
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
// 解析mapper接口的所有方法
parser.parse();
// 该接口加载完成
loadCompleted = true;
} finally {
// 如果没有加载成功,异常添加的代理对象工厂
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
}
/**
* Mapper接口的注解构建器
* 和{@link XMLStatementBuilder}的作用一样
* 该类是处理Mapper接口方法注解的形式
*/
public class MapperAnnotationBuilder {
// 增删查改的注解,类似与xml中的<insert,delete...>标签
public static final Set<Class<? extends Annotation>> statementAnnotationTypes = Stream.of(Select.class, Update.class, Insert.class, Delete.class, SelectProvider.class, UpdateProvider.class, InsertProvider.class, DeleteProvider.class).collect(Collectors.toSet());
// 配置类信息
public final Configuration configuration;
// Mapper.xml/Mapper接口构造者的辅助类
public final MapperBuilderAssistant assistant;
// Mapper接口类型
public final Class<?> type;
// 解析mapper接口的所有方法
public void parse() {
// 当前mapper
String resource = type.toString();
/**
* 如果当前mapper接口没有被加载过,才需要加载
* {@link XMLMapperBuilder#parse()}
* {@link XMLMapperBuilder#bindMapperForNamespace()}
*/
if (!configuration.isResourceLoaded(resource)) {
// 加载Mapper.xml,因为注册和xml可以共存
this.loadXmlResource();
// 标记该mapper被加载过,防止重复加载
configuration.addLoadedResource(resource);
// 遍历所有的方法
for (Method method : type.getMethods()) {
// 方法是否可以有SQL语句
// 不能是桥接方法并且不能是默认方法,才可以写sql
if (!canHaveStatement(method)) {
continue;
}
// 解析Statement,映射语句
parseStatement(method);
}
}
}
// 加载Mapper.xml
public void loadXmlResource() {
// 防止多次加载
if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
// 找到Mapper接口文件下的Mapper.xml文件
String xmlResource = type.getName().replace('.', '/') + ".xml";
InputStream inputStream = type.getResourceAsStream("/" + xmlResource);
if (inputStream == null) {
inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
}
if (inputStream != null) {
// Mapper.xml构建器,解析器
XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
// 解析该Mapper.xml
xmlParser.parse();
}
}
}
// 解析Statement,映射语句
public void parseStatement(Method method) {
// 获取参数类型,如果有参数类型为RowBounds,ResultHandler的话,那么返回ParamMap类型
final Class<?> parameterTypeClass = getParameterType(method);
// 获取方法中的@Lang注解的语言驱动信息
final LanguageDriver languageDriver = getLanguageDriver(method);
// 获取所有增删查改的注解,类似与xml中的<insert,delete...>标签
// 就是从该method中,找到注解列表中指定的注解信息,并包装成AnnotationWrapper对象
Optional<AnnotationWrapper> annotationWrapper = this.getAnnotationWrapper(method, true, statementAnnotationTypes);
// 如果存在列表中的注解信息,就需要解析该注解
annotationWrapper.ifPresent(statementAnnotation -> {
// 构造SQL源,就是通过语言模板对象,解析为指定的SQL源,可以是动态SQl,也可以是静态SQL
final SqlSource sqlSource = this.buildSqlSource(statementAnnotation.getAnnotation(), parameterTypeClass, languageDriver, method);
// 获取SQL类型
final SqlCommandType sqlCommandType = statementAnnotation.getSqlCommandType();
// 获取方法中Options注解的包装信息,如果不存在返回null
final Options options = this.getAnnotationWrapper(method, false, Options.class).map(x -> (Options) x.getAnnotation()).orElse(null);
// 获取该语句的statementID
final String mappedStatementId = type.getName() + "." + method.getName();
// 主键生成器
final KeyGenerator keyGenerator;
// 保存该映射语句的详细信息
assistant.addMappedStatement(mappedStatementId, sqlSource, statementType, sqlCommandType, fetchSize, timeout,
null, parameterTypeClass, resultMapId, getReturnType(method, type), resultSetType, flushCache, useCache,
false, keyGenerator, keyProperty, keyColumn, statementAnnotation.getDatabaseId(), languageDriver,
options != null ? nullOrEmpty(options.resultSets()) : null, statementAnnotation.isDirtySelect());
});
}
}
// mapper.xml中<insert,select,delete,update>元素的映射语句信息的构造者
public class XMLStatementBuilder extends BaseBuilder {
// Mapper.xml/Mapper接口构造者的辅助类
public final MapperBuilderAssistant builderAssistant;
// 当前<select|insert|delete|update>标签的节点
public final XNode context;
// 是否指定了在指定的数据库中执行
public final String requiredDatabaseId;
// 解析该select|insert|delete|update标签标签
public void parseStatementNode() {
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
// 判断该语句是否符合指定数据库的执行条件
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
// 获取标签名称
String nodeName = context.getNode().getNodeName();
// 解析SQL类型
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
// 是否是select标签
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
// 获取标签的属性
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// 将解析好的映射语句保存到配置类中
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap,
parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets, dirtySelect);
}
}