MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO为数据库中的记录。
1.利用maven项目管理工具添加mybatis(版本3.5.3)依赖和MySQL驱动包(版本8.0.17)
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.17</version>
</dependency>
</dependencies>
2.在resources下添加MyBatis的核心配置文件mybatis.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>
<!-- default引用environment的id,当前所使用的环境 -->
<environments default="default">
<!-- 声明可以使用的环境 -->
<environment id="default">
<!-- 使用原生JDBC事务 -->
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
</configuration>
3.添加实体类model和mapper接口以及mapper.xml
3.1 User.java
/**
* @author yly
* @ClassName User
* @Date 2020/2/28 15:51
* @Version 1.0
**/
public class User {
private Integer id;
private String name;
private String age;
//忽略getter/setter方法
}
3.2 UserMapper接口
/**
* @author yly
* @ClassName UserMapper
* @Date 2020/2/28 17:59
* @Version 1.0
**/
public interface UserMapper {
int SaveUser(User user);
List<User> selectUser();
}
3.3 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.yly.mysql.mapper.UserMapper">
<select id="selectUser" resultType="com.yly.mysql.model.User">
select * from user
</select>
<insert id="SaveUser" parameterType="com.yly.mysql.model.User">
insert into user VALUES (#{name},#{age},#{id})
</insert>
</mapper>
3.4 创建MyBatisSoundCodeDemo执行mybatis。
/**
* @author yly
* @ClassName SqlDemo
* @Date 2020/2/28 15:58
* @Version 1.0
**/
public class MyBatisSoundCodeDemo {
public static void main(String[] args) throws IOException {
//获取mybatis的核心配置文件
InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
//获取SqlSessionFactory实例
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//从 SqlSessionFactory 中获取 SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> users = mapper.selectUser();
users.forEach(i -> System.out.println(i.getName()));
sqlSession.commit();
sqlSession.close();
}
}
每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先定制的 Configuration 的实例构建出 SqlSessionFactory 的实例。
//获取mybatis的核心配置文件
InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
//获取SqlSessionFactory实例
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//从 SqlSessionFactory 中获取 SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
//获取mybatis的核心配置文件
InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
//获取SqlSessionFactory实例
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
通过build(InputStream inputStream)方法构建SqlSessionFactory实例
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
追踪源码,首先我们看到build()方法new一个XMLConfigBuilder(XML配置构建器),通过XMLConfigBuilder类中的parse()方法返回一个Configuration对象。
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
parse()方法首先对XMLConfigBuilder 进行判断是否解析过,如果已经解析则抛出异常,如果没有则设置parsed 为true。然后通过XPathParser对象解析mybatis的核心配置文件中的configuration根节点,通过parseConfiguration(XNode root)方法解析configuration根节点中的子节点。
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
找到 mapperElement(root.evalNode(“mappers”));着重看解析mappers节点。
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
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) {
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) {
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.");
}
}
}
}
}
在mapperElement(XNode parent)方法中,通过解析mybatis.xml文件中的mappers节点,首先判断父节点是否为空,若不为空,遍历mappers父节点的子节点
<mappers>
<package name=""/>
</mappers>
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
}
通过child.getStringAttribute("name");获取到package的属性值,调用configuration对象的addMappers()方法,将包配置到configuration对象中去。
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
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) {
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) {
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.");
}
其中通过源码可以看到mapper中的属性值resource、url、mapperClass只配置一个即可。由于我们配置的是resource,所以跟踪配置resource的源码,
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
通过resource配置的mapper/UserMapper.xml路径获取到UserMapper.xml资源,然后通过XMLMapperBuilder的parse()方法进行xml解析
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
XMLMapperBuilder的parse()首先判断resource是否被装载过,如果没有被装载过,则进行mapper节点解析(详解在下一部分MyBatis是如何执行SQL语句中进行讲解),将resource装载进configuration对象中,通过bindMapperForNamespace();方法获取到namespace属性值,然后通过namespace获取interface com.yly.mysql.mapper.UserMapper接口对象,并赋值给configuration对象。
private void bindMapperForNamespace() {
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
if (!configuration.hasMapper(boundType)) {
// Spring may not know the real resource name so we set a flag
// to prevent loading again this resource from the mapper interface
// look at MapperAnnotationBuilder#loadXmlResource
configuration.addLoadedResource("namespace:" + namespace);
configuration.addMapper(boundType);
}
}
}
}
在 configuration.addMapper(boundType);中将UserMapper接口对象加入到一个人HashMap中。
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory<>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
将所有方法执行完之后返回一个configuration对象继续调用SqlSessionFactory的build(Configuration config)方法,将Configuration 对象传入进去,new一个DefaultSqlSessionFactory(config)对象并返回。
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
至此SqlSessionFactory的实例对象获取完成。
其中在获取inputStream资源之后不需要关闭资源,是因为在mybatis在构建SqlSessionFactory 之后替我们关闭了资源。
1.1SqlSessionFactory 通过openSession();方法获取SqlSession 对象。
//从 SqlSessionFactory 中获取 SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
因为之前构建的SqlSessionFactory 为DefaultSqlSessionFactory对象,所以构建SqlSession对象时使用的是DefaultSqlSessionFactory的openSession()方法。
@Override
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
返回一个SqlSession 接口对象,通过调用openSessionFromDataSource()方法,默认的Executor(执行器)类型为SIMPLE(低级的)执行器。
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
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();
}
}
进入openSessionFromDataSource()方法中,首先获取一个默认的环境配置对象Environment,然后通过Environment对象获取一个默认的TransactionFactory对象,调用newTransaction();方法来获取一个事务对象,值得注意的是,在mybatis框架中,设置自动提交autoCommit默认为false。最后通过new DefaultSqlSession(configuration, executor, autoCommit);返回SqlSession对象
MyBatis在执行SQL语句是需要一个非常重要的对象MapperMethod对象。
在上一部分中,我们通过查看XMLMapperBuilder中的parse()方法,代码如下
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
其中configurationElement(parser.evalNode("/mapper"));则用于解析mapper节点,进行执行SQL语句配置。进入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);
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
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的namespace属性值进行判断,然后解析mapper中的各个子节点,我们着重看buildStatementFromContext(context.evalNodes(“select|insert|update|delete”));方法,从buildStatementFromContext(List list)方法中传入解析过后的SQL集合。如果从configuration对象中获取到DatabaseId的值,则将SQL集合传入buildStatementFromContext()方法中去进行解析
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
其中真正处理解析SQL语句的是 statementParser.parseStatementNode();方法,在此方法中有SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); 获取SQL资源,进入到LanguageDriver接口的XMLLanguageDriver实现类中的createSqlSource()方法中,new了一个XMLScriptBuilder对象,调用parseScriptNode()方法进行SQL解析。
public SqlSource parseScriptNode() {
MixedSqlNode rootSqlNode = parseDynamicTags(context);
SqlSource sqlSource;
if (isDynamic) {
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
首先查看是不是动态SQL,如果不是则new一个RawSqlSource对象。
public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) {
this(configuration, getSql(configuration, rootSqlNode), parameterType);
}
其中getSql(configuration, rootSqlNode)是将xml文件中的SQL通过递归的方式转换成字符串类型。
通过this调用其构造方法为
public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> clazz = parameterType == null ? Object.class : parameterType;
sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<>());
}
利用SqlSourceBuilder的parse()方法解析并替换SQL语句中的#{}替换成成?占位符并返回成SqlSource对象
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
String sql = parser.parse(originalSql);
return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}
mybatis默认通过jdk动态代理创建MapperMethod对象。
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
将mapperInterface,Method,config存入SqlCommand和MethodSignature对象中。调用MapperMethod对象的execute()方法,获取SQL执行的类型,通过switch语句进行类型调用处理。我们着重看INSERT语句。
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
进入sqlSession.insert(command.getName(), param)方法中,再次调用了update()方法,即源代码如下。
@Override
public int update(String statement, Object parameter) {
try {
dirty = true;
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
在update中,将dirty设置为true并获取MappedStatement对象,通过执行器Executor调用update()方法,其中MappedStatement对象中存在着我们所需要的所有数据。MappedStatement数据是之前构建SqlSessionFactory实例的时候就已经将UserMapper接口对象存入到HashMap中。所以MappedStatement ms = configuration.getMappedStatement(statement);也只是从一个HashMap中取值。
跟随源码进入到update()方法中,一直到SimpleExecutor,默认处理的执行器,简单执行器。调用doUpdate()方法。
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
知道这里。我们已经看到了非常熟悉的对象Statement 对象,这已经离我们原生的JDBC执行仅有一步之遥。我们继续跟进stmt = prepareStatement(handler, ms.getStatementLog());,进入到prepareStatement()方法中。
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
我们看到Connection 对象,连接数据库对象,你可能会问getConnection()方法是如何获取到数据库连接的,因为在返回SqlSession的时候调用newTransaction()方法时初始化了DataSource,所以可以通过DataSource对象获取数据库连接。
获取数据库连接之后,继续跟踪源码到BaseStatementHandler对象的prepare()方法。
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
statement = instantiateStatement(connection);
setStatementTimeout(statement, transactionTimeout);
setFetchSize(statement);
return statement;
} catch (SQLException e) {
closeStatement(statement);
throw e;
} catch (Exception e) {
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + e, e);
}
}
其中ErrorContext.instance().sql(boundSql.getSql());获取到SQL执行语句,statement = instantiateStatement(connection);进行Statement对象的创建
其中对于Statement对象的赋值在handler.parameterize(stmt);方法中;跟踪源码到DefaultParameterHandler对象的setParameters()方法中,即源码如下
@Override
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
进入typeHandler.setParameter(ps, i + 1, value, jdbcType);之后,通过调用setNonNullParameter(ps, i, parameter, jdbcType);方法完成对PreparedStatement赋值。
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
throws SQLException {
ps.setString(i, parameter);
}
返回一个Statement对象即一个完整的SQL语句
继续跟踪handler.update(stmt)得到PreparedStatementHandler对象的update()方法
@Override
public int update(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
int rows = ps.getUpdateCount();
Object parameterObject = boundSql.getParameterObject();
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
return rows;
}
我们看到非常熟悉的JDBC执行语句 ps.execute();至此SQL语句执行完毕。
之前我们知道JDBC中设置的AutoCommit(自动提交为)true,而mybatis中设置的自动提交为false,需要我们手动commit(),我们开始在MyBatis执行流程中提到过,mybatis根据dirty的属性值决定提交或回滚,当dirty为true时,SQL执行完毕后,可以进行事务提交,当dirty为false时,SQL语句执行错误,事务进行回滚。
跟进sqlSession.commit();代码进入DefaultSqlSession类中的commit()方法中。
@Override
public void commit(boolean force) {
try {
executor.commit(isCommitOrRollbackRequired(force));
dirty = false;
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error committing transaction. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
其中isCommitOrRollbackRequired(force),是否进行提交或者回滚,传入的值force=false,由于autoCommit 为false,dirty为true(具体赋值在执行DefaultSqlSession的update()方法时对dirty进行赋值),所以(!autoCommit && dirty)为true即执行executor.commit(true);
private boolean isCommitOrRollbackRequired(boolean force) {
return (!autoCommit && dirty) || force;
}
@Override
public void commit(boolean required) throws SQLException {
delegate.commit(required);
tcm.commit();
}
跟进 delegate.commit(required);中进入BaseExecutor的commit()方法中
@Override
public void commit(boolean required) throws SQLException {
if (closed) {
throw new ExecutorException("Cannot commit, transaction is already closed");
}
clearLocalCache();
flushStatements();
if (required) {
transaction.commit();
}
}
在跟进transaction.commit();方法中,我们豁然发现 connection.commit();提交事务。
@Override
public void commit() throws SQLException {
if (connection != null && !connection.getAutoCommit()) {
if (log.isDebugEnabled()) {
log.debug("Committing JDBC Connection [" + connection + "]");
}
connection.commit();
}
}
当执行器将commit()方法执行完毕之后将dirty设置成false,然后执行sqlSession.close();至此MyBatis执行完毕,将值也写入了数据库。