1. 程式人生 > >2018年最全Go語言實戰抽獎系統教程

2018年最全Go語言實戰抽獎系統教程


Student studentSelect = new Student();
studentSelect.setStudentId(new Long(1));
List<Student> students = studentMapper.selectWithCondition(studentSelect);

相關的mapper.java和mapper.xml程式碼如下:
package com.fengxing.mapper;
 
import java.util.List;
 
import com.fengxing.dto.Student;
 
public interface StudentMapper {
   
   public List<Student> selectWithCondition(Student student);
   
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.fengxing.mapper.StudentMapper" >
    <select id="selectWithCondition" resultType="com.fengxing.dto.Student" 
        parameterType="com.fengxing.dto.Student">
        select * 
        from student stu
        <where>
            <if test="studentId!=null">
                stu.student_id = #{studentId}
            </if>
            <if test="major!=null">
                stu.major = #{major}
            </if>
            <if test="name!=null">
                stu.name = #{name}
            </if>
        </where>
    </select>
</mapper>
整個查詢的時序圖如下:

下面對這些步驟進行詳細講解:

 (一)MapperProxy和MappedMethod

在呼叫studentMapper.selectWithCondition(studentSelect)的時候,studentMapper只是個介面,我們並沒有具體去實現這個介面,這個mybatis使用了動態代理,真正執行的是MapperProxy的invoke方法。

mapper代理物件的建立通過MapperProxyFactory建立,具體方法如下,其中mapperProxy是一個實現了InvocationHandler的物件

protected T newInstance(MapperProxy<T> mapperProxy) {
  return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
MapperProxy的部分方法如下:

@Override
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;
}
invoke方法中先判斷傳入的方法是不是Object中宣告的方法,是的話直接invoke放過

否者先獲取MapperMethod,然後呼叫MapperMethod的execute方法。MapperMethod中儲存了查詢方法的型別,名稱,返回型別等詳細資訊

MapperMethod的execute(SqlSession sqlSession, Object[] args)方法如下,根據增刪改查的型別以及返回值的型別執行不同的方法,這裡將呼叫executeForMany(sqlSession, args)

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;
}
MapperMethod的executeForMany方法:

裡面呼叫sqlSession的selectList方法

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的executeForMany方法中呼叫sqlSession的selectList方法,這裡的sqlSession是sqlSessionTemplate,具體會呼叫代理物件sqlSessionProxy的selectList,然後到invoke方法,invoke方法中建立具體的DefaultSqlSession,然後執行selectList,這個過程還會建立四大物件之一的executor,具體過程可以看mybatis(一)

DefaultSqlSession的selectList方法如下:

@Override
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();
  }
}
先獲取了MappedStatement物件,MappedStatement物件的資訊如下,主要記錄了mapper.xml檔案中select標籤的各種屬性值

接著開始呼叫Executor的query方法

(三)Executor

Executor是一個介面,BaseExecutor和CachingExecutor都實現了Executor,其中BaseExecutor是抽象類,SimpleExecutor又繼承並實現了BaseExecutor

這裡先進入CachingExecutor的4個引數的query方法,這個方法有可能被攔截器攔截,比如pageHelper就會攔截這個方法,該方法如下:

@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
  BoundSql boundSql = ms.getBoundSql(parameterObject);
  CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
  return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
這個方法會建立 boundSql,boundSql的屬性如下,有sql語句,查詢引數,已經查詢引數的型別等 ,可以看到boundSql已經沒有了mybatis拼接sql的各種標籤了(這裡的sql和mapper.xml中寫的不一樣,原因是我自己定義了攔截器,對sql做了更改)

所以BoundSql boundSql = ms.getBoundSql(parameterObject)這行程式碼是做sql語句的拼接

接著呼叫CachingExecutor自身的5個引數的query方法, 

