Mybatis核心组件介绍及源码解读
Mybatis核心成员
Configuration MyBatis所有的配置信息都保存在Configuration对象之中,配置文件中的大部分配置都会存储到该类中
SqlSession 作为MyBatis工作的主要顶层API,表示和数据库交互时的会话,完成必要数据库增删改查功能
Executor MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护
StatementHandler 封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数等
ParameterHandler 负责对用户传递的参数转换成JDBC Statement 所对应的数据类型
ResultSetHandler 负责将JDBC返回的ResultSet结果集对象转换成List类型的集合
TypeHandler 负责java数据类型和jdbc数据类型(也可以说是数据表列类型)之间的映射和转换
MappedStatement MappedStatement维护一条<select|update|delete|insert>节点的封装
SqlSource 负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回
BoundSql 表示动态生成的SQL语句以及相应的参数信息
Configuration SqlSessionFactory SqlSession sqlSession创建流程图
1 2 3 4 5 String resource = "mybatis.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 然后根据 sqlSessionFactory 得到 session SqlSession session = sqlSessionFactory.openSession();
首先会创建SqlSessionFactory建造者对象,从它来创建SqlSession
org.apache.ibatis.session.SqlSessionFactoryBuilder#build
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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) { } } }
parser.parse()方法会加载mybatis配置文件,初始化Configuration
org.apache.ibatis.builder.xml.XMLConfigBuilder#parse
1 2 3 4 5 6 7 8 9 public Configuration parse () { if (this .parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once." ); } else { this .parsed = true ; this .parseConfiguration(this .parser.evalNode("/configuration" )); return this .configuration; } }
MapperProxy MapperProxy创建流程图
1 2 3 4 5 6 String resource = "mybatis.xml" ; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession session = sqlSessionFactory.openSession(); ICategoryMapper mapper = session.getMapper(ICategoryMapper.class);
在mybatis中,通过MapperProxy动态代理咱们的dao, 也就是说, 当咱们执行自己写的dao里面的方法的时候,其实是对应的mapperProxy在代理。
org.apache.ibatis.session.defaults.DefaultSqlSession#getMapper
1 2 3 public <T> T getMapper (Class<T> type) { return this .configuration.getMapper(type, this ); }
最终MapperProxyFactory来创建代理
org.apache.ibatis.binding.MapperRegistry#getMapper
1 2 3 4 5 6 7 8 9 10 11 12 public <T> T getMapper (Class<T> type, SqlSession sqlSession) { MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this .knownMappers.get(type); if (mapperProxyFactory == null ) { throw new BindingException("Type " + type + " is not known to the MapperRegistry." ); } else { try { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception var5) { throw new BindingException("Error getting mapper instance. Cause: " + var5, var5); } } }
Excutor sql执行
1 2 3 4 5 6 7 8 String resource = "mybatis.xml" ; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession session = sqlSessionFactory.openSession(); ICategoryMapper mapper = session.getMapper(ICategoryMapper.class); Category category = mapper.selectOne(1L );
每个MapperProxy对应一个dao接口, 那么咱们在使用的时候,都会进入invoke方法
org.apache.ibatis.binding.MapperProxy.PlainMethodInvoker#invoke
1 2 3 4 5 6 7 8 9 10 11 12 13 @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { if (Object.class.equals(method.getDeclaringClass())) { try { return method.invoke(this , args); } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args); }
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 public Object execute (SqlSession sqlSession, Object[] args) { Object result; if (SqlCommandType.INSERT == command.getType()) { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.insert(command.getName(), param)); } else if (SqlCommandType.UPDATE == command.getType()) { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.update(command.getName(), param)); } else if (SqlCommandType.DELETE == command.getType()) { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.delete(command.getName(), param)); } else if (SqlCommandType.SELECT == command.getType()) { 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 { Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(command.getName(), param); } } else { 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; }
查询方法会进入sqlSession的 selectList方法
org.apache.ibatis.session.defaults.DefaultSqlSession#selectList
1 2 3 4 5 6 7 8 9 10 11 12 13 private <E> List<E> selectList (String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) { List var6; try { MappedStatement ms = this .configuration.getMappedStatement(statement); var6 = this .executor.query(ms, this .wrapCollection(parameter), rowBounds, handler); } catch (Exception var10) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + var10, var10); } finally { ErrorContext.instance().reset(); } return var6; }
然后,通过一层一层的调用,最终会来到doQuery方法 (以SimpleExecutor为例)org.apache.ibatis.executor.SimpleExecutor#doQuery
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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(); 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; }
结果集交给ResultSetHandler去处理
org.apache.ibatis.executor.statement.PreparedStatementHandler#query
1 2 3 4 5 public <E> List<E> query (Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement)statement; ps.execute(); return this .resultSetHandler.handleResultSets(ps); }
四大核心对象 MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) –执行sql
ParameterHandler (getParameterObject, setParameters) –获取、设置参数
ResultSetHandler (handleResultSets, handleOutputParameters) –处理结果集
StatementHandler (prepare, parameterize, batch, update, query) –记录sql
自定义插件示例 定义插件 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 @Intercepts(value = {@Signature( type= Executor.class, //这里对应4个类 method = "update", //这里对应4个类里面的参数 args = {MappedStatement.class,Object.class})}) public class DrafirePlugin implements Interceptor { @Override public Object intercept (Invocation invocation) throws Throwable { System.out.println("mybatis插件打印了乐乐" ); Category obj = (Category)invocation.getArgs()[1 ]; MappedStatement streDemo = (MappedStatement)invocation.getArgs()[0 ]; SqlSource sqlSource = streDemo.getSqlSource(); BoundSql boundSql = sqlSource.getBoundSql(streDemo.getParameterMap()); String sql = boundSql.getSql(); obj.setCat_name("家电7" ); return invocation.proceed(); } @Override public Object plugin (Object target) { return Plugin.wrap(target, this ); } @Override public void setProperties (Properties properties) { } }
配置插件 1 2 3 <plugins> <plugin interceptor="com.song.plugin.DrafirePlugin"> </plugin> </plugins>
动态sql 相关接口和类 SqlNode 简单理解就是xml 中的每个标签,比如上述sql的update,trim,if标签:
1 2 3 public interface SqlNode { boolean apply (DynamicContext context) ; }
SqlSource Sql源接口,代表从xml文件或注解映射的sql内容,主要就是用于创建BoundSql,有实现类DynamicSqlSource(动态Sql源),StaticSqlSource(静态Sql源)等:
org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseScriptNode
1 2 3 4 5 6 7 8 9 10 11 12 13 public SqlSource parseScriptNode () { MixedSqlNode rootSqlNode = parseDynamicTags(context); SqlSource sqlSource; if (isDynamic) { sqlSource = new DynamicSqlSource(configuration, rootSqlNode); } else { sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType); } return sqlSource; }
1 2 3 public interface SqlSource { BoundSql getBoundSql (Object parameterObject) ; }
BoundSql BoundSql类,封装mybatis最终产生sql的类,包括sql语句 ,参数,参数源数据等参数:
查询源码链示例 1 2 3 4 5 6 7 8 9 10 11 12 String resource = "mybatis.xml" ; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); sqlSessionFactory.openSession(true ); SqlSession session = sqlSessionFactory.openSession(); ICategoryMapper mapper = session.getMapper(ICategoryMapper.class); Category cagte = new Category(); cagte.setCat_id(2L ); cagte.setCat_name("家电5" ); Category category = mapper.selectOne(cagte); session.clearCache(); session.close();
MapperProxy#invoke invoke 代理方法
org.apache.ibatis.binding.MapperProxy#invoke
1 2 3 4 5 6 7 public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { try { return Object.class.equals(method.getDeclaringClass()) ? method.invoke(this , args) : this .cachedInvoker(method).invoke(proxy, method, args, this .sqlSession); } catch (Throwable var5) { throw ExceptionUtil.unwrapThrowable(var5); } }
MapperMethod#execute execute 选择具体的CRUD方法
org.apache.ibatis.binding.MapperMethod#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 public Object execute (SqlSession sqlSession, Object[] args) { Object result; Object param; switch (this .command.getType()) { case INSERT: param = this .method.convertArgsToSqlCommandParam(args); result = this .rowCountResult(sqlSession.insert(this .command.getName(), param)); break ; case UPDATE: param = this .method.convertArgsToSqlCommandParam(args); result = this .rowCountResult(sqlSession.update(this .command.getName(), param)); break ; case DELETE: param = this .method.convertArgsToSqlCommandParam(args); result = this .rowCountResult(sqlSession.delete(this .command.getName(), param)); break ; case SELECT: if (this .method.returnsVoid() && this .method.hasResultHandler()) { this .executeWithResultHandler(sqlSession, args); result = null ; } else if (this .method.returnsMany()) { result = this .executeForMany(sqlSession, args); } else if (this .method.returnsMap()) { result = this .executeForMap(sqlSession, args); } else if (this .method.returnsCursor()) { result = this .executeForCursor(sqlSession, args); } else { param = this .method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(this .command.getName(), param); if (this .method.returnsOptional() && (result == null || !this .method.getReturnType().equals(result.getClass()))) { result = Optional.ofNullable(result); } } break ; case FLUSH: result = sqlSession.flushStatements(); break ; default : throw new BindingException("Unknown execution method for: " + this .command.getName()); } }
DefaultSqlSession#selectList selectList 查询单个也交给列表方法执行
org.apache.ibatis.session.defaults.DefaultSqlSession#selectList
1 2 3 4 5 6 7 8 9 10 11 12 13 private <E> List<E> selectList (String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) { List var6; try { MappedStatement ms = this .configuration.getMappedStatement(statement); var6 = this .executor.query(ms, this .wrapCollection(parameter), rowBounds, handler); } catch (Exception var10) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + var10, var10); } finally { ErrorContext.instance().reset(); } return var6; }
CachingExecutor#query query方法 生成BoundSql 和二级缓存key 然后交由下游执行
org.apache.ibatis.executor.CachingExecutor#query
1 2 3 4 5 public <E> List<E> query (MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameterObject); CacheKey key = this .createCacheKey(ms, parameterObject, rowBounds, boundSql); return this .query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
CachingExecutor#query query 判断是否走一二级缓存还是从数据查
org.apache.ibatis.executor.CachingExecutor#query
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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 ) { this .flushCacheIfRequired(ms); if (ms.isUseCache() && resultHandler == null ) { this .ensureNoOutParams(ms, boundSql); List<E> list = (List)this .tcm.getObject(cache, key); if (list == null ) { list = this .delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); this .tcm.putObject(cache, key, list); } return list; } } return this .delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
SimpleExecutor#doQuery doQuery 具体执行执行查询操作, 先获取StatementHandler 生成Statement 再查询
org.apache.ibatis.executor.SimpleExecutor#doQuery
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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(); 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; }
DefaultParameterHandler#setParameters setParameters 生成具体的Statement 过程中 填充下参数(即填充sql问号的值)
org.apache.ibatis.scripting.defaults.DefaultParameterHandler#setParameters
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 public void setParameters (PreparedStatement ps) { ErrorContext.instance().activity("setting parameters" ).object(this .mappedStatement.getParameterMap().getId()); List<ParameterMapping> parameterMappings = this .boundSql.getParameterMappings(); if (parameterMappings != null ) { for (int i = 0 ; i < parameterMappings.size(); ++i) { ParameterMapping parameterMapping = (ParameterMapping)parameterMappings.get(i); if (parameterMapping.getMode() != ParameterMode.OUT) { String propertyName = parameterMapping.getProperty(); Object value; if (this .boundSql.hasAdditionalParameter(propertyName)) { value = this .boundSql.getAdditionalParameter(propertyName); } else if (this .parameterObject == null ) { value = null ; } else if (this .typeHandlerRegistry.hasTypeHandler(this .parameterObject.getClass())) { value = this .parameterObject; } else { MetaObject metaObject = this .configuration.newMetaObject(this .parameterObject); value = metaObject.getValue(propertyName); } TypeHandler typeHandler = parameterMapping.getTypeHandler(); JdbcType jdbcType = parameterMapping.getJdbcType(); if (value == null && jdbcType == null ) { jdbcType = this .configuration.getJdbcTypeForNull(); } try { typeHandler.setParameter(ps, i + 1 , value, jdbcType); } catch (SQLException | TypeException var10) { throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + var10, var10); } } } } }