2025年Mybatis源码解析一(SqlSessionFactory和SqlSession的获取)

Mybatis源码解析一(SqlSessionFactory和SqlSession的获取)一 SqlSessionFa SqlSessionFa 是 MyBatis 的关键对象 它是个单个数据库映射关系经过编译后的内存镜像 SqlSessionFa 对象的实例可以通过 SqlSessionFa 对象类获得

一、SqlSessionFactory

SqlSessionFactory是MyBatis的关键对象, 它是个单个数据库映射关系经过编译后的内存镜像; SqlSessionFactory对象的实例可以通过SqlSessionFactoryBuilder对象类获得; SqlSessionFactoryBuilder从XML配置文件或一个预先定制的Configuration的实例构建出SqlSessionFactory的实例; 每一个MyBatis的应用程序都以一个SqlSessionFactory对象的实例为核心, 同时SqlSessionFactory也是线程安全的, SqlSessionFactory一旦被创建, 应该在应用执行期间都存在; 在应用运行期间不要重复创建多次, 建议使用单例模式SqlSessionFactory是创建SqlSession的工厂;

Configuration.xml相关配置:


"http://mybatis.org/dtd/mybatis-3-config.dtd">

























UserMapper.xml配置:


"http://mybatis.org/dtd/mybatis-3-mapper.dtd">



测试类:

package com.lic.ibatis.test;

import com.lic.ibatis.entity.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.Reader;

public class MybatisHelloWorld {
public static void main(String[] args) {
try {
Reader reader = Resources.getResourceAsReader("Configuration.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
SqlSession session = sqlSessionFactory.openSession();
try {
User user = (User) session.selectOne("com.lic.ibatis.dao.UserMapper.getUserById", 1);
System.out.println(user.toString());

User user2 = (User) session.selectOne("com.lic.ibatis.dao.UserMapper.getUserById", 1);
System.out.println("第二次查询"+user2.toString());
} finally {
session.close();
}

} catch (IOException e) {
e.printStackTrace();
}
}
}
分析:

对Configuration.xml配置文件进行解析, 生成SqlSessionFactory

通过SqlSessionFactory获取一个SqlSession实例

使用selectOne()方法进行查询

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);

SqlSessionFactoryBuilder#build(java.io.Reader)方法实现:

 public SqlSessionFactory build(Reader reader) {
return build(reader, null, null);
}

public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
/**
* 创建XML配置解析器
*/
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
/**
* 1. parser.parse(): 解析配置文件,创建配置类Configuration
* 2. build(): 创建SqlSessionFactory对象,并返回
*/
return build(parser.parse());

} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
分析:

创建XML配置解析器

解析配置文件

创建DefaultSqlSessionFactory对象

1. 创建XML配置解析器

XMLConfigBuilder()方法实现:

public XMLConfigBuilder(Reader reader, String environment, Properties props) {
/**
* new XPathParser(reader, true, props, new XMLMapperEntityResolver())的参数含义: Reader,是否进行DTD 校验,属性配置,XML实体节点解析器
*/
this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
}


public XPathParser(Reader reader, boolean validation, Properties variables, EntityResolver entityResolver) {
//参数配置
commonConstructor(validation, variables, entityResolver);
//将配置信息输入流封装为Document对象, 以便后面进行解析
this.document = createDocument(new InputSource(reader));
}

private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}

2. 解析配置文件

XMLConfigBuilder#parse()方法实现:

 public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
/**
* mybatis配置文件解析的主流程:
* 1. parser.evalNode("/configuration") --> 获取到根节点
* 2. 根据根标签开始解析
*/
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}

XMLConfigBuilder#parseConfiguration()方法实现:

