温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

详解MyBatis Mapper 代理实现数据库调用原理

发布时间:2020-09-14 10:16:34 来源:脚本之家 阅读:142 作者:coderbee笔记 栏目:编程语言

1. Mapper 代理层执行

Mapper 代理上执行方法调用时,调用被委派给 MapperProxy 来处理。

public class MapperProxy<T> implements InvocationHandler, Serializable {
 private final SqlSession sqlSession;
 private final Class<T> mapperInterface;
 private final Map<Method, MapperMethod> methodCache;

 public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
  this.sqlSession = sqlSession;
  this.mapperInterface = mapperInterface;
  this.methodCache = methodCache;
 }

 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);
   }
  }
  // 接口里声明的方法,转换为 MapperMethod 来调用
  final MapperMethod mapperMethod = cachedMapperMethod(method);

  // 与 Spring 集成时此处的 sqlSession 仍然 SqlSessionTemplate
  return mapperMethod.execute(sqlSession, args);
 }

 private MapperMethod cachedMapperMethod(Method method) {
  MapperMethod mapperMethod = methodCache.get(method);
  if (mapperMethod == null) {
   mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
   methodCache.put(method, mapperMethod);
  }
  return mapperMethod;
 }
}

MapperMethod 根据 mapperInterface.getName() + "." + method.getName() 从 Configuration 对象里找到对应的 MappedStatement ,从而得到要执行的 SQL 操作类型(insert/delete/update/select/flush),然后调用传入的 sqlSession 实例上的相应的方法。

public Object execute(SqlSession sqlSession, Object[] args) {
 Object result;
 if (SqlCommandType.INSERT == command.getType()) {
  // 把参数转换为 SqlSession 能处理的格式
  Object param = method.convertArgsToSqlCommandParam(args);

  // 在 sqlSession 上执行并处理结果
  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));
 ...省略

如果上述方法传入的是 SqlSessionTemplate ,那么这些方法调用会被 SqlSessionInterceptor 拦截,加入与 Spring 事务管理机制协作的逻辑,具体可以看这篇文章MyBatis 事务管理,这里不再展开,最终会调用到 DefaultSqlSession 实例上的方法。

2. 会话层的执行过程

SqlSession 里声明的所有方法的第一个参数如果是 String statement ,则都是 mapperInterface.getName() + "." + method.getName() ,表示要调用的 SQL 语句的标识符。通过它从 configuration 找到 MappedStatement 。

会话层最主要的逻辑是进行参数的包装,获取对应的 MappedStatement ,然后调用持有的 Executor 的方法去执行。

public class DefaultSqlSession implements SqlSession {
 private Configuration configuration;
 private Executor executor;

 private boolean autoCommit;
 private boolean dirty;
 private List<Cursor<?>> cursorList;

 public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
  this.configuration = configuration;
  this.executor = executor;
  this.dirty = false;
  this.autoCommit = autoCommit;
 }

 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();
  }
 }

3. Executor 执行的过程

我们知道 JDBC 里有三种数据库语句: java.sql.Statement/PreparedStatement/CallableStatement ,每种语句的执行方式是不一样的,MyBatis 创建了 StatementHandler 抽象来表示数据库语句的处理逻辑,有对应的三种具体实现: SimpleStatementHandler/PreparedStatementHandler/CallableStatementHandler 。

RoutingStatementHandler 是个门面模式,构建时根据要执行的数据库语句类型实例化 SimpleStatementHandler/PreparedStatementHandler/CallableStatementHandler 中的一个类作为目标 delegate,并把调用都转给这个 delegate 的方法。

不同的 handler 实现实现了对应的:数据库语句的创建、参数化设置、执行语句。

通过这层抽象,MyBatis 统一了 Executor 里的执行流程,以下以 SimpleExecutor 的流程为例:

1. 对于传入的 MappedStatement ms ,得到 Configuration configuration 。

2. configuration 通过 ms 的语句类型得到一个 RoutingStatementHandler 的实例(内部有个 delegate 可以委派) handler ,并用 InterceptorChain 对 handler 实例进行装饰。

3. 通过 SimpleExecutor 持有的 Transaction 实例获取对应的数据库连接 connection。

4. handler 通过数据库连接初始化数据库语句 java.sql.Statement 或其子类 stmt ,设置超时时间和 fetchSize 。

5. 用 handler 对 stmt 进行参数化处理(比如 PreparedStatement 设置预编译语句的参数值)。

6. handler 执行相应的 stmt 完成数据库操作。

7. 用 ResultSetHandler 对结果集进行处理。 ResultSetHandler 会调用 TypeHandler 来进行 Java 类型与数据库列类型之间转换。

// SimpleExecutor
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
 Statement stmt = null;
 try {
  Configuration configuration = ms.getConfiguration();

  // 创建 handler 来负责具体的执行
  StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);

  // 创建数据库语句
  stmt = prepareStatement(handler, ms.getStatementLog());

  // 执行数据库操作
  return handler.<E>query(stmt, resultHandler);
 } finally {
  closeStatement(stmt);
 }
}

// Configuration
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
 StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
 statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
 return statementHandler;
}

// RoutingStatementHandler
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
 // 根据SQL语句的执行方式创建对应的 handler 实例
 switch (ms.getStatementType()) {
  case STATEMENT:
  delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
  break;
  case PREPARED:
  delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
  break;
  case CALLABLE:
  delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
  break;
  default:
  throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
 }
}

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
 // 创建数据库连接
 Connection connection = getConnection(statementLog);

 // 创建数据库语句
 Statement stmt = handler.prepare(connection, transaction.getTimeout());

 // 参数化设置
 handler.parameterize(stmt);
 return stmt;
}

protected Connection getConnection(Log statementLog) throws SQLException {
 Connection connection = transaction.getConnection();
 if (statementLog.isDebugEnabled()) {
  return ConnectionLogger.newInstance(connection, statementLog, queryStack);
 } else {
  return connection;
 }
}

// BaseStatementHandler
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
 ErrorContext.instance().sql(boundSql.getSql());
 Statement statement = null;
 try {
  // 由具体的子类来创建对应的 Statement 实例
  statement = instantiateStatement(connection);

  // 通用参数设置
  setStatementTimeout(statement, transactionTimeout);
  setFetchSize(statement);
  return statement;
 } catch (SQLException e) {
  closeStatement(statement);
  throw e;
 } catch (Exception e) {
  closeStatement(statement);
  throw new ExecutorException("Error preparing statement. Cause: " + e, e);
 }
}

// 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() != null) {
  return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
 } else {
  return connection.prepareStatement(sql);
 }
}

// PreparedStatementHandler
public void parameterize(Statement statement) throws SQLException {
 parameterHandler.setParameters((PreparedStatement) statement);
}

4. 问题

只在 XML 里定义 SQL、没有对应的 Java 接口类能否使用 MyBatis ?

答:可以,通过 SqlSession 的方法来调用 XML 里的 SQL 语句。

Mapper 接口类里可以进行方法重载吗?

答:不能,因为 MyBatis 里根据 类名 + “.” + 方法名 来查找 SQL 语句,重载会导致这样的组合出现多条结果。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持亿速云。

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI