Nicksxs's Blog

What hurts more, the pain of hard work or the pain of regret?

先补充两个点,
第一是前面我们说了
使用org.apache.ibatis.builder.xml.XMLConfigBuilder 创建了parser解析器,那么解析的结果是什么
看这个方法的返回值

1
2
3
4
5
6
7
8
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}

返回的是 org.apache.ibatis.session.Configuration , 而这个 Configuration 也是 mybatis 中特别重要的配置核心类,贴一下里面的成员变量,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
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 boolean nullableOnForEach;
protected boolean argNameBasedConstructorAutoMapping;

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<>();

这么多成员变量,先不一一解释作用,但是其中的几个参数我们应该是已经知道了的,第一个就是 mappedStatements ,上一篇我们知道被解析的mapper就是放在这里,后面的 resultMapsparameterMaps 也比较常用的就是我们参数和结果的映射map,这里跟我之前有一篇解释为啥我们一些变量的使用会比较特殊,比如list,可以参考这篇keyGenerators是在我们需要定义主键生成器的时候使用。
然后第二点是我们创建的 org.apache.ibatis.session.SqlSessionFactory 是哪个,

1
2
3
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}

是这个 DefaultSqlSessionFactory ,这是其中一个 SqlSessionFactory 的实现
接下来我们看看 openSession 里干了啥

1
2
3
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}

这边有几个参数,第一个是默认的执行器类型,往上找找上面贴着的 Configuration 的成员变量里可以看到默认是
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;

因为没有指明特殊的执行逻辑,所以默认我们也就用简单类型的,第二个参数是是事务级别,第三个是是否自动提交

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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();
}
}

具体是调用了 Configuration 的这个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : 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;
}

上面传入的 executorTypeConfiguration 的默认类型,也就是 simple 类型,并且 cacheEnabledConfiguration 默认为 true,所以会包装成CachingExecutor ,然后后面就是插件了,这块我们先不展开
然后我们的openSession返回的就是创建了DefaultSqlSession

1
2
3
4
5
6
public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
this.configuration = configuration;
this.executor = executor;
this.dirty = false;
this.autoCommit = autoCommit;
}

然后就是调用 selectOne, 因为前面已经把这部分代码说过了,就直接跳转过来
org.apache.ibatis.session.defaults.DefaultSqlSession#selectList(java.lang.String, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler)

1
2
3
4
5
6
7
8
9
10
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}

因为前面说了 executor 包装了 CachingExecutor ,所以会先调用

1
2
3
4
5
6
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

然后是调用的真实的query方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@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);
}

这里是第一次查询,没有缓存就先到最后一行,继续是调用到 org.apache.ibatis.executor.BaseExecutor#queryFromDatabase

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@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;
}

然后是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 的执行过程

1
2
3
4
5
6
7
8
9
10
11
12
@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);
}
}

接下去其实就是跟jdbc交互了

1
2
3
4
5
6
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.handleResultSets(ps);
}

com.mysql.cj.jdbc.ClientPreparedStatement#execute

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public boolean execute() throws SQLException {
try {
synchronized(this.checkClosed().getConnectionMutex()) {
JdbcConnection locallyScopedConn = this.connection;
if (!this.doPingInstead && !this.checkReadOnlySafeStatement()) {
throw SQLError.createSQLException(Messages.getString("PreparedStatement.20") + Messages.getString("PreparedStatement.21"), "S1009", this.exceptionInterceptor);
} else {
ResultSetInternalMethods rs = null;
this.lastQueryIsOnDupKeyUpdate = false;
if (this.retrieveGeneratedKeys) {
this.lastQueryIsOnDupKeyUpdate = this.containsOnDuplicateKeyUpdate();
}

this.batchedGeneratedKeys = null;
this.resetCancelledState();
this.implicitlyCloseAllOpenResults();
this.clearWarnings();
if (this.doPingInstead) {
this.doPingInstead();
return true;
} else {
this.setupStreamingTimeout(locallyScopedConn);
Message sendPacket = ((PreparedQuery)this.query).fillSendPacket(((PreparedQuery)this.query).getQueryBindings());
String oldDb = null;
if (!locallyScopedConn.getDatabase().equals(this.getCurrentDatabase())) {
oldDb = locallyScopedConn.getDatabase();
locallyScopedConn.setDatabase(this.getCurrentDatabase());
}

CachedResultSetMetaData cachedMetadata = null;
boolean cacheResultSetMetadata = (Boolean)locallyScopedConn.getPropertySet().getBooleanProperty(PropertyKey.cacheResultSetMetadata).getValue();
if (cacheResultSetMetadata) {
cachedMetadata = locallyScopedConn.getCachedMetaData(((PreparedQuery)this.query).getOriginalSql());
}

locallyScopedConn.setSessionMaxRows(this.getQueryInfo().getFirstStmtChar() == 'S' ? this.maxRows : -1);
rs = this.executeInternal(this.maxRows, sendPacket, this.createStreamingResultSet(), this.getQueryInfo().getFirstStmtChar() == 'S', cachedMetadata, false);
if (cachedMetadata != null) {
locallyScopedConn.initializeResultsMetadataFromCache(((PreparedQuery)this.query).getOriginalSql(), cachedMetadata, rs);
} else if (rs.hasRows() && cacheResultSetMetadata) {
locallyScopedConn.initializeResultsMetadataFromCache(((PreparedQuery)this.query).getOriginalSql(), (CachedResultSetMetaData)null, rs);
}

if (this.retrieveGeneratedKeys) {
rs.setFirstCharOfQuery(this.getQueryInfo().getFirstStmtChar());
}

if (oldDb != null) {
locallyScopedConn.setDatabase(oldDb);
}

if (rs != null) {
this.lastInsertId = rs.getUpdateID();
this.results = rs;
}

return rs != null && rs.hasRows();
}
}
}
} catch (CJException var11) {
throw SQLExceptionsMapping.translateException(var11, this.getExceptionInterceptor());
}
}

