MyBatis原理初探
SqlSessionFactoryBuilder对象
每一个mybatis的应用程序都是SqlSessionFactoryBuilder,它是根据传入的对象创建SqlSessionFactory实例。
在spring项目中,我们通常使用xml配置文件,通过xml文件创建Configuration对象,然后通过重载选取合适的build方法,进而创建SqlSessionFactory对象。此外,我们还可以通过Properties或者String对象创建SqlSessionFactory。
mybatis-config.xml中的配置文件会被解析为Configuration对象,SqlSessionFactoryBuilder根据传入的(XML)数据流生成Configuration对象,然后根据Configuration对象创建默认的SqlSessionFactory实现。具体方法如下:
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
没有必要每次访问mybatis都创建SqlSessionFactoryBuilder对象,通常是创建一个全局对象。
Configuration对象
Configuration对象位于org.apache.ibatis.session包中,它是mybatis初始化的核心。Configuration对象对应了mybatis的配置文件
以下是它包含的字段。各字段对应的含义在https://mybatis.org/mybatis-3/zh/configuration.html#中可以查到。
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 boolean shrinkWhitespacesInSql;
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 ResultSetType defaultResultSetType;
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;
Environment对象
environment对象主要存储了mybatis配置文件中对应的环境配置,包括DataSource以及TransactionFactory。其中DataSource是数据源的配置;而TransactionFactory是事务工厂,通过事务工厂获取对应的事务,进行事务相关的提交,回滚等操作。
public final class Environment {
private final String id;
private final TransactionFactory transactionFactory;
private final DataSource dataSource;
public Environment(String id, TransactionFactory transactionFactory, DataSource dataSource) {
if (id == null) {
throw new IllegalArgumentException("Parameter 'id' must not be null");
}
if (transactionFactory == null) {
throw new IllegalArgumentException("Parameter 'transactionFactory' must not be null");
}
this.id = id;
if (dataSource == null) {
throw new IllegalArgumentException("Parameter 'dataSource' must not be null");
}
this.transactionFactory = transactionFactory;
this.dataSource = dataSource;
}
public static class Builder {
private final String id;
private TransactionFactory transactionFactory;
private DataSource dataSource;
public Builder(String id) {
this.id = id;
}
public Builder transactionFactory(TransactionFactory transactionFactory) {
this.transactionFactory = transactionFactory;
return this;
}
public Builder dataSource(DataSource dataSource) {
this.dataSource = dataSource;
return this;
}
public String id() {
return this.id;
}
public Environment build() {
return new Environment(this.id, this.transactionFactory, this.dataSource);
}
}
/*省略了get方法*/
}
解析Configuration对象
我们选取使用InputStream进行创建SqlSessionFactory对象,查看其中如何解析Configuration对象。
当我们使用InputStream构造对象的时候,真正调用的方法是如下方法。
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.
}
}
}
而XMLConfigBuilder对象调用了它的parse方法
// 解析mybatis的xml配置文件时,调用如下构造函数,通过InputStream构造了XPathParser对象,该对象就是用于解析xml文件的
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
// 上述构造函数最终调用了如下构造函数进行了XMLConfigBuilder对象的构建
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;
}
// 该方法解析我们的配置文件,并生成Configuration对象
public Configuration parse() {
// 如果对象解析过就不需要再解析了,因为程序的配置文件是统一的,只需要加载一次,单例模式
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
// 该方法执行真实的解析,分别对应xml配置文件中不同的标签结点,将这些标签结点的属性设置给configuration对象,最后供程序使用
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);
}
}
通过上述分析我们知道了parser.parse()的作用,就是根据配置文件生成我们的Configuration对象。此时xml配置文件的解析已经完成了,接下来再调用如下build方法,生成了DefaultSqlSessionFactory对象,接下来就是SqlSessionFactory对象上场的时间。
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
SqlSessionFactory对象
SqlSessionFactory接口,定义了基本操作
DefaultSqlSessionFactory实现了SqlSessionFactory接口,并且新增了通过数据源、连接打开SqlSession,通过Environment获取事务工厂,关闭事务的方法。
SqlSessionFactory的主要功能是创建SqlSession对象,和SqlSessionFactoryBuilder对象一样,没有必要每次访问mybatis都创建,所以只需要创建一个全局对就可以。
SqlSessionFactory有一个属性就是Configuration,它是保存mybatis全局配置的一个配置对象(上面已经介绍过)。就是SqlSessionFactoryBuilder从xml文件创建的Configuration对象,在build是传递给了SqlSessionFactory对象。这个对象保存着全局的配置。
获取SqlSession的具体实现
// 通过executor类型、事务级别以及是否开启事务来获取对应的sqlsession
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();
}
}
// 通过连接和executor类型获取sqlsession
private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
try {
boolean autoCommit;
try {
autoCommit = connection.getAutoCommit();
} catch (SQLException e) {
// Failover to true, as most poor drivers
// or databases won't support transactions
autoCommit = true;
}
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
final Transaction tx = transactionFactory.newTransaction(connection);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
// 获取事务工厂,用于控制事务,这就是上文介绍的Environment
private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
if (environment == null || environment.getTransactionFactory() == null) {
return new ManagedTransactionFactory();
}
return environment.getTransactionFactory();
}
SqlSession
SqlSession是一个接口。
SqlSession主要实现了如下方法:获取连接Connection,获取Mapper,数据库的增删查改操作,数据库的提交、回滚操作,刷新批处理语句flushStatements,清空缓存、关闭本次session、获取配置文件Configuration操作。
通过其中包含的方法,我们可以知道SqlSession对象的主要功能就是完成一次数据库的访问和结果映射(通俗来说就是执行我们写好的SQL语句并处理返回的数据),它类似于数据库的session概念。SqlSession的默认实现类是DefaultSqlSession,此外它还有两个实现类分别是SqlSessionManager和SqlSessionTemplate。
DefaultSqlSession
DefaultSqlSession对应的字段和构造函数如下,官方给出的注释表示它不是线程安全的。
它有两个必须配置的属性Configuration以及Executor,Configuration就是全局配置文件,SqlSession对数据库的操作都是通过Executor执行的。
/**
* The default implementation for {@link SqlSession}.
* Note that this class is not Thread-Safe.
*
* @author Clinton Begin
*/
public class DefaultSqlSession implements SqlSession {
private final Configuration configuration;
private final Executor executor;
private final boolean autoCommit;
private boolean dirty;
private List<Cursor<?>> cursorList;
public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
this.configuration = configuration;
this.executor = executor;
this.dirty = false;
this.autoCommit = autoCommit;
}
}
我们来看看其中的selectList方法是如何执行的,这里首先获取了MappedStatement对象,然后使用executor的query进行执行,我们先介绍下MappedStatement方法。
@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();
}
}
MappedStatement
MappedStatement中的SqlSource用于存储从注解或者xml文件得到的sql语句。
它与mapper配置文件中的select|update|insert|delete节点相对应,mapper中配置的标签都被封装到了这个对象中。主要用于描述一条sql语句。
public final class MappedStatement {
private String resource;
private Configuration configuration;
private String id;
private Integer fetchSize;
private Integer timeout;
private StatementType statementType;
private ResultSetType resultSetType;
private SqlSource sqlSource;
private Cache cache;
private ParameterMap parameterMap;
private List<ResultMap> resultMaps;
private boolean flushCacheRequired;
private boolean useCache;
private boolean resultOrdered;
private SqlCommandType sqlCommandType;
private KeyGenerator keyGenerator;
private String[] keyProperties;
private String[] keyColumns;
private boolean hasNestedResultMaps;
private String databaseId;
private Log statementLog;
private LanguageDriver lang;
private String[] resultSets;
}
下述的select标签在初始化配置文件时会被解析为MappedStatement对象,存储在Configuration对象的MappedStatements属性中
<select id="getUserById" parameterType="java.lang.Integer" resultType="user">
select * from user where id=#{id}
</select>
// 配置文件中的mappedStatements属性
protected final Map<String, MappedStatement> mappedStatements
而这个属性的配置是在XMLConfigBuilder中解析的
// 解析mappers标签
private void parseConfiguration(XNode root) {
try {
// 省略其他节点的解析
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
// 解析mappers标签中的节点信息
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
// 如果是package标签,直接调用addMappers将其对应的类包名加入到mapperRegistry中,否则就逐个解析mapper标签的内容
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);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.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();
} 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.");
}
}
}
}
}
mapperRegistry
MapperRegistry是Configuration中的一个属性,它内部维护一个HashMap用于存放mapper接口的代理工厂类,每个接口对应一个代理工厂类。mappers中可以配置接口的包路径,或者某个具体的接口类。
// 属性
private final Configuration config;
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
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);
}
}
}
}
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);
}
}
Configuration getMapper方法
SqlSession有一个重要的方法就是获取Mapper对象,mapper对象的获取主要使用Configuration的getMapper方法。
使用过mybatis都知道,我们除了要配置mybatis以外,还需要定义接口,在接口中定义访问数据库的方法,此外还要在resources路径或者同级下存放名称一致的mapper.xml文件。这样才能调用这个接口进行数据库的操作。(这里省略了注解机制)
为什么我们只写了接口没有实现就可以调用这个方法呢?这里的奥秘就是使用了动态代理。
SqlSession中的getMapper方法是连接应用程序和mybatis的桥梁。当应用程序访问getMapper方法时,Mybatis会根据传入的接口类型和对应的XML配置文件生成一个代理对象,这个代理对象就叫Mapper对象。应用程序获得Mapper对象后,就应该通过这个Mapper对象来访问Mybatis的SqlSession对象,这样就达到里插入到Mybatis流程的目的。
@Override
public Configuration getConfiguration() {
return configuration;
}
@Override
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
configuration对象主要调用了MapperRegistry对象的方法获取Mapper对象。
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
下面是getMapper的实现方法
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 根据类型type获得对应的代理工厂
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对象
MapperProxyFactory对象就是通过动态代理获得mapper接口对应的代理类。
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) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
获取到实例以后,就可以调用mapper中的方法了,MapperProxy中是实现了invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
return mapperMethod.execute(sqlSession, args);
}
这里又调用了MapperMethod的execute方法,从这里我们可以看到最终是使用sqlSession执行的相关sql语句
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
Executor对象
我们来看看DefaultSqlSession中的某个方法
@Override
public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
通过上述方法我们可以知道,select方法的执行首先是从configuration中获取到MappedStatement,然后再使用executor.query方法执行。
Executor是一个接口,它包含了基本的增删查改方法,和对结果集的操作,是否进行缓存,清空本地缓存,获取事务并进行提交回滚,关闭会话等操作,它有如下六种基本的实现。从它的函数来看,我们可以知道,Executor主要是通过StatementHandler来访问数据库进行实际的操作。
以下是BaseExecutor的query方法,BaseExecutor是抽象类
@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;
}
protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
throws SQLException;
SimpleExecutor继承自BaseExecutor,其中的doQuery如下所示:它使用了
@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);
}
}
doQuery又调用了StatementHandler的query方法,其源码如下所示(以下是BaseStatementHandler的源码,它继承自StatementHandler),从这里我们可以看出来,最终执行方法使用jdbc中的包。
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
// 通过boundSql获取sql语句
String sql = boundSql.getSql();
// 执行sql语句
statement.execute(sql);
// 使用resultHandler处理执行结果
return resultSetHandler.handleResultSets(statement);
}
SqlSource
/*
代表从xml文件或者注解读取的映射statement内容,它将从用户接收的输入参数创建为SQL语句传递到数据库
*/
public interface SqlSource {
BoundSql getBoundSql(Object parameterObject);
}
SqlSource有如下四种实现,我将选择使用DynamicSqlSource进行讲解。
以下是DynamicSqlSource的源码
public class DynamicSqlSource implements SqlSource {
private final Configuration configuration;
private final SqlNode rootSqlNode;
public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
this.configuration = configuration;
this.rootSqlNode = rootSqlNode;
}
@Override
public BoundSql getBoundSql(Object parameterObject) {
DynamicContext context = new DynamicContext(configuration, parameterObject);
rootSqlNode.apply(context);
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
context.getBindings().forEach(boundSql::setAdditionalParameter);
return boundSql;
}
}
BoundSql
在处理了动态内容之后从SqlSource获取的实际SQL字符串,这些SQL可能有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;
public BoundSql(Configuration configuration, String sql, List<ParameterMapping> parameterMappings, Object parameterObject) {
this.sql = sql;
this.parameterMappings = parameterMappings;
this.parameterObject = parameterObject;
this.additionalParameters = new HashMap<>();
this.metaParameters = configuration.newMetaObject(additionalParameters);
}
public String getSql() {
return sql;
}
public List<ParameterMapping> getParameterMappings() {
return parameterMappings;
}
public Object getParameterObject() {
return parameterObject;
}
public boolean hasAdditionalParameter(String name) {
String paramName = new PropertyTokenizer(name).getName();
return additionalParameters.containsKey(paramName);
}
public void setAdditionalParameter(String name, Object value) {
metaParameters.setValue(name, value);
}
public Object getAdditionalParameter(String name) {
return metaParameters.getValue(name);
}
}
StatementHandler
StatementHandler是真正访问数据库的地方,此处通过java.sql.Statement执行sql语句,并通过ResultHandler处理结果。以下是StatementHandler实现的方法。
StatementHandler是对Statement的封装
StatementHandler有以下五种实现。其中SimpleStatementHandler继承自BaseStatementHandler
以下是SimpleStatementHandler的源码:
通过源码可以知道resultHandler会调用handleResultSets去处理查询返回的结果,而handleResultSets又会调用ResultSetWrapper去处理这个结果集,这个结果集又会通过Configuration获取typeHandlerRegistry,最后通过typeHandlerRegistry获取typeHandler进行类型转换。typeHandler是通过TypeHandlerRegistry进行注册的,需要的时候我们会直接从里面获取。
public class SimpleStatementHandler extends BaseStatementHandler {
public SimpleStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
}
@Override
public int update(Statement statement) throws SQLException {
String sql = boundSql.getSql();
Object parameterObject = boundSql.getParameterObject();
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
int rows;
if (keyGenerator instanceof Jdbc3KeyGenerator) {
statement.execute(sql, Statement.RETURN_GENERATED_KEYS);
rows = statement.getUpdateCount();
keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
} else if (keyGenerator instanceof SelectKeyGenerator) {
statement.execute(sql);
rows = statement.getUpdateCount();
keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
} else {
statement.execute(sql);
rows = statement.getUpdateCount();
}
return rows;
}
@Override
public void batch(Statement statement) throws SQLException {
String sql = boundSql.getSql();
statement.addBatch(sql);
}
// resultHandler会调用handleResultSets去处理查询返回的结果
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
String sql = boundSql.getSql();
statement.execute(sql);
return resultSetHandler.handleResultSets(statement);
}
@Override
public <E> Cursor<E> queryCursor(Statement statement) throws SQLException {
String sql = boundSql.getSql();
statement.execute(sql);
return resultSetHandler.handleCursorResultSets(statement);
}
@Override
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);
}
}
@Override
public void parameterize(Statement statement) {
// N/A
}
}
ResultHandler
ResultHandler是一个接口,主要用于处理查询结果。ResultHandler配合Executor进行数据库操作并处理操作返回的结果。
public interface ResultHandler<T> {
void handleResult(ResultContext<? extends T> resultContext);
}
DefaultMapResultHandler通过泛型的方式实现了ResultHandler接口,以下是其源码
public class DefaultMapResultHandler<K, V> implements ResultHandler<V> {
private final Map<K, V> mappedResults;
private final String mapKey;
private final ObjectFactory objectFactory;
private final ObjectWrapperFactory objectWrapperFactory;
private final ReflectorFactory reflectorFactory;
@SuppressWarnings("unchecked")
public DefaultMapResultHandler(String mapKey, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
this.objectFactory = objectFactory;
this.objectWrapperFactory = objectWrapperFactory;
this.reflectorFactory = reflectorFactory;
this.mappedResults = objectFactory.create(Map.class);
this.mapKey = mapKey;
}
@Override
public void handleResult(ResultContext<? extends V> context) {
final V value = context.getResultObject();
final MetaObject mo = MetaObject.forObject(value, objectFactory, objectWrapperFactory, reflectorFactory);
// TODO is that assignment always true?
final K key = (K) mo.getValue(mapKey);
mappedResults.put(key, value);
}
public Map<K, V> getMappedResults() {
return mappedResults;
}
}
TypeHandler
typeHandler主要用于类型转换,是mybatis中用于实现java类型和JDBC类型的相互转换。typehandler是在xml配置文件中配置的,所以在初始化加载配置文件的时候就将typehandler加载了进来。它是通过typehandlerRegistry进行注册的,然后存放在configuration之中。
typehandler是被ResultSetWrapper使用的,而ResultSetWrapper又是DefaultResultSetHandler(继承自ResultSetHandler)所使用的,而ResultSetHandler又是SimpleStatementHandler(继承自StatementHandler)所使用的,而StatementHandler又被SimpleExecutor(Executor)所使用,sqlsession使用executor执行查询。这样一条链就串联起来了。
总结
mybatis流程分析:
- mybatis在启动的时候,首先解析用户定义的mybatis.xml配置文件,将配置文件的所有信息加载到configuration中,并且还会将对应的mapper加载进来,使用MappedStatement存储获得的sql文件,这里会使用XMLConfigBuilder解析xml文件
- 当获取到configuration后,就经过SqlSessionFactoryBuilder的builder方法创建SqlSessionFactory对象。
- 通过sqlsessionfactory方法创建sqlsession对象,sqlsession包含执行数据库查询的方法
- sqlsession通过调用executor进行实际的查询操作,在sqlsession中获取configuration的MappedStatement(mapper中select|update|insert|delete节点映射的语句)属性,后面会通过mappedStatement创建对应的SimpleStatementHandler对象,协助执行sql语句
- 在executor执行的时候又会使用boundsql以及sqlsource获取对应的sql语句,将sql语句传递到statementhandler对象,此时executor会创建缓存
- statementhandler对象又使用statement进行数据库的查询操作,并且使用resulthandler进行结果的处理
- resulthandler会通过typehandler进行结果的转换操作,将jdbc的类型转换为Java的类型,其中已经为我们提供了许多typehandler,同样,我们也可以自己实现typehandler,进行自己的类型转换。
当我们使用接口开发的时候,只实现mapper.xml文件,不需要实现接口,这是怎么实现的?
- 主要是依赖configuration的getMapper方法,而getMapper方法使用mapperRegistry对象获取其中的mapper对象,这个mapperRegistry在解析mybatis.xml文件的时候会调用mapperRegistry.addMapper将解析的mapper进行注册,在需要的时候将其取出使用即可。
- 当去mapper时,会调用mapperProxyFactory对象生成新的代理对象MapperProxy,在我们执行接口的方法时,会调用该代理类MapperProxy对象的invoke方法
- invoke方法会调用MapperMethod的execute方法,该方法会根据SqlCommand的类型选取不同的SqlSession中的方法,然后处理执行后的结果。SqlSession方法会选择其实现类执行对应的方法。
Mybatis最重要的一点就是启动时读取配置文件,然后存入Configuration,然后的所有操作都要使用Configuration
参考
引用
文章首发于个人博客,欢迎来访。
风在哪个人博客