private void parseConfiguration(XNode root) {
//解析配置文件中根标签下的所有子标签
try {
//issue #117 read properties first
/**
* 1. 解析properties节点
*/
propertiesElement(root.evalNode("properties"));
/**
* 2. 解析settings节点
*/
Properties settings = settingsAsProperties(root.evalNode("settings"));
/**
* 3. VFS主要用来加载容器内的各种资源,比如jar或者class文件
*/
loadCustomVfs(settings);
loadCustomLogImpl(settings);
/**
* 4. 解析类型别名typeAliasesElement
*/
typeAliasesElement(root.evalNode("typeAliases"));
/**
* 5. 加载插件pluginElement
*
* 比如: 分页插件PageHelper,再比如druid连接池提供的各种监控、拦截、预发检查功能,
* 在使用其它连接池比如dbcp的时候,在不修改连接池源码的情况下,就可以借助mybatis的插件体系实现
*/
pluginElement(root.evalNode("plugins"));

/**
* 6. 加载对象工厂objectFactoryElement
*
* MyBatis 每次创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成。
* 默认的对象工厂DefaultObjectFactory做的仅仅是实例化目标类,要么通过默认构造方法,要么在参数映射存在的时候通过参数构造方法来实例化。
*/
objectFactoryElement(root.evalNode("objectFactory"));
/**
* 7. 创建对象包装器工厂objectWrapperFactoryElement
*
* 对象包装器工厂主要用来包装返回result对象,比如说可以用来设置某些敏感字段脱敏或者加密等。
* 默认对象包装器工厂是DefaultObjectWrapperFactory,也就是不使用包装器工厂。
*/
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
/**
* 8. 加载反射工厂reflectorFactoryElement
*
* 因为加载配置文件中的各种插件类等等,为了提供更好的灵活性,
* mybatis支持用户自定义反射工厂,不过总体来说,用的不多,
* 要实现反射工厂,只要实现ReflectorFactory接口即可。默认的反射工厂是DefaultReflectorFactory。
* 一般来说,使用默认的反射工厂就可以了。
*/
reflectorFactoryElement(root.evalNode("reflectorFactory"));
/**
* 得到setting之后,调用settingsElement(Properties props)将各值赋值给configuration,
* 同时在这里有重新设置了默认值,所有这一点很重要,configuration中的默认值不一定是真正的默认值。
*/
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
/**
* 9. 加载环境配置environmentsElement
*
* 环境可以说是mybatis-config配置文件中最重要的部分,
* 它类似于spring和maven里面的profile,允许给开发、
* 生产环境同时配置不同的environment,根据不同的环境加载不同的配置,
* 这也是常见的做法,如果在SqlSessionFactoryBuilder调用期间没有传递使用哪个环境的话,
* 默认会使用一个名为default”的环境。找到对应的environment之后,就可以加载事务管理器和数据源了。
* 事务管理器和数据源类型这里都用到了类型别名,JDBC/POOLED都是在mybatis内置提供的,
* 在Configuration构造器执行期间注册到TypeAliasRegister。
*
*  mybatis内置提供JDBC和MANAGED两种事务管理方式,前者主要用于简单JDBC模式,
* 后者主要用于容器管理事务,一般使用JDBC事务管理方式。
* mybatis内置提供JNDI、POOLED、UNPOOLED三种数据源工厂,一般情况下使用POOLED数据源。
*/
environmentsElement(root.evalNode("environments"));
/**
* 10. 数据库厂商标识加载databaseIdProviderElement(了解即可)
*/
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
/**
* 11. 加载类型处理器typeHandlerElement
*
* 无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,
* 还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。
* mybatis提供了两种方式注册类型处理器,package自动检索方式和显示定义方式。
* 使用自动检索(autodiscovery)功能的时候,只能通过注解方式来指定 JDBC 的类型。
*/
typeHandlerElement(root.evalNode("typeHandlers"));

/**
* 12. 加载mapper文件 或 mapperElement ---> (重点)
*/
mapperElement(root.evalNode("mappers"));

} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
分析: 对配置文件中各个节点进行解析, 将解析结果封装到Configuration类中; 这里主要看对标签的解析

XMLConfigBuilder#mapperElement()方法实现:

 /**
*
* -----> 引入类路径(编译后以classes为跟的路径)下的资源
* -----> 入网络或磁盘路径下的资源
* -----> 引用(注册)接口: 1.有sql映射文件,映射文件名必须和接口同名,并且放在与接口同一目录下;2.没有sql映射文件,所有的sql都是基于注解写在接口上。
* -----> 扫描包下所有的引用接口
*

* @param parent
* @throws Exception
*/
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
/**
* 如果要同时使用package自动扫描和通过mapper明确指定要加载的mapper,
* 一定要确保package自动扫描的范围不包含明确指定的mapper,否则在通过
* package扫描的interface的时候,尝试加载对应xml文件的loadXmlResource()
* 的逻辑中出现判重出错,报org.apache.ibatis.binding.BindingException异常,
* 即使xml文件中包含的内容和mapper接口中包含的语句不重复也会出错,
* 包括加载mapper接口时自动加载的xml mapper也一样会出错。
*/
//如果配置包扫描
if ("package".equals(child.getName())) {
//获取需要扫描的包路径
String mapperPackage = child.getStringAttribute("name");
//解析包信息, 注册该包下的Mappers
configuration.addMappers(mapperPackage);
} else {
//获取resource,url,mapperClass属性的值
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");

//resource属性解析
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();

} else if (resource == null && url != null && mapperClass == null) {
//url属性解析
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();

} else if (resource == null && url == null && mapperClass != null) {
//mapperClass属性解析
Class mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
分析: 根据 中配置资源的方式的不同采用不同的解析方案, 这里主要看resource方式的解析

XMLMapperBuilder#parse()方法实现:

public void parse() {
if (!configuration.isResourceLoaded(resource)) {
/**
* 解析mapper.xml中的标签
*/
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
/**
* 根据接口创建MapperProxyFactory工厂
*/
bindMapperForNamespace();
}

parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
分析:

解析mapper.xml中的标签

根据接口创建MapperProxyFactory工厂, 用于创建mapper接口的代理对象

XMLMapperBuilder#configurationElement()方法实现:

private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
//设置名称空间
builderAssistant.setCurrentNamespace(namespace);
//开始对mapper.xml中各个标签进行解析
/**
* 1. 解析缓存映射
*/
cacheRefElement(context.evalNode("cache-ref"));
/**
* 2. 解析缓存
*/
cacheElement(context.evalNode("cache"));
/**
* 3. 解析参数映射
*/
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
/**
* 4. 解析结果集映射
*/
resultMapElements(context.evalNodes("/mapper/resultMap"));
/**
* 5. 解析
*/
sqlElement(context.evalNodes("/mapper/sql"));
/**
* 6. 解析CRUD语句 | | | (重点)
*/
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);
}
}
分析: 对的各种子标签进行解析, 主要看CRUD语句标签 | | | 的解析

