在这里排除建表、编写mybatis配置文件的过程,启动代码如下所示
public class App { public static void main(String[] args) { //mybatis配置文件名称 String resource = "mybatis-config.xml"; Reader reader; try { //把mybatis配置文件转换程一个reader reader = Resources.getResourceAsReader(resource); // 通过加载配置文件流构建一个SqlSessionFactory DefaultSqlSessionFactory, // 将XML配置文件构建为Configuration配置类 SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader); // 创建SqlSession SqlSession session = sqlMapper.openSession(); try { // 执行查询 第一种方式,mybatis3之前的方式 User user = (User)session.selectOne("com.zhai.mapper.UserMapper.selectById", 1); // 执行查询 第二种方式,mybatis3之后的方式 UserMapper mapper1 = session.getMapper(UserMapper.class); User user1 = mapper1.selectById(1L); //提交session session.commit(); System.out.println(user.getUserName()); } catch (Exception e) { e.printStackTrace(); }finally { //关闭session session.close(); } } catch (IOException e) { e.printStackTrace(); } } }上述代码
// 将XML配置文件构建为Configuration配置类,并封装成DefaultSqlSessionFactory SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);在Mybatis环境的初始化过程中,SqlSessionFactoryBuilder会调用XMLConfigBuilder 读取所有的MybatisMapConfig.xml 和所有的*Mapper.xml文件,构建Mybatis运行的核心对象Configuration对象,然后将该Configuration对象作为参数构建一个SqlSessionFactory对象。
SqlSessionFactory使用的是工厂模式。可以看到,该Factory的openSession方法重载了很多个,分别支持autoCommit、Executor、Transaction等参数的输入,来构建核心的SqlSession对象。
其中XMLConfigBuilder在构建Configuration对象时,也会调用XMLMapperBuilder用于读取Mapper文件,而XMLMapperBuilder会使用XMLStatementBuilder来读取和build所有的SQL语句。在这个过程中,有一个相似的特点,就是这些Builder会读取文件或者配置,然后做大量的XpathParser解析、配置或语法的解析、反射生成对象、存入结果缓存等步骤,这么多的工作都不是一个构造函数所能包括的,因此在构建Configuration对象时大量采用了建造者模式来解决。
不同的xxxConfigBuilder都继承抽象类BaseBuilder,实现对xml文件中不同位置节点的解析,关系图如下所示
2.1 Configuration类中解析数据源
2.2 Configuration类中解析增删改查节点(sql)
//查找节点 <select id="selectById" resultMap="result" > select id,userName,createTime from user <where> <if test="param1>0"> and id=#{param1} </if> </where> </select>如上图,在xml文件中每一个增删改查节点都会被解析成一个 MapperStatement,被放入MapperStatement集合中
final Map<String, MappedStatement> mappedStatementsSQL语句是根据不同的sqlNode拼接而成的,sqlNode通过责任链的方式调用的,比如上文中的select、where、if 都是sqlNode,每一个sqlNode都有text文本,最后通过责任链的方式把这些文本拼接成真正的sql语句。如果sql上有参数,则先把参数预编译成 ?,再根据对应的 typeHandler 完成参数填充!
2.3 Configuration类中解析结果集映射
Configuration类中还封装了返回结果集 和 javabean的映射,可以看出Configuration确实帮我们包办了很多事情!
Configuration类如下:
public class Configuration { // 环境,封装了dateSource,可根据dataSource获取connection protected Environment environment; protected boolean safeRowBoundsEnabled; protected boolean safeResultHandlerEnabled = true; protected boolean mapUnderscoreToCamelCase; protected boolean aggressiveLazyLoading; protected boolean multipleResultSetsEnabled = true; protected boolean useGeneratedKeys; protected boolean useColumnLabel = true; protected boolean cacheEnabled = true; 。。。。。。 protected final MapperRegistry mapperRegistry = new MapperRegistry(this); protected final InterceptorChain interceptorChain = new InterceptorChain(); protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry(); protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry(); protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry(); 。。。。。。 属性太多 这里不一一列举了!!然后把Configuration配置类在构建成DefaultSqlSessionFactory
// 到这里配置文件已经解析成了Configuration public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); }整个解析流程如下所示: 对于MyBatis启动的流程(获取SqlSession的过程)这边简单总结下:
SqlSessionFactoryBuilder解析配置文件,包括属性配置、别名配置、拦截器配置、环境(数据源和事 务管理器)、Mapper配置等;解析完这些配置后会生成一个Configration对象,这个对象中包含了MyBatis 需要的所有配置,然后会用这个Configration对象创建一个SqlSessionFactory对象,这个对象中包含了 Configration配置类对象;
SqlSession代表一次数据库会话,它是一个门面,增删改查的职责由其内部的executor执行器实现的。
SqlSession session = SqlSessionFactory.openSession()源码如下:
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); /** * 创建一个sql执行器对象 * 一般情况下 若我们的mybaits的全局配置文件的cacheEnabled默认为ture就返回 * 一个cacheExecutor,若关闭的话返回的就是一个SimpleExecutor */ final Executor executor = configuration.newExecutor(tx, execType); /** * 创建返回一个DeaultSqlSessoin对象返回 */ 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(); } }可以看到openSession中创建了一个执行器Executor,返回了一个带有执行器executor的DefaultSqlSession对象,而DefaultSqlSession类内部的增删改查方法底层都是调用execute执行器完成!
// DefaultSqlSession中的update方法 @Override public int update(String statement, Object parameter) { try { dirty = true; MappedStatement ms = configuration.getMappedStatement(statement); //可以看到这里调用的是executor.update方法! return executor.update(ms, wrapCollection(parameter)); } catch (Exception e) { throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }在创建执行器Executor时,会先判断执行器类型并选择,然后调用Mybatis中注册的插件,完成对执行器Executor的增强,如果没有对Executor增强的插件,则返回原来的Executor
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 { //简单的sql执行器对象 executor = new SimpleExecutor(this, transaction); } //判断mybatis的全局配置文件是否开启缓存 if (cacheEnabled) { //把当前的简单的执行器包装成一个CachingExecutor executor = new CachingExecutor(executor); } /** * TODO:调用所有的拦截器对象plugin方法, * 可以使用Mybatis拦截器在这里对执行器Executor进行扩展,实现自定义功能 */ executor = (Executor) interceptorChain.pluginAll(executor); return executor; }如果有使用Mybatis的@Interceptor插件对执行器进行了增强,那么在创建完执行器后,会执行插件中的增强逻辑!
Mybatis插件原理:动态代理
public static Object wrap(Object target, Interceptor interceptor) { // 获得interceptor配置的@Signature的type Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor); // 当前代理类型 Class<?> type = target.getClass(); // 根据当前代理类型 和 @signature指定的type进行配对, 配对成功则可以代理 Class<?>[] interfaces = getAllInterfaces(type, signatureMap); if (interfaces.length > 0) { //创建动态代理 return Proxy.newProxyInstance( type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)); } return target; }代理内容如下:
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { Set<Method> methods = signatureMap.get(method.getDeclaringClass()); if (methods != null && methods.contains(method)) { //如果有插件增强了Executor,则会执行插件中的intercept方法 return interceptor.intercept(new Invocation(target, method, args)); } return method.invoke(target, args); } catch (Exception e) { throw ExceptionUtil.unwrapThrowable(e); } }流程图:
Mybatis查询数据代码有两种方式:
// 执行查询 第一种方式,mybatis3之前的方式 User user = (User)session.selectOne("com.zhai.mapper.UserMapper.selectById", 1); // 执行查询 第二种方式,mybatis3之后的方式 UserMapper mapper1 = session.getMapper(UserMapper.class); User user1 = mapper1.selectById(1L);其中session.getMapper的方式使用的更多,在底层代码也是对session.selectOne代码的包装,所以我们从源头session.getMapper说起!进入sqlSession.getMapper方法,会发现调的是Configration对象的getMapper方法:
@Override public <T> T getMapper(Class<T> type) { return configuration.getMapper(type, this); } public <T> T getMapper(Class<T> type, SqlSession sqlSession) { //mapperRegistry实质上是一个Map,里面注册了启动过程中解析的各种Mapper.xml //mapperRegistry的key是接口的Class类型 //mapperRegistry的Value是MapperProxyFactory,用于生成对应的MapperProxy(动态代理类) return mapperRegistry.getMapper(type, sqlSession); }在mapperRegistry类中根据Mapper.class获取MapperProxyFactory,用于生成Mapper接口的代理类!
public T newInstance(SqlSession sqlSession) { /** * 创建我们的代理对象 */ final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache); /** * 创建我们的Mapper代理对象返回 */ return newInstance(mapperProxy); }Mapper接口代理类生成流程图: MapperProxy是Mapper接口的代理类,调用Mapper接口的所有方法都会先调用到这个代理类的 invoke方法。MapperProxy的invoke方法非常简单,主要干的工作就是创建MapperMethod对象或者是从 缓存中获取MapperMethod对象。获取到这个对象后执行execute方法。execute方法会判断你当前执行的方式是增删改 查哪一种,并通过SqlSession执行相应的操作。 execute方法如下!
public Object execute(SqlSession sqlSession, Object[] args) { Object result; /** * 判断我们执行sql命令的类型 */ switch (command.getType()) { //insert操作 case INSERT: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.insert(command.getName(), param)); break; } //update操作 case UPDATE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.update(command.getName(), param)); break; } //delete操作 case DELETE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.delete(command.getName(), param)); break; } //select操作 case SELECT: //返回值为空 if (method.returnsVoid() && method.hasResultHandler()) { executeWithResultHandler(sqlSession, args); result = null; } else if (method.returnsMany()) { //返回值是一个List result = executeForMany(sqlSession, args); } else if (method.returnsMap()) { //返回值是一个map result = executeForMap(sqlSession, args); } else if (method.returnsCursor()) { //返回游标 result = executeForCursor(sqlSession, args); } else { //查询返回单个 /** * 解析参数 */ Object param = method.convertArgsToSqlCommandParam(args); //重点来了! 查询方法会调用selectOne,接上了mybatis3之前的查询方式 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; }以查询为例,最后会调用selectOne()方法,这就接上了标题1中的 mybatis3之前的查询方式!selectOne()方法查询方法其实是利用了sqlSession的门面,底层是executor执行的查询,如果在mapper.xml映射文件中配置了缓存cache,查询时会首先去二级缓存中查,没有查到再去一级缓存中找,如果还是没有,才去查询数据库。查到后,把查询结果立即放入一级缓存。当前session提交后,再把数据放入二级缓存!
缓存这一块比较简单,就不贴这段代码了,着重看一下去数据库查询时的代码!
// queryFromDatabase去数据库查询数据 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 { // doQuery 真正的查询逻辑 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; }其中,查询数据库的逻辑都封装在doQuery方法中,跟一下SimpleExecutor的doQuery源码!
@Override public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { //获取Mybatis的最主要的配置类 Configuration configuration = ms.getConfiguration(); /* 四大核心对象之一 StatementHandler 内部封装了ParameterHandler和ResultSetHandler, * 如果有插件对这三个对象进行了增强,则都在newStatementHandler中进行 * 插件执行顺序为:Executor -> StatementHandler -> ParameterHandler -> ResultSetHandler * / StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); //prepareStatement参数赋值 stmt = prepareStatement(handler, ms.getStatementLog()); //执行查询 return handler.query(stmt, resultHandler); } finally { closeStatement(stmt); } }可以使用插件增强的Mybatis的四个核心类如下:
接着进入handler.query执行查询方法:
@Override public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; //调用PreparedStatement进行查询 ps.execute(); //处理结果集 return resultSetHandler.handleResultSets(ps); }SqlSession执行sql操作数据流程总结如下:
会把xml文件中的sql解析成一个个的SqlNode,然后通过责任链的方式去调用每一个SqlNode的解析实现类,然后把所有解析的sql片段追加到一个静态变量中去,就得到了可执行的sql
3.1 解析Configuration
3.2 创建SqlSession,创建执行器executor 3.3 使用sqlSession操作sql,处理返回数据
