MyBatis 源码
一、准备工作
MyBatis 工作流程:应用程序首先加载 mybatis-config.xml 配置文件,并根据配置文件的内容创建 SqlSessionFactory 对象;然后,通过 SqlSessionFactory 对象创建 SqlSession 对象,SqlSession 接口中定义了执行 SQL 语句所需要的各种方法。之后,通过 SqlSession 对象执行映射配置文件中定义的 SQL 语句,完成相应的数据操作。最后通过 SqlSession 对象提交事务,关闭 SqlSession 对象,整个过程具体实现如下:就按照下面的流程进行源码分析
1 public void test01() throws IOException { 2 // 1、获取sqlSessionFactory对象 3 String resource = "mybatis-config.xml"; 4 InputStream inputStream = Resources.getResourceAsStream(resource); 5 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); 6 // 2、获取sqlSession对象 7 SqlSession openSession = sqlSessionFactory.openSession(); 8 try { 9 // 3、获取接口的实现类对象 10 //会为接口自动的创建一个代理对象,代理对象去执行增删改查方法 11 EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class); 12 Employee employee = mapper.getEmpById(1); 13 System.out.println(mapper); 14 System.out.println(employee); 15 } finally { 16 openSession.close(); 17 } 18 19 }
二、SqlSessionFactory 对象的初始化过程
SqlSessionFactory 对象的初始化序列图如下:
【3】解析 mapper.xml 文件,也是通过 XPathParser 类型的解析器,具体源码如下:将结果保存在 Configuration 中
1 /** 配置信息如下: 2 <mappers> 3 <mapper resource="EmployeeMapper.xml" /> 4 </mappers> 5 **/ 6 private void mapperElement(XNode parent) throws Exception { 7 if (parent != null) { 8 for (XNode child : parent.getChildren()) { 9 String resource = child.getStringAttribute("resource"); 10 String url = child.getStringAttribute("url"); 11 String mapperClass = child.getStringAttribute("class"); 12 if (resource != null && url == null && mapperClass == null) { 13 ErrorContext.instance().resource(resource); 14 //将配置文件转化为流文件,mapper.xml 文件 15 InputStream inputStream = Resources.getResourceAsStream(resource); 16 XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); 17 //解析 mapper.xml 文件 18 mapperParser.parse(); 19 } 20 } 21 } 22 } 23 24 //进入 mapperParser.parse(); 方法 25 public void parse() { 26 if (!configuration.isResourceLoaded(resource)) { 27 //解析 mapper标签中的内容 28 configurationElement(parser.evalNode("/mapper")); 29 configuration.addLoadedResource(resource); 30 bindMapperForNamespace(); 31 } 32 33 parsePendingResultMaps(); 34 parsePendingChacheRefs(); 35 parsePendingStatements(); 36 } 37 38 //进入 configurationElement(parser.evalNode("/mapper")); 方法 39 //解析mapper中的标签内容 40 private void configurationElement(XNode context) { 41 String namespace = context.getStringAttribute("namespace"); 42 builderAssistant.setCurrentNamespace(namespace); 43 cacheRefElement(context.evalNode("cache-ref")); 44 cacheElement(context.evalNode("cache")); 45 parameterMapElement(context.evalNodes("/mapper/parameterMap")); 46 resultMapElements(context.evalNodes("/mapper/resultMap")); 47 sqlElement(context.evalNodes("/mapper/sql")); 48 //解析增删改查标签 49 buildStatementFromContext(context.evalNodes("select|insert|update|delete")); 50 }
【4】解析 mapper 中的增删改查标签:拿到所有标签能写的属性,将详细信息保存进 MappedStatement(是一个Map,Key 存放的是 命令空间+id) 中,一个 MappedStatement 就代表一个增删改查标签的详细信息。
1 // 进入增删改查标签的源码 buildStatementFromContext 2 private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) { 3 for (XNode context : list) { 4 //获取到 解析增删改查标签的解析器 statementParser 5 final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId); 6 //解析标签中的内容 7 statementParser.parseStatementNode(); 8 } 9 } 10 public void parseStatementNode() { 11 String id = context.getStringAttribute("id"); 12 String databaseId = context.getStringAttribute("databaseId"); 13 14 if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) { 15 return; 16 } 17 18 Integer fetchSize = context.getIntAttribute("fetchSize"); 19 Integer timeout = context.getIntAttribute("timeout"); 20 String parameterMap = context.getStringAttribute("parameterMap"); 21 String parameterType = context.getStringAttribute("parameterType"); 22 //.... 23 24 //将解析的结果进行封装 25 builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, 26 fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, 27 resultSetTypeEnum, flushCache, useCache, resultOrdered, 28 keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); 29 } 30 } 31 32 //进入 builderAssistant.addMappedStatement 方法 33 public MappedStatement addMappedStatement( 34 //...... 35 MappedStatement statement = statementBuilder.build(); 36 //并将结果添加到 Configuration 中,MappedStatement 是一个 map 对象 37 configuration.addMappedStatement(statement); 38 return statement; 39 }
【5】Configuration 对象保存了所有配置文件的详细信息,包括全局配置文件和 sql 映射文件。
【6】DefaultSqlSessionFactory:传入上面返回的 Configuration 对象,通过 build 的方法创建一个 DefaultSqlSessionFactory 包含配置了全局信息的 Configuration;
public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); }
DefaultSqlSessionFactory
: SqlSessionFactory 的默认实现类,是真正生产会话的工厂类,这个类的实例的生命周期是全局的,它只会在首次调用时生成一个实例(单例模式),就一直存在直到服务器关闭。
三、SqlSession 对象的初始化过程
SqlSession 对象的初始化序列图如下:会创建 Executor
SqlSession 对象是 MyBatis 中最重要的一个对象,这个接口能够让你执行命令,获取映射,管理事务。SqlSession 中定义了一系列模版方法,让你能够执行简单的
CRUD
操作,也可以通过getMapper
获取 Mapper 层,执行自定义 SQL 语句,因为 SqlSession 在执行 SQL 语句之前是需要先开启一个会话,涉及到事务操作,所以还会有commit
、rollback
、close
等方法。这也是模版设计模式的一种应用。
【1】通过 DefaultSqlSessionFactory 的 openSession 方法获取 SqlSession;
【2】进入 openSession方法:需要传入 Executor 的类型,在配置文件中可以指定,默认是 simple;
【3】进入 openSessionFromDataSource 方法:重点是创建了 Executor执行器对象和 SqlSession
对象,传入事务和类型。
【4】进入创建 Executor 的方法:configuration.newExecutor(tx, execType); Exeutor 是用来做增删改查的,里面包含了 query等方法;
【5】展示 Executor 对象中的信息:
1 public interface Executor { 2 //增删改查操作 3 int update(MappedStatement ms, Object parameter) throws SQLException; 4 <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException; 5 List<BatchResult> flushStatements() throws SQLException; 6 7 //事务相关操作 8 void commit(boolean required) throws SQLException; 9 void rollback(boolean required) throws SQLException; 10 //...... 11 }
【6】创建 DefaultSqlSession 并将上述创建的 Executor传入,并包含 Configuration;
return new DefaultSqlSession(configuration, executor, autoCommit);
四、getMapper 对象的初始化过程
getMapper 对象的初始化序列图如下: 会创建 MapperProxy
代理对象;MapperProxy 是 Mapper 映射 SQL 语句的关键对象,我们写的 Dao 层或者 Mapper 层都是通过 MapperProxy
来和对应的 SQL语句进行绑定的。下面我们就来解释一下绑定过程。
【1】通过 getMapper 获取代理对象;
【2】进入 getMapper 方法内部,发现调用的是 Configuration 对象的 getMapper 方法,并将 SqlSession 作为参数传入;
public <T> T getMapper(Class<T> type) { return configuration.<T>getMapper(type, this); }
【3】进入 Configuration 对象的 getMapper 方法,通过调用 mapperRegistry 对象的 getMapper 方法;
public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); }
【4】进入 MapperRegistry 的 getMapper 方法,根据接口类型获取其代理对象工厂 mapperProxyFactory;
1 @SuppressWarnings("unchecked") 2 public <T> T getMapper(Class<T> type, SqlSession sqlSession) { 3 //根据接口类型获取其代理对象 4 final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); 5 // 会创建一个 mapperProxy 6 return mapperProxyFactory.newInstance(sqlSession); 7 }
【5】进入 mapperProxyFactory.newInstance(sqlSession); 方法,其主要是创建 MapperProxy 代理对象;
1 public T newInstance(SqlSession sqlSession) { 2 //获取代理对象 3 final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache); 4 return newInstance(mapperProxy); 5 }
【6】进入 MapperProxy 代理对象,主要包含如下三个属性:并且实现了 InvocationHandler,是JDK 动态代理的一部分;
1 public class MapperProxy<T> implements InvocationHandler, Serializable { 2 3 private static final long serialVersionUID = -6424540398559729838L; 4 //主要包含 sqlSession 、 mapper接口和 其中的方法 5 private final SqlSession sqlSession; 6 private final Class<T> mapperInterface; 7 private final Map<Method, MapperMethod> methodCache; 8 //...... 9 }
【7】我们进入 5 中的 newInstance 方法:使用 JDK 实现动态代理 Proxy.newProxyInstance(....),创建 MapperProxy 代理对象。内部包含 SqlSession 进行增删改查。也就是说,MyBatis 中 Mapper 和 SQL 语句的绑定正是通过动态代理来完成的。通过动态代理,我们就可以方便的在 Dao 层或者 Mapper 层定义接口,实现自定义的增删改查操作了。
1 @SuppressWarnings("unchecked") 2 //JDK 动态代理 3 protected T newInstance(MapperProxy<T> mapperProxy) { 4 return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); 5 }
【8】代理对象展示:
五、通过代理对象调用目标方法的初始化过程
mapper.getEmpById(1) 查询执行的序列图如下:
【1】进入代理类调用目标方法入口:
【2】首先会进入代理方法的 invoke 方法:
1 //调用代理对象的入口 2 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 3 //首先判断调用的是不是 Object 对象的方法 4 if (Object.class.equals(method.getDeclaringClass())) { 5 try { 6 return method.invoke(this, args); 7 } catch (Throwable t) { 8 throw ExceptionUtil.unwrapThrowable(t); 9 } 10 } 11 //将当前方法包装成 MapperMethod 12 final MapperMethod mapperMethod = cachedMapperMethod(method); 13 return mapperMethod.execute(sqlSession, args); 14 }
【3】进入 mapperMethod.execute(sqlSession, args); 目标方法执行,传入 sqlSession 和参数;
1 public Object execute(SqlSession sqlSession, Object[] args) { 2 Object result; 3 switch (command.getType()) { 4 case INSERT: { 5 Object param = method.convertArgsToSqlCommandParam(args); 6 result = rowCountResult(sqlSession.insert(command.getName(), param)); 7 break; 8 } 9 case UPDATE: { 10 Object param = method.convertArgsToSqlCommandParam(args); 11 result = rowCountResult(sqlSession.update(command.getName(), param)); 12 break; 13 } 14 case DELETE: { 15 Object param = method.convertArgsToSqlCommandParam(args); 16 result = rowCountResult(sqlSession.delete(command.getName(), param)); 17 break; 18 } 19 case SELECT: 20 //当前方法无返回值时执行 21 if (method.returnsVoid() && method.hasResultHandler()) { 22 executeWithResultHandler(sqlSession, args); 23 result = null; 24 //返回多个执行方法 25 } else if (method.returnsMany()) { 26 result = executeForMany(sqlSession, args); 27 //返回Map执行 28 } else if (method.returnsMap()) { 29 result = executeForMap(sqlSession, args); 30 //返回游标执行 31 } else if (method.returnsCursor()) { 32 result = executeForCursor(sqlSession, args); 33 } else { 34 //其余执行此方法 35 //该方法是将当前参数:如果是1个参数则返回,如果多个则组装成 map 返回 36 Object param = method.convertArgsToSqlCommandParam(args); 37 result = sqlSession.selectOne(command.getName(), param); 38 } 39 break; 40 return result; 41 }
【4】调用单个查询 sqlSession.selectOne 方法:调用查询多个的方法,返回 list的第一个元素即可;statement 就是当前sql 的唯一表示,对应 xml 中的 namespace;
1 public <T> T selectOne(String statement, Object parameter) { 2 List<T> list = this.<T>selectList(statement, parameter); 3 if (list.size() == 1) { 4 return list.get(0); 5 } else if (list.size() > 1) { 6 throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size()); 7 } else { 8 return null; 9 } 10 }
【5】进入 selectList(statement, parameter); 方法:会获取 mapperedStatement 对象,同时调用 Executor 的query 方法执行
【6】获取 mapperedStatement 通过 BoundSql 它代表 sql 语句的详细信息:通过query 方法进行查询;
1 public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { 2 BoundSql boundSql = ms.getBoundSql(parameter); 3 CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); 4 return query(ms, parameter, rowBounds, resultHandler, key, boundSql); 5 }
【7】进入上述的 query 方法,先调用 cacheExecutor 缓存,如果不存在则调用全局配置 Executor ,我们这里使用的默认 simpleExecutor 方法:先会调用二级缓存,再调用一级缓存;如果不存在则执行 queryFromDatabase 查出以后也会保存在以及缓存中;
1 public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { 2 //先调用本地缓存 3 list = resultHandler == null ? (List<E>) localCache.getObject(key) : null; 4 if (list != null) { 5 handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); 6 } else { 7 //如果缓存中不存在,则调用 queryFromDatabase 8 list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); 9 } 10 return list; 11 }
【8】进入 queryFromDatabase 方法:调用 BaseExecutor 对象中的 doQuery方法 ms 当前增删改查标签的详细信息
1 private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { 2 try { 3 //调用 BaseExecutor对象中的 doQuery方法 ms 当前增删改查标签的详细信息 4 list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); 5 } 6 return list; 7 }
【9】进入 SimpleExecutor 对象的 doQuery 方法:底层包含了 Statement 对象,表示对 JDBC 的封装,同时创建了四大对象之一 StatementHandler 作用:创建 Statement 对象;在创建 StatementHandler 的时候构造器里面会同时创建 ParameterHandler和 ResultSetHandler。
1 public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { 2 //原生 JDBC 的 Statement对象 3 Statement stmt = null; 4 try { 5 Configuration configuration = ms.getConfiguration(); 6 //创建四个对象之一 StatementHandler 7 StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); 8 //从 StatementHandler 中获取 Statement 对象 9 stmt = prepareStatement(handler, ms.getStatementLog()); 10 return handler.<E>query(stmt, resultHandler); 11 } 12 }
【10】进入 newStatementHandler 方法,查看 StatementHandler 对象的创建;
1 public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { 2 StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql); 3 //使用拦截器链包装 StatementHandler 4 statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler); 5 return statementHandler; 6 }
【11】进入 RoutingStatementHandler 方法:我们可以在查询标签中设置 StatementType 就根据如下方法创建我们需要的 Statement 对象;默认使用的是 PREPARED(预编译的形式),会创建一个 PreparedStatementHandler;
1 public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { 2 3 switch (ms.getStatementType()) { 4 case STATEMENT: 5 delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); 6 break; 7 case PREPARED: 8 delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); 9 break; 10 case CALLABLE: 11 delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); 12 break; 13 default: 14 throw new ExecutorException("Unknown statement type: " + ms.getStatementType()); 15 } 16 }
【12】通过 StatementHandler 创建 Statement 对象,进入 stmt = prepareStatement(handler, ms.getStatementLog()); 方法:预编译 sql 产生的 PreparedStatement 对象,调用四大对象之一 ParameterHandler 进行参数的预编译;也是 JDBC 原生的对象;
1 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { 2 Statement stmt; 3 //创建一个链接 4 Connection connection = getConnection(statementLog); 5 //进行预编译 6 stmt = handler.prepare(connection, transaction.getTimeout()); 7 //进行参数预编译 8 handler.parameterize(stmt); 9 return stmt; 10 }
【13】进入创建 ParameterHandler 的方法:newParameterHandler(mappedStatement, parameterObject, boundSql); 同时也调用了 pluginAll 方法,通过拦截器链对 ParameterHandler 进行封装;
1 public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) { 2 ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql); 3 //也调用了拦截器链 4 parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler); 5 return parameterHandler; 6 }
【14】进入创建 ResultSetHandler 的方法:configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql); 同时也调用了 pluginAll 方法,通过拦截器链对 ParameterHandler 进行封装;也输入四大对象之一;用于处理查询得到的数据;
1 public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler, 2 ResultHandler resultHandler, BoundSql boundSql) { 3 ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds); 4 //也调用了拦截器链 5 resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler); 6 return resultSetHandler; 7 }
【15】最终返回执行的结果和关闭连接,以及查询流程总结:
■ StatementHandler:处理 sql语句预编译,设置参数等相关工作;
■ ParameterHandler:设置预编译参数用的;
■ResultHandler:处理结果集;
■TypeHandler:在整个过程中,进行数据库类型和 javaBean类型的映射;
六、总结
【1】根据配置文件(全局,sql映射)初始化出 Configuration 对象;
【2】创建一个 DefaultSqlSession对象,他里面包含 Configuration以及 Executor(根据全局配置文件中的 defaultExecutorType创建出对应的 Executor);
【3】DefaultSqlSession.getMapper():拿到 Mapper接口对应的 MapperProxy;
【4】MapperProxy里面有(DefaultSqlSession);
【5】执行增删改查方法:
1)、代理对象调用 DefaultSqlSession 的增删改查(最终调用 Executor 的增删改查);
2)、会创建一个 StatementHandler 对象。(同时也会创建出 ParameterHandler 和 ResultSetHandler)
3)、调用 StatementHandler预编译参数以及设置参数值,使用 ParameterHandler 来给 sql设置参数;
4)、调用 StatementHandler 的增删改查方法;
5)、ResultSetHandler 封装结果;
注意:四大对象每个创建的时候都有一个 interceptorChain.pluginAll(parameterHandler);后面的插件应用中使用;