MyBatis的運行的核心原理解析(二)

SqlSession的運行原理解析

  • SqlSession的運行過程是MyBatis的重點也是難點。SqlSession是一個接口,使用它並不複雜。咱們構建SqlSessionFactory就能夠輕鬆的拿到SqlSession了。SqlSessiuon給出了查詢,插入,刪除的方法,舊版本的的Mybatis常常使用這些接口,可是新版的MyBatis推薦這樣使用了,而是使用Mapper,這個方式是目前最經常使用的。
  • SqlSession內部是很複雜的,須要要想理解它,須要剖析它的內部結構。

映射器的動態代理

  • Mapper映射是經過動態代理來實現的(由一能夠知道)sql

    public <T> void addMapper(Class<T> type) {
      if (type.isInterface()) {
        if (hasMapper(type)) {
      	throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
        }
        boolean loadCompleted = false;
        try {
      	knownMappers.put(type, new MapperProxyFactory<T>(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.
      	MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
      	parser.parse();
      	loadCompleted = true;
        } finally {
      	if (!loadCompleted) {
      	  knownMappers.remove(type);
      	}
        }
      }
    }

代理類以下: 這裏事動態代理將接口綁定,生成動態代理的對象(佔位),代理的方法放到了MapperProxy類中。數據庫

public class MapperProxyFactory<T> {
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
  public MapperProxyFactory(Class<T> mapperInterface) {
	this.mapperInterface = mapperInterface;
  }
  public Class<T> getMapperInterface() {
	return mapperInterface;
  }
  public Map<Method, MapperMethod> getMethodCache() {
	return methodCache;
  }
  @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);
  }
}

MapperProxy類的代碼以下: 在invoke()中判斷判斷是否是一個類,若是使用的是Mapper接口,單麼就跳入else,在後面的代碼中生成了MapperMethod對象,使用了cachedMapperMethod()進行初始化,而後執行mapperMethod.execute(sqlSession, args),將參數和sqlSession傳遞進去. public class MapperProxy<T> implements InvocationHandler, Serializable {app

private static final long serialVersionUID = -6424540398559729838L;
  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;
  }

  [@Override](https://my.oschina.net/u/1162528)
  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);
  }

  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類的代碼以下:框架

  • MapperMethod纔有了命令模式運行,根據上下文跳轉,它可能跳轉到許多方法中。例如executeForMany(SqlSession sqlSession, Object[] args),經過內部的代碼能夠知道該方法實際上仍是執行的sqlSession實例的的方法去執行SQL。ide

  • 映射器的XML文件的命名控件對應的即是這個接口的全路徑,那麼它根據全路徑和方法名便呢呢剛剛綁定起來,經過動態代理技術,讓這個接口跑起來。二胡纔有命令模式,最後還使用SqlSession接口的方法使得它可以執行查詢,有了這層封裝咱們邊可使用接口變成。 public class MapperMethod {ui

    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;
    }
    //這個是經常使用的查詢返回多條記錄的方法
    private <E> Object executeForMany(SqlSession sqlSession, Object[] args) 	{
      List<E> result;
      Object param = method.convertArgsToSqlCommandParam(args);
      if (method.hasRowBounds()) {
        RowBounds rowBounds = method.extractRowBounds(args);
        result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
      } else {
        result = sqlSession.<E>selectList(command.getName(), param);
      }
      // issue #510 Collections & arrays support
      if (!method.getReturnType().isAssignableFrom(result.getClass())) {
        if (method.getReturnType().isArray()) {
      	return convertToArray(result);
        } else {
      	return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
        }
      }
      return result;
    }
    }

SqlSession下的四大對象

  • MapperMethod的execute方法。它經經的判斷以後就進入了SqlSession的刪除,更新,插入,選擇等方法,那麼這些方法如何執行是咱們須要知道的。this

  • Mapper的執行的過程是經過的Executor,StatementHandler,ParameterHandler和ResultHandler來完成數據庫操做和結果返回的。.net

  1. Executor表明執行器,由它來調度StatementHandler,ParameterHandler,ResultHandler等來執行對應的SQL 執行器(Executor)起到了相當重要的做用。他是一個真正執行Java和數據庫交互的東西。在MyBatis中存在三種執行器。咱們能夠在MyBatis的配置文件中進行選擇(在setting元素的屬性defaultExecutorType)
  • SIMPLE,簡單執行器,不須要配置它,默認的就是這個執行器
  • REUSE,是一種執行器重用的預處理語句
  • BATCH,執行器重的語句和批量更新,它是針對批量專用的執行器 它們都提供了查詢和更新的方法,以及相關的事務方法。這些和其餘框架內並沒有不一樣,Executor的實例的構造過程以下:

