标签:isp 互动 ann 指定 text hash gets attr end
XMLStatementBuilder类中的parseStatementNode方法是真正开始解析指定的SQL节点。
从上文中可知context就是SQL标签对应的XNode对象,该方法前面大部分内容都是从XNode对象中获取各个数据。其实该方法的大致意思就是解析这个SQL标签里的所有数据(SQL语句以及标签属性),并把所有数据通过addMappedStatement这个方法封装在MappedStatement这个对象中。这个对象中封装了一条SQL所在标签的所有内容,比如这个SQL标签的id、SQL语句、输入值、输出值等,我们要清楚一个SQL的标签就对应一个MappedStatement对象。
public void parseStatementNode() {
// 通过XNode对象获取标签的各个数据
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
String nodeName = context.getNode().getNodeName();
// 省略其他内容...
// 获取SQL语句并封装成一个SqlSource对象
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
String keyColumn = context.getStringAttribute("keyColumn");
String resultSets = context.getStringAttribute("resultSets");
// 将SQL标签的所有数据添加至MappedStatement对象中
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
讲解一下关于SQL语句的获取,我们着重关注这一行代码:
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
这里LanguageDriver接口实现类是XMLLanguageDriver,再进入createSqlSource方法,又是熟悉的XxxBuilder对象,很明显是用来解析SQL内容的。parseScriptNode方法最终会创建一个RawSqlSource对象,里面存储一个BoundSql对象,而BoundSql对象才是真正存储SQL语句的类。
在创建RawSqlSource对象的时候,会调用GenericTokenParser类中的parse方法来解析SQL语句中的通用标记(GenericToken),比如“#{ }”,并且将所有的通用标记都变为“?”占位符。
// XMLLanguageDriver类
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
return builder.parseScriptNode();
}
// XMLScriptBuilder类
public SqlSource parseScriptNode() {
MixedSqlNode rootSqlNode = parseDynamicTags(context);
SqlSource sqlSource;
if (isDynamic) {
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
// 创建的是RawSqlSource对象,里面存有一个BoundSql对象
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
// 真正存储SQL语句以及参数的对象
public class BoundSql {
private final String sql;
private final List<ParameterMapping> parameterMappings;
private final Object parameterObject;
private final Map<String, Object> additionalParameters;
private final MetaObject metaParameters;
// 省略其他内容...
}
进入到addMappedStatement方法,又是一个很长的方法。很明显这里又使用了建造者模式,依靠MappedStatement类中的一个内部类Builder来构造MappedStatement对象。Configuration类中使用了一个Map集合来存储所有的MappedStatement对象,Key值就是这个SQL标签的id值,我们这里应该就是“getPaymentById”,Value值就是我们创建的对应的MapperStatement对象。
有个地方需要注意一下,在创建MapperStatement对象前会对id(即接口方法名)进行处理,在id前加上命名空间,也就成了该接口方法的全限定名,因此我们在调用selectOne等方法时应该填写接口方法的全限定名。
其实我们解析XML文件的目的就是把每个XML文件中的所有增、删、改、查SQL标签解析成一个个MapperStatement,并把这些对象装到Configuration的Map中备用。
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");
}
// 修改存入Map集合的Key值为全限定名
id = applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
// 创建MappedStatement的构造器
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对象,并添加至Configuration对象中
MappedStatement statement = statementBuilder.build();
configuration.addMappedStatement(statement);
return statement;
}
继续回到XMLMapperBuilder类中的parse方法,当解析完一个XML文件后就会把该文件的路径存入loadedResources集合中。
接下来我们看看bindMapperForNamespace方法,看名字就知道它的作用是通过命名空间绑定mapper。一开始获取名称空间,名称空间一般都是我们mapper的全限定名,它通过反射获取这个mapper的Class对象。Configuration中维护了一个名为knownMappers的Map集合,Key值是我们刚才通过反射创建的Class对象,Value值则是通过动态代理创建的Class对象的代理对象。
因为knownMappers集合中还没有存入我们创建的Class对象,所以会进入判断语句,它先把名称空间存到我们刚才存XML文件路径名的Set集合中,表示该命名空间已经加载过,然后再把mapper的Class对象存到knownMappers集合中。
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
// 将解析完的XML文件路径存入Configuration的loadedResources集合中
configuration.addLoadedResource(resource);
// 通过名称空间绑定mapper
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
private void bindMapperForNamespace() {
// 获取到Mapper接口的全限定名
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
// 通过全限定名创建该Mapper接口的Class对象
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
// ignore, bound type is not required
}
if (boundType != null && !configuration.hasMapper(boundType)) {
// 将命名空间的名称存入已加载的Set集合中
configuration.addLoadedResource("namespace:" + namespace);
// 将Class对象存入Map集合中
configuration.addMapper(boundType);
}
}
}
public class Configuration {
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}
}
public class MapperRegistry {
private final Configuration config;
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
// 添加已注册的Mapper接口的Class对象
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);
}
}
}
}
// 省略其他内容...
}
在存入Class对象的代理对象后,后面还有一步MapperAnnotationBuilder类的解析工作,我们进入到parse方法,可以看到它会使用Class对象的字符串进行判断该是否已经解析过该Class对象,通常这里是不会进入判断的(下面的mapperClass方式才会进来)。
public void parse() {
String resource = type.toString();
// 判断该Mapper接口的Class对象是否已经解析过
if (!configuration.isResourceLoaded(resource)) {
loadXmlResource();
configuration.addLoadedResource(resource);
// 省略其他内容...
}
parsePendingMethods();
}
基于resource的方式讲解完毕了,接下来就是url和mapperClass。url的方式和resource一样,这里就不再赘述了。关于mapperClass,这种方式的解析步骤其实和resource是相反的,即先通过反射创建Mapper接口的Class对象,再通过Class对象的全限定名来寻找对应的XML映射文件,
else if (resource == null && url == null && mapperClass != null) {
// 反射创建Mapper接口的Class对象
Class<?> mapperInterface = Resources.classForName(mapperClass);
// 将该Class对象添加至knownMappers集合中
configuration.addMapper(mapperInterface);
}
// Configuration类
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}
mapperClass方式解析XML映射文件和resource方式略有不同,mapperClass首先使用的是MapperAnnotationBuilder类进行解析,虽然看名字貌似该类只是负责注解的映射,但是其实暗藏玄机。
// MapperRegistry类
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));
// 使用的是MapperAnnotationBuilder解析器
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
// mapperClass方式进行XML映射文件的解析
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
我们进入到该类的parse方法,与resource方式不同的是这里会进入该判断,并且执行了loadXmlResource方法,这个方法就是通过Class对象的名称来加载XML映射文件。loadXmlResource方法中首先也是进行一个判断,这里肯定是没有加载该XML映射文件的命名空间的。后面有一行代码非常关键,它将Class对象的名称中所有的“.”替换为了“/”,然后拼接上了XML文件的后缀,这表示MyBatis会在Mapper接口的同层级目录来寻找对应的XML映射文件。后面的步骤就和之前resource方式一样了,通过XMLMapperBuilder类来解析。
这也解释了为啥使用mapperClass方式时,XML映射文件要与Mapper接口放在同一层级目录下。
// MapperAnnotationBuilder
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
// 加载XML映射文件
loadXmlResource();
// 加载完成后同样存入Map集合中
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
parseCache();
parseCacheRef();
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();
}
// 通过Class对象的名称来加载XML映射文件
private void loadXmlResource() {
if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
// 构建XML映射文件路径
String xmlResource = type.getName().replace(‘.‘, ‘/‘) + ".xml";
// 后续步骤和resource方式一致
InputStream inputStream = type.getResourceAsStream("/" + xmlResource);
if (inputStream == null) {
try {
inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
} catch (IOException e2) {
// ignore, resource is not required
}
}
if (inputStream != null) {
XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
xmlParser.parse();
}
}
}
至此,单文件映射的加载已经讲解完毕,接下里进入多文件映射的加载!
最后再讲讲多文件映射的加载。它首先或得XML所在的包名,然后调用configuration的addMappers对象,是不是有点眼熟,单文件映射是addMapper,多文件映射就是addMappers。
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
}
进入addMappers方法,就是通过ResolverUtil这个解析工具类找出该包下的所有mapper的名称并通过反射创建mapper的Class对象装进集合中,然后循环调用addMapper(mapperClass)这个方法,这就和单文件映射的Class类型一样了,把mapper接口的Class对象作为参数传进去,然后生成代理对象装进集合然后再解析XML。
// Configuration类
public void addMappers(String packageName) {
mapperRegistry.addMappers(packageName);
}
// MapperRegistry类
public void addMappers(String packageName) {
addMappers(packageName, Object.class);
}
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);
}
}
// ResolveUtil类
public ResolverUtil<T> find(Test test, String packageName) {
String path = getPackagePath(packageName);
try {
List<String> children = VFS.getInstance().list(path);
for (String child : children) {
if (child.endsWith(".class")) {
addIfMatching(test, child);
}
}
} catch (IOException ioe) {
log.error("Could not read package: " + packageName, ioe);
}
return this;
}
protected void addIfMatching(Test test, String fqn) {
try {
String externalName = fqn.substring(0, fqn.indexOf(‘.‘)).replace(‘/‘, ‘.‘);
ClassLoader loader = getClassLoader();
if (log.isDebugEnabled()) {
log.debug("Checking to see if class " + externalName + " matches criteria [" + test + "]");
}
// 反射创建Class对象并存入Set集合中
Class<?> type = loader.loadClass(externalName);
if (test.matches(type)) {
matches.add((Class<T>) type);
}
} catch (Throwable t) {
log.warn("Could not examine class ‘" + fqn + "‘" + " due to a "
+ t.getClass().getName() + " with message: " + t.getMessage());
}
}
终于把MyBatis的初始化步骤讲完了,这里只是重点讲解了<mappers>节点的解析,还有许多节点比如<environments>、<settings>、<typeAliases>等都大同小异。
这一步的主要目的就是通过之前初始化的SqlSessionFactory实现类来开启一个SQL会话。
SqlSession sqlSession = sqlSessionFactory.openSession();
可以看出这里SqlSessionFactory实现类为DefaultSqlSessionFactory类。
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
我们知道SqlSession是我们与数据库互动的顶级接口,所有的增删改查都要通过SqlSession,所以进入DefaultSqlSessionFactory类的openSession方法,而openSession方法又调用了openSessionFromDataSource方法。
因为我们解析的XML主配置文件把所有的节点信息都保存在了Configuration对象中,它开始直接获得Environment节点的信息,这个节点配置了数据库的连接信息和事务信息。
之后通过Environment创建了一个事务工厂TransactionFactory,这里其实是实现类JdbcTransactionFactory。然后通过事务工厂实例化了一个事务对象Transaction,这里其实是实现类JdbcTransaction。在JdbcTransaction类中存有我们配置的数据库环境相关信息,例如数据源、数据库隔离界别、数据库连接以及是否自动提交事务。
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);
}
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);
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 TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
if (environment == null || environment.getTransactionFactory() == null) {
return new ManagedTransactionFactory();
}
return environment.getTransactionFactory();
}
// 省略其他内容...
}
默认情况下,不会传入Connection参数,而是等到使用时才通过DataSource创建Connection对象。
public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) {
dataSource = ds;
level = desiredLevel;
autoCommit = desiredAutoCommit;
}
public Connection getConnection() throws SQLException {
// 使用时才创建Connection
if (connection == null) {
openConnection();
}
return connection;
}
protected void openConnection() throws SQLException {
if (log.isDebugEnabled()) {
log.debug("Opening JDBC Connection");
}
connection = dataSource.getConnection();
if (level != null) {
connection.setTransactionIsolation(level.getLevel());
}
setDesiredAutoCommit(autoCommit);
}
重点来了,最后他创建了一个执行器Executor ,我们知道SqlSession是与数据库交互的顶层接口,SqlSession中会维护一个Executor来负责SQL生产和执行和查询缓存等。
由源码可知最终它是创建一个SqlSession实现类DefaultSqlSession,并且维护了一个Executor实例。
// DefaultSqlSessionFactory类中关于创建Excutor和SqlSession的代码片段
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
public class DefaultSqlSession implements SqlSession {
private final Configuration configuration;
// 维护了一个Executor实例
private final Executor executor;
private final boolean autoCommit;
private boolean dirty;
private List<Cursor<?>> cursorList;
// 省略其他内容...
}
我们再来看看这个执行器的创建过程,其实就是判断生成哪种执行器,defaultExecutorType默认指定使用SimpleExecutor。
MyBatis有三种的执行器:
SimpleExecutor(默认)。
ReuseExecutor。
BatchExecutor。
SimpleExecutor:简单执行器。
是MyBatis中默认使用的执行器,每执行一Update或Select,就开启一个Statement对象(或PreparedStatement对象),用完就直接关闭Statement对象(或PreparedStatment对象)。
ReuseExecutor:可重用执行器。
这里的重用指的是重复使用Statement(或PreparedStatement),它会在内部使用一个Map把创建的Statement(或PreparedStatement)都缓存起来,每次执行SQL命令的时候,都会去判断是否存在基于该SQL的Statement对象,如果存在Statement对象(或PreparedStatement对象)并且对应的Connection还没有关闭的情况下就继续使用之前的Statement对象(或PreparedStatement对象),并将其缓存起来。
因为每一个SqlSession都有一个新的Executor对象,所以我们缓存在ReuseExecutor上的Statement作用域是同一个SqlSession。
BatchExecutor:批处理执行器。
用于将多个SQL一次性输出到数据库。
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;
}
标签:isp 互动 ann 指定 text hash gets attr end
原文地址:https://www.cnblogs.com/SunnyGao/p/14136757.html