【深入浅出MyBatis笔记】MyBatis的解析和运行原理
MyBatis的解析和运行原理
构建SqlSessionFactory过程
SqlSessionFactory提供创建MyBatis的核心接口SqlSession。MyBatis采用构造模式去创建SqlSessionFactory,我们可以通过SqlSessionFactoryBuilder去构建。
- 第一步,通过XMLConfigBuilder解析配置的XML文件,读出配置参数,并将读取的数据存入这个Configuration类中。
- 第二步,使用Configuration对象去创建SqlSessionFactory。
SqlSessionFactoryBuilder的源码:
public class SqlSessionFactoryBuilder { ..... public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); // XMLConfigBuilder解析配置的XML文件,构建Configuration 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. } } } // 使用Configuration对象去创建SqlSessionFactory public SqlSessionFactory build(Configuration config) { // SqlSessionFactory是一个接口,为此MyBatis提供了一个默认实现类 return new DefaultSqlSessionFactory(config); } }
构建Configuration
在XMLConfigBuilder中,MyBatis会读出所有XML配置的信息,然后将这些信息保存到Configuration类的单例中。
它会做如下初始化:
- properties全局参数
- setting设置
- typeAliases别名
- typeHandler类型处理器
- ObjectFactory对象
- plugin插件
- environment环境
- DatabaseIdProvider数据库标识
- Mapper映射器
XMLConfigBuilder的源码:
public class XMLConfigBuilder extends BaseBuilder { ... public Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; // 解析配置文件,设置Configuration parseConfiguration(parser.evalNode("/configuration")); return configuration; } private void parseConfiguration(XNode root) { // 读出MyBatis配置文件中的configuration下的各个子标签元素 // 把全部信息保存到Configuration类的单例中 try { //issue #117 read properties first propertiesElement(root.evalNode("properties")); Properties settings = settingsAsProperties(root.evalNode("settings")); loadCustomVfs(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); } } }
映射器的内部组成
XMLMapperBuilder负责对配置文件中的Mapper映射器进行解析,其中在configurationElement方法中可以看出来,会分别对配置文件中的parameterMap、resultMap、sql、select|insert|update|delete元素进行解析。
public class XMLMapperBuilder extends BaseBuilder { public void parse() { if (!configuration.isResourceLoaded(resource)) { // 解析配置文件中的mapper映射器 configurationElement(parser.evalNode("/mapper")); configuration.addLoadedResource(resource); bindMapperForNamespace(); } parsePendingResultMaps(); parsePendingCacheRefs(); parsePendingStatements(); } private void configurationElement(XNode context) { try { String namespace = context.getStringAttribute("namespace"); if (namespace == null || namespace.equals("")) { throw new BuilderException("Mapper's namespace cannot be empty"); } builderAssistant.setCurrentNamespace(namespace); cacheRefElement(context.evalNode("cache-ref")); cacheElement(context.evalNode("cache")); // 解析我们配置的parameterMap元素 parameterMapElement(context.evalNodes("/mapper/parameterMap")); // 解析我们配置的resultMap元素 resultMapElements(context.evalNodes("/mapper/resultMap")); // 解析我们配置的sql元素 sqlElement(context.evalNodes("/mapper/sql")); // 解析我们配置的select、insert、update、delete元素 buildStatementFromContext(context.evalNodes("select|insert|update|delete")); } catch (Exception e) { throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e); } } }
在方法buildStatementFromContext()中,会根据配置信息创建一个MappedStatement对象。
- MappedStatement,它保存映射器的一个节点(select|insert|update|delete)。包括许多我们配置的SQL、SQL的id、缓存信息、resultMap、parameterType、resultType、languageDriver等重要配置内容。
public final class MappedStatement { private Configuration configuration; private String id; 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 SqlCommandType sqlCommandType; private KeyGenerator keyGenerator; private String databaseId; private LanguageDriver lang; ...... }
- SqlSource,它是提供BoundSql对象的地方,它是MappedStatement的一个属性。
public interface SqlSource { BoundSql getBoundSql(Object parameterObject); }
- BoundSql,它是建立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; }
SqlSession运行过程
SqlSession是一个接口,在MyBatis中有一个默认实现DefaultSqlSession。我们构建SqlSessionFactory就可以轻易地拿到SqlSession了。通过SqlSession,我们拿到Mapper,之后可以做查询、插入、更新、删除的方法。
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
其实getMapper()方法拿到的mapper是通过Java动态代理实现的。从getMapper()方法逐级往下看,可以发现在MapperRegistry类的getMapper()方法中会拿到一个MapperProxyFactory的对象,最后是通过MapperProxyFactory对象去生成一个Mapper的。
public class DefaultSqlSession implements SqlSession { ..... @Override public <T> T getMapper(Class<T> type) { return configuration.<T>getMapper(type, this); } } public class Configuration { ..... public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); } } public class MapperRegistry { ...... public <T> T getMapper(Class<T> type, SqlSession sqlSession) { final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } try { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } } }
映射器的动态代理
Mapper映射是通过动态代理实现的,MapperProxyFactory用来生成动态代理对象。
public class MapperProxyFactory<T> { ...... 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<T>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } }
在MapperProxyFactory的newInstance方法中可以看到有一个MapperProxy对象,MapperProxy实现InvocationHandler接口(动态代理需要实现这一接口)的代理方法invoke(), 这invoke()方法实现对被代理类的方法进行拦截。
而在invoke()方法中,MapperMethod对象会执行Mapper接口的查询或其他方法。
public class MapperProxy<T> implements InvocationHandler, Serializable { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { // 先判断是否一个类,在这里Mapper显然是一个接口 if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); // 判断是不是接口默认实现方法 } else if (isDefaultMethod(method)) { return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } // 缓存中取出MapperMethod,不存在的话,则根据Configuration初始化一个 final MapperMethod mapperMethod = cachedMapperMethod(method); // 执行Mapper接口的查询或其他方法 return mapperMethod.execute(sqlSession, args); } private MapperMethod cachedMapperMethod(Method method) { MapperMethod mapperMethod = methodCache.get(method); if (mapperMethod == null) { mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()); methodCache.put(method, mapperMethod); } return mapperMethod; } }
MapperMethod采用命令模式运行,并根据上下文跳转。MapperMethod在构造器初始化时会根据Configuration和Mapper的Method方法解析为SqlCommand命令。之后在execute方法,根据SqlCommand的Type进行跳转。然后采用命令模式,SqlSession通过SqlCommand执行插入、更新、查询、选择等方法。
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) { // 根据Configuration和Mapper的Method方法解析为SqlCommand this.command = new SqlCommand(config, mapperInterface, method); this.method = new MethodSignature(config, mapperInterface, method); } public Object execute(SqlSession sqlSession, Object[] args) { Object result; // 根据Type进行跳转,通过sqlSession执行相关的操作 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); } 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; }
看到这里,应该大概知道了MyBatis为什么只用Mapper接口便能够运行SQL,因为映射器的XML文件的命名空间namespace对应的便是这个接口的全路径,那么它根据全路径和方法名便能够绑定起来,通过动态代理技术,让这个接口跑起来。而后采用命令模式,最后还是使用SqlSession接口的方法使得它能够执行查询,有了这层封装我们便可以使用这个接口编程。
不过还是可以看到,最后插入、更新、删除、查询操作还是会回到SqlSession中进行处理。
Sqlsession下的四大对象
我们已经知道了映射器其实就是一个动态代理对象,进入到了MapperMethod的execute方法。它经过简单判断就是进入了SqlSession的删除、更新、插入、选择等方法。sqlSession执行一个查询操作。可以看到是通过一个executor来执行的。
其实SqlSession中的Executor执行器负责调度StatementHandler、ParameterHandler、ResultHandler等来执行相关的SQL。
- StatementHandler:使用数据库的Statement(PrepareStatement)执行操作
- ParameterHandler:用于SQL对参数的处理
- ResultHandler:进行最后数据集(ResultSet)的封装返回处理
Sqlsession其实是一个接口,它有一个DefaultSqlSession的默认实现类。
public class DefaultSqlSession implements SqlSession { private final Configuration configuration; // Executor执行器,负责调度SQL的执行 private final Executor executor; ...... @Override public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { MappedStatement ms = configuration.getMappedStatement(statement); // 通过executor执行查询操作 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(); } } }
Executor执行器
执行器起到了至关重要的作用,它是一个真正执行Java和数据库交互的东西。在MyBatis中存在三种执行器,我们可以在MyBatis的配置文件中进行选择。
- SIMPLE,简易执行器
- REUSE,是一种执行器重用预处理语句
- BATCH,执行器重用语句和批量更新,她是针对批量专用的执行器
它们都提供了查询和更新方法,以及相关的事务方法。
Executor是通过Configuration类创建的,MyBatis将根据配置类型去确定你需要创建三种执行器中的哪一种。
public class Configuration { ...... 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); } // MyBatis插件,构建一层层的动态代理对象 // 在调度真实的方法之前执行配置插件的代码 executor = (Executor) interceptorChain.pluginAll(executor); return executor; } }显然MyBatis根据Configuration来构建StatementHandler,然后使用prepareStatement方法,对SQL编译并对参数进行初始化,resultHandler再组装查询结果返回给调用者来完成一次查询。
public class SimpleExecutor extends BaseExecutor { ..... @Override public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { Configuration configuration = ms.getConfiguration(); // 根据Configuration来构建StatementHandler StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); // 对SQL编译并对参数进行初始化 stmt = prepareStatement(handler, ms.getStatementLog()); // 组装查询结果返回给调用者 return handler.<E>query(stmt, resultHandler); } finally { closeStatement(stmt); } } private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; Connection connection = getConnection(statementLog); // 进行预编译和基础设置 stmt = handler.prepare(connection, transaction.getTimeout()); // 设置参数 handler.parameterize(stmt); return stmt; } }
StatementHandler数据库会话器
StatementHandler就是专门处理数据库会话的。
创建StatementHandler:
public class Configuration { ...... public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql); // MyBatis插件,生成一层层的动态代理对象 statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler); return statementHandler; } }
RoutingStatementHandler其实不是我们真实的服务对象,它是通过适配模式找到对应的StatementHandler来执行。
StatementHandler分为三种:
- SimleStatementHandler
- PrepareStatementHandler
- CallableStatementHandler
在初始化RoutingStatementHandler对象的时候它会根据上下文环境来决定创建哪个StatementHandler对象。
public class RoutingStatementHandler implements StatementHandler { ...... private final StatementHandler delegate; public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { switch (ms.getStatementType()) { case STATEMENT: delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; case PREPARED: delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; case CALLABLE: delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; default: throw new ExecutorException("Unknown statement type: " + ms.getStatementType()); } } }
数据库会话器定义了一个对象的适配器delegate,它是一个StatementHandler接口对象,构造器根据配置来适配对应的StatementHandler对象。它的作用是给实现类对象的使用提供一个统一、简易的使用适配器。此为对象的适配模式,可以让我们使用现有的类和方法对外提供服务,也可以根据实际的需求对外屏蔽一些方法,甚至加入新的服务。
在执行器Executor执行查询操作的时候,我们看到PreparedStatementHandler的三个方法:prepare、parameterize和query。
public abstract class BaseStatementHandler implements StatementHandler { ..... @Override public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException { ErrorContext.instance().sql(boundSql.getSql()); Statement statement = null; try { // 对SQL进行了预编译 statement = instantiateStatement(connection); setStatementTimeout(statement, transactionTimeout); setFetchSize(statement); return statement; } catch (SQLException e) { closeStatement(statement); throw e; } catch (Exception e) { closeStatement(statement); throw new ExecutorException("Error preparing statement. Cause: " + e, e); } } } public class PreparedStatementHandler extends BaseStatementHandler { @Override public void parameterize(Statement statement) throws SQLException { // 设置参数 parameterHandler.setParameters((PreparedStatement) statement); } @Override protected Statement instantiateStatement(Connection connection) throws SQLException { String sql = boundSql.getSql(); if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) { String[] keyColumnNames = mappedStatement.getKeyColumns(); if (keyColumnNames == null) { return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS); } else { return connection.prepareStatement(sql, keyColumnNames); } } else if (mappedStatement.getResultSetType() != null) { return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY); } else { return connection.prepareStatement(sql); } } @Override public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; // 执行SQL ps.execute(); // resultSetHandler封装结果返回 return resultSetHandler.<E> handleResultSets(ps); } }一条查询SQL的执行过程,Executor会先调用StatementHandler的prepare()方法预编译SQL语句,同时设置一些基本运行的参数。然后用parameterize()方法启动ParameterHandler设置参数,完成预编译,跟着就是执行查询。