上面的代碼是構建Executor的過程,在構建完執行器的實例以後,最後調用了
executor = (Executor) interceptorChain.pluginAll(executor);這就是MyBatis的插件,這裏它將爲咱們構建一層層的動態代理對象。在調度真是的Executor方法以前執行配置插件的代碼能夠修改。
  1. StatementHandler的做用是使用數據庫的Statement(PrepareStatement)執行操做,它是四大對象的核心,起到了承上啓下的做用。
  • 數據庫會話對象定義了一個對象的適配delegate,它是一個StatementHandler接口對象,構造方法根據配置來適配對應的StatementHandler對象。它的做用是給實現類對象的使用提供一個統一的,簡易的使用適配器。此爲對象的適配模式,可讓咱們使用現有的類和方法對外提供服務,有俄能夠根據實際的需求對外屏蔽一些方法,甚至能夠是加入新的服務。
  • 咱們經常使用的StatementHandler好比PreparedStatementHandler爲例:

public class PreparedStatementHandler extends BaseStatementHandler {
  public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
	super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
  }
  [@Override](https://my.oschina.net/u/1162528)
  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;
  }
  [@Override](https://my.oschina.net/u/1162528)
  public void batch(Statement statement) throws SQLException {
	PreparedStatement ps = (PreparedStatement) statement;
	ps.addBatch();
  }
  [@Override](https://my.oschina.net/u/1162528)
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
	PreparedStatement ps = (PreparedStatement) statement;
	ps.execute();
	return resultSetHandler.<E> handleResultSets(ps);
  }
  @Override
  public <E> Cursor<E> queryCursor(Statement statement) throws SQLException {
	PreparedStatement ps = (PreparedStatement) statement;
	ps.execute();
	return resultSetHandler.<E> handleCursorResultSets(ps);
  }
  @Override
  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);
	}
  }
  @Override
  public void parameterize(Statement statement) throws SQLException {
	parameterHandler.setParameters((PreparedStatement) statement);
 	}
	}

public abstract class BaseStatementHandler implements StatementHandler {
  @Override
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
	ErrorContext.instance().sql(boundSql.getSql());
	Statement statement = null;
	try {
	  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);
	}
  }
....
}

instantiateStatement()方法是對SQL進行了預編譯。首先,作一些基礎配置,好比超時,獲取的最大行數等的設置。而後,Executor活調用parameterize()方法去設置參數,這個時候使用了PreparedStatementHandler的parameterize()去設置參數 3. ParameterHandler用於SQL對參數的處理 public class PreparedStatementHandler extends BaseStatementHandler { @Override public void parameterize(Statement statement) throws SQLException { parameterHandler.setParameters((PreparedStatement) statement); } }插件

因爲在執行前參數和SQL都已經被prepare()方法預編譯,參數在parameterize()方法上進行設置。因此到這裏已經很簡單了。咱們只要執行SQL,而後返回結果用ResultHandler3d

參數處理器

MyBatis是經過參數處理器(ParameterHandler)對預編譯語句進行參數設置的。它的做用是完成對預編譯參數的設置。 public interface ParameterHandler { Object getParameterObject(); void setParameters(PreparedStatement ps) throws SQLException; } 其中,getParameterObject()方法做用是返回參數對象,setParameters()方法的做用是設置預編譯SQL語句的參數. MyBatis爲Parameter提供了一個實現類DefaultParameterHandler

public class DefaultParameterHandler implements ParameterHandler {

