MyBatis源码分析
MyBatis源码分析
流程分析:
1.MyBatis如何获得数据源对象
1.1.进入
//获得session工厂对象
//1.2.
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
1.2.buid()
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
SqlSessionFactory var5;
try {
//environment, properties皆为数据源环境
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
//调用解析方法
//1.3.
var5 = this.build(parser.parse());
} catch (Exception var14) {
throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException var13) {
}
}
return var5;
}
1.3.parse():解析内容
public Configuration parse() {
if (this.parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
} else {
this.parsed = true;
//1.4
this.parseConfiguration(this.parser.evalNode("/configuration"));
return this.configuration;
}
}
1.4.parseConfiguration
private void parseConfiguration(XNode root) {
try {
this.propertiesElement(root.evalNode("properties"));
Properties settings = this.settingsAsProperties(root.evalNode("settings"));
this.loadCustomVfs(settings);
this.typeAliasesElement(root.evalNode("typeAliases"));
this.pluginElement(root.evalNode("plugins"));
this.objectFactoryElement(root.evalNode("objectFactory"));
this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
this.settingsElement(settings);
//数据源环境
//1.5.
this.environmentsElement(root.evalNode("environments"));
this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
this.typeHandlerElement(root.evalNode("typeHandlers"));
this.mapperElement(root.evalNode("mappers"));
} catch (Exception var3) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
}
}
1.5.environmentsElement
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (this.environment == null) {
this.environment = context.getStringAttribute("default");
}
Iterator var2 = context.getChildren().iterator();
while(var2.hasNext()) {
XNode child = (XNode)var2.next();
String id = child.getStringAttribute("id");
if (this.isSpecifiedEnvironment(id)) {
TransactionFactory txFactory = this.transactionManagerElement(child.evalNode("transactionManager"));
//1.6.
//获得dataSource
DataSourceFactory dsFactory = this.dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
Builder environmentBuilder = (new Builder(id)).transactionFactory(txFactory).dataSource(dataSource);
//1.8.
this.configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
1.6.dataSourceElement
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
if (context != null) {
//获得数据库连接池类型
String type = context.getStringAttribute("type");
Properties props = context.getChildrenAsProperties();
//1.7
DataSourceFactory factory = (DataSourceFactory)this.resolveClass(type).newInstance();
factory.setProperties(props);
return factory;
} else {
throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}
}
1.7.resolveClass
protected Class<?> resolveClass(String alias) {
if (alias == null) {
return null;
} else {
try {
return this.resolveAlias(alias);
} catch (Exception var3) {
throw new BuilderException("Error resolving class. Cause: " + var3, var3);
}
}
}
1.8.setEnvironment
public void setEnvironment(Environment environment) {
this.environment = environment;
}
public final class Environment {
private final String id;
private final TransactionFactory transactionFactory;
private final DataSource dataSource;
sqlMapConfig.xml:
<configuration>
<!--通过properties标签加载外部properties文件-->
<properties resource="jdbc.properties"></properties>
<!--自定义别名-->
<typeAliases>
<typeAlias type="com.itheima.domain.User" alias="user"></typeAlias>
</typeAliases>
<!--数据源环境-->
<environments default="developement">
<environment id="developement">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<!--加载映射文件-->
<mappers>
<mapper resource="com/itheima/mapper/UserMapper.xml"></mapper>
<mappers>
</configuration>
可看见,xml中的配置与源码中的数据如id,
transactionFactory,dataSource是一一对应的,就像实体类与数据库中的内容一样。
Configuration
public class Configuration {
protected Environment environment;
protected boolean safeRowBoundsEnabled;
protected boolean safeResultHandlerEnabled;
protected boolean mapUnderscoreToCamelCase;
protected boolean aggressiveLazyLoading;
protected boolean multipleResultSetsEnabled;
protected boolean useGeneratedKeys;
protected boolean useColumnLabel;
protected boolean cacheEnabled;
protected boolean callSettersOnNulls;
protected boolean useActualParamName;
protected boolean returnInstanceForEmptyRow;
protected String logPrefix;
protected Class<? extends Log> logImpl;
protected Class<? extends VFS> vfsImpl;
protected LocalCacheScope localCacheScope;
protected JdbcType jdbcTypeForNull;
protected Set<String> lazyLoadTriggerMethods;
protected Integer defaultStatementTimeout;
protected Integer defaultFetchSize;
protected ExecutorType defaultExecutorType;
protected AutoMappingBehavior autoMappingBehavior;
protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior;
protected Properties variables;
protected ReflectorFactory reflectorFactory;
protected ObjectFactory objectFactory;
protected ObjectWrapperFactory objectWrapperFactory;
protected boolean lazyLoadingEnabled;
protected ProxyFactory proxyFactory;
protected String databaseId;
protected Class<?> configurationFactory;
protected final MapperRegistry mapperRegistry;
protected final InterceptorChain interceptorChain;
protected final TypeHandlerRegistry typeHandlerRegistry;
protected final TypeAliasRegistry typeAliasRegistry;
protected final LanguageDriverRegistry languageRegistry;
protected final Map<String, MappedStatement> mappedStatements;
protected final Map<String, Cache> caches;
protected final Map<String, ResultMap> resultMaps;
protected final Map<String, ParameterMap> parameterMaps;
protected final Map<String, KeyGenerator> keyGenerators;
protected final Set<String> loadedResources;
protected final Map<String, XNode> sqlFragments;
protected final Collection<XMLStatementBuilder> incompleteStatements;
protected final Collection<CacheRefResolver> incompleteCacheRefs;
protected final Collection<ResultMapResolver> incompleteResultMaps;
protected final Collection<MethodResolver> incompleteMethods;
protected final Map<String, String> cacheRefMap;
2.MyBatis如何拿到sql语句?
2.1.XMLConfigBuilder.class:parseConfiguration
private void parseConfiguration(XNode root) {
try {
this.propertiesElement(root.evalNode("properties"));
Properties settings = this.settingsAsProperties(root.evalNode("settings"));
this.loadCustomVfs(settings);
this.typeAliasesElement(root.evalNode("typeAliases"));
this.pluginElement(root.evalNode("plugins"));
this.objectFactoryElement(root.evalNode("objectFactory"));
this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
this.settingsElement(settings);
this.environmentsElement(root.evalNode("environments"));
this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
this.typeHandlerElement(root.evalNode("typeHandlers"));
//2.2
this.mapperElement(root.evalNode("mappers"));
} catch (Exception var3) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
}
}
2.2.XMLConfigBuilder.class:mapperElement
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
Iterator var2 = parent.getChildren().iterator();
while(true) {
while(var2.hasNext()) {
XNode child = (XNode)var2.next();
String resource;
if ("package".equals(child.getName())) {
resource = child.getStringAttribute("name");
this.configuration.addMappers(resource);
} else {
resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
XMLMapperBuilder mapperParser;
InputStream inputStream;
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
inputStream = Resources.getResourceAsStream(resource);
mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
inputStream = Resources.getUrlAsStream(url);
mapperParser = new XMLMapperBuilder(inputStream, this.configuration, url, this.configuration.getSqlFragments());
//2.3.
mapperParser.parse();
} else {
if (resource != null || url != null || mapperClass == null) {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
Class<?> mapperInterface = Resources.classForName(mapperClass);
this.configuration.addMapper(mapperInterface);
}
}
}
return;
}
}
}
2.3.XMLConfigBuilder.class:parse()
public void parse() {
if (!this.configuration.isResourceLoaded(this.resource)) {
// 2.4.
this.configurationElement(this.parser.evalNode("/mapper"));
this.configuration.addLoadedResource(this.resource);
this.bindMapperForNamespace();
}
this.parsePendingResultMaps();
this.parsePendingCacheRefs();
this.parsePendingStatements();
}
2.4.XMLConfigBuilder.class:configurationElement
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace != null && !namespace.equals("")) {
this.builderAssistant.setCurrentNamespace(namespace);
this.cacheRefElement(context.evalNode("cache-ref"));
this.cacheElement(context.evalNode("cache"));
this.parameterMapElement(context.evalNodes("/mapper/parameterMap"));
this.resultMapElements(context.evalNodes("/mapper/resultMap"));
this.sqlElement(context.evalNodes("/mapper/sql"));
//2.5.
this.buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} else {
throw new BuilderException("Mapper's namespace cannot be empty");
}
} catch (Exception var3) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + this.resource + "'. Cause: " + var3, var3);
}
}
sqlMapperXml:
<mapper namespace="userMapper">
<!--删除操作-->
<delete id="delete" parameterType="int">
delete from user where id=#{abc}
</delete>
<!--修改操作-->
<update id="update" parameterType="com.itheima.domain.User">
update user set username=#{username},password=#{password} where id=#{id}
</update>
</mapper>
2.5.XMLConfigBuilder.class:buildStatementFromContext
private void buildStatementFromContext(List<XNode> list) {
if (this.configuration.getDatabaseId() != null) {
this.buildStatementFromContext(list, this.configuration.getDatabaseId());
}
//2.6.
this.buildStatementFromContext(list, (String)null);
}
2.6.XMLConfigBuilder.class:buildStatementFromContext
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
Iterator var3 = list.iterator();
while(var3.hasNext()) {
XNode context = (XNode)var3.next();
XMLStatementBuilder statementParser = new XMLStatementBuilder(this.configuration, this.builderAssistant, context, requiredDatabaseId);
try {
//2.7.
statementParser.parseStatementNode();
} catch (IncompleteElementException var7) {
this.configuration.addIncompleteStatement(statementParser);
}
}
}
2.6.XMLConfigBuilder.class:parseStatementNode()
sqlMapper中的各种属性
public void parseStatementNode() {
String id = this.context.getStringAttribute("id");
String databaseId = this.context.getStringAttribute("databaseId");
if (this.databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
Integer fetchSize = this.context.getIntAttribute("fetchSize");
Integer timeout = this.context.getIntAttribute("timeout");
String parameterMap = this.context.getStringAttribute("parameterMap");
String parameterType = this.context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = this.resolveClass(parameterType);
String resultMap = this.context.getStringAttribute("resultMap");
String resultType = this.context.getStringAttribute("resultType");
String lang = this.context.getStringAttribute("lang");
LanguageDriver langDriver = this.getLanguageDriver(lang);
Class<?> resultTypeClass = this.resolveClass(resultType);
String resultSetType = this.context.getStringAttribute("resultSetType");
StatementType statementType = StatementType.valueOf(this.context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
ResultSetType resultSetTypeEnum = this.resolveResultSetType(resultSetType);
String nodeName = this.context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = this.context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = this.context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = this.context.getBooleanAttribute("resultOrdered", false);
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(this.configuration, this.builderAssistant);
includeParser.applyIncludes(this.context.getNode());
this.processSelectKeyNodes(id, parameterTypeClass, langDriver);
SqlSource sqlSource = langDriver.createSqlSource(this.configuration, this.context, parameterTypeClass);
String resultSets = this.context.getStringAttribute("resultSets");
String keyProperty = this.context.getStringAttribute("keyProperty");
String keyColumn = this.context.getStringAttribute("keyColumn");
String keyStatementId = id + "!selectKey";
keyStatementId = this.builderAssistant.applyCurrentNamespace(keyStatementId, true);
Object keyGenerator;
if (this.configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = this.configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = this.context.getBooleanAttribute("useGeneratedKeys", this.configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
//2.7.
ths.builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, (KeyGenerator)keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
}
2.7.XMLConfigBuilder.class:addMappedStatement
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 (this.unresolvedCacheRef) {
throw new IncompleteElementException("Cache-ref not yet resolved");
} else {
id = this.applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
org.apache.ibatis.mapping.MappedStatement.Builder statementBuilder = (new org.apache.ibatis.mapping.MappedStatement.Builder(this.configuration, id, sqlSource, sqlCommandType)).resource(this.resource).fetchSize(fetchSize).timeout(timeout).statementType(statementType).keyGenerator(keyGenerator).keyProperty(keyProperty).keyColumn(keyColumn).databaseId(databaseId).lang(lang).resultOrdered(resultOrdered).resultSets(resultSets).resultMaps(this.getStatementResultMaps(resultMap, resultType, id)).resultSetType(resultSetType).flushCacheRequired((Boolean)this.valueOrDefault(flushCache, !isSelect)).useCache((Boolean)this.valueOrDefault(useCache, isSelect)).cache(this.currentCache);
ParameterMap statementParameterMap = this.getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}
MappedStatement statement = statementBuilder.build();
//2.8.
this.configuration.addMappedStatement(statement);
return statement;
}
}
2.8.Configuration:addMappedStatement
public void addMappedStatement(MappedStatement ms) {
this.mappedStatements.put(ms.getId(), ms);
}
3.MyBatis如何操作数据库的?
3.1.获得session回话对象
//获得session回话对象
SqlSession sqlSession = sqlSessionFactory.openSession();//openSession() method just do some prepared jobs
3.2.DeFaultSqlSessionFactory.class:openSessionFromDataSource
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
DefaultSqlSession var8;
try {
Environment environment = this.configuration.getEnvironment();
TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//3.3. Executor
Executor executor = this.configuration.newExecutor(tx, execType);
var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
} catch (Exception var12) {
this.closeTransaction(tx);
throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12);
} finally {
ErrorContext.instance().reset();
}
return var8;
}
How many kide of Executor?
answer:three
public enum ExecutorType {
SIMPLE,
REUSE,
BATCH;//a group of people or things
private ExecutorType() {
}
}
3.3.execute operation
//执行操作 参数:namespace+id
User user = sqlSession.selectOne("userMapper.findById", 1);
3.4.DefaultSqlSession:selectList()
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
List var5;
try {
MappedStatement ms = this.configuration.getMappedStatement(statement);
//3.5.enter query method
var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception var9) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + var9, var9);
} finally {
ErrorContext.instance().reset();
}
return var5;
}
3.5.CachingExecutor:query()
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//binding sql
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql);
return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
3.6.BaseExecutor.java:
query about cache
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 (this.closed) {
throw new ExecutorException("Executor was closed.");
} else {
if (this.queryStack == 0 && ms.isFlushCacheRequired()) {
this.clearLocalCache();
}
List list;
try {
++this.queryStack;
// to judge weather has the cache or not
list = resultHandler == null ? (List)this.localCache.getObject(key) : null;
//the cache is't null
if (list != null) {
this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// the cache is null,to query from the database
//3.7
list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
--this.queryStack;
}
if (this.queryStack == 0) {
Iterator var8 = this.deferredLoads.iterator();
while(var8.hasNext()) {
BaseExecutor.DeferredLoad deferredLoad = (BaseExecutor.DeferredLoad)var8.next();
deferredLoad.load();
}
this.deferredLoads.clear();
if (this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
this.clearLocalCache();
}
}
return list;
}
}
3.7.BaseExecutor.java:queryFromDatabase
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
//First Level Cache: put the key in cache
this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);
List list;
try {
//3.8.
list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
this.localCache.removeObject(key);
}
this.localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
this.localOutputParameterCache.putObject(key, parameter);
}
return list;
}
3.8.SimpleExecutor.java:doQuery
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
List var9;
try {
Configuration configuration = ms.getConfiguration();
//3.9.
StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = this.prepareStatement(handler, ms.getStatementLog());
var9 = handler.query(stmt, resultHandler);
} finally {
this.closeStatement(stmt);
}
return var9;
}
3.9.RoutingStatementHandler:
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
//3.10.
return this.delegate.query(statement, resultHandler);
}
3.10.PreparedStatementHandler:query
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
//JDBC
PreparedStatement ps = (PreparedStatement)statement;
ps.execute();
//get the result
return this.resultSetHandler.handleResultSets(ps);
}
3.11.DefaultResultSetHandler.java: getFirstResultSet
private ResultSetWrapper getFirstResultSet(Statement stmt) throws SQLException {
ResultSet rs = stmt.getResultSet();
while(rs == null) {
if (stmt.getMoreResults()) {
rs = stmt.getResultSet();
} else if (stmt.getUpdateCount() == -1) {
break;
}
}
//3.12.
return rs != null ? new ResultSetWrapper(rs, this.configuration) : null;
}
3.12.ResultSetWrapper.java:ResultSetWrapper
public ResultSetWrapper(ResultSet rs, Configuration configuration) throws SQLException {
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.resultSet = rs;
ResultSetMetaData metaData = rs.getMetaData();
int columnCount = metaData.getColumnCount();
for(int i = 1; i <= columnCount; ++i) {
//convert data type between java and sql.As the picture shown below
this.columnNames.add(configuration.isUseColumnLabel() ? metaData.getColumnLabel(i) : metaData.getColumnName(i));
this.jdbcTypes.add(JdbcType.forCode(metaData.getColumnType(i)));
this.classNames.add(metaData.getColumnClassName(i));
}
}