我们知道,mybatisplus的BaseMapper接口中提供了一些如updateById的方法,框架本身已经实现了这些CRUD功能,基本的CRUD我们就没必要写sql,直接使用java语法就能对数据进行操控,很方便。那么这些功能是如何被实现的呢?这是我研读源码的动机
关键类
- AbstractSqlInjector及其子类DefaultSqlInjector
- AbstractMethod及其子类
- SqlMethod
- mapper代理:MybatisMapperProxy
- Configuration
MybatisMapperProxy 以代理类作为切入点
BaseMapper是一个接口,里面的方法都是抽象方法,所以很明显框架是通过代理的方式去实现这些抽象方法。
public class MybatisMapperProxy<T> implements InvocationHandler, Serializable {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 (method.isDefault()) {return invokeDefaultMethod(proxy, method, args);}} catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);}final MybatisMapperMethod mapperMethod = cachedMapperMethod(method);return mapperMethod.execute(sqlSession, args);}......省略
}
这段代码是来自 MyBatis 库中的 MapperProxy 类,用于为 Mapper 接口创建动态代理实例,通过该实例调用实际的 SQL 语句。
invoke 方法会在动态代理实例上调用,当 Mapper 接口中的方法被调用时,该方法将被执行。其中,proxy 参数是动态代理实例本身,method 参数是被调用的方法,args 参数包含传递给方法的参数。
第一个 if 语句检查方法是否属于 Object 类。如果是,则在 MapperProxy 实例本身上调用该方法,并将 args 数组作为参数传递。
第二个 else if 语句检查方法是否为默认方法,这是在 Java 8 中引入的。如果是默认方法,则使用 invokeDefaultMethod 方法调用它,传递 proxy、method 和 args 参数。
如果方法既不属于 Object 类也不是默认方法,则使用 method 参数从缓存中获取一个 MybatisMapperMethod 实例,并在其上调用 execute 方法,传递 sqlSession 和 args 参数。execute 方法负责使用 SqlSession 对象执行实际的 SQL 语句,并返回结果。
可以看到,BaseMapper的抽象方法的实现是在这两步:
final MybatisMapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
MybatisMapperMethod#execute主要是一下逻辑
- 判断command类型,可以看到类型包括INSERT、UPDATE等;
- 转化参数;
- command.getName()作为入参执行sqlSession对应方法;
public class MybatisMapperMethod {private final MapperMethod.SqlCommand command;private final MapperMethod.MethodSignature method;public MybatisMapperMethod(Class<?> mapperInterface, Method method, Configuration config) {this.command = new MapperMethod.SqlCommand(config, mapperInterface, method);this.method = new MapperMethod.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:
......}
}
接下来,要看一下command的type和name具体是什么?
可以看到,将mapper的接口类和方法作为入参,调用resolveMappedStatement方法得到一个
MappedStatement 对象,取出id作为command的name,sqlCommandType作为command的type;
在resolveMappedStatement中,String statementId = mapperInterface.getName() + "." + methodName;
将mapper的完全限定名和调用方法名拼接起来,作为statementId;
configuration.getMappedStatement(statementId);
然后根据statementId从configuration对象中获取到对应的MappedStatement对象;
另外,这里引申出了一个关键的类com.baomidou.mybatisplus.core.MybatisConfiguration,很显然MybatisConfiguration对象维护了MappedStatement对象集合,所以要想弄明白对应sqlCommandType,需要研究MappedStatement对象如何被加载到MybatisConfiguration中;
public static class SqlCommand {private final String name;private final SqlCommandType type;public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {final String methodName = method.getName();final Class<?> declaringClass = method.getDeclaringClass();MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,configuration);if (ms == null) {if (method.getAnnotation(Flush.class) != null) {name = null;type = SqlCommandType.FLUSH;} else {throw new BindingException("Invalid bound statement (not found): "+ mapperInterface.getName() + "." + methodName);}} else {name = ms.getId();type = ms.getSqlCommandType();if (type == SqlCommandType.UNKNOWN) {throw new BindingException("Unknown execution method for: " + name);}}}
# 类:org.apache.ibatis.binding.MapperMethodprivate MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,Class<?> declaringClass, Configuration configuration) {String statementId = mapperInterface.getName() + "." + methodName;if (configuration.hasStatement(statementId)) {return configuration.getMappedStatement(statementId);} else if (mapperInterface.equals(declaringClass)) {return null;}for (Class<?> superInterface : mapperInterface.getInterfaces()) {if (declaringClass.isAssignableFrom(superInterface)) {MappedStatement ms = resolveMappedStatement(superInterface, methodName,declaringClass, configuration);if (ms != null) {return ms;}}}return null;}
以insert为例,继续往下看sqlSession.insert做了哪些操作?
- 根据statementId,从configuration对象中获取到对应的MappedStatement,然后调用执行器执行MappedStatement对象;
MybatisSimpleExecutor#doUpdate
中调用configuration对象的newStatementHandler方法创建一个StatementHandler 对象;prepareStatement(handler, ms.getStatementLog(), false)
生成PreparedStatement对象;ps.execute()
执行sql;
# org.apache.ibatis.session.defaults.DefaultSqlSessionpublic int insert(String statement, Object parameter) {return update(statement, parameter);}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();}}
public class MybatisSimpleExecutor extends AbstractBaseExecutor {public MybatisSimpleExecutor(Configuration configuration, Transaction transaction) {super(configuration, transaction);}@Overridepublic int doUpdate(MappedStatement ms, Object parameter) throws SQLException {Statement stmt = null;try {Configuration configuration = ms.getConfiguration();StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);stmt = prepareStatement(handler, ms.getStatementLog(), false);return stmt == null ? 0 : handler.update(stmt);} finally {closeStatement(stmt);}}private Statement prepareStatement(StatementHandler handler, Log statementLog, boolean isCursor) throws SQLException {Statement stmt;Connection connection = getConnection(statementLog);stmt = handler.prepare(connection, transaction.getTimeout());//游标不支持返回null.if (stmt == null && !isCursor) {return null;} else {handler.parameterize(stmt);return stmt;}}......
public class PreparedStatementHandler extends BaseStatementHandler {@Overridepublic int update(Statement statement) throws SQLException {PreparedStatement ps = (PreparedStatement) statement;ps.execute();int rows = ps.getUpdateCount();Object parameterObject = boundSql.getParameterObject();KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);return rows;}
......
PreparedStatement对象的创建
boundSql.getSql()
获取sql模版;connection.prepareStatement
创建PreparedStatement对象;- BaseStatementHandler#BaseStatementHandler中,获取BoundSql对象;
# org.apache.ibatis.executor.statement.PreparedStatementHandlerprotected 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() == ResultSetType.DEFAULT) {return connection.prepareStatement(sql);} else {return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);}}
# org.apache.ibatis.executor.statement.BaseStatementHandlerprotected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {this.configuration = mappedStatement.getConfiguration();this.executor = executor;this.mappedStatement = mappedStatement;this.rowBounds = rowBounds;this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();this.objectFactory = configuration.getObjectFactory();if (boundSql == null) { // issue #435, get the key before calculating the statementgenerateKeys(parameterObject);boundSql = mappedStatement.getBoundSql(parameterObject);}this.boundSql = boundSql;this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);}
注入抽象方法
前提:
mybatis的mapper类不能有重载方法Mapper类中存在名称相同的方法重载报错,所以生成的MappedStatement的id只由完全类限定名 + 方法名构成。
- com.baomidou.mybatisplus.core.injector.AbstractSqlInjector#inspectInject
- com.baomidou.mybatisplus.core.injector.AbstractMethod#injectMappedStatement
以deleteById为例,
public class DeleteById extends AbstractMethod {@Overridepublic MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {String sql;SqlMethod sqlMethod = SqlMethod.LOGIC_DELETE_BY_ID;if (tableInfo.isLogicDelete()) {sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(), sqlLogicSet(tableInfo),tableInfo.getKeyColumn(), tableInfo.getKeyProperty(),tableInfo.getLogicDeleteSql(true, false));SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, Object.class);return addUpdateMappedStatement(mapperClass, modelClass, sqlMethod.getMethod(), sqlSource);} else {sqlMethod = SqlMethod.DELETE_BY_ID;sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(), tableInfo.getKeyColumn(),tableInfo.getKeyProperty());SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, Object.class);return this.addDeleteMappedStatement(mapperClass, sqlMethod.getMethod(), sqlSource);}}
}