看了很多博客和书籍,上来直接介绍某一个组件是用来干啥的、起什么作用等,这样直接讲原理让人看不下去,阅读源码最好方式是通过案例debug来一步一步看调用过程,先了解代码调用的主干,理解了主干之后再去仔细研究每个组件的作用以及原因会比较好,接下来通过案例debug+画图的方式一步一步了解mybatis的执行流程。
一、加载mybatis主配置,生成会话工厂SqlSessionFactory
1.1案例
- 首先编写测试代码:MyBatis中SqlSession实例使用工厂模式创建,所以在创建SqlSession实例之前,需要先调用SqlSessionFactoryBuilder的openSession()方法创建会话工厂SqlSessionFactory,然后通过SqlSessionFactory对象的openSession()方法来创建SqlSession对象,接下来来看案例代码:
public class UserTest {
private InputStream inputStream;
private SqlSessionFactoryBuilder sqlSessionFactoryBuilder;
private SqlSessionFactory sqlSessionFactory;
private SqlSession sqlSession;
private IUserDao iUserDao;
@Before
public void init() throws IOException {
inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
sqlSession = sqlSessionFactory.openSession();
iUserDao = sqlSession.getMapper(IUserDao.class);
}
@After
public void destory() throws IOException {
sqlSession.commit();
sqlSession.close();
inputStream.close();
}
@Test
public void testFindUserById(){
//同一个mapper,不同的sqlSession
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
//
IUserDao iUserDao1 = sqlSession1.getMapper(IUserDao.class);
IUserDao iUserDao2 = sqlSession2.getMapper(IUserDao.class);
//
User user1 = iUserDao1.findUserById(1);
sqlSession1.close();
User user2 = iUserDao2.findUserById(1);
sqlSession2.close();
System.out.println(user1);
System.out.println(user2);
System.out.println(user1==user2);
}
}
从上面的代码可以看到,为了创建出SqlSessionFactory对象,首先需要new出一个SqlSessionFactoryBuilder对象,然后以mybatis-config.xml主配置文件为参数,调用build(InputStream inputStream)方法来生成一个SqlSessionFactory对象。
- mybatis框架启动之后,首先创建SqlSessionFactoryBuilder对象,然后调用Resources的getResourceAsStream(“mybatis-config.xml”)方法将mybatis主配置文件生成一个InputStream输入流对象,然后以输入流对象作为参数,调用SqlSessionFactoryBuilder对象的build(inputStream)方法,然后build(inputStream)方法会调用另一个重载方法build(InputStream inputStream,Environment environment,Properties properties)方法。首先来看DefaultSqlSessionFactory的build()方法:
public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
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.
}
}
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
SqlSessionFactoryBuilder对象主要提供了根据字节流、字符流以及配置参数这三个参数组成的各种build()方法来实现创建SqlSessionFactory对象的,最终会在build(inputStream,environment,properties)方法中调用build(Configuration configuration)这个重载方法来构造SqlSessionFactory工厂。
- 上述build()方法首先会调用XMLConfigBuilder构建器的构造方法来新建一个parser对象,然后调用XMLConfiguration类中的parse()方法来解析mybatis主配置文件mybatis-config.xml以及SQL映射文件Mapper.xml,然后将解析的参数放入Configuration对象中,最终Configuration对象返回,接下来看XMLConfiguration对象的parse方法:
public class XMLConfigBuilder extends BaseBuilder {
public Configuration parse() {
//防止pase()方法被同一个实例调用多次,即同一个实例只能创建一个Configuration对象
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
//调用parseConfiguration()方法对XNode进行处理
parseConfiguration(parser.evalNode("/configuration"));
//处理完成之后会将解析后的主配置文件属性以及数值放到Configuration对象中,然后将此对象返回给SqlSessionFactoryBuilder的build()方法进行调用
return configuration;
}
}
- 在XMLConfigBuilder对象中,首先会调用BaseBuilder来构造一个Configuration对象,将此对象返回给XMLConfigBuilder,具体的代码如下:
public class XMLConfigBuilder extends BaseBuilder {
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;
}
}
public abstract class BaseBuilder {
protected final Configuration configuration;
public BaseBuilder(Configuration configuration) {
this.configuration = configuration;
this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
}
public Configuration getConfiguration() {
return configuration;
}
}
- 在parse()方法中首先会调用XMLPathPaser对象的evalNode()方法获取XML配置文件中节点对应的XNode对象,然后调用XMLConfigBuilder的parseConfiguration()方法来解析配置文件获取更多信息,parseConfiguration()的具体实现如下:
public class XMLConfigBuilder extends BaseBuilder {
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);
}
}
}
MyBatis的主配置文件和Mapper映射配置文件都使用XML格式,MyBatis中的Configuration组件用于描述主配置文件信息,框架启动的时候会解析XML配置,将配置信息转换为Configuration对象。在parseConfiguration()方法中,对于标签的子节点,都有一个单独的处理方法,例如使用propertiesElement()方法解析标签,使用pluginElement()方法解析标签,在解析完成之后会将标签名和属性放到Configuration对象中,然后将Configuration对象作为参数,使用XMLConfigBuilder的build方法进行解析,至于具体的解析细节篇幅原因这里不做过多解读。
- 从上面的代码可以知道,从propertiesElement(root.evalNode(“properties”))到typeHandlerElement(root.evalNode(“typeHandlers”))均是对mybatis主配置文件mybatis-config.xml进行解析的,主要过程是解析mybatis主配置文件中的节点,然后将节点中的节点名称以及节点对应的数值放到Configuration对象中。这里主要看一下最后一行:mapperElement(root.evalNode(“mappers”)),这一行代码是对mapper映射文件进行解析的,最后也是将mapper.xml文件中各个节点的名称以及数值放到Configuration对象中。接下来看XMLConfigBuilder中的mapperElement(XNode xnode)方法的具体实现:
public class XMLConfigBuilder extends BaseBuilder {
private void mapperElement(XNode parent) throws Exception {
//首先判断通过<mappers>节点拿到的元素是否为空
if (parent != null) {
//如果父节点parent不为空,则遍历其字节点
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
//如果字节点的名称是name,则调用Configuration类的addMappers方法将package包对应的所有mapper加入到Configuration对象中
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
//如果是通过resource的方式加载mapper.xml配置文件则调用下面的方法:
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
//调用XMLConfigBuiler构造器来解析mapper配置文件
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
//如果是通过url的方式来调用配置文件,也是通过XMLConfiguration的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();
//如果package为空、url为空并且resource也为空,则先加载mapper接口,然后将接口放到Configuration对象中
} 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.");
}
}
}
}
}
}
- 从上面可以看到,如果在mybatis主配置文件中,选择加载SQL映射文件的方式为resource或者url方式加载的时候,都会调用XMLConfigBuilder的parse()方法进行解析,源码实现如下:
public class XMLMapperBuilder extends BaseBuilder {
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
//如果Resource没有被加载进Configuration对象中,
configurationElement(parser.evalNode("/mapper"));
//那么首先会解析mapper节点,并将节点中的元素名称和值放入到Configuration对象中
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
}
- XMLConfigBuilder类的configurationElement()方法的具体实现如下:
public class XMLMapperBuilder extends BaseBuilder {
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.isEmpty()) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
//构造处理器
builderAssistant.setCurrentNamespace(namespace);
//缓存对象的解析
cacheRefElement(context.evalNode("cache-ref"));
//处理cache节点
cacheElement(context.evalNode("cache"));
//处理mapper节点下面的parameter节点
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//处理mapper节点下面的resultMap节点
resultMapElements(context.evalNodes("/mapper/resultMap"));
//处理mapper下面的sql语句
sqlElement(context.evalNodes("/mapper/sql"));
//绑定crud语句
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
}
- 如果在mybatis主配置文件中,选择加载SQL映射文件的方式为package方式加载所有Mapper类的时候,会调用Configuration类的addMappers()方法将包名放到Configuration对象中,addMappers()方法会调用MapperRegistry类的addMappers(packageName)方法遍历mapper包中的所有mapper接口,然后调用MapperRegistry类的addMapper方法将所有mapper接口名及其对应的值加入到Configuration对象中, 源码实现如下:
- Configuration类的addMappers()方法源码如下:
public class Configuration {
public void addMappers(String packageName) {
mapperRegistry.addMappers(packageName);
}
}
- Configuration类的addMappers()方法会调用MapperRegistry类的addMappers()方法,源码实现如下:
public class MapperRegistry {
//addMappers()方法
public void addMappers(String packageName) {
addMappers(packageName, Object.class);
}
//addMappers()方法
public void addMappers(String packageName, Class<?> superType) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
for (Class<?> mapperClass : mapperSet) {
addMapper(mapperClass);
}
}
//addMapper方法
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);
}
}
}
}
}
- 接下来看看上述调用过程的时序图(图比较大,可以点开看):
- 时序图中的调用过程如下:
>org.apache.ibatis.io.Resourcess#getResourceAsStream()
>org.apache.ibatis.session.SqlSessionFactoryBuilder#build()
>org.apache.ibatis.session.XMLConfigBuilder#parse()
>org.apache.ibatis.session.XMLConfigBuilder#parseConfiguration()
>org.apache.ibatis.session.Configuration#addMappers()
>org.apache.ibatis.binding.MapperRegistry#addMappers()
>org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#parse()
1.2Configuration
从上面的解析过程中,我们可以看到Configuration用得最多,是一个非常重要的类,接下来先说一下Configuration对象。MyBatis框架的配置信息有两个,一个是MyBatis主配置文件mybatis-config.xml,另一个是执行SQL语句的Mapper映射文件mapper.xml。Configuration对象的主要作用是描述MapBatis主配置文件以及SQL映射文件信息,Configuration定义了一系列的属性来控制MyBatis的运行时行为。首先看一下MyBatis主配置文件mybatis-config.xml的结构:
- mybatis-config.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 />
<!--打开二级缓存-->
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
<typeAliases>
<package name="com.mybatis.domain"/>
</typeAliases>
<typeHandlers></typeHandlers>
<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/mybatis"/>
<property name="username" value="root"/>
<property name="password" value="Shezeq1,"/>
</dataSource>
</environment>
</environments>
<mappers>
<package name="com.mybatis.dao"/>
</mappers>
</configuration>
主配置文件中的所有属性的作用以及配置说明如下表:
属性 | 作用 | 有效值 | 默认值 |
---|---|---|---|
cacheEnabled | 是否开启Mapper缓存即二级缓存 | true/false | true |
lazyLoadingEnabled | 延迟加载的全局开关,当开启的时候,所有的关联对象都会延迟加载。特定关联关系中可以通过设置fetchType属性来覆盖该项的开关状态 | true/false | false |
aggressiveLazyLoading | 当开启的时候,任何方法的调用都会加载该对象的所有属性。否则每个属性都会按需加载 | true/false | true |
useColumnLabel | 使用列标签代替列名。不同的驱动在这方面会有不同的表现 | true/false | true |
useGeneratedKeys | 允许JDBC支持自动生成主键,需要驱动兼容。如果设置为true,则这个设置强制使用自动生成主键 | true/false | false |
- mapper.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.mybatis.dao.IUserDao">
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"></cache>
<select id="findUserById" resultType="user" useCache="true" flushCache="false">
select * from user where id = #{id}
</select>
</mapper>
配置文件的解析流程是将上述XML描述的文件元素转换为Java对象的过程,mybatis-config.xml以及mapper.xml映射文件最终都会变成Configuration对象,其最终的对象转换关系如下:
上图左边是mybatis-config.xml全局配置文件信息,右边是mapper.xml的SQL映射文件信息。
- 接下来看Configuration对象的部分源码:
public class Configuration {
protected Environment environment;
protected boolean safeRowBoundsEnabled;
protected boolean safeResultHandlerEnabled = true;
protected boolean mapUnderscoreToCamelCase;
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;
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();
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");
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<>();
public Configuration(Environment environment) {
this();
this.environment = environment;
}
public Configuration() {
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
typeAliasRegistry.registerAlias("LRU", LruCache.class);
typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
languageRegistry.register(RawLanguageDriver.class);
}
}
上述代码的含义如下:
-
MapperRegistry:用于注册Mapper接口信息,建立Mapper接口的Class对象和MapperProxyFactory对象之间的关系,其中
-
MapperProxyFactory对象用于创建Mapper动态代理对象,静态对象。
-
InterceptorChain:用于注册MyBatis插件信息,MyBatis插件实际上就是一个拦截器。
-
TypeHandlerRegistry: 用于注册所有的TypeHandler,并建立JDBC类型与TypeHandler之间的关系。
-
TypeAliasRegistry:用于注册所有类型的别名。
-
LanguageDriverRegistry:用于注册LanguageDriver,LanguageDriver用于解析SQL配置,将配置信息转换为SqlSource对象。
-
MappedStatement:MappedStatement对象描述<insert|delete|update|select>等标签或者通过@Select,@Delete,@Update,@Insert等注解配置的SQL信息。MyBatis将所有的MappedStatement对象注册到mappedStatements属性中,其中key为Mapper的id,即Mapper的namespace命名空间然后加上id,value为MappedStatement对象
-
caches:用于注册Mapper中配置的所有缓存信息,其中Key为Cache的id,也就是Mapper的命名空间,value为Cache对象,Cache是一个动态对象。
-
resultMaps:用于注册Mapper配置文件中通过标签配置的ResultMap信息,ResultMap用于建立Java实体属性与数据库字段之间的映射关系,其中Key为ResultMap的id,该id是由Mapper命名空间和标签的id属性组成的,value为解析标签后得到的ResultMap对象。
-
parameterMaps:用于注册Mapper中通过标签注册的参数映射信息。Key为ParameterMap的Id,由Mapper命名空间和标签的id属性构成,Value为解析标签后得到的ParameterMap对象。
-
keyGenerators:用于注册KeyGenerator,KeyGenerator是MyBatis的主键生成器,MyBatis中提供了3种KeyGenerator,即Jdbc3KeyGenerator(数据库自增主键)、NoKeyGenerator(无自增主键)、SelectKeyGenerator(通过select语句查询自增主键,例如oracle的sequence)。
-
loadedResources:用于注册所有Mapper XML配置文件路径。sqlFragments:用于注册Mapper中通过标签配置的SQL片段,Key为SQL片段的Id,Value为MyBatis封装的表示XML节点的XNode对象。
-
incompleteStatements:用于注册解析出现异常的XMLStatementBuilder对象。
-
incompleteCacheRefs:用于注册解析出现异常的CacheRefResolver对象。
-
incompleteResultMaps:用于注册解析出现异常的ResultMapResolver对象。
-
incompleteMethods:用于注册解析出现异常的MethodResolver对象。
-
除了上述功能之外,Configuration还作为Executor、StatementHandler、ResultSetHandler、ParameterHandler等组件的工厂类,用于创建这些组件的实例,Configuration类中提供了这些组件的工厂方法,这些工厂方法的签名如下:
-
newParameterHandler
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
- newResultSetHandler
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
ResultHandler resultHandler, BoundSql boundSql) {
ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
}
- newStatementHandler
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
- newExecutor
public Executor newExecutor(Transaction transaction) {
return newExecutor(transaction, defaultExecutorType);
}
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
这些工厂方法会根据MyBatis不同的配置创建对应的实现类。例如,Executor组件有4种不同的实现,分别为BatchExecutor、ReuseExecutor、SimpleExecutor、CachingExecutor,当defaultExecutorType的参数值为REUSE时,newExecutor()方法返回的是ReuseExecutor实例,当参数值为SIMPLE时,返回的是SimpleExecutor实例,这是典型的工厂方法模式的应用。
二、使用会话工厂SqlSessionFactory,创建SqlSession
2.1案例
- 首先编写测试代码:MyBatis中SqlSession实例使用工厂模式创建,所以在创建SqlSession实例之前,需要先调用SqlSessionFactoryBuilder创建一个会话工厂SqlSessionFactory,然后通过SqlSessionFactory对象的openSessin()重载方法来创建SqlSession对象。清除其他断点,在sqlSession=SqlSessionFactory.openSession(true);上打断点,然后进行debug,接下来看案例代码:
- UserTest
public class UserTest {
private InputStream inputStream;
private SqlSessionFactoryBuilder sqlSessionFactoryBuilder;
private SqlSessionFactory sqlSessionFactory;
private SqlSession sqlSession;
private IUserDao iUserDao;
@Before
public void init() throws IOException {
inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
sqlSession = sqlSessionFactory.openSession();
iUserDao = sqlSession.getMapper(IUserDao.class);
}
@After
public void destory() throws IOException {
sqlSession.commit();
sqlSession.close();
inputStream.close();
}
@Test
public void testFindUserById(){
//同一个mapper,不同的sqlSession
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
//
IUserDao iUserDao1 = sqlSession1.getMapper(IUserDao.class);
IUserDao iUserDao2 = sqlSession2.getMapper(IUserDao.class);
//
User user1 = iUserDao1.findUserById(1);
sqlSession1.close();
User user2 = iUserDao2.findUserById(1);
sqlSession2.close();
System.out.println(user1);
System.out.println(user2);
System.out.println(user1==user2);
}
}
- 首先DefaultSqlsessionFactory对象为了创建SqlSession对象重载了多个方法,除了默认的无参数构造方法,还指定了自动提交、事物隔离级别以及指定执行器的类型,其中执行器类型是一个枚举,对应的MyBatis提供了三种类型的执行器:SIMPLE、REUSE、BATCH。会调用DefaultSqlSessionFactory类的open()方法。下面看DefaultSqlSessionFactory类的openSession()方法:
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);
}
}
- 如上图所示,openSession()方法直接调用DefaultSqlSessionFactory类的openSessionFromDataSource方法来创建SqlSession实例,从数据库中查找资源构建会话,具体的实现如下:
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
//首先这里拿到MyBatis的主配置文件的environment的环境变量信息
final Environment environment = configuration.getEnvironment();
//拿到事物管理器工厂
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
//创建事物管理器,需要的参数是dataSource,level,第三个参数是是否自动提交
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//根据MyBatis主配置文件中指定的Executoer类型创建对应的Executor实例,工厂模式
final Executor executor = configuration.newExecutor(tx, execType);
//创建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();
}
}
}
上述代码中,首先通过Configurartion对象获取MyBatis主配置文件中通过标签配置的环境信息,然后根据配置的事物管理器类型创建对应的事物管理器工厂。MyBatis提供两种类型的事物管理器,分别是JDBCTransaction和ManagedTransaction。其中JDBCTransaction中是使用JDBC中的Connection对象实现事物管理,而ManagedTransaction表示事物由外部容器管理。而ManagedTransaction表示事物由外部容器管理。在上述方法中会调用transactionFactory的newTransaction方法调用参数来创建一个事物对象,构建条件有dataSource、隔离级别level以及是否自动提交等参数,Configuration的newTransaction方法会调用参数来创建事物对象,接下来看JDBCTransactionFactory类的newTrasnaction()方法,直接调用JDBCTransaction的构造函数返回一个Transaction对象,具体的实现如下:
public class JdbcTransactionFactory implements TransactionFactory {
@Override
public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
return new JdbcTransaction(ds, level, autoCommit);
}
}
- JdbcTransaction是Transaction的一个实现,它实现了Transaction的getConnection、commit、rollback、close以及getTimeout方法。具体的实现如下:
public class JdbcTransaction implements Transaction {
protected Connection connection;
protected DataSource dataSource;
protected TransactionIsolationLevel level;
protected boolean autoCommit;
public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) {
dataSource = ds;
level = desiredLevel;
autoCommit = desiredAutoCommit;
}
}
数据源是从configuration中获取的,在mybatis中数据源DataSource有两种实现:PooledDataSource、UnPooledDataSource以及UnPooledDataSource,由配置文件dataSource节点的type来确定。
- 接下来看看执行器的创建,执行器executor是通过Configuration对象创建,newExecutor方法首先会判断创建的Executor对象的类型,然后根据类型创建对应的对象,创建成功之后如果开启了缓存,还会把新创建的Executor对象作为参数创建CachingExecutor对象。接下来首先看看configuration对象的newExecutor()方法创建Executor对象的源码:
public class Configuration {
public Executor newExecutor(Transaction transaction) {
//在这里加上默认的Executor的类型,然后根据类型来创建Executor对象
return newExecutor(transaction, defaultExecutorType);
}
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
//根据Executor的类型来创建其他的Executor对象,即根据执行类比来构造对应的执行器
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
//如果是SimpleExecutor,则调用new SimpleExecutor构造方法
executor = new SimpleExecutor(this, transaction);
}
//如果开启了缓存,即设置cacheEnabled设置为true的时候,会把上述传递进来的Executor类型作为参数创建CachingExecutor对象。
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
}
接下来看一下具体调用时序图:
三、获取映射
3.1案例
- 在上述过程中,我们已经创建出了SqlSession对象,接着需要获取Mapper对象,然后取消之前的断点,在sqlSession.getMapper(IUserDao.class)方法处打断点以及在DefaultSqlSession类的selectList()方法处打断点,然后进行debug。
- UserTest类
public class UserTest {
private InputStream inputStream;
private SqlSessionFactoryBuilder sqlSessionFactoryBuilder;
private SqlSessionFactory sqlSessionFactory;
private SqlSession sqlSession;
private IUserDao iUserDao;
@Before
public void init() throws IOException {
inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
sqlSession = sqlSessionFactory.openSession();
iUserDao = sqlSession.getMapper(IUserDao.class);
}
@After
public void destory() throws IOException {
sqlSession.commit();
sqlSession.close();
inputStream.close();
}
@Test
public void testFindUserById(){
//同一个mapper,不同的sqlSession
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
//
IUserDao iUserDao1 = sqlSession1.getMapper(IUserDao.class);
IUserDao iUserDao2 = sqlSession2.getMapper(IUserDao.class);
//
User user1 = iUserDao1.findUserById(1);
sqlSession1.close();
User user2 = iUserDao2.findUserById(1);
sqlSession2.close();
System.out.println(user1);
System.out.println(user2);
System.out.println(user1==user2);
}
}
- 在iUserDao = sqlSession.getMapper(IUserDao.class);这一行这里打上断点,即调用DefaultSqlSession中的getMapper()方法,源码调用如下:
public class DefaultSqlSession implements SqlSession {
@Override
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
}
- 然后这里我们可以看到,DefaultSqlSession类的getMapper()方法会调用Configuration类的getMapper()方法,具体的调用过程如下:
public class Configuration {
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
}
- 然后这里我们也可以看到,Configuration类的getMapper()方法也会调用MapperRegistry的getMapper()方法,mapper注册器用于将所有的mapper接口添加到内存中,Mapper注册器自身维护着两个属性,config和knownMappers,其中knownMappers是一个 Class<?>, MapperProxyFactory<?>的集合,表示某个类路径对应的Mapper代理工厂,MapperProxyFactory做的事比较简单,目的就是通过代理模式创建处一个MapperProxy,MapperProxy实现了InvocationHandler接口,这表示MapperProxy会通过invoke()方法实现Mapper接口指定方法的调用,MapperProxy并不直接实现Mapper接口的调用,而是在内部维系着一个<Mapper.Method,MapperMethod>的map集合,在上节看到,MapperMethod中包装了Sqlsession的相关方法,所以MapperProxy本质上只是代理了<Method,MapperMethod>之间的映射关系,getMappers() 获取knownMappers集合中所有已经注册的Mapper接口。MapperRegistry的getMapper方法具体的调用过程如下:
package org.apache.ibatis.binding;
public class MapperRegistry {
//主要是存放配置信息
private final Configuration config;
//MapperProxyFactory的映射
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
public MapperRegistry(Configuration config) {
this.config = config;
}
//获取Mapper对象
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
//创建实例
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
}
- MapperProxyFactory对象里面保存了mapper接口的class对象,就是一个普通类,没有什么逻辑。接下来从MyBatis初始化时创建的MapperProxyFactory的Map中,取出当前mapper的接口类MapperProxyFactory,利用这个工厂类构造MapperProxy,然后会去调用jdk的代理方法去注册。在MapperProxyFactory类中使用了两种设计模式:单例模式methodCache和工厂模式getMapper。下面看详细过程:
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return mapperInterface;
}
public Map<Method, MapperMethodInvoker> getMethodCache() {
return methodCache;
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
//最终以JDK动态代理创建对象并返回
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
//创建MapperProxy对象
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
从上面的代码可以看出,依然是基于JDK Proxy实现的,而InvocationHandler参数是MapperProxy对象,代码的实现如下:
//com.mybatis.dao.IUserDao的类加载器
//接口是com.mybatis.dao.IUserDao
//h是mapperProxy对象
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h){
}
注册完成之后,然后各个类逐一将实例返回,然后将注册的接口存储到Configuration对象中。接下来看一下具体的调用时序图:
四、StatementHandler
(1)我们在搭建原生JDBC的时候,会有这样一行代码:
Statement stmt = conn.createStatement();//也可以使用PreparedStatemet来做
这行代码创建的Statement对象或者是PreparedStatemetn对象就是由StatementHandler对象来进行管理的。StatementHandler组件封装了对JDBCStatement的操作,负责操作Statement对象和数据库进行交互,在工作的时候还会使用ParameterHandler和ResultSetHandler对参数进行映射,对结果进行实体类绑定。例如设置Statement对象的fetchSize属性、设置查询超时时间、调用JDBC Statement与数据库交互等。具体实现如下:
public interface StatementHandler {
Statement prepare(Connection connection, Integer transactionTimeout)
throws SQLException;
void parameterize(Statement statement)
throws SQLException;
void batch(Statement statement)
throws SQLException;
int update(Statement statement)
throws SQLException;
<E> List<E> query(Statement statement, ResultHandler resultHandler)
throws SQLException;
<E> Cursor<E> queryCursor(Statement statement)
throws SQLException;
BoundSql getBoundSql();
ParameterHandler getParameterHandler();
}
- prepare:该方法用于创建一个具体的Statement对象的实现类或者JDBCStatement对象,并完成Statemetn对象的属性设置。
- parameterize:该方法使用MyBatis中的ParameterHandler组件为ParameterStatement和CallableStatement参数占位符设置值。
- batch:将SQL命令添加到批量执行列表中。
- update:调用Statement对象的execute()方法执行更新语句,例如UPDATE、INSERT、DELETE语句。
- query:执行查询语句,并使用ResultSetHandler处理结果集。
- queryCusor:带游标的查询,返回CUrsor对象,并能够通过Iterator动态的从数据库中加载数据,适用于查询数据量比较大的情况,比买你所有数据加载到内存。
- getBoundSql:获取Mapper中配置的SQL信息,BoundSQL封装了动态SQL解析后的SQL文本和参数映射信息。
- getParameterHandler:获取ParameterHandler实例。
(2)StatementHandler的继承结构如下:
StatementHandler有两个实现类:BaseStatementHandler和RoutingStatementHander。BaseStatementHandler有三个实现类,他们分别是:SimpleStatementHandler、PreparedStatementHandler以及CallableStatementHandler
- RoutingStatementHandler:它并没有对Statement对象起作用,它只是根据StatementType来创建代理,代理的对应Handler一共有三种实现类。在MyBatis工作的时候,使用StatementHandler接口对象实际上是使用RoutingStatementHandler对象,我们可以理解为:
public class Configuration {
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
}
public class RoutingStatementHandler implements StatementHandler {
private final StatementHandler delegate;
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
}
}
- BaseStatementHandler:是StatementHandler接口的另一个体现,本身是一个抽象类,用于简化StatementHandler接口的实现难度,属于适配器模式的体现,有三个实现类。
- SimpleStatementHandler:管理Statement对象并向数据库中推送不需要编译的SQL语句。
- PreparedStatement:管理Statement对象并向数据库中推送需要预编译的SQL语句。
- CallableStatementHandler:管理Statement对象并调用数据库中的存储过程。
4.1StatementHandler对象创建以及源码分析
- 首先调用DefaultSqlSession的selectList()方法,具体源码如下:
public class DefaultSqlSession implements SqlSession {
@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();
}
}
}
- 该方法会调用CachingExecutor的query方法来查看二级缓存中是否存在该缓存对象,具体实现如下:
public class CachingExecutor implements Executor {
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
}
- 该方法会调用代理对象BaseExecutor的query()方法,具体实现如下:
public abstract class BaseExecutor implements Executor {
@Override
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.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
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);
} finally {
localCache.removeObject(key);
}
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
}
- 该方法会调用SimpleExecutor的doQuery方法,具体实现如下:
public class SimpleExecutor extends BaseExecutor {
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
//获取配置环境
Configuration configuration = ms.getConfiguration();
//创建StatementHandler,解析SQL语句
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
//由handler来对SQL语句进行解析
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
}
public class Configuration {
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
}
可以看出,StatementHandler 默认创建一个 RoutingStatementHandler ,这也就是 StatementHandler 的默认实现,由 RoutingStatementHandler 负责根据 StatementType 创建对应的StatementHandler 来处理调用。
4.2prepare()方法调用流程分析
prepare 方法的调用过程是这样的,在上面的源码分析过程中,我们分析到了执行器 Executor 在执行SQL语句的时候会创建 StatementHandler 对象,进而经过一系列的 StatementHandler 类型的判断并初始化。再拿到StatementHandler 返回的 statementhandler 对象的时候,会调用其prepareStatement() 方法,下面就来一起看一下 preparedStatement() 方法(我们以简单执行器为例,因为创建其 StatementHandler 对象的流程和执行 preparedStatement() 方法的流程是差不多的):
public class SimpleExecutor extends BaseExecutor {
public SimpleExecutor(Configuration configuration, Transaction transaction) {
super(configuration, transaction);
}
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
//获取环境配置
Configuration configuration = ms.getConfiguration();
//创建StatementHandler解析SQL语句
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter,
RowBounds.DEFAULT, null, null);
stmt = prepareStatement(handler, ms.getStatementLog());
//由handler来对SQL执行解析工作
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
@Override
protected <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, null, boundSql);
Statement stmt = prepareStatement(handler, ms.getStatementLog());
Cursor<E> cursor = handler.queryCursor(stmt);
stmt.closeOnCompletion();
return cursor;
}
@Override
public List<BatchResult> doFlushStatements(boolean isRollback) {
return Collections.emptyList();
}
//
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
//prepare()方法会调用StatementHandler实现类的RoutingStatementHandler,再由RoutingStatemetnHandler调用BaseStatementHandler类中的prepare方法
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
}
- RoutingStatementHandler的实现类
public class RoutingStatementHandler implements StatementHandler {
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
}
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
return delegate.prepare(connection, transactionTimeout);
}
}
- BaseStatementHandler的实现类如下:
public abstract class BaseStatementHandler implements StatementHandler {
@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);
}
}
}
其中最重要的方法就是 instantiateStatement() 方法了,在得到数据库连接 connection 的对象的时候,会去调用 instantiateStatement() 方法,instantiateStatement 方法位于 StatementHandler 中,是一个抽象方法由子类去实现,实际执行的是三种 StatementHandler 中的一种,我们还以 PreparedStatementHandler为例:
public class SimpleStatementHandler extends BaseStatementHandler {
protected Statement instantiateStatement(Connection connection) throws SQLException {
if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
return connection.createStatement();
} else {
return connection.createStatement(mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
}
}
}
从上面代码我们可以看到,instantiateStatement() 最终返回的也是Statement对象,经过一系列的调用会把statement 对象返回到 SimpleExecutor 简单执行器中,为 parametersize 方法所用。也就是说,prepare 方法负责生成 Statement 实例对象,而 parameterize 方法用于处理 Statement 实例多对应的参数。
4.3parametersize 方法调用流程分析
parametersize 方法看的就比较畅快了,也是经由执行器来管理 parametersize 的方法调用,这次我们还想以SimpleStatementHandler 为例但是却不行了?为什么呢?因为 SimpleStatementHandler 是个空实现了,为什么是null呢?因为 SimpleStatementHandler 只负责处理简单SQL,能够直接查询得到结果的SQL,例如:
select studenname from Student
而 SimpleStatementHandler 又不涉及到参数的赋值问题,那么参数赋值该在哪里进行呢?实际上为参数赋值这步操作是在 PreparedStatementHandler 中进行的,因此我们的主要关注点在 PreparedStatementHandler 中的parameterize 方法
public void parameterize(Statement statement) throws SQLException {
parameterHandler.setParameters((PreparedStatement) statement);
}
我们可以看到,为参数赋值的工作是由一个叫做 parameterHandler 对象完成的,都是这样的吗?来看一下CallableStatementHandler
public void parameterize(Statement statement) throws SQLException {
registerOutputParameters((CallableStatement) statement);
parameterHandler.setParameters((CallableStatement) statement);
}
上面代码可以看到,CallableStatementHandler 也是由 parameterHandler 进行参数赋值的。
4.4 update 方法调用流程分析
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZNmUDZvg-1611920840885)(http://note.youdao.com/yws/res/47894/AFB03C6818B740C39FEBB64A1E042FDC)]
- MyBatis 接收到 update 请求后会先找到CachingExecutor缓存执行器查询是否需要刷新缓存,然后找到BaseExecutor 执行 update 方法;
- BaseExecutor 基础执行器会清空一级缓存,然后交给再根据执行器的类型找到对应的执行器,继续执行 update 方法;
- 具体的执行器会先创建 Configuration 对象,根据 Configuration 对象调用 newStatementHandler 方法,返回 statementHandler 的句柄;
- 具体的执行器会调用 prepareStatement 方法,找到本类的 prepareStatement 方法后,再有prepareStatement 方法调用 StatementHandler 的子类
- BaseStatementHandler 中的 prepare 方法BaseStatementHandler 中的 prepare 方法会调用 instantiateStatement 实例化具体的 Statement 对象并返回给具体的执行器对象
- 由具体的执行器对象调用 parameterize 方法给参数进行赋值。