前一篇讲了mybatis的初始化使用,如果我第一次看到这个使用入门文档,比较会产生疑惑的是配置了mapper,怎么就能通过selectOne跟语句id就能执行sql了,那么第一个问题,就是mapper是怎么被解析的,存在哪里,怎么被拿出来的

添加解析mapper

1
2
3
4
org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.InputStream)
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}

通过读取mybatis-config.xml来构建SqlSessionFactory,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// 创建下xml的解析器
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 {
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}

创建XMLConfigBuilder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
// --------> 创建 XPathParser
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}

public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
commonConstructor(validation, variables, entityResolver);
this.document = createDocument(new InputSource(inputStream));
}

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;
}

这里主要是创建了Builder包含了Parser
然后调用parse方法

1
2
3
4
5
6
7
8
9
10
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
// 标记下是否已解析,但是这里是否有线程安全问题
parsed = true;
// --------> 解析配置
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}

实际的解析区分了各类标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private void parseConfiguration(XNode root) {
try {
// issue #117 read properties first
// 解析properties,这个不是spring自带的,需要额外配置,并且在config文件里应该放在最前
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"));
// ----------> 我们需要关注的是mapper的处理
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}

然后就是调用到mapperElement方法了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
try(InputStream inputStream = Resources.getResourceAsStream(resource)) {
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
// --------> 我们这没有指定package,所以是走到这
mapperParser.parse();
}
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
try(InputStream inputStream = Resources.getUrlAsStream(url)){
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
}
} else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}

核心就在这个parse()方法

1
2
3
4
5
6
7
8
9
10
11
12
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
// -------> 然后就是走到这里,配置xml的mapper节点的内容
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}

parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}

具体的处理逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
// -------> 走到这,从上下文构建statement
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);
}
}

具体代码在这,从上下文构建statement,只不过区分了下databaseId

1
2
3
4
5
6
7
private void buildStatementFromContext(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
// -----> 判断databaseId
buildStatementFromContext(list, null);
}

判断下databaseId

1
2
3
4
5
6
7
8
9
10
11
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
// -------> 解析statement节点
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}

接下来就是真正处理的xml语句内容的,各个节点的信息内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public void parseStatementNode() {
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");

if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}

String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

// Include Fragments before parsing
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());

String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);

String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);

// Parse selectKey after includes and remove them.
processSelectKeyNodes(id, parameterTypeClass, langDriver);

// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}

// 语句的主要参数解析
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String resultType = context.getStringAttribute("resultType");
Class<?> resultTypeClass = resolveClass(resultType);
String resultMap = context.getStringAttribute("resultMap");
String resultSetType = context.getStringAttribute("resultSetType");
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
if (resultSetTypeEnum == null) {
resultSetTypeEnum = configuration.getDefaultResultSetType();
}
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
String resultSets = context.getStringAttribute("resultSets");

// --------> 添加映射的statement
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}

添加的逻辑具体可以看下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
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();
// ------> 正好是这里在configuration中添加了映射好的statement
configuration.addMappedStatement(statement);
return statement;
}

而里面就是往map里添加

1
2
3
public void addMappedStatement(MappedStatement ms) {
mappedStatements.put(ms.getId(), ms);
}

获取mapper

1
StudentDO studentDO = session.selectOne("com.nicksxs.mybatisdemo.StudentMapper.selectStudent", 1);

