1. 简单Demo
String resource = "mybatis-config.xml";
//1.流形式读取mybatis配置文件
InputStream stream = Resources.getResourceAsStream(resource);
//2.通过配置文件创建SqlSessionFactory
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(stream);
//3.通过SqlSessionFactory创建sqlSession
SqlSession session = sessionFactory.openSession();
//4.通过SqlSession执行Sql语句获取结果
List<User> userList = session.selectList("selectAll");
System.out.println(userList.size());
1.通过InputStream获取mybatis的配置文件
2.通过SqlSessionFactoryBuilder创建SqlSessionFactory
3.通过SqlSessionFactory创建一个SqlSession
4.通过SqlSession执行Sql语句并获取结果
那么接下来先来了解下SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession都是什么,是怎么工作的?
2. SqlSessionFactoryBuilder 分析
SqlSessionFactoryBuilder是SqlSessionFactory的构造类,而SqlSessionFactory又是SqlSession的工厂接口,SqlSession从字面意思可知是sql会话接口;
所以SqlSessionFactoryBuilder可定义为Sql会话工厂构造类,顾名思义此类的作用就是创建SqlSessionFactory
package org.apache.ibatis.session;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.Properties;
import org.apache.ibatis.builder.xml.XMLConfigBuilder;
import org.apache.ibatis.exceptions.ExceptionFactory;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.session.defaults.DefaultSqlSessionFactory;
public class SqlSessionFactoryBuilder {
//方法1
public SqlSessionFactory build(Reader reader) {
return build(reader, null, null);
}
//方法2
public SqlSessionFactory build(Reader reader, String environment) {
return build(reader, environment, null);
}
//方法3
public SqlSessionFactory build(Reader reader, Properties properties) {
return build(reader, null, properties);
}
//方法4
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
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.
}
}
}
//方法5
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
//方法6
public SqlSessionFactory build(InputStream inputStream, String environment) {
return build(inputStream, environment, null);
}
//方法7
public SqlSessionFactory build(InputStream inputStream, Properties properties) {
return build(inputStream, null, properties);
}
//方法8
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// 解析mybatis的xml配置文件,封装为Configuration对象
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.
}
}
}
//方法9
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
由源码可知该类共有9个public方法,方法1、2、3最终都是调用了方法4;而方法5、6、7最终都是调用了方法8;而方法4和方法8都是根据传参创建了一个XMLConfiguration对象。
然后根据XMLConfiguration对象的parse方法创建了一个Configuration对象,然后都调用了方法9,所以该类的所有方法最终都是调用了方法9。而方法9是根据传入的Configuration参数新建了一个DefaultSqlSessionFactory对象返回。
很显然DefaultSqlSessionFactory是SqlSessionFactory接口的默认实现类(SqlSessionFactory还有一个SqlSessionManager实现类,后续继续了解)
总结:SqlSessionFactoryBuilder根据mybatis的配置文件流创建Configuration对象,然后新建一个SqlSessionFactory的默认实现类DefaultSqlSessionFactory的对象
3. SqlSessionFactory 分析
package org.apache.ibatis.session;
import java.sql.Connection;
/**
* Creates an {@link SqlSession} out of a connection or a DataSource
*
* @author Clinton Begin
*/
public interface SqlSessionFactory {
SqlSession openSession();
SqlSession openSession(boolean autoCommit);
SqlSession openSession(Connection connection);
SqlSession openSession(TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType);
SqlSession openSession(ExecutorType execType, boolean autoCommit);
SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType, Connection connection);
/**
* 获取Configuration 对象,这个对象包含了MyBatis的配置信息,Mapper接口等信息的描述
* @return MyBatis配置信息
*/
Configuration getConfiguration();
}
SqlSessionFactory接口定义了8个创建SqlSession的接口,和一个获取Configuration的方法,该接口的主要作用也就是创建SqlSession
可以看出这里面涉及到了几个参数:
autoCommit:数据库是否自动提交
Connection:数据库连接
TransactionIsolationLevel:数据库隔离级别
ExecutorType:执行器类型
这几个参数具体含义暂不讨论,后续继续了解,那么现在继续了解下SqlSessionFactory的默认实现类DefaultSqlSessionFactory
4. DefaultSqlSessionFactory 分析
package org.apache.ibatis.session.defaults;
import java.sql.Connection;
import java.sql.SQLException;
import org.apache.ibatis.exceptions.ExceptionFactory;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.TransactionIsolationLevel;
import org.apache.ibatis.transaction.Transaction;
import org.apache.ibatis.transaction.TransactionFactory;
import org.apache.ibatis.transaction.managed.ManagedTransactionFactory;
/**
* @author Clinton Begin
*/
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private final Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
@Override
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
@Override
public SqlSession openSession(boolean autoCommit) {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, autoCommit);
}
@Override
public SqlSession openSession(ExecutorType execType) {
return openSessionFromDataSource(execType, null, false);
}
@Override
public SqlSession openSession(TransactionIsolationLevel level) {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), level, false);
}
@Override
public SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level) {
return openSessionFromDataSource(execType, level, false);
}
@Override
public SqlSession openSession(ExecutorType execType, boolean autoCommit) {
return openSessionFromDataSource(execType, null, autoCommit);
}
@Override
public SqlSession openSession(Connection connection) {
return openSessionFromConnection(configuration.getDefaultExecutorType(), connection);
}
@Override
public SqlSession openSession(ExecutorType execType, Connection connection) {
return openSessionFromConnection(execType, connection);
}
@Override
public Configuration getConfiguration() {
return configuration;
}
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();
}
}
private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
try {
boolean autoCommit;
try {
autoCommit = connection.getAutoCommit();
} catch (SQLException e) {
// Failover to true, as most poor drivers
// or databases won't support transactions
autoCommit = true;
}
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
final Transaction tx = transactionFactory.newTransaction(connection);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
if (environment == null || environment.getTransactionFactory() == null) {
return new ManagedTransactionFactory();
}
return environment.getTransactionFactory();
}
private void closeTransaction(Transaction tx) {
if (tx != null) {
try {
tx.close();
} catch (SQLException ignore) {
// Intentionally ignore. Prefer previous error.
}
}
}
}
DefaultSqlSessionFactory除了实现了SqlSessionFactory的9个接口,还有一个构造方法,也就是SqlSessionFactoryBuilder中调用的那个,然后还有4个私有方法。
可以看出实现的8个创建SqlSession对象的接口最终都是调用了openSessionFromDataSource()或是openSessionFromConnection()两个方法,以openSessionFromDataSource()为例,源码如下:
4.1. openSessionFromDataSource()
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
//根据Configuration获取环境参数对象
final Environment environment = configuration.getEnvironment();
//根据环境参数对象获取事务工厂对象
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
//根据事务工厂创建新的事务对象
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//根据Configuration获取执行器对象
final Executor executor = configuration.newExecutor(tx, execType);
//根据3个参数创建DefaultSqlSession对象
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() 共有三个参数
- ExecutorType:执行器类型
- TransactionIsolationLevel:事务隔离级别
- autoCommit:自动提交标识
通过源码分析,最终是创建了一个DefaultSqlSession对象,很明显是SqlSession接口的默认实现类;
openSessionFromConnection方法和openSessionFromDataSource的区别就是参数不同,效果是一样的,最终也是返回一个DefaultSqlSession对象
而具体的Sql的执行都是通过DefaultSqlSession对象去执行的。具体怎么执行的后续再了解。
总结:
1.先获取mybatis的配置文件,解析成流对象(字符流和字节流都可以)Reader和InputStream都可以
2.通过SqlSessionFactoryBuilder根据mybatis的配置文件流创建一个Configuration对象
3.SqlSessionFactoryBuilder根据Configuration对象创建一个DefaultSqlSessionFactory(SqlSessionFactory的默认实现类)
4.DefaulatSqlSessionFacotry根据传入的参数,创建一个DefaultSqlSession对象(SqlSession的默认实现类)
5. XMLConfigBuilder 分析
XMLConfigBuilder类的作用是对mybatis-config.xml配置文件的解析,在SqlSessionFactoryBuilder 类中解析MyBatis配置文件时创建,位于org.apache.ibatis.builder.xml目录下,继承于BaseBuilder类,关于BaseBuilder类后续再看。
XMLConfigBuilder看名字能猜到是关于mybatis的XML配置的构造类,负责构造mybatis的XML配置的。
XMLConfigBuilder共有四个属性,代码如下:
//解析标识,因为Configuration是全局变量,只需要解析创建一次即可,true表示已经解析创建过,false则表示没有
private boolean parsed;
private final XPathParser parser;
//环境参数
private String environment;
private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();
XMLConfigBuilder共有6个public构造方法和一个private的构造方法
public XMLConfigBuilder(Reader reader) {
this(reader, null, null);
}
public XMLConfigBuilder(Reader reader, String environment) {
this(reader, environment, null);
}
public XMLConfigBuilder(Reader reader, String environment, Properties props) {
this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
}
public XMLConfigBuilder(InputStream inputStream) {
this(inputStream, null, null);
}
public XMLConfigBuilder(InputStream inputStream, String environment) {
this(inputStream, environment, null);
}
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
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;
}
这6个public的构造方法都是根据mybatis的配置文件流创建一个XPathParser对象,然后最终都调用了私有的构造方法,而私有的构造方法先是调用了父类BaseBuilder的构造方法,然后分别根据参数给四个属性赋值。
在SqlSessionFactoryBuilder中解析MyBatis文件是通过创建一个XMLConfigBuilder对象,然后调用了对象的parse()方法获取到一个Configuration对象。接下来就先看看XMLConfigBuilder的parse()方法,如下:
public Configuration parse() {
//判断Configuration是否解析过,Configuration是全局变量,只需要解析创建一次即可
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
//调用下面的方法,parser.evalNode("/configuration")解析XML配置的configuration节点的内容,得到XNode对象
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
/**
* 根据root中存储的是configuration节点的内容
* @param root
*/
private void parseConfiguration(XNode root) {
try {
// issue #117 read properties first
//设置properties配置
propertiesElement(root.evalNode("properties"));
//设置settings配置
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
//设置typeAliases配置
typeAliasesElement(root.evalNode("typeAliases"));
//设置plugins配置
pluginElement(root.evalNode("plugins"));
//设置objectFactory配置
objectFactoryElement(root.evalNode("objectFactory"));
//设置objectWrapperFactory配置
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
//设置reflectFactory配置
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
//设置environments配置
environmentsElement(root.evalNode("environments"));
//设置databaseIdProvider配置
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//设置typeHandlers配置
typeHandlerElement(root.evalNode("typeHandlers"));
//设置mappers配置
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
可以看出parse的作用是解析mybatis-config.xml的configuration节点的内容,然后挨个赋值给configuration对象的属性。
而XMLConfigBuilder的其他私有方法都是给根据XNode对象(XML配置的configuration节点内容)来给全局配置变量configuration的属性进行赋值,关于Configuration类的解析下一章会解析
总结:XMLConfigBuilder类的作用是根据全局配置文件mybatis-config.xml的流文件进行解析,解析xml中的各个节点,然后创建一个Configuration对象,并将xml中的节点属性赋值给Configuration对象
6. Configuration 分析
Configuration类位于mybatis包的org.apache.ibatis.session目录下,是mybatis的全局变量,属性就是对应于mybatis的全局配置文件mybatis-config.xml的配置,将XML配置中的内容解析赋值到Configuration对象中。
由于XML配置项有很多,所以Configuration类的属性也很多。先来看下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中的配置文件 -->
<properties resource="db.propertis">
<property name="XXX" value="XXX"/>
</properties>
<!-- 类型别名 -->
<typeAliases>
<!-- 在用到User类型的时候,可以直接使用别名,不需要输入User类的全部路径 -->
<typeAlias type="com.luck.codehelp.entity.User" alias="user"/>
</typeAliases>
<!-- 类型处理器 -->
<typeHandlers>
<!-- 类型处理器的作用是完成JDBC类型和java类型的转换,mybatis默认已经由了很多类型处理器,正常无需自定义-->
</typeHandlers>
<!-- 对象工厂 -->
<!-- mybatis创建结果对象的新实例时,会通过对象工厂来完成,mybatis有默认的对象工厂,正常无需配置 -->
<objectFactory type=""></objectFactory>
<!-- 插件 -->
<plugins>
<!-- 可以自定义拦截器通过plugin标签加入 -->
<plugin interceptor="com.lucky.interceptor.MyPlugin"></plugin>
</plugins>
<!-- 全局配置参数 -->
<settings>
<setting name="cacheEnabled" value="false" />
<setting name="useGeneratedKeys" value="true" /><!-- 是否自动生成主键 -->
<setting name="defaultExecutorType" value="REUSE" />
<setting name="lazyLoadingEnabled" value="true"/><!-- 延迟加载标识 -->
<setting name="aggressiveLazyLoading" value="true"/><!--有延迟加载属性的对象是否延迟加载 -->
<setting name="multipleResultSetsEnabled" value="true"/><!-- 是否允许单个语句返回多个结果集 -->
<setting name="useColumnLabel" value="true"/><!-- 使用列标签而不是列名 -->
<setting name="autoMappingBehavior" value="PARTIAL"/><!-- 指定mybatis如何自动映射列到字段属性;NONE:自动映射;PARTIAL:只会映射结果没有嵌套的结果;FULL:可以映射任何复杂的结果 -->
<setting name="defaultExecutorType" value="SIMPLE"/><!-- 默认执行器类型 -->
<setting name="defaultFetchSize" value=""/>
<setting name="defaultStatementTimeout" value="5"/><!-- 驱动等待数据库相应的超时时间 ,单位是秒-->
<setting name="safeRowBoundsEnabled" value="false"/><!-- 是否允许使用嵌套语句RowBounds -->
<setting name="safeResultHandlerEnabled" value="true"/>
<setting name="mapUnderscoreToCamelCase" value="false"/><!-- 下划线列名是否自动映射到驼峰属性:如user_id映射到userId -->
<setting name="localCacheScope" value="SESSION"/><!-- 本地缓存(session是会话级别) -->
<setting name="jdbcTypeForNull" value="OTHER"/><!-- 数据为空值时,没有特定的JDBC类型的参数的JDBC类型 -->
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/><!-- 指定触发延迟加载的对象的方法 -->
<setting name="callSettersOnNulls" value="false"/><!--如果setter方法或map的put方法,如果检索到的值为null时,数据是否有用 -->
<setting name="logPrefix" value="XXXX"/><!-- mybatis日志文件前缀字符串 -->
<setting name="logImpl" value="SLF4J"/><!-- mybatis日志的实现类 -->
<setting name="proxyFactory" value="CGLIB"/><!-- mybatis代理工具 -->
</settings>
<!-- 环境配置集合 -->
<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://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8" />
<property name="username" value="root" />
<property name="password" value="*****" />
</dataSource>
</environment>
</environments>
<!-- mapper文件映射配置 -->
<mappers>
<mapper resource="com/luck/codehelp/mapper/UserMapper.xml"/>
</mappers>
</configuration>
而对于XML的配置,Configuration类的属性是和XML配置对应的。Configuration类属性如下:
protected Environment environment;
protected boolean safeRowBoundsEnabled;
protected boolean safeResultHandlerEnabled = true;
protected boolean mapUnderscoreToCamelCase;
//true:有延迟加载属性的对象被调用时完全加载任意属性;false:每个属性按需要加载
protected boolean aggressiveLazyLoading;
//是否允许多种结果集从一个单独的语句中返回
protected boolean multipleResultSetsEnabled = true;
//是否支持自动生成主键
protected boolean useGeneratedKeys;
//是否使用列标签
protected boolean useColumnLabel = true;
//是否使用缓存标识
protected boolean cacheEnabled = true;
protected boolean callSettersOnNulls;
protected boolean useActualParamName = true;
protected boolean returnInstanceForEmptyRow;
protected boolean shrinkWhitespacesInSql;
protected String logPrefix;
protected Class<? extends Log> logImpl;
protected Class<? extends VFS> vfsImpl;
protected Class<?> defaultSqlProviderType;
protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
protected Set<String> lazyLoadTriggerMethods = new HashSet<>(Arrays.asList("equals", "clone", "hashCode", "toString"));
protected Integer defaultStatementTimeout;
protected Integer defaultFetchSize;
protected ResultSetType defaultResultSetType;
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
//指定mybatis如果自动映射列到字段和属性,PARTIAL会自动映射简单的没有嵌套的结果,FULL会自动映射任意复杂的结果
protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;
protected Properties variables = new Properties();
protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
protected ObjectFactory objectFactory = new DefaultObjectFactory();
protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
//是否延时加载,false则表示所有关联对象即使加载,true表示延时加载
protected boolean lazyLoadingEnabled = false;
protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL
protected String databaseId;
/**
* Configuration factory class.
* Used to create Configuration for loading deserialized unread properties.
*
* @see <a href='https://github.com/mybatis/old-google-code-issues/issues/300'>Issue 300 (google code)</a>
*/
protected Class<?> configurationFactory;
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
protected final InterceptorChain interceptorChain = new InterceptorChain();
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry(this);
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
.conflictMessageProducer((savedValue, targetValue) ->
". please check " + savedValue.getResource() + " and " + targetValue.getResource());
protected final Map<String, Cache> caches = new StrictMap<>("Caches collection");
protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");
protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection");
protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<>("Key Generators collection");
//已经加载过的resource(mapper)
protected final Set<String> loadedResources = new HashSet<>();
protected final Map<String, XNode> sqlFragments = new StrictMap<>("XML fragments parsed from previous mappers");
protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<>();
protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<>();
protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<>();
protected final Collection<MethodResolver> incompleteMethods = new LinkedList<>();
/*
* A map holds cache-ref relationship. The key is the namespace that
* references a cache bound to another namespace and the value is the
* namespace which the actual cache is bound to.
*/
protected final Map<String, String> cacheRefMap = new HashMap<>();
而Configuration中的方法也差不多都是关于这些属性的set和get方法。由于属性太多,就不一一解析,现在只知道Configuration类是和mybatis的XML配置是对应的。加载的过程是SqlSessionFactoryBuilder根据xml配置的文件流,通过XMLConfigBuilder的parse方法进行解析得到一个Configuration对象。那么Configuration什么时候会用到呢?
7. SqlSession 分析
mybatis启动的时候会加载XML配置文件解析生成全局配置对象Configuration对象,SqlSessionFactoryBuilder类会根据Configuration对象创建一个DefaultSqlSessionFactory对象,而DefaultSqlSessionFactory对象实现了SqlSessionFactory中的创建SqlSession的方法,最终新建了一个SqlSession接口的默认实现类DefaultSqlSession,现在先来了解下SqlSession以及它的实现类
SqlSession解析
SqlSession位于mybatis包的org.apache.ibatis.session目录下,字面意思就是sql的会话,用于程序和数据库直接的sql会话,程序执行一次数据库操作就需要创建一个sqlSession,操作结束即关闭sqlSession;
既然是程序和数据库之间的会话,那么sqlSession接口的方法应该是程序和数据库都容易理解的,sqlSession中定义的方法都是关于数据库操作的方法,源码如下:
package org.apache.ibatis.session;
import java.io.Closeable;
import java.sql.Connection;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.executor.BatchResult;
public interface SqlSession extends Closeable {
//根据Sql语句查询单条记录
<T> T selectOne(String statement);
<T> T selectOne(String statement, Object parameter);//根据Sql语句和参数查询单条记录
//根据Sql语句查询多条记录
<E> List<E> selectList(String statement);
<E> List<E> selectList(String statement, Object parameter);//根据Sql语句和参数查询多条记录
<E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);//根据Sql语句和参数以及分页参数查询多条记录
//selectMap和selectList原理一样,只是将结果集映射成Map对象返回
<K, V> Map<K, V> selectMap(String statement, String mapKey);
<K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey);
<K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds);
//返回游标对象
<T> Cursor<T> selectCursor(String statement);
<T> Cursor<T> selectCursor(String statement, Object parameter);
<T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds);
//查询的结果对象由指定的ResultHandler处理
void select(String statement, Object parameter, ResultHandler handler);
void select(String statement, ResultHandler handler);
void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);
//执行insert语句
int insert(String statement);
int insert(String statement, Object parameter);
//执行update语句
int update(String statement);
int update(String statement, Object parameter);
//执行delete语句
int delete(String statement);
int delete(String statement, Object parameter);
//提交事务
void commit();
void commit(boolean force);
//事务回滚
void rollback();
void rollback(boolean force);
//将请求刷新到数据库
List<BatchResult> flushStatements();
//关闭sqlSession
@Override
void close();
//清除缓存
void clearCache();
//获取Configuration对象
Configuration getConfiguration();
//获取Type对象的Mapper对象
<T> T getMapper(Class<T> type);
//获取sqlSession对象的数据库连接
Connection getConnection();
}
SqlSession中的方法全是和数据库相关的增删改查以及事务的提交方法。
SqlSession有三个实现类,除了默认的DefaultSqlSession之外,还有SqlSessionManager和SqlSessionTemplate
接下来先看下SqlSession默认的实现类DefaultSqlSession是如何实现的。
7.1. DefaultSqlSession
DefaultSqlSession有5个属性和2个构造方法如下:
//全局配置
private final Configuration configuration;
//执行器
private final Executor executor;
//自动提交标识
private final boolean autoCommit;
private boolean dirty;
//游标列表
private List<Cursor<?>> cursorList;
/**
* 构造方法
* @param configuration
* @param executor
* @param autoCommit
*/
public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
this.configuration = configuration;
this.executor = executor;
this.dirty = false;
this.autoCommit = autoCommit;
}
/**
* 构造方法
* @param configuration
* @param executor
*/
public DefaultSqlSession(Configuration configuration, Executor executor) {
this(configuration, executor, false);
}
以selectOne()方法为例
@Override
public <T> T selectOne(String statement) {
return this.<T>selectOne(statement, null);
}
@Override
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
List<T> list = this.<T>selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
selectOne()最终都是调用了selectList方法,然后取唯一的一条数据返回。那在看看selectList相关的代码
@Override
public <E> List<E> selectList(String statement) {
return this.selectList(statement, null);
}
@Override
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
共有三个selectList()方法,最终都是调用了最后一个selectList()方法,这里有三个参数:statement是sql语句、
parameter是传入的参数、RowBounds是和分页相关的参数
RowBounds源码如下:
package org.apache.ibatis.session;
/**
* @author Clinton Begin
*/
public class RowBounds {
public static final int NO_ROW_OFFSET = 0;
public static final int NO_ROW_LIMIT = Integer.MAX_VALUE; //int的最大值
public static final RowBounds DEFAULT = new RowBounds();
private int offset;
private int limit;
public RowBounds() {
this.offset = NO_ROW_OFFSET;
this.limit = NO_ROW_LIMIT;
}
public RowBounds(int offset, int limit) {
this.offset = offset;
this.limit = limit;
}
public int getOffset() {
return offset;
}
public int getLimit() {
return limit;
}
}
RowBounds的两个属性:offSet是指查询数据时从多少位置开始查询,limit是指返回数据的调试,默认是从0位置开始查询到Integer的最大值,默认是不做分页处理。
在selectList()执行如下:
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
// 获取MappedStatment对象
MappedStatement ms = configuration.getMappedStatement(statement);
// 通过执行器查询数据结果
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
前面讲了数据是如何查询的,我们在看看其他的insert、update、delete方法
@Override
public int insert(String statement) {
return insert(statement, null);
}
@Override
public int insert(String statement, Object parameter) {
return update(statement, parameter);
}
@Override
public int update(String statement) {
return update(statement, null);
}
@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();
}
}
@Override
public int delete(String statement) {
return update(statement, null);
}
@Override
public int delete(String statement, Object parameter) {
return update(statement, parameter);
}
可以看出SqlSession的insert和delete的方法最终都是调用了update方法,而update方法最终也是调用了Executor的update由此可得出结论sqlSession虽然叫程序和数据库之间的SQL会话,但是它并没有具体去执行sql语句,最终的sql语句的执行是由执行器Executor执行的,而SqlSession的作用只是创建了MappedStatement对象以及调用执行器去执行SQL其他的commit、rollback方法同样最终都是调用的执行器Executor的对应的方法,那么接下来就去了解下执行器Executor是如何执行的,以及SqlSession创建的MappedStatement又是什么?
DefaultSqlSession完整源码如下:
/**
* Copyright 2009-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ibatis.session.defaults;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.binding.BindingException;
import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.exceptions.ExceptionFactory;
import org.apache.ibatis.exceptions.TooManyResultsException;
import org.apache.ibatis.executor.BatchResult;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.result.DefaultMapResultHandler;
import org.apache.ibatis.executor.result.DefaultResultContext;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.session.SqlSession;
/**
*
* The default implementation for {@link SqlSession}.
* Note that this class is not Thread-Safe.
*
* @author Clinton Begin
*/
public class DefaultSqlSession implements SqlSession {
private Configuration configuration;
private Executor executor;
private boolean autoCommit;
private boolean dirty;
private List<Cursor<?>> cursorList;
public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
this.configuration = configuration;
this.executor = executor;
this.dirty = false;
this.autoCommit = autoCommit;
}
public DefaultSqlSession(Configuration configuration, Executor executor) {
this(configuration, executor, false);
}
@Override
public <T> T selectOne(String statement) {
return this.<T>selectOne(statement, null);
}
@Override
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
List<T> list = this.<T>selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
@Override
public <K, V> Map<K, V> selectMap(String statement, String mapKey) {
return this.selectMap(statement, null, mapKey, RowBounds.DEFAULT);
}
@Override
public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey) {
return this.selectMap(statement, parameter, mapKey, RowBounds.DEFAULT);
}
@Override
public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
final List<? extends V> list = selectList(statement, parameter, rowBounds);
final DefaultMapResultHandler<K, V> mapResultHandler = new DefaultMapResultHandler<K, V>(mapKey,
configuration.getObjectFactory(), configuration.getObjectWrapperFactory(), configuration.getReflectorFactory());
final DefaultResultContext<V> context = new DefaultResultContext<V>();
for (V o : list) {
context.nextResultObject(o);
mapResultHandler.handleResult(context);
}
return mapResultHandler.getMappedResults();
}
@Override
public <T> Cursor<T> selectCursor(String statement) {
return selectCursor(statement, null);
}
@Override
public <T> Cursor<T> selectCursor(String statement, Object parameter) {
return selectCursor(statement, parameter, RowBounds.DEFAULT);
}
@Override
public <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
Cursor<T> cursor = executor.queryCursor(ms, wrapCollection(parameter), rowBounds);
registerCursor(cursor);
return cursor;
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
@Override
public <E> List<E> selectList(String statement) {
return this.selectList(statement, null);
}
@Override
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
@Override
public void select(String statement, Object parameter, ResultHandler handler) {
select(statement, parameter, RowBounds.DEFAULT, handler);
}
@Override
public void select(String statement, ResultHandler handler) {
select(statement, null, RowBounds.DEFAULT, handler);
}
@Override
public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
@Override
public int insert(String statement) {
return insert(statement, null);
}
@Override
public int insert(String statement, Object parameter) {
return update(statement, parameter);
}
@Override
public int update(String statement) {
return update(statement, null);
}
@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();
}
}
@Override
public int delete(String statement) {
return update(statement, null);
}
@Override
public int delete(String statement, Object parameter) {
return update(statement, parameter);
}
@Override
public void commit() {
commit(false);
}
@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();
}
}
@Override
public void rollback() {
rollback(false);
}
@Override
public void rollback(boolean force) {
try {
executor.rollback(isCommitOrRollbackRequired(force));
dirty = false;
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error rolling back transaction. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
@Override
public List<BatchResult> flushStatements() {
try {
return executor.flushStatements();
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error flushing statements. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
@Override
public void close() {
try {
executor.close(isCommitOrRollbackRequired(false));
closeCursors();
dirty = false;
} finally {
ErrorContext.instance().reset();
}
}
private void closeCursors() {
if (cursorList != null && cursorList.size() != 0) {
for (Cursor<?> cursor : cursorList) {
try {
cursor.close();
} catch (IOException e) {
throw ExceptionFactory.wrapException("Error closing cursor. Cause: " + e, e);
}
}
cursorList.clear();
}
}
@Override
public Configuration getConfiguration() {
return configuration;
}
@Override
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
@Override
public Connection getConnection() {
try {
return executor.getTransaction().getConnection();
} catch (SQLException e) {
throw ExceptionFactory.wrapException("Error getting a new connection. Cause: " + e, e);
}
}
@Override
public void clearCache() {
executor.clearLocalCache();
}
private <T> void registerCursor(Cursor<T> cursor) {
if (cursorList == null) {
cursorList = new ArrayList<Cursor<?>>();
}
cursorList.add(cursor);
}
private boolean isCommitOrRollbackRequired(boolean force) {
return (!autoCommit && dirty) || force;
}
private Object wrapCollection(final Object object) {
if (object instanceof Collection) {
StrictMap<Object> map = new StrictMap<Object>();
map.put("collection", object);
if (object instanceof List) {
map.put("list", object);
}
return map;
} else if (object != null && object.getClass().isArray()) {
StrictMap<Object> map = new StrictMap<Object>();
map.put("array", object);
return map;
}
return object;
}
public static class StrictMap<V> extends HashMap<String, V> {
private static final long serialVersionUID = -5741767162221585340L;
@Override
public V get(Object key) {
if (!super.containsKey(key)) {
throw new BindingException("Parameter '" + key + "' not found. Available parameters are " + this.keySet());
}
return super.get(key);
}
}
}
8. MappedStatement 分析
MappedStatement类位于mybatis包的org.apache.ibatis.mapping目录下,是一个final类型也就是说实例化之后就不允许改变MappedStatement对象对应Mapper.xml配置文件中的一个select/update/insert/delete节点,描述的就是一条SQL语句,属性如下:
//mapper配置文件名,如:UserMapper.xml
private String resource;
//全局配置
private Configuration configuration;
//Mapper.xml文件中节点的id属性加命名空间,如:com.lucky.mybatis.dao.UserMapper.selectByExample
private String id;
private Integer fetchSize;
//超时时间
private Integer timeout;
//操作SQL的对象的类型
private StatementType statementType;
//结果类型
private ResultSetType resultSetType;
//sql语句
private SqlSource sqlSource;
//缓存
private Cache cache;
private ParameterMap parameterMap;
private List<ResultMap> resultMaps;
private boolean flushCacheRequired;
//是否使用缓存,默认为true
private boolean useCache;
//结果是否排序
private boolean resultOrdered;
//sql语句的类型,如select、update、delete、insert
private SqlCommandType sqlCommandType;
private KeyGenerator keyGenerator;
private String[] keyProperties;
private String[] keyColumns;
private boolean hasNestedResultMaps;
//数据库ID
private String databaseId;
private Log statementLog;
private LanguageDriver lang;
private String[] resultSets;
StatementType指操作SQL对象的类型,是个枚举类型,值分别为:
- STATEMENT(直接操作SQL,不进行预编译),
- PREPARED(预处理参数,进行预编译,获取数据),
- CALLABLE(执行存储过程)
ResultSetType指返回结果集的类型,也是个枚举类型,值分别为:
- FORWARD_ONLY:结果集的游标只能向下滚动
- SCROLL_INSENSITIVE:结果集的游标可以上下移动,当数据库变化时当前结果集不变
- SCROLL_SENSITIVE:结果集客自由滚动,数据库变化时当前结果集同步改变
现在我们知道一个MappedStatement对象对应一个mapper.xml中的一个SQL节点,而Mapper.xml文件是初始化Configuration对象的时候进行解析加载的,则说明MappedStatement对象就是在初始化Configuration对象的时候创建的,并且是final类型不可更改。
之前我们知道Configuration对象的初始化过程,是通过XMLConfigBuilder类的parse方法进行初始化的,现在来看下是如何初始化MappedStatement对象的和Configuration对象初始化代码如下:
8.1. 创建Configuration对象
/**
* 在SqlSessionFactoryBuilder类中调用
*/
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
// 创建Configuration对象,此时的Configuration对象没有任何内容,仅仅是创建这么个对象
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
public Configuration parse() {
//判断Configuration是否解析过,Configuration是全局变量,只需要解析创建一次即可
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
//调用下面的方法,parser.evalNode("/configuration")解析XML配置得到XNode对象
//parseConfiguration 为Configuration对象赋值
parseConfiguration(parser.evalNode("/configuration"));
// 返回Configuration对象
return configuration;
}
/**
* 根据root中存储的是configuration节点的内容
* @param root
*/
private void parseConfiguration(XNode root) {
try {
// issue #117 read properties first
//设置properties配置
propertiesElement(root.evalNode("properties"));
//设置settings配置
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
//设置typeAliases配置
typeAliasesElement(root.evalNode("typeAliases"));
//设置plugins配置
pluginElement(root.evalNode("plugins"));
//设置objectFactory配置
objectFactoryElement(root.evalNode("objectFactory"));
//设置objectWrapperFactory配置
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
//设置reflectFactory配置
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
//设置environments配置
environmentsElement(root.evalNode("environments"));
//设置databaseIdProvider配置
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//设置typeHandlers配置
typeHandlerElement(root.evalNode("typeHandlers"));
//设置mappers配置
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
8.2. 解析xxxMapper.xml文件
parseConfiguration()方法是根据XNode对Configuration对象进行属性赋值,mapperElement()方法即解析标签中的内容,mapperElement()方法源码如下:
private void mapperElement(XNode parent) throws Exception {
//parent是Configuration配置文件中的<mappers>标签
if (parent != null) {
//遍历<mappers>标签下的所有子标签
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
//加载package包下的所有mapper
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);//加载packege包下的所有mapper
} else {
//按resource或url或class加载单个mapper
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();//解析xml文件流
} 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();//解析xml文件流
} else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);//加载指定接口的mapper
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
mapperElement()方法有三种方式加载mapper,一个是批量加载指定package下所有mapper,一个是根据mapper接口路径加载指定mapper,还有一种是解析mapper.xml文件流进行加载
8.3. 初始化MappedStatement
先来看个最简单的,根据指定接口加载mapper,也就是configuration.addMapper(mapperInterface)方法,源码如下:
addMapper()方法
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}
调用了mapperRegistry的addMapper方法。mapperRegistry是Configuration类的一个属性
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
根据MapperRegistry的名字可以理解为此类的作用是mapper的注册中心,用于注册mapper,MapperRegistry类源码如下:
package org.apache.ibatis.binding;
import org.apache.ibatis.builder.annotation.MapperAnnotationBuilder;
import org.apache.ibatis.io.ResolverUtil;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class MapperRegistry {
//...省略代码
//新增一个mapper
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 {
//加载指定的mapper接口
knownMappers.put(type, new MapperProxyFactory<T>(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);
}
}
}
}
// 省略代码
}
看MapperAnnotationBuilder(mapper注解构造类)MapperAnnotationBuilder类的构造方法如下:
public MapperAnnotationBuilder(Configuration configuration, Class<?> type) {
String resource = type.getName().replace('.', '/') + ".java (best guess)";
this.assistant = new MapperBuilderAssistant(configuration, resource);
this.configuration = configuration;
this.type = type;
sqlAnnotationTypes.add(Select.class);
sqlAnnotationTypes.add(Insert.class);
sqlAnnotationTypes.add(Update.class);
sqlAnnotationTypes.add(Delete.class);
sqlProviderAnnotationTypes.add(SelectProvider.class);
sqlProviderAnnotationTypes.add(InsertProvider.class);
sqlProviderAnnotationTypes.add(UpdateProvider.class);
sqlProviderAnnotationTypes.add(DeleteProvider.class);
}
MapperAnnotationBuilder类的主要属性有:
private final Set<Class<? extends Annotation>> sqlAnnotationTypes = new HashSet<Class<? extends Annotation>>();//sql语句上的注解
private final Set<Class<? extends Annotation>> sqlProviderAnnotationTypes = new HashSet<Class<? extends Annotation>>();//指定类指定方法上的sql语句注解
private Configuration configuration;//全局配置对象
private MapperBuilderAssistant assistant;//Mapper构建助手类,组装解析出来的配置,生成Cache、ResultMap以及MappedStatement对象
private Class<?> type;//解析的目标mapper接口的Class对象
MapperAnnotationBuilder构造方法就是给自己的属性进行了初始化,其中两个Set集合在初始化时表明了支持 @Select、@Insert、@Update、@Delete以及对应的Provider注解,本篇不再介绍,和xml配置的写法只是写法上不一样而已,实际的作用是一样,本文只分析xml这种用法。MapperAnnotationBuilder有一个parse()方法,也是创建MappedStatement的方法,源码如下:
public void parse() {
//mapper接口名,如:interface com.lucky.test.mapper.UserMapper
String resource = type.toString();
//Configuration中Set<String> loadedResources 保存着已经加载过的mapper信息
//判断是否已经加载过
if (!configuration.isResourceLoaded(resource)) {
//加载XML资源
loadXmlResource();
//加载的resource添加到Configuration的loadedResources中
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
parseCache();
parseCacheRef();
//遍历mapper的所有方法
for (Method method : type.getMethods()) {
if (!canHaveStatement(method)) {
continue;
}
if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()
&& method.getAnnotation(ResultMap.class) == null) {
parseResultMap(method);
}
try {
//
parseStatement(method);
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
}
xxxMapper.xml文件的加载是通过调用loadXmlResource()方法加载的,源码如下:
private void loadXmlResource() {
// Spring may not know the real resource name so we check a flag
// to prevent loading again a resource twice
// this flag is set at XMLMapperBuilder#bindMapperForNamespace
if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
//通过mapper接口名找到对应的mapper.xml路径
String xmlResource = type.getName().replace('.', '/') + ".xml";
// #1347
InputStream inputStream = type.getResourceAsStream("/" + xmlResource);
if (inputStream == null) {
// Search XML mapper that is not in the module but in the classpath.
try {
//读取mapper.xml文件流
inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
} catch (IOException e2) {
// ignore, resource is not required
}
}
if (inputStream != null) {
//根据mapper.xml文件流创建XMLMapperBuilder对象
XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
//执行parse方法
xmlParser.parse();
}
}
}
解析xxxMapper.xml文件后封装为XMLMapperBuilder对象,它是mapper.xml构建者类,构造方法源码如下
public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments, String namespace) {
//调用下面一个构造方法
this(inputStream, configuration, resource, sqlFragments);
this.builderAssistant.setCurrentNamespace(namespace);
}
public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
//调用下面一个构造方法
this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()),
configuration, resource, sqlFragments);
}
private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
//对自己的属性进行赋值
super(configuration);
//mapper构造者助手
this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
//XML解析类
this.parser = parser;
//sql片段集合
this.sqlFragments = sqlFragments;
//mapper名称
this.resource = resource;
}
那么再看XMLMapperBuilder类的parse()方法,源码如下:
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
//从XML解析类中获取mapper标签的内容
configurationElement(parser.evalNode("/mapper"));
//将加载过的resource添加到Configuration中
configuration.addLoadedResource(resource);
//绑定mapper到对应mapper的命名空间中
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
解析xml的主要方法就是configurationElement()方法,源码如下:
private void configurationElement(XNode context) {
try {
//从xml中获取namespace标签内容
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.isEmpty()) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
//获取cache-ref标签内容
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"));
//解析获取各种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);
}
}
至此,我们已经对xxxMapper.xml文件进行解析,并封装为XMLMapperBuilder对象。然后我们回到MapperAnnotationBuilder类的parse()方法中是如何将XMLMapperBuilder对象转换为MappedStatement对象的,在parse()方法中会调用parseStatement()方法创建MappedStatement对象,源码如下:
void parseStatement(Method method) {
Class<?> parameterTypeClass = getParameterType(method);
LanguageDriver languageDriver = getLanguageDriver(method);
SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
if (sqlSource != null) {
Options options = method.getAnnotation(Options.class);
final String mappedStatementId = type.getName() + "." + method.getName();
Integer fetchSize = null;
Integer timeout = null;
StatementType statementType = StatementType.PREPARED;
ResultSetType resultSetType = ResultSetType.FORWARD_ONLY;
SqlCommandType sqlCommandType = getSqlCommandType(method);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = !isSelect;
boolean useCache = isSelect;
KeyGenerator keyGenerator;
String keyProperty = "id";
String keyColumn = null;
if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
// first check for SelectKey annotation - that overrides everything else
SelectKey selectKey = method.getAnnotation(SelectKey.class);
if (selectKey != null) {
keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
keyProperty = selectKey.keyProperty();
} else if (options == null) {
keyGenerator = configuration.isUseGeneratedKeys() ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
} else {
keyGenerator = options.useGeneratedKeys() ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
keyProperty = options.keyProperty();
keyColumn = options.keyColumn();
}
} else {
keyGenerator = new NoKeyGenerator();
}
if (options != null) {
if (FlushCachePolicy.TRUE.equals(options.flushCache())) {
flushCache = true;
} else if (FlushCachePolicy.FALSE.equals(options.flushCache())) {
flushCache = false;
}
useCache = options.useCache();
fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348
timeout = options.timeout() > -1 ? options.timeout() : null;
statementType = options.statementType();
resultSetType = options.resultSetType();
}
String resultMapId = null;
ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
if (resultMapAnnotation != null) {
String[] resultMaps = resultMapAnnotation.value();
StringBuilder sb = new StringBuilder();
for (String resultMap : resultMaps) {
if (sb.length() > 0) {
sb.append(",");
}
sb.append(resultMap);
}
resultMapId = sb.toString();
} else if (isSelect) {
resultMapId = parseResultMap(method);
}
assistant.addMappedStatement(
mappedStatementId,
sqlSource,
statementType,
sqlCommandType,
fetchSize,
timeout,
// ParameterMapID
null,
parameterTypeClass,
resultMapId,
getReturnType(method),
resultSetType,
flushCache,
useCache,
// TODO gcode issue #577
false,
keyGenerator,
keyProperty,
keyColumn,
// DatabaseID
null,
languageDriver,
// ResultSets
options != null ? nullOrEmpty(options.resultSets()) : null);
}
}
调用MapperBuilderAssistant对象的addMappedStatement方法创建了MappedStatement对象,源码如下:
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();
// 将MappedStatment对象放到Configuration对象中的map集合中
configuration.addMappedStatement(statement);
return statement;
}
创建了MappedStatement对象之后,就调用了Configuration的addMappedStatement方法,从而添加到了Configuration中的mappedStatements集合中,而key就是MappedStatement的id
总结:
MapperRegistry就是Mapper的注册中心,有两个属性一个是全局配置Configuration还有一个是已经加载过的mapper集合 knownMappers
新增一个mapper的方法就是addMapper,就是向knownsMappers集合中put一条新的mapper记录,key就是mapper的类名全称,value是这个mapper的代理工厂;
分析到这里,发现Configuration对象初始化的时候会解析所有的xml文件中配置的所有mapper接口,并添加到Configuration的mapper集合knowMappers中,但是貌似还没有MappedStatement的影子,也没有看到哪里解析了mapper.xml配置。
不用急,上面源码的第52行就是了,52到54行的意思目前还没有看源码,但是先猜测下:52行是通过Configuration对象和mapper类来构造一个MapperAnnotationBuilder对象,通过字面意思是Mapper的构建类,而第53行的parse(),应该就是解析mapper.xml文件的,第54行标记加载完成,
只有当mapper接口和mapper.xml匹配成功才能叫做是加载成功,所以下一章篇就再来看看MappedStatement是如何创建的。
总结:MappedStatement类就是对应的Mapper.xml中的一个sql语句
9. 执行Mapper接口
我们已经知道MyBatis是如何加载Mapper接口和Mapper.xml对应的sql语句到Configuration对象的,这里我们分析Mapper接口是如何执行的
首先还是SqlSession接口的一个方法说起,也就是
T getMapper(Class type);
很显然这个方法是更加Class名获取该类的一个实例,而Mapper接口只定义了接口没有实现类,那么猜想可知返回的应该就是更加mapper.xml生成的实例了。分析默认实现类DefaultSqlSession类
@Override
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
方法很简单,调用了Configuration对象的getMapper方法,那么接下来再看下Configuration里面是如何实现的。代码如下:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
调用了mapperRegistry的getMapper方法,参数分别是Class对象和sqlSession,再继续看MapperRegistry的实现,代码如下:
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession)
{
// 从konwMappers获取MapperProxyFactory对象
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>)knownMappers.get(type);
if (mapperProxyFactory == null)
{
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try
{
// 通过mapper代理工厂创建新实例
return mapperProxyFactory.newInstance(sqlSession);
}
catch (Exception e)
{
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
可以看出是根据type从knowMappers集合中获取该mapper的代理工厂类,如何通过该代理工厂新建一个实例。再看下代理工厂是如何创建实例的,代码如下:
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
//通过代理获取mapper接口的新实例
//代理对象的InvocationHandler对象是MapperProxy对象
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
//创建mapper代理对象,调用newInstance方法
// MapperProxy是一个InvocationHandler
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
那么现在我们就知道是如何根据Mapper.class来获取Mapper接口的实例的了,不过,到目前为止貌似还是没有看到和MappedStatement产生联系,别急,再往下看通过代理产生的mapper实例执行具体的方法是如何进行的。代理对象首先会执行InvocationHandler的invoke()方法,也就是MapperProxy类的invoke()方法,代码如下:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//判断执行的方法是否是来自父类Object类的方法,也就是如toString、hashCode等方法
//如果是则直接通过反射执行该方法,如果不是Object的方法则再往下走,如果不加这个判断会发生什么呢?
//由于mapper接口除了定义的接口方法还包括继承于Object的方法,如果不加判断则会继续往下走,而下面的执行过程是从mapper.xml寻找对应的实现方法,
//由于mapper.xml只实现了mapper中的接口方法,而没有toString和hashCode方法,从而就会导致这些方法无法被实现。
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
//执行mapperMethod对象的execute方法
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
//从缓存中根据method对象获取MapperMethod对象
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
//如果mapperMethod为空则新建MapperMethod方法
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
可以看出代理执行mapper接口的方法会先创建一个MapperMethod对象,然后执行execute方法,代码如下:
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
if (SqlCommandType.INSERT == command.getType()) {//如果执行insert命令
Object param = method.convertArgsToSqlCommandParam(args);//构建参数
result = rowCountResult(sqlSession.insert(command.getName(), param));//调用sqlSession的insert方法
} else if (SqlCommandType.UPDATE == command.getType()) {//如果执行update命令
Object param = method.convertArgsToSqlCommandParam(args);//构建参数
result = rowCountResult(sqlSession.update(command.getName(), param));//调用sqlSession的update方法
} else if (SqlCommandType.DELETE == command.getType()) {//如果执行delete命令
Object param = method.convertArgsToSqlCommandParam(args);//构建参数
result = rowCountResult(sqlSession.delete(command.getName(), param));//调用sqlSession的delete方法
} else if (SqlCommandType.SELECT == command.getType()) {//如果执行select命令
if (method.returnsVoid() && method.hasResultHandler()) {//判断接口返回类型,更加返回数据类型执行对应的select语句
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
} else if (SqlCommandType.FLUSH == command.getType()) {
result = sqlSession.flushStatements();
} else {
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
可以看出大致的执行过程就是更加MapperMethod的方法类型,然后构建对应的参数,然后执行sqlSession的方法。到现在还是没有MappedStatement的影子,再看看MapperMethod是被创建的。
private final SqlCommand command;
private final MethodSignature method;
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
这里涉及到了两个类SqlCommand和MethodSignature,先从SqlCommand看起。
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
String statementName = mapperInterface.getName() + "." + method.getName();//接口方法名
MappedStatement ms = null;
if (configuration.hasStatement(statementName)) {
//从configuration中根据接口名获取MappedStatement对象
ms = configuration.getMappedStatement(statementName);
} else if (!mapperInterface.equals(method.getDeclaringClass())) { // issue #35
//如果该方法不是该mapper接口的方法,则从mapper的父类中找寻该接口对应的MappedStatement对象
String parentStatementName = method.getDeclaringClass().getName() + "." + method.getName();
if (configuration.hasStatement(parentStatementName)) {
ms = configuration.getMappedStatement(parentStatementName);
}
}
if (ms == null) {
if(method.getAnnotation(Flush.class) != null){
name = null;
type = SqlCommandType.FLUSH;
} else {
throw new BindingException("Invalid bound statement (not found): " + statementName);
}
} else {
name = ms.getId();//设置name为MappedStatement的id,而id的值就是xml中对应的sql语句
type = ms.getSqlCommandType();//设置type为MappedStatement的sql类型
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
}
到这里终于是看到了MappedStatement的身影,根据mapper的Class对象和method对象从Configuration对象中获取指定的MappedStatement对象,然后根据MappedStatement对象的值初始化SqlCommand对象的属性。而MethodSignature则是sql语句的签名,主要作用就是对sql参数与返回结果类型的判断。
总结:
1、sqlSession调用configuration对象的getMapper方法,configuration调用mapperRegistry的getMapper方法
2、mapperRegistry根据mapper获取对应的Mapper代理工厂
3、通过mapper代理工厂创建mapper的代理
4、执行mapper方法时,通过代理调用,创建该mapper方法的MapperMethod对象
5、MapperMethod对象的创建是通过从configuration对象中获取指定的MappedStatement对象来获取具体的sql语句以及参数和返回结果类型
6、调用sqlSession对应的insert、update、delete和select方法执行mapper的方法
10. 执行器Executor解析
我们知道了sql的具体执行是通过调用SqlSession接口的对应的方法去执行的,而SqlSession最终都是通过调用了自己的Executor对象的query和update去执行的。本文就分析下sql的执行器——-Executor
Executor是mybatis的sql执行器,SqlSession是面向程序的,而Executor则就是面向数据库的,先看下Executor接口的方法有哪些,源码如下:
public interface Executor {
ResultHandler NO_RESULT_HANDLER = null;
int update(MappedStatement ms, Object parameter) throws SQLException;
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
<E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;
List<BatchResult> flushStatements() throws SQLException;
void commit(boolean required) throws SQLException;
void rollback(boolean required) throws SQLException;
CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);
boolean isCached(MappedStatement ms, CacheKey key);
void clearLocalCache();
void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);
Transaction getTransaction();
void close(boolean forceRollback);
boolean isClosed();
void setExecutorWrapper(Executor executor);
}
和SqlSession一样定义了各种各样的sql执行的方法,有查询的query方法,有更新的update方法,以及和事务有关的commit方法和rollback方法等,接下来就以query方法为例,看下具体是如何执行的。
Executor接口共有两个实现类,分别是BaseExecutor和CachingExecutor,CachingExecutor是缓存执行器,后面会提到,现在先看下BaseExecutor
BaseExecutor的属性有:
private static final Log log = LogFactory.getLog(BaseExecutor.class);
//事务
protected Transaction transaction;
//执行器包装者
protected Executor wrapper;
//线程安全队列
protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
//本地缓存
protected PerpetualCache localCache;
protected PerpetualCache localOutputParameterCache;
protected Configuration configuration;
//查询次数栈
protected int queryStack;
//是否已关闭(回滚的时候会被关闭)
private boolean closed;
再看下BaseExecutor执行query方法的源码:
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {//判断执行器是否已关闭
throw new ExecutorException("Executor was closed.");
}
//如果查询次数栈为0并且MappedStatement可以清除缓存,则清除本地缓存
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;//查询次数+1
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;//从缓存中根据缓存key查询是否有缓存
if (list != null) {
//如果缓存中有数据,则处理缓存
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
//如果缓存中没有数据,则从数据库查询数据
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
//查询次数-1
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
可以看出BaseExecutor会优先从缓存中查询数据,如果缓存不为空再从数据库数据。在这里有一个queryStack会进行自增自减,它的作用是什么呢?先看下如果没有缓存的话,BaseExecutor是怎么从数据库查询数据的:
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);//执行doQuery方法
} finally {
localCache.removeObject(key);
}
localCache.putObject(key, list);//将查询结果放入缓存
if (ms.getStatementType() == StatementType.CALLABLE) {//如果callable类型查询
localOutputParameterCache.putObject(key, parameter);//将参数放入缓存中
}
return list;
}
可见该方法是调用了doQuery方法从数据库查询了数据,然后将查询的结果及查询用的参数放入了缓存中,而doQuery方法是BaseExecutor中的抽象方法,具体的实现是由BaseExecutor的子类进行实现
protected abstract int doUpdate(MappedStatement ms, Object parameter)
throws SQLException;
protected abstract List<BatchResult> doFlushStatements(boolean isRollback)
throws SQLException;
protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
throws SQLException;
protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)
throws SQLException;
protected void closeStatement(Statement statement) {
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
// ignore
}
}
}
BaseExecutor共有SimpleExecutor、ReuseExecutor、BatchExecutor以及ClosedExecutor四个子类,这个后面再分析,现在我们以及知道了SqlSession是调用了Executor的方法来执行sql,而Executor的默认实现类的BaseExecutor,而BaseExecutor又是调用了其子类的方法。而BaseExecutor则对查询的结果进行了缓存处理以及查询的时候会从缓存中进行查询。
11. 总结
- 读取mybatis.xml配置文件到Reader或者InputStream流对象中
- 在SqlSessionFactoryBuilder中通过build方法中创建XmlConfigBuilder 对象解析流生成Configuration对象,Configuration对象中包括读取Mapper接口(接口代理工厂对象)、读取每个接口对应的每条Sql封装成MapperStatment对象等,都放在Configuration对象中
- 在SqlSessionFactoryBuilder中的build方法中,通过Configuration对象创建SqlSessionFactory对象
- 通过SqlSessionFactory对象的openSession方法创建SqlSession对象
- 在SqlSession对象中调用getMapper方法获取接口代理工厂对象
- 执行代理接口方法时,接口代理对象中的invok方法会调用MethodProxy对象的invoke方法,MethodProxy是一个InvocationHandler对象。这个方法会创建一个MapperMethod对象,MapperMethod构造函数会根据执行接口名+方法名找出对应的MapperStatment,execute方法会根据Sql类型调用sqlSession的不同insert、update、delete、select方法。
- SqlSession对象最终会将方法执行转交给xecutor对象执行