使用xml形式配置 sqlSesionFactory
<!-- myBatis文件 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
//设置数据源
<property name="dataSource" ref="dataSource" />
//mybatis 的config
<property name="configLocation" value="classpath:mybatis/mybatis-config.xml" />
//要处理的 sql 的xml,上篇文说过
<property name="mapperLocations" value="classpath*:mappers/*/*Mapper.xml" />
</bean>
//springboot 项目中是spingboot帮我们注册的我们无需自己配置
//是在 mybatisAutoConfiguration 中创建的
@Bean
@ConditionalOnMissingBean
// 这个 dataSource 是在 dataSourceConfiguraation 中 @bean 创建的 这里就不介绍了
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
//就是 SqlSessionFactoryBean 创建了 SqlSessionFactory
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
//下面 给 factoryBean 设置属性
factory.setDataSource(dataSource);
factory.setVfs(SpringBootVFS.class);
if (StringUtils.hasText(this.properties.getConfigLocation())) {
factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
}
Configuration configuration = this.properties.getConfiguration();
if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {
configuration = new Configuration();
}
if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
for (ConfigurationCustomizer customizer : this.configurationCustomizers) {
customizer.customize(configuration);
}
}
factory.setConfiguration(configuration);
if (this.properties.getConfigurationProperties() != null) {
factory.setConfigurationProperties(this.properties.getConfigurationProperties());
}
if (!ObjectUtils.isEmpty(this.interceptors)) {
factory.setPlugins(this.interceptors);
}
if (this.databaseIdProvider != null) {
factory.setDatabaseIdProvider(this.databaseIdProvider);
}
if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
}
if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
}
if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
//这些设置属性的 就这个比较的重要,获取到了我们的指定的所有的mapper文件
//这个地方有个 properties 这个是 MybatisProperties
//MybatisProperties类上面有个@ConfigurationProperties(prefix = MybatisProperties.MYBATIS_PREFIX),意思就是
//把配置的参数前缀为MybatisProperties.MYBATIS_PREFIX 绑定到 MybatisProperties类,这个自动绑定有时间单独讲
//这里只要知道获取到了 所有的 mapper文件的路径就行了
factory.setMapperLocations(this.properties.resolveMapperLocations());
}
//这里 . 重点
return factory.getObject();
}
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
//这里
afterPropertiesSet();
}
//最后返回的是 sqlSeeionFactoryBean 的 sqlSessionFactory 属性
return this.sqlSessionFactory;
}
@Override
public void afterPropertiesSet() throws Exception {
notNull(dataSource, "Property 'dataSource' is required");
notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
"Property 'configuration' and 'configLocation' can not specified with together");
//上面说了 返回的是 sqlSessionFactory 这里就赋值了,所以主要看看 buildSqlSessionFactory(); 干了什么
this.sqlSessionFactory = buildSqlSessionFactory();
}
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
Configuration configuration;
XMLConfigBuilder xmlConfigBuilder = null;
..........................................................................................................
if (!isEmpty(this.mapperLocations)) {
//这个地方循环的是我们的 mapper文件
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
//创建 xmlMapperBuilder 看下这个方法
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
configuration, mapperLocation.toString(), configuration.getSqlFragments());
// 去解析
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
}
}
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
}
}
//把 configuration 当做参数 构造 sqlSessionFactory
return this.sqlSessionFactoryBuilder.build(configuration);
}
//
public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
//主要就是干了这么一件事
this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()),
configuration, resource, sqlFragments);
}
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
commonConstructor(validation, variables, entityResolver);
//创建了 document 点进去会发现使用的 是 w3c 的 dom
this.document = createDocument(new InputSource(inputStream));
}
看下相关的结构
xmlMapperBuilder
configuration
XPathParser
document
XPath
MapperBuilderAssistant
configuration
resource
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
//最重要的就是这行代码的两个方法
//先看evalNode 在看 configurationElement
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
public XNode evalNode(String expression) {
return evalNode(document, expression);
}
//获取 mapper 标签 的 document
public XNode evalNode(Object root, String expression) {
//看下这个方法 获取node
Node node = (Node) evaluate(expression, root, XPathConstants.NODE);
if (node == null) {
return null;
}
//封装为 XNode
return new XNode(this, node, variables);
}
private void configurationElement(XNode context) {
try {
//获取 名字属性名为 namespace 也就是我们 mapper 文件的命名空间
String namespace = context.getStringAttribute("namespace");
//命名空间不能为空,否则报错了
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
//给MapperBuilderAssistant属性 传递命名空间的参数
builderAssistant.setCurrentNamespace(namespace);
//是否存在缓存,这里多说点,我做过的项目都没使用过这个缓存
cacheRefElement(context.evalNode("cache-ref"));
//是否引用缓存
cacheElement(context.evalNode("cache"));
//这个基本被废弃了,官网上也是不推荐使用了
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//映射结果集的标签 我们常用的
resultMapElements(context.evalNodes("/mapper/resultMap"));
//获取 sql 标签,也是经常使用的
sqlElement(context.evalNodes("/mapper/sql"));
//context.evalNodes 获取 增删改查的标签,,主要看下buildStatementFromContext方法
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);
}
}
private void buildStatementFromContext(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
//这里
buildStatementFromContext(list, null);
}
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
//循环着去处理每个sql语句
for (XNode context : list) {
//创建构造器,注意下传递的参数
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
//具体操作
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
//这里面的代码有点多
public void parseStatementNode() {
//获取sql 的id
String id = context.getStringAttribute("id");
//数据库厂商标识 一般是用不到的
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
//这是一个给驱动的提示,尝试让驱动程序每次批量返回的结果行数和这个设置值相等 (没啥用)
Integer fetchSize = context.getIntAttribute("fetchSize");
// 这个sql 执行的超时时间
Integer timeout = context.getIntAttribute("timeout");
//当前sql 的 parameterMap 基本被废弃了
String parameterMap = context.getStringAttribute("parameterMap");
//常用的请求参数类型
String parameterType = context.getStringAttribute("parameterType");
//转化为 class 类型
Class<?> parameterTypeClass = resolveClass(parameterType);
//返回值 map 都是常用的
String resultMap = context.getStringAttribute("resultMap");
//resultType
String resultType = context.getStringAttribute("resultType");
//这个我不知道是干啥的
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
// 转 class
Class<?> resultTypeClass = resolveClass(resultType);
//不常用的属性
String resultSetType = context.getStringAttribute("resultSetType");
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
//获取标签名称 select insert update delete
String nodeName = context.getNode().getNodeName();
//转大写
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);
// Include Fragments before parsing
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
//处理 include
includeParser.applyIncludes(context.getNode());
// Parse selectKey after includes and remove them.
//处理 selectKey 说实话 这个标签没咋用过
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
//这个是我们的重点了,获取sql 资源 , 先看下这个方法
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
.............................下面的代码先省略,先看下上面这行代码的流程.......................................
}
@Override
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
//创建 XMLScriptBuilder , 除了创建一个实例出来,最重要是的是添加 不同 标签的 处理器,放在map中
//例如 <where> 的 nodeHandlerMap.put("where", new WhereHandler());
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
//去解析
return builder.parseScriptNode();
}
public SqlSource parseScriptNode() {
//这里 , 这里的话我们先看下 SqlNode 的继承结构 , 然后看下 parseDynamicTags 方法
MixedSqlNode rootSqlNode = parseDynamicTags(context);
SqlSource sqlSource = null;
if (isDynamic) {
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
//封装为 sqlSource
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
// 看到下面的实现了,应该明白每个实现类 很对的是不同的标签
sqlNode
方法
boolean apply(DynamicContext context);
实现类
MixedSqlNode
属性
List<SqlNode> contents
ChooseSqlNode
IfSqlNode
ForEachSqlNode
StaticTextSqlNode
TextSqlNode
TrimSqlNode
SetSqlNode
WhereSqlNode
VarDeclSqlNode
//具体的解析slq , 这里先属性下, 在 sqlSeeisonFactory 初始化的过程中 虽然解析了sql,但是并没有拼接sql
//解析sql 只是把 每个标签 解析为对应的 sqlNode 存放到 SqlSource
//说明下: 这个方法是会被递归调用的,也不是递归调用,就是每个 动态sql的解析 都会多次调用这个方法
protected MixedSqlNode parseDynamicTags(XNode node) {
//首先创建一个 list 存放 sqlNode 这个contents 会一直传递下去
List<SqlNode> contents = new ArrayList<SqlNode>();
//获取node 的所有自孩子,就假如 现在传进来的是 <select>
//那么它的孩子 会出现 textSqlnode,或许还有 WhereSqlNode IfSqlNode,ChooseSqlNode....
//假如是 <where> 孩子中会有 textSqlnode 获取还会有 IfSqlNode,ChooseSqlNode....
NodeList children = node.getNode().getChildNodes();
//循环这些 ChildNode
for (int i = 0; i < children.getLength(); i++) {
//获取sqlNode
XNode child = node.newXNode(children.item(i));
//这个地方判断这个当前的sqlNode 是不是 textSqlNode 类型的....
//为啥要做这个判断呢,因为 假如是 textSqlNode 那么就可以直接保存了,假如不是说明还是个标签,需要继续解析
if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
//获取当前sqlNode的文本
String data = child.getStringBody("");
//创建一个 textSqlNode
TextSqlNode textSqlNode = new TextSqlNode(data);
//判断是否动态的
if (textSqlNode.isDynamic()) {
//添加到 contents
contents.add(textSqlNode);
isDynamic = true;
} else {
//不是动态的添加 StaticTextSqlNode
contents.add(new StaticTextSqlNode(data));
}
} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
//获取标签名称
String nodeName = child.getNode().getNodeName();
//根据名称,获取对应的处理器
NodeHandler handler = nodeHandlerMap.get(nodeName);
if (handler == null) {
//假如没有找到处理器,会报错,基本保证错 就是你的抱歉名称写错了
throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
}
//处理 .... 我们就看一个 WhereHandler 的吧 别的不看了
handler.handleNode(child, contents);
isDynamic = true;
}
}
return new MixedSqlNode(contents);
}
//whereHandler 代码如下
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
//又调用了parseDynamicTags方法
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
//封装为对应的sqlNode
WhereSqlNode where = new WhereSqlNode(configuration, mixedSqlNode);
//存放到 targetContents 就是 第一次调用的parseDynamicTags 中创建的contents
targetContents.add(where);
}
//接着看主线代码
public void parseStatementNode() {
//获取sql 的id
String id = context.getStringAttribute("id");
//数据库厂商标识 一般是用不到的
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
//这是一个给驱动的提示,尝试让驱动程序每次批量返回的结果行数和这个设置值相等 (没啥用)
Integer fetchSize = context.getIntAttribute("fetchSize");
// 这个sql 执行的超时时间
Integer timeout = context.getIntAttribute("timeout");
//当前sql 的 parameterMap 基本被废弃了
String parameterMap = context.getStringAttribute("parameterMap");
//常用的请求参数类型
String parameterType = context.getStringAttribute("parameterType");
//转化为 class 类型
Class<?> parameterTypeClass = resolveClass(parameterType);
//返回值 map 都是常用的
String resultMap = context.getStringAttribute("resultMap");
//resultType
String resultType = context.getStringAttribute("resultType");
//这个我不知道是干啥的
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
// 转 class
Class<?> resultTypeClass = resolveClass(resultType);
//不常用的属性
String resultSetType = context.getStringAttribute("resultSetType");
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
//获取标签名称 select insert update delete
String nodeName = context.getNode().getNodeName();
//转大写
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);
// Include Fragments before parsing
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
//处理 include
includeParser.applyIncludes(context.getNode());
// Parse selectKey after includes and remove them.
//处理 selectKey 说实话 这个标签没咋用过
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
//获取了 sqlSource
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
//下面也是设置一些属性
String resultSets = context.getStringAttribute("resultSets");
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
KeyGenerator keyGenerator;
//获取 sql id 大概张这个样 XXX!selectKey
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
//前面在 拼接上命名空间 属性值
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
//把上面获取的 值 封装
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
public MappedStatement addMappedStatement(
String id,
SqlSource sqlSource,
StatementType statementType,
SqlCommandType sqlCommandType,
Integer fetchSize,
Integer timeout,
String parameterMap,
Class<?> parameterType,
String resultMap,
Class<?> resultType,
ResultSetType resultSetType,
boolean flushCache,
boolean useCache,
boolean resultOrdered,
KeyGenerator keyGenerator,
String keyProperty,
String keyColumn,
String databaseId,
LanguageDriver lang,
String resultSets) {
if (unresolvedCacheRef) {
throw new IncompleteElementException("Cache-ref not yet resolved");
}
//在检查下 id
id = applyCurrentNamespace(id, false);
//判断是不是 select 语句
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
//创建 statementBuilder
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
.resource(resource)
.fetchSize(fetchSize)
.timeout(timeout)
.statementType(statementType)
.keyGenerator(keyGenerator)
.keyProperty(keyProperty)
.keyColumn(keyColumn)
.databaseId(databaseId)
.lang(lang)
.resultOrdered(resultOrdered)
.resultSets(resultSets)
.resultMaps(getStatementResultMaps(resultMap, resultType, id))
.resultSetType(resultSetType)
.flushCacheRequired(valueOrDefault(flushCache, !isSelect))
.useCache(valueOrDefault(useCache, isSelect))
.cache(currentCache);
//获取 statementParameterMap
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}
获取 statement
MappedStatement statement = statementBuilder.build();
//存放在 configuration 的 mappedStatements 属性中
configuration.addMappedStatement(statement);
//结束
return statement;
}
总结
1. 创建sqlSessionFactory 是在 sqlSessionFactoryBean 创建的
2. 只要干了两件事情,设置数据源,解析mapper文件
3. 解析出来的sql 是放在 configuration中的, configuration 是sqlSessionFactory的一个属性