Mybatis

数据读写的本质

不管是哪种ORM框架,数据读写其本质都是对JDBC的封装,其目的主要都是简化JDBC的开发流程,进而让开发人员更关注业务。下面是JDBC的核心流程:

  1. 注册 JDBC 驱动(Class.forName(“XXX”);)
  2. 打开连接(DriverManager.getConnection(“url”,”name”,”password”))
  3. 根据连接,创建 Statement(conn.prepareStatement(sql))
  4. 设置参数(stmt.setString(1, “wyf”);)
  5. 执行查询(stmt.executeQuery();)
  6. 处理结果,结果集映射(resultSet.next())
  7. 关闭资源(finally)

Mybatis也是在对JDBC进行封装,它将注册驱动打开连接交给了数据库连接池来负责,Mybatis支持第三方数据库连接池,也可以使用自带的数据库连接池;创建 Statement执行查询交给了StatementHandler来负责;设置参数交给ParameterHandler来负责;最后两步交给了ResultSetHandler来负责。

Executor内部运作过程

测试方法org.apache.ibatis.binding.BindingTest#shouldFindThreeSpecificPosts

下面是一个简单的数据读写过程,在宏观上先来了解一下每一个组件在整个数据读写上的作用:
Mybatis中的数据读写 - 图1
Mybatis的数据读写主要是通Excuter来协调StatementHandler、ParameterHandler和ResultSetHandler三个组件来实现的:

  • StatementHandler:它的作用是使用数据库的Statement或PrepareStatement执行操作,启承上启下作用;
  • ParameterHandler:对预编译的SQL语句进行参数设置,SQL语句中的的占位符“?”都对应BoundSql.parameterMappings集合中的一个元素,在该对象中记录了对应的参数名称以及该参数的相关属性
  • ResultSetHandler:对数据库返回的结果集(ResultSet)进行封装,返回用户指定的实体类型;

    StatementHandler

    StatementHandler类图

    Mybatis中的数据读写 - 图2

    RoutingStatementHandler

    通过StatementType来创建StatementHandler,使用静态代理模式来完成方法的调用,主要起到路由作用。它是Excutor组件真正实例化的组件。
    1. public class RoutingStatementHandler implements StatementHandler {
    2. /**
    3. * 静态代理模式
    4. */
    5. private final StatementHandler delegate;
    6. public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    7. // 根据{@link org.apache.ibatis.mapping.StatementType} 来创建不同的实现类 (策略模式)
    8. switch (ms.getStatementType()) {
    9. case STATEMENT:
    10. delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
    11. break;
    12. case PREPARED:
    13. delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
    14. break;
    15. case CALLABLE:
    16. delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
    17. break;
    18. default:
    19. throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    20. }
    21. }
    22. @Override
    23. public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    24. return delegate.prepare(connection, transactionTimeout);
    25. }
    26. ...
    27. }

    BaseStatementHandler

    所有子类的抽象父类,定义了初始化statement的操作顺序,由子类实现具体的实例化不同的statement(模板模式)。
    1. @Override
    2. public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    3. ErrorContext.instance().sql(boundSql.getSql());
    4. Statement statement = null;
    5. try {
    6. // 实例化Statement(由子类实现)【模板方法+策略模式】
    7. statement = instantiateStatement(connection);
    8. // 设置超时时间
    9. setStatementTimeout(statement, transactionTimeout);
    10. // 设置获取数据记录条数
    11. setFetchSize(statement);
    12. return statement;
    13. } catch (SQLException e) {
    14. closeStatement(statement);
    15. throw e;
    16. } catch (Exception e) {
    17. closeStatement(statement);
    18. throw new ExecutorException("Error preparing statement. Cause: " + e, e);
    19. }
    20. }
    21. protected abstract Statement instantiateStatement(Connection connection) throws SQLException;
    instantiateStatement()就是一个模板方法,由子类实现。

    SimpleStatementHandler

    使用JDBCStatement执行模式,不需要做参数处理,源码如下:
    1. @Override
    2. protected Statement instantiateStatement(Connection connection) throws SQLException {
    3. // 实例化Statement
    4. if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
    5. return connection.createStatement();
    6. } else {
    7. return connection.createStatement(mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    8. }
    9. }
    10. @Override
    11. public void parameterize(Statement statement) {
    12. // N/A
    13. // 使用Statement是直接执行sql 所以没有参数
    14. }

    PreparedStatementHandler

    使用JDBCPreparedStatement预编译执行模式。
    1. @Override
    2. protected Statement instantiateStatement(Connection connection) throws SQLException {
    3. // 实例化PreparedStatement
    4. String sql = boundSql.getSql();
    5. if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
    6. String[] keyColumnNames = mappedStatement.getKeyColumns();
    7. if (keyColumnNames == null) {
    8. return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
    9. } else {
    10. return connection.prepareStatement(sql, keyColumnNames);
    11. }
    12. } else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
    13. return connection.prepareStatement(sql);
    14. } else {
    15. return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    16. }
    17. }
    18. @Override
    19. public void parameterize(Statement statement) throws SQLException {
    20. // 参数处理
    21. parameterHandler.setParameters((PreparedStatement) statement);
    22. }

    CallableStatementHandler

    使用JDBCCallableStatement执行模式,用来调用存储过程。现在很少用。

    ParameterHandler

    主要作用是给PreparedStatement设置参数,源码如下:
    1. @Override
    2. public void setParameters(PreparedStatement ps) {
    3. ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    4. // 获取参数映射关系
    5. List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    6. if (parameterMappings != null) {
    7. // 循环获取处理参数
    8. for (int i = 0; i < parameterMappings.size(); i++) {
    9. // 获取对应索引位的参数
    10. ParameterMapping parameterMapping = parameterMappings.get(i);
    11. if (parameterMapping.getMode() != ParameterMode.OUT) {
    12. Object value;
    13. // 获取参数名称
    14. String propertyName = parameterMapping.getProperty();
    15. // 判断是否是附加参数
    16. if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
    17. value = boundSql.getAdditionalParameter(propertyName);
    18. }
    19. // 判断是否是没有参数
    20. else if (parameterObject == null) {
    21. value = null;
    22. }
    23. // 判断参数是否有相应的 TypeHandler
    24. else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
    25. value = parameterObject;
    26. } else {
    27. // 以上都不是,通过反射获取value值
    28. MetaObject metaObject = configuration.newMetaObject(parameterObject);
    29. value = metaObject.getValue(propertyName);
    30. }
    31. // 获取参数的类型处理器
    32. TypeHandler typeHandler = parameterMapping.getTypeHandler();
    33. JdbcType jdbcType = parameterMapping.getJdbcType();
    34. if (value == null && jdbcType == null) {
    35. jdbcType = configuration.getJdbcTypeForNull();
    36. }
    37. try {
    38. // 根据TypeHandler设置参数
    39. typeHandler.setParameter(ps, i + 1, value, jdbcType);
    40. } catch (TypeException | SQLException e) {
    41. throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
    42. }
    43. }
    44. }
    45. }
    46. }
  1. 获取参数映射关系
  2. 获取参数名称
  3. 根据参数名称获取参数值
  4. 获取参数的类型处理器
  5. 根据TypeHandler设置参数值