就是调用了 org.apache.ibatis.session.defaults.DefaultSqlSession#selectOne(java.lang.String, java.lang.Object)

1
2
3
4
5
6
7
8
9
10
11
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.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;
}
}

调用实际的实现方法

1
2
3
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}

这里还有一层

1
2
3
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
return selectList(statement, parameter, rowBounds, Executor.NO_RESULT_HANDLER);
}

根本的就是从configuration里获取了mappedStatement

1
2
3
4
5
6
7
8
9
10
11
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
// 这里进行了获取
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}

mybatis是我们比较常用的orm框架,下面是官网的介绍

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

mybatis一大特点,或者说比较为人熟知的应该就是比 hibernate 是更轻量化,为国人所爱好的orm框架,对于hibernate目前还没有深入的拆解过,后续可以也写一下,在使用体验上觉得是个比较精巧的框架,看代码也比较容易,所以就想写个系列,第一篇先是介绍下使用
根据官网的文档上我们先来尝试一下简单使用
首先我们有个简单的配置,这个文件是mybatis-config.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 需要加入的properties-->
<properties resource="application-development.properties"/>
<!-- 指出使用哪个环境,默认是development-->
<environments default="development">
<environment id="development">
<!-- 指定事务管理器类型-->
<transactionManager type="JDBC"/>
<!-- 指定数据源类型-->
<dataSource type="POOLED">
<!-- 下面就是具体的参数占位了-->
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<!-- 指定mapper xml的位置或文件-->
<mapper resource="mapper/StudentMapper.xml"/>
</mappers>
</configuration>

在代码里创建mybatis里重要入口

1
2
3
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

然后我们上面的StudentMapper.xml

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.nicksxs.mybatisdemo.StudentMapper">
<select id="selectStudent" resultType="com.nicksxs.mybatisdemo.StudentDO">
select * from student where id = #{id}
</select>
</mapper>

那么我们就要使用这个mapper,

1
2
3
4
5
6
7
8
9
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
try (SqlSession session = sqlSessionFactory.openSession()) {
StudentDO studentDO = session.selectOne("com.nicksxs.mybatisdemo.StudentMapper.selectStudent", 1);
System.out.println("id is " + studentDO.getId() + " name is " +studentDO.getName());
} catch (Exception e) {
e.printStackTrace();
}

sqlSessionFactory是sqlSession的工厂,我们可以通过sqlSessionFactory来创建sqlSession,而SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。可以看到mapper.xml中有定义mapper的namespace,就可以通过session.selectOne()传入namespace+id来调用这个方法
但是这样调用比较不合理的点,或者说按后面mybatis优化之后我们可以指定mapper接口

1
2
3
4
public interface StudentMapper {

public StudentDO selectStudent(Long id);
}

就可以可以通过mapper接口获取方法,这样就不用涉及到未知的变量转换等异常

1
2
3
4
5
6
7
try (SqlSession session = sqlSessionFactory.openSession()) {
StudentMapper mapper = session.getMapper(StudentMapper.class);
StudentDO studentDO = mapper.selectStudent(1L);
System.out.println("id is " + studentDO.getId() + " name is " +studentDO.getName());
} catch (Exception e) {
e.printStackTrace();
}

这一篇咱们先介绍下简单的使用,后面可以先介绍下这些的原理。

powershell创建数组也很方便
可以这样

1
$nums=2,0,1,2

顺便可以用下我们上次学到的gettype()

如果是想创建连续数字的数组还可以用这个方便的方法

1
$nums=1..5


而且数组还可以存放各种类型的数据

1
$array=1,"哈哈",([System.Guid]::NewGuid()),(get-date)


还有判断类型可以用-is

创建一个空数组

1
$array=@()


数组添加元素

1
$array+="a"


数组删除元素

1
2
$a=1..4
$a=$a[0..1]+$a[3]

powershell变量

变量命名类似于php

1
2
3
4
PS C:\Users\Nicks> $a=1
PS C:\Users\Nicks> $b=2
PS C:\Users\Nicks> $a*$b
2

有一个比较好用的是变量交换
一般的语言做两个变量交换一般需要一个临时变量

1
2
3
$tmp=$a
$a=$b
$b=$tmp

而在powershell中可以这样

1
2
3
4
5
6
$a,$b=$b,$a
PS C:\Users\Nicks> $a,$b=$b,$a
PS C:\Users\Nicks> $a
2
PS C:\Users\Nicks> $b
1

还可以通过这个

1
2
3
4
5
6
7
8
9
10
PS C:\Users\Nicks> ls variable:

Name Value
---- -----
$ $b
? True
^ $b
a 2
args {}
b 1

查看现存的变量
当然一般脚本都是动态类型的,
可以通过
gettype方法

0%