  private final TypeHandlerRegistry typeHandlerRegistry;
  private final MappedStatement mappedStatement;
  private final Object parameterObject;
  private BoundSql boundSql;
  private Configuration configuration;
  public DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
	this.mappedStatement = mappedStatement;
	this.configuration = mappedStatement.getConfiguration();
	this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
	this.parameterObject = parameterObject;
	this.boundSql = boundSql;
  }
  @Override
  public Object getParameterObject() {
	return parameterObject;
  }
  @Override
  public void setParameters(PreparedStatement ps) {
	ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
	List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
	if (parameterMappings != null) {
	  for (int i = 0; i < parameterMappings.size(); i++) {
		ParameterMapping parameterMapping = parameterMappings.get(i);
		if (parameterMapping.getMode() != ParameterMode.OUT) {
		  Object value;
		  String propertyName = parameterMapping.getProperty();
		  if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
			value = boundSql.getAdditionalParameter(propertyName);
		  } else if (parameterObject == null) {
			value = null;
		  } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
			value = parameterObject;
		  } else {
			MetaObject metaObject = configuration.newMetaObject(parameterObject);
			value = metaObject.getValue(propertyName);
		  }
		  TypeHandler typeHandler = parameterMapping.getTypeHandler();
		  JdbcType jdbcType = parameterMapping.getJdbcType();
		  if (value == null && jdbcType == null) {
			jdbcType = configuration.getJdbcTypeForNull();
		  }
		  try {
			typeHandler.setParameter(ps, i + 1, value, jdbcType);
		  } catch (TypeException e) {
			throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
		  } catch (SQLException e) {
			throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
		  }
		}
	  }
	}
  }
}

從源碼中咱們能夠知道,參數是從parameterObject對象中獲取,而後使用typeHandler進行參數設置,typeHandler也是在MyBatis初始化的時候,註冊在Configuration裏面的,隨時能夠獲取獲得。

  1. ResultHandler是進行最後數據集的封裝返回處理的 有了ResultSetHandler的描述,咱們知道它就是組裝結果集返回的。結果處理器的接口以下:

    public interface ResultSetHandler {
       <E> List<E> handleResultSets(Statement stmt) throws SQLException;
       <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;
       void handleOutputParameters(CallableStatement cs) throws SQLException;
     }

handleOutputParameters()負責處理存儲國車給輸出參數的,handleResultSets(),它是包裝結果集的。MyBatis爲咱們了它的實現類DefaultResultSetHandler,它負責包裝結果集,在默認的狀況下,都是經過這個類進行處理的。它涉及使用使用JAVASSIST和CGLIB,比較複雜,經過typeHandler和ObjectFactory進行組裝結果再返回。

@Override
  public List<Object> handleResultSets(Statement stmt) throws SQLException {
	ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

	final List<Object> multipleResults = new ArrayList<Object>();

	int resultSetCount = 0;
	ResultSetWrapper rsw = getFirstResultSet(stmt);

	List<ResultMap> resultMaps = mappedStatement.getResultMaps();
	int resultMapCount = resultMaps.size();
	validateResultMapsCount(rsw, resultMapCount);
	while (rsw != null && resultMapCount > resultSetCount) {
	  ResultMap resultMap = resultMaps.get(resultSetCount);
	  handleResultSet(rsw, resultMap, multipleResults, null);
	  rsw = getNextResultSet(stmt);
	  cleanUpAfterHandlingResultSet();
	  resultSetCount++;
	}

	String[] resultSets = mappedStatement.getResultSets();
	if (resultSets != null) {
	  while (rsw != null && resultSetCount < resultSets.length) {
		ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
		if (parentMapping != null) {
		  String nestedResultMapId = parentMapping.getNestedResultMapId();
		  ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
		  handleResultSet(rsw, resultMap, null, parentMapping);
		}
		rsw = getNextResultSet(stmt);
		cleanUpAfterHandlingResultSet();
		resultSetCount++;
	  }
	}

	return collapseSingleResultList(multipleResults);
  }

#SqlSession運行總結 SqlSession運行原理

SqlSession是經過Executor建立StatementHandler來運行的,而StatementHandler主要有三步: 1.prepared預編譯SQL 2.parameter設置參數 3.query/update執行SQL 在這幾步中parameterize式調用parameterHandler的方法去設置的,而參數是根據類型處理器typeHnadler去處理的。query/update方法是經過resultHandler進行處理結果的,若是是update的語句,它就是返回整數,不然是使用typeHandler處理返回的結果類型,而後用ObjectFactory提供的規則組裝對象,返回給調用者。

相關文章
相關標籤/搜索