@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
    throws SQLException {
  Cache cache = ms.getCache();
  if (cache != null) {
    flushCacheIfRequired(ms);
    if (ms.isUseCache() && resultHandler == null) {
      ensureNoOutParams(ms, parameterObject, boundSql);
      @SuppressWarnings("unchecked")
      List<E> list = (List<E>) tcm.getObject(cache, key);
      if (list == null) {
        list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
        tcm.putObject(cache, key, list); // issue #578 and #116
      }
      return list;
    }
  }
  return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
 

這個查詢方法中有對二級快取的處理,如果沒有從快取中拿到,就呼叫delegate的query,這裡delegate是SimpleExecutor,由於SimpleExecutor繼承於BaseExecutor,這個方法是BaseExecutor實現的,所以先到BaseExecutor

BaseExecutor的query方法如下:

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
  if (closed) {
    throw new ExecutorException("Executor was closed.");
  }
  if (queryStack == 0 && ms.isFlushCacheRequired()) {
    clearLocalCache();
  }
  List<E> list;
  try {
    queryStack++;
    list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
    if (list != null) {
      handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
    } else {
      list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }
  } finally {
    queryStack--;
  }
  if (queryStack == 0) {
    for (DeferredLoad deferredLoad : deferredLoads) {
      deferredLoad.load();
    }
    // issue #601
    deferredLoads.clear();
    if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
      // issue #482
      clearLocalCache();
    }
  }
  return list;
}
這個方法體現了一級快取,一級快取沒有命中的話,就呼叫queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql)查詢資料庫。

BaseExecutor的queryFromDataBase方法:

private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  List<E> list;
  localCache.putObject(key, EXECUTION_PLACEHOLDER);
  try {
    list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
  } finally {
    localCache.removeObject(key);
  }
  localCache.putObject(key, list);
  if (ms.getStatementType() == StatementType.CALLABLE) {
    localOutputParameterCache.putObject(key, parameter);
  }
  return list;
}
查詢資料庫前先在localCacha中放一個佔位符,查詢完成後,先清除之前的快取,然後將查到的資料放入快取中

查詢的doQuery方法BaseExecutor沒有實現,是由SimpleExecutor實現的,所以又到了SimpleExecutor的doQuery方法

SimpleExecutor的doQuery方法:

@Override
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();
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
    stmt = prepareStatement(handler, ms.getStatementLog());
    return handler.<E>query(stmt, resultHandler);
  } finally {
    closeStatement(stmt);
  }
}
這個方法包括三個步驟

1.建立四大物件之一的StatementHandler

2.利用StatementHandler獲取JDBC原生的Statement

3.利用1,2獲取的物件執行查詢操作

(四)除Executor之外的其餘三大物件的建立以及其中的查詢處理流程

configuration中newStatementHandler方法如下:

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,然後包裝成攔截器鏈。

RoutingStatementHandler的建立方法如下:

public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
 
  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());
  }
 
}
根據statementType的不同,分別建立SimpleStatementHandler, PreparedStatementHandler和CallableStatementHandler,依次對應了jdbc的三種statement,statementType是在mapper.xml檔案中的增刪改查標籤中定義的,預設是PREPARED

PreparedStatementHandler繼承自PreparedStatementHandler,在BaseStatementHandler的構造方法中建立了四大物件的其餘兩個,即ParameterHandler和ResultSetHandler

PreparedStatementHandler和PreparedStatementHandler的構造方法如下:

public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
  super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
}
protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
  this.configuration = mappedStatement.getConfiguration();
  this.executor = executor;
  this.mappedStatement = mappedStatement;
  this.rowBounds = rowBounds;
 
  this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
  this.objectFactory = configuration.getObjectFactory();
 
  if (boundSql == null) { // issue #435, get the key before calculating the statement
    generateKeys(parameterObject);
    boundSql = mappedStatement.getBoundSql(parameterObject);
  }
 
  this.boundSql = boundSql;
 
  this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
  this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}
StatementHandler建立完成後,開始建立JDBC原生的Statement

SimpleExecutor中的prepareStatement方法:

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
  Statement stmt;
  Connection connection = getConnection(statementLog);
  stmt = handler.prepare(connection, transaction.getTimeout());
  handler.parameterize(stmt);
  return stmt;
}
stmt = handler.prepare(connection, transaction.getTimeout());是預編譯sql生成Statement物件

handler.parameterize(stmt);是設定預編譯引數

第三步就是執行查詢操作

最終會呼叫PrepareStatementHandler的query方法

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  PreparedStatement ps = (PreparedStatement) statement;
  ps.execute();
  return resultSetHandler.<E> handleResultSets(ps);
}
這裡包括兩個步驟

1. 呼叫PrepareStatement的excute方法查詢資料庫

2.利用ResultSetHandler物件對結果進行處理和封裝

 

 

 

 

 

閱讀更多
--------------------- 
作者:科西嘉獅子 
來源:CSDN 
原文:https://blog.csdn.net/qq_38478903/article/details/84928862 
版權宣告:本文為博主原創文章,轉載請附上博文連結!