大家好,又见面了,我是你们的朋友全栈君。
一、SqlSessionFactory
SqlSessionFactory是MyBatis的关键对象, 它是个单个数据库映射关系经过编译后的内存镜像; SqlSessionFactory对象的实例可以通过SqlSessionFactoryBuilder对象类获得; SqlSessionFactoryBuilder从XML配置文件或一个预先定制的Configuration的实例构建出SqlSessionFactory的实例; 每一个MyBatis的应用程序都以一个SqlSessionFactory对象的实例为核心, 同时SqlSessionFactory也是线程安全的, SqlSessionFactory一旦被创建, 应该在应用执行期间都存在; 在应用运行期间不要重复创建多次, 建议使用单例模式SqlSessionFactory是创建SqlSession的工厂;
Configuration.xml相关配置:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties>
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true"/>
<property name="username" value="root"/>
<property name="password" value="m123"/>
</properties>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?characterEncoding=utf-8"/>
<property name="username" value="root"/>
<property name="password" value="m123"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com\lic\ibatis\mapper\UserMapper.xml"/>
<mapper class="com.lic.ibatis.mapper.UserMapperAnn"></mapper>
</mappers>
</configuration>
UserMapper.xml配置:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lic.ibatis.dao.UserMapper">
<select id="getUserById" parameterType="int" resultType="com.lic.ibatis.entity.User">
select * from user where id = #{id}
</select>
</mapper>
测试类:
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. 根据根标签<configuration>开始解析
*/
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类中; 这里主要看对<mappers>标签的解析
XMLConfigBuilder#mapperElement()方法实现:
/**
* <mappers>
* <mapper resource="org/mybatis/builder/AuthorMapper.xml"/> -----> 引入类路径(编译后以classes为跟的路径)下的资源
* <mapper url="file:///var/mappers/BlogMapper.xml"/> -----> 入网络或磁盘路径下的资源
* <mapper class="org.mybatis.builder.BlogMapper"/> -----> 引用(注册)接口: 1.有sql映射文件,映射文件名必须和接口同名,并且放在与接口同一目录下;2.没有sql映射文件,所有的sql都是基于注解写在接口上。
* <package name="org.mybatis.builder"/> -----> 扫描包下所有的引用接口
* </mappers>
* @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.");
}
}
}
}
}
分析: 根据<mappers> 中配置资源的方式的不同采用不同的解析方案, 这里主要看resource方式的解析
XMLMapperBuilder#parse()方法实现:
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
/**
* 解析mapper.xml中的<mapper></mapper>标签
*/
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
/**
* 根据接口创建MapperProxyFactory工厂
*/
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
分析:
- 解析mapper.xml中的<mapper></mapper>标签
- 根据接口创建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. 解析缓存映射<cache-ref></cache-ref>
*/
cacheRefElement(context.evalNode("cache-ref"));
/**
* 2. 解析缓存<cache></cache>
*/
cacheElement(context.evalNode("cache"));
/**
* 3. 解析参数映射<parameterMap></parameterMap>
*/
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
/**
* 4. 解析结果集映射<resultMap></resultMap>
*/
resultMapElements(context.evalNodes("/mapper/resultMap"));
/**
* 5. 解析<sql></sql>
*/
sqlElement(context.evalNodes("/mapper/sql"));
/**
* 6. 解析CRUD语句<select></select> |<insert></insert> |<update></update> |<delete></delete> (重点)
*/
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);
}
}
分析: 对<mapper>的各种子标签进行解析, 主要看CRUD语句标签<select></select> |<insert></insert> |<update></update> |<delete></delete> 的解析
XMLMapperBuilder#buildStatementFromContext(java.util.List<org.apache.ibatis.parsing.XNode>)方法实现:
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) {
//用每个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:
* <select id="getUserById" parameterType="int" resultType="com.lic.ibatis.entity.User">
* select * from user where id = #{id}
* </select>
*/
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: <selectKey> and <include> 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的获取)
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/139673.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...