在说Mybatis-Plus(后面简称MP)之前,我们先来了解了解Mybatis的工作流程。
Mybatis的执行流程的分析
先来认识一些Mybatis的主要的类和接口:
Configuration:将mybatis配置文件中的信息保存到该类中
SqlSessionFactory:解析Configuration类中的配置信息,获取SqlSession
SqlSession:负责和数据库交互,完成增删改查
Executor:mybatis的调度核心,负责SQL的生成
StatementHandler:封装了JDBC的statement操作
ParameterHandler:负责完成JavaType到jdbcType的转换
ResultSetHandler:负责完成结果集到Java Bean的转换
MappedStatement:代表一个select|update|insert|delete元素
SqlSource:根据传入的ParamterObject生成SQL
BoundSql:包含SQL和参数信息
获取SqlSession
的流程图:
获取SqlSessionFactory
public SqlSessionFactory build(Reader reader, String environment, Properties properties) { try { XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties); return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession.", e); } finally { ErrorContext.instance().reset(); try { reader.close(); } catch (IOException e) { // Intentionally ignore. Prefer previous error. } } }
通过XMLConfigBuilder解析配置文件,解析的配置相关信息都会封装为一个Configuration对象,build(parser.parse())
这一步就是获取DefaultSqlSessionFactory
获取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(); } }
configuration类里包含了Mybatis的相关配置信息,Environment包含了数据源和事务的配置,Executor是mybatis的调度核心(表面上是sqlSession完成与数据库的增删改查,实际上是通过Executor执行,因为它是对于Statement的封装)(Statement则是java操作数据库的重要对象)。上面代码获取了一个包含configuration和executor的DefaultSqlSession
对象。
sqlSession调用增删改查
查看DefaultSqlSession
源码可以看出,它的增删改查方法,最终都会走到其中三个方法上,如下:
@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(); } } @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(); } } @Override public int update(String statement, Object parameter) { try { dirty = true; MappedStatement ms = configuration.getMappedStatement(statement); return executor.update(ms, wrapCollection(parameter)); } catch (Exception e) { throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }
而最终的SQL执行都是使用Executor执行,Executor接口有一个抽象实现类BaseExecutor,而该类的query()
和update()
方法,最终都会走到其三个子类(SimpleExecutor、ReuseExecutor、BatchExecutor)上面,这三个子类也就是具体的实现:
SimpleExecutor:是Mybatis执行Mapper语句默认执行的Executor,从名称可以看出就是简单的执行器,执行每个语句都会创建一个新的预处理语句;
ReuseExecutor:指可以复用的执行器,执行每个语句会去检查是否有缓存的预处理语句(实际上是指缓存的Statement对象),有的话就直接使用,没有的会新建
BatchExecutor:批量处理的执行器,主要是用于做批量的更新操作的,其底层会调用Statement的
executeBatch()
方法实现批量操作
Mapper中的方法调用
首先,我们通过@Autowired来对mapper的进行注入,Spring扫描dao层,为每一个mapper接口创建一个MapperFactoryBean,
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> { private Class<T> mapperInterface; public MapperFactoryBean(Class<T> mapperInterface) { this.mapperInterface = mapperInterface; } @Override public T getObject() throws Exception { return getSqlSession().getMapper(this.mapperInterface); } }
mapperInterface就是dao的class对象,因为实现了FactoryBean接口,因此通过@Autowired获取对象时,实际是调用getObject方法获取对象,也就是sqlSession.getMapper()
;其流程如下:
我们剥开一层又一层,来到了MapperRegistry,这里面getMapper实际上返回的是MapperProxyFactory,我们再来看看它:
public class MapperProxyFactory<T> { //我们的Dao接口的class对象 private final Class<T> mapperInterface; @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<T>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } }
可以看出,是通过了动态代理创建mapper接口的代理类对象,而对接口所有方法的调用,最后都会回到调用mapperProxy的invoke方法上(这就是JDK动态代理)。
什么是JDK的动态代理
我们去看看mapperProxy对象的invoke方法,我们去看看mapperProxy对象的invoke方法:
public class MapperProxy<T> implements InvocationHandler, Serializable { private static final long serialVersionUID = -6424540398559729838L; private final SqlSession sqlSession; private final Class<T> mapperInterface; private final Map<Method, MapperMethod> methodCache; @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 if (isDefaultMethod(method)) { return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args); } }
if判断我们调用的方法是否是对象中的,我们调用的都是接口的方法,所以直接走mapperMethod.execute()
。mapperMethod标识我们调用接口中的那个方法
public class MapperMethod { private final SqlCommand command; private final MethodSignature method; public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) { this.command = new SqlCommand(config, mapperInterface, method); this.method = new MethodSignature(config, mapperInterface, method); } 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); } 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; } }
这样可以看出了,最终还是落到了sqlSession的update、select等;而这其实我们已经知道了,最后SqlSession其实是交给Statement执行SQL命令;
那问题又来了,Statement执行的sql是怎拼接出来的呢,这个我之后再去详细了解,(目前了解到时把Mapper.xml解析,然后把#{}的字符替换成?,最后包装成预表达式供给PrepareStatement执行)
Mybatis-Plus与Mybatis的异同点
前面我们大概了解到了Mybatis的工作原理,那MP是怎样在他之上只做增强不做改变的呢?
MP去Mybatis的区别在哪儿呢,MP继承了MapperRegistry这个类然后重写了addMapper方法
public <T> void addMapper(Class<T> type) { if (type.isInterface()) { if (hasMapper(type)) { // TODO 如果之前注入 直接返回 return; // throw new BindingException("Type " + type + // " is already known to the MybatisPlusMapperRegistry."); } 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. // TODO 自定义无 XML 注入 MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type); parser.parse(); loadCompleted = true; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } }
将原方法中的MapperAnnotationBuilder替换成了自家的MybatisMapperAnnotationBuilder,在这里特别说明一下,mp为了不更改mybatis原有的逻辑,会用继承或者直接粗暴地将其复制过来,然后在原有的类名上加上前缀
Mybatis
然后关键是parser.parse()
这个方法:
@Override public void parse() { String resource = type.toString(); if (!configuration.isResourceLoaded(resource)) { loadXmlResource(); configuration.addLoadedResource(resource); assistant.setCurrentNamespace(type.getName()); parseCache(); parseCacheRef(); Method[] methods = type.getMethods(); // TODO 注入 CURD 动态 SQL (应该在注解之前注入) if (GlobalConfigUtils.getSuperMapperClass(configuration).isAssignableFrom(type)) { GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type); } for (Method method : methods) { try { // issue #237 if (!method.isBridge()) { parseStatement(method); } } catch (IncompleteElementException e) { configuration.addIncompleteMethod(new MethodResolver(this, method)); } } } parsePendingMethods(); }
sql注入器就是从这个方法里面添加上去的,首先判断Mapper是否是BaseMapper的超类或者超接口,BaseMapper是mp的基础Mapper,里面定义了很多默认的基础方法,意味着我们一旦使用上mp,通过sql注入器,很多基础的数据库单表操作都可以直接继承BaseMapper实现,如果是自定义的方法,sql注入器也会解析然后注入自定义的方法(这部分以后进一步了解后会补充)。
下面我们来看一个另一实例,ServiceImpl的saveBatch()方法(Service层调用的批量插入):
@Transactional(rollbackFor = Exception.class) @Override public boolean saveBatch(Collection<T> entityList, int batchSize) { String sqlStatement = sqlStatement(SqlMethod.INSERT_ONE); try (SqlSession batchSqlSession = sqlSessionBatch()) { int i = 0; for (T anEntityList : entityList) { batchSqlSession.insert(sqlStatement, anEntityList); if (i >= 1 && i % batchSize == 0) { batchSqlSession.flushStatements(); } i++; } batchSqlSession.flushStatements(); } return true; }
sqlSessionBatch()
是用来获取sqlSession
,并且指定Executor类型为BATCH,然后循环batchSqlSession.insert(sqlStatement, anEntityList)
,发现实际上是调用的BatchExecutor的doUpdate()方法,
@Override public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException { final Configuration configuration = ms.getConfiguration(); final StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, RowBounds.DEFAULT, null, null); final BoundSql boundSql = handler.getBoundSql(); final String sql = boundSql.getSql(); final Statement stmt; if (sql.equals(currentSql) && ms.equals(currentStatement)) { int last = statementList.size() - 1; stmt = statementList.get(last); applyTransactionTimeout(stmt); handler.parameterize(stmt);//fix Issues 322 BatchResult batchResult = batchResultList.get(last); batchResult.addParameterObject(parameterObject); } else { Connection connection = getConnection(ms.getStatementLog()); stmt = handler.prepare(connection, transaction.getTimeout()); handler.parameterize(stmt); //fix Issues 322 currentSql = sql; currentStatement = ms; statementList.add(stmt); batchResultList.add(new BatchResult(ms, sql, parameterObject)); } // handler.parameterize(stmt); handler.batch(stmt); return BATCH_UPDATE_RETURN_VALUE; }
循环添加多个Statement,但是没有但是还没有提交,一直要到batchSqlSession.flushStatements()
实际上是调用BatchExecutor的doFlushStatements(),这里才是真正的提交,把所有的Statement批量提交了
@Override public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException { try { List<BatchResult> results = new ArrayList<BatchResult>(); if (isRollback) { return Collections.emptyList(); } for (int i = 0, n = statementList.size(); i < n; i++) { Statement stmt = statementList.get(i); applyTransactionTimeout(stmt); BatchResult batchResult = batchResultList.get(i); try { batchResult.setUpdateCounts(stmt.executeBatch()); MappedStatement ms = batchResult.getMappedStatement(); List<Object> parameterObjects = batchResult.getParameterObjects(); KeyGenerator keyGenerator = ms.getKeyGenerator(); if (Jdbc3KeyGenerator.class.equals(keyGenerator.getClass())) { Jdbc3KeyGenerator jdbc3KeyGenerator = (Jdbc3KeyGenerator) keyGenerator; jdbc3KeyGenerator.processBatch(ms, stmt, parameterObjects); } else if (!NoKeyGenerator.class.equals(keyGenerator.getClass())) { //issue #141 for (Object parameter : parameterObjects) { keyGenerator.processAfter(this, ms, stmt, parameter); } } // Close statement to close cursor #1109 closeStatement(stmt); } catch (BatchUpdateException e) { StringBuilder message = new StringBuilder(); message.append(batchResult.getMappedStatement().getId()) .append(" (batch index #") .append(i + 1) .append(")") .append(" failed."); if (i > 0) { message.append(" ") .append(i) .append(" prior sub executor(s) completed successfully, but will be rolled back."); } throw new BatchExecutorException(message.toString(), e, results, batchResult); } results.add(batchResult); } return results; } finally { for (Statement stmt : statementList) { closeStatement(stmt); } currentSql = null; statementList.clear(); batchResultList.clear(); } }
之前我有些疑惑,为什么要添加多个Statement,然后循环执行
stmt.executeBatch()
来提交,而MP的saveBatch()
只是做了一个表的批量提交,为什么不循环statement.addBatch
然后在statement.executeBatch()
一次就提交了,后来我猜想可能是为了无侵入吧,不在原来的Mybatis上修改太多的东西,只做增强,不做改变
以上内容来源于
https://blog.csdn.net/heyrian/article/details/81558109
https://cloud.tencent.com/developer/article/1445998




