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提供的规则组装对象,返回给调用者。

相关文章
相关标签/搜索