我们知道,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.MapperMethod
private 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.DefaultSqlSession
public 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);
}
@Override
public 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 {
@Override
public 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.PreparedStatementHandler
protected 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.BaseStatementHandler
protected 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 statement
generateKeys(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 {
@Override
public 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);
}
}
}