通过上面的流程可以发现,真正设置参数是由TypeHandler来实现的。

TypeHandler

Mybatis基本上提供了所需要用到的所有TypeHandler,当然也可以自己实现。TypeHandler的主要作用是:

  1. 设置PreparedStatement参数值
  2. 获取查询结果值
    1. public interface TypeHandler<T> {
    2. /**
    3. * 给{@link PreparedStatement}设置参数值
    4. *
    5. * @param ps {@link PreparedStatement}
    6. * @param i 参数的索引位
    7. * @param parameter 参数值
    8. * @param jdbcType 参数类型
    9. * @throws SQLException
    10. */
    11. void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
    12. /**
    13. * 根据列名获取结果值
    14. *
    15. * @param columnName Colunm name, when configuration <code>useColumnLabel</code> is <code>false</code>
    16. */
    17. T getResult(ResultSet rs, String columnName) throws SQLException;
    18. /**
    19. * 根据索引位获取结果值
    20. */
    21. T getResult(ResultSet rs, int columnIndex) throws SQLException;
    22. /**
    23. * 获取存储过程结果值
    24. */
    25. T getResult(CallableStatement cs, int columnIndex) throws SQLException;
    26. }

    TypeHandler的本质就是对JDBC中stmt.setString(1, "wyf");resultSet.getString("name")的封装,JDBC完整代码可以查看JDBC 面试要点。

ResultSetHandler

ResultSetHandler主要作用是:对数据库返回的结果集(ResultSet)进行封装,通过通过ResultMap配置和反射完成自动映射,返回用户指定的实体类型;核心思路如下:

  1. 根据RowBounds做分页处理
  2. 根据ResultMap配置的返回值类型和constructor配置信息实例化目标类
  3. 根据ResultMap配置的映射关系,获取到TypeHandler,进而从ResultSet中获取值
  4. 根据ResultMap配置的映射关系,获取到目标类的属性名称,然后通过反射给目标类赋值

源码太多了,这里就不发了,有兴趣就在下面的源码上看注释吧,下面的流程图会画出方法的调用栈。

Mybatis自带的RowBounds分页是逻辑分页,数据量大了有可能会内存溢出,所以不建议使用Mybatis默认分页。

数据读取流程图

Mybatis中的数据读写 - 图3Mybatis中的数据读写 - 图4

总结

Mybatis的整个数据读取流程其实就是对JDBC的一个标准实现。