1. 程式人生 > 其它 >mybatis foreach執行多條sql配置_MyBatis原始碼分析三:Sql執行

mybatis foreach執行多條sql配置_MyBatis原始碼分析三:Sql執行

技術標籤:mybatis foreach執行多條sql配置

一、MyBatis中Sql執行過程

先上一段程式碼,看直接呼叫MyBatis Api是如何執行Sql的:

  // 獲取配置檔案輸入流  InputStream inputStream = Resources.getResourceAsStream("META-INF/spring/mybatis-config.xml");  // 通過SqlSessionFactoryBuilder的build()方法建立SqlSessionFactory例項  SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);//構造資料來源  BoneCPDataSource dataSource= new BoneCPDataSource();  dataSource.setDriverClass(driverClass);  dataSource.setJdbcUrl(jdbcUrl);  dataSource.setUsername(userNmae);  dataSource.setPassword(password);  String id = "SqlSessionFactoryBean";  TransactionFactory transactionFactory = new SpringManagedTransactionFactory();  Environment newEnv = new Environment(id, transactionFactory, dataSource);sqlSessionFactory.getConfiguration().setEnvironment(newEnv);  SqlSession sqlSession = sqlSessionFactory.openSession();  // 獲取UserMapper代理物件  UserMapper userMapper = sqlSession.getMapper(UserMapper.class);  // 執行Mapper方法,獲取執行結果  List userList = userMapper.listAllUser();

大概過程是先獲取到SqlSession例項,然後獲取Mapper,再執行Mapper中相應的方法。

在前面文章 MyBatis3使用介紹了Spring中如何使用MyBatis,這裡再總結下:

1、先編寫Mapper

@Mapperpublic interface UserMapper {    ListlistAllUser();}

2、再編寫Sql語句

<?xml version="1.0"encoding="UTF-8"?>/span>        "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.liujh.mapper.UserMapper">    <sql id="userFields">      id,name, phone,create_time    sql>    <select id="listAllUser"  resultType="com.liujh.entity.UserEntity" >        select        <include refid="userFields"/>        from user    select>mapper>

注意上面介面中的方法名要和Xml中id對應上,並且Namespace也要對上。

那Sql到底是怎麼執行的,明明第一步只定義了一個介面,Java中介面是不能例項化的,只能通過類來例項的,它是如何和我們在Xml中編寫的Sql繫結的呢?

二、介面和Sql繫結過程

只要加了@Mapper的註解,框架就會為這個Bean定義的型別設定為MapperFactoryBean:

7561e09f75a64eded52d99fd703e748a.png

而MapperFactoryBean的getObject為:

 public T getObject() throws Exception {    return getSqlSession().getMapper(this.mapperInterface);  }

其中mapperInterface為定義的介面。

getSqlSession返回SqlSessionTemplate,它的getMapper方法交給Configuration物件了:

 public  T getMapper(Classtype) {    return getConfiguration().getMapper(type, this);  }

Configuration物件的getMapper方法則交給了MapperProxyFactory:

public  T getMapper(Class<T> type, SqlSession sqlSession) {    final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type);    if (mapperProxyFactory == null) {      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");    }    try {      return mapperProxyFactory.newInstance(sqlSession);    } catch (Exception e) {      throw new BindingException("Error getting mapper instance. Cause: " + e, e);    }  }

MapperProxyFactory則返生成一個MapperProxy,再用JDK的動態代理來完成相關操作:

 public T newInstance(SqlSession sqlSession) {    final MapperProxy mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache);    return newInstance(mapperProxy);  }    protected T newInstance(MapperProxy mapperProxy) {    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);  }

即最後由MapperProxy來執行,我們知道JDK的動態代理要實現InvocationHandler介面,即實現invoke方法:

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

cachedMapperMethod生成MapperMethod並且快取:

 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來完成的:

public MapperMethod(Class> mapperInterface, Method method, Configuration config) {    this.command = new SqlCommand(config, mapperInterface, method);    this.method = new MethodSignature(config, mapperInterface, method);  }

在初始化SqlCommand的時候會得到對應的sql語句的一些資訊,如id和sql的型別:SELECT/UPDATE/INSERT:

public SqlCommand(Configuration configuration, Class> mapperInterface, Method method) {      final String methodName = method.getName();      final Class> declaringClass = method.getDeclaringClass();//查詢對應的sql對應的MapperedStatement物件      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);        }      }    }

其中resolveMappedStatement會在Configuration中查詢是否有註冊相應的MappedStatement,具體過程參考下一篇文章MyBatis原始碼分析二:啟動過程。

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

最後我們再看下執行的過程:

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

這裡以SELECT命令並且返回一個集合為例,執行的堆疊如下:

65973db4394dc5386a3623957bb7581f.png

在執行過程中會通過方法和類名從Configuration物件中得到相應的MappedStatement,然後交由相應的Executor執行

 @Override  public  List 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();    }  }

因程式碼比較多就不一一詳列了,大概的互動如下:

MapperMethod——》SqlSession(SqlSessionTemplate)——》Executor(CachingExecutor)——》SimpleExecutor

接下來是呼叫JDBC相關API完成操作。

@Override  public Listquery(Statement statement, ResultHandler resultHandler) throws SQLException {    PreparedStatement ps = (PreparedStatement) statement;    ps.execute();    return resultSetHandler. handleResultSets(ps);  }

三、總結

MyBatis使用動態代理技術將介面和Xml中的Sql語句關聯起來,具體來說為每一個介面建立一個MapperProxy;

介面中每一個方法對應一個MapperMethod,執行介面的方法就是執行MapperMethod的execute方法,在執行過程中通過查詢介面名稱對應的MapperedStatement物件(代表一條Sql語句)來執行相應的Sql,從而達到介面和Sql關聯 。


MyBatis原始碼分析二:啟動過程

MyBatis原始碼分析一:核心元件

MyBatis3使用

RabbitMQ Fedration外掛