XMLMapperBuilder#buildStatementFromContext(java.util.List)方法实现:

 private void buildStatementFromContext(List list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}

private void buildStatementFromContext(List list, String requiredDatabaseId) {
//遍历解析每条sql语句
for (XNode context : list) {
//用每个sql标签的上下文对象创建statementParser解析器
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
/**
* 解析SQL节点
*/
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
分析: 创建statementParser解析器, 调用parseStatementNode()对标签进行解析

XMLStatementBuilder#parseStatementNode()方法实现:

 public void parseStatementNode() {
/**
* context:
*
*/
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");

if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}

String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
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);
includeParser.applyIncludes(context.getNode());

String parameterType = context.getStringAttribute("parameterType");
Class parameterTypeClass = resolveClass(parameterType);

String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);

// Parse selectKey after includes and remove them.
processSelectKeyNodes(id, parameterTypeClass, langDriver);

// Parse the SQL (pre: and were parsed and removed)
KeyGenerator keyGenerator;
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;
}

SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String resultType = context.getStringAttribute("resultType");
Class resultTypeClass = resolveClass(resultType);
String resultMap = context.getStringAttribute("resultMap");
String resultSetType = context.getStringAttribute("resultSetType");
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
if (resultSetTypeEnum == null) {
resultSetTypeEnum = configuration.getDefaultResultSetType();
}
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
String resultSets = context.getStringAttribute("resultSets");

/**
* 将解析内容封装到MappedStatement中
*/
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
分析:

对之前解析好的各种属性进行解析配置

将所有的sql配置进行封装

MapperBuilderAssistant#addMappedStatement(. . . )方法实现:

 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 = applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;

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); //二级缓存配置

ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}

MappedStatement statement = statementBuilder.build();
/**
* 将解析好的statement实例加入mappedStatements集合中
*/
configuration.addMappedStatement(statement);
return statement;
}

Configuration#addMappedStatement()方法实现:

 public void addMappedStatement(MappedStatement ms) {
mappedStatements.put(ms.getId(), ms);
}
分析: 将解析完成的MappedStatement实例加入mappedStatements集合中, key为接口路径+方法名称 value为该接口方法对应的MappedStatement实例;

3. 创建DefaultSqlSessionFactory对象

SqlSessionFactoryBuilder#build(org.apache.ibatis.session.Configuration)方法实现:

public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}


public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}

二、SqlSession

SqlSession是MyBatis的关键对象, 是执行持久化操作的独享, 类似于JDBC中的Connection; 它是应用程序与持久层之间执行交互操作的一个单线程对象, 也是MyBatis执行持久化操作的关键对象; SqlSession对象完全包含以数据库为背景的所有执行SQL操作的方法, 它的底层封装了JDBC连接, 可以用SqlSession实例来直接执行被映射的SQL语句; 每个线程都应该有它自己的SqlSession实例; SqlSession的实例不能被共享, 同时SqlSession也是线程不安全的, 绝对不能讲SqlSeesion实例的引用放在一个类的静态字段甚至是实例字段中; 也绝不能将SqlSession实例的引用放在任何类型的管理范围中, 比如Servlet当中的HttpSession对象中; 使用完SqlSeesion之后关闭Session很重要, 应该确保使用finally块来关闭它.

SqlSession session = sqlSessionFactory.openSession()

DefaultSqlSessionFactory#openSession()方法实现:

  @Override
public SqlSession openSession() {
// 使用默认的执行器类型(默认是SIMPLE),默认隔离级别,非自动提交 委托给openSessionFromDataSource方法
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
// 获取事务管理器, 支持从数据源或者直接获取
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// 从数据源创建一个事务, 同样,数据源必须配置, mybatis内置了JNDI、POOLED、UNPOOLED三种类型的数据源,
// 其中POOLED对应的实现为org.apache.ibatis.datasource.pooled.PooledDataSource,它是mybatis自带实现的一个同步、
// 线程安全的数据库连接池 一般在生产中,我们会使用dbcp或者druid连接池
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//创建执行器
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}

DefaultSqlSession#DefaultSqlSession()方法的实现:

public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
this.configuration = configuration;
this.executor = executor;
this.dirty = false;
this.autoCommit = autoCommit;
}

相关文章:

Mybatis源码解析一(SqlSessionFactory和SqlSession的获取)

Mybatis源码解析二(请求处理过程解析)

Mybatis源码解析三(模拟Mybatis)

编程小号
上一篇 2025-02-17 08:33
下一篇 2025-04-02 14:40

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/hz/131733.html