中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

MyBatis的SQL語句執行過程是什么

發布時間:2022-05-20 16:48:43 來源:億速云 閱讀:178 作者:iii 欄目:開發技術

這篇文章主要介紹“MyBatis的SQL語句執行過程是什么”,在日常操作中,相信很多人在MyBatis的SQL語句執行過程是什么問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”MyBatis的SQL語句執行過程是什么”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!

    1 SQL語句的執行過程介紹

    MyBatis核心執行組件:

    MyBatis的SQL語句執行過程是什么

    2 SQL執行的入口分析

    2.1 為Mapper接口創建代理對象

    // 方式1:
    User user = session.selectOne("com.oldlu.dao.UserMapper.findUserById", 101);
    // 方式2:
    UserMapper mapper = session.getMapper(UserMapper.class);
    List<User> userList = mapper.findAll();

    2.2 執行代理邏輯

    方式1入口分析:
    session是DefaultSqlSession類型的,因為sqlSessionFactory默認生成的SqlSession是
    DefaultSqlSession類型。
    selectOne()會調用selectList()。

    // DefaultSqlSession類
    public <E> List<E> selectList(String statement, Object parameter, RowBounds
    rowBounds) {
      try {
        MappedStatement ms = configuration.getMappedStatement(statement);
        // CURD操作是交給Excetor去處理的
        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();
     }
    }

    方式2入口分析:
    獲取代理對象:

    //DefaultSqlSession類 ====================>
    @Override
    public <T> T getMapper(Class<T> type) {
      return configuration.getMapper(type, this);
      }
    // Configuration類 ====================>
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
      return mapperRegistry.getMapper(type, sqlSession);
    }
    //MapperRegistry ----> apperProxyFactory.newInstance ====================>
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
      //從緩存中獲取該Mapper接口的代理工廠對象
      final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>)
    knownMappers.get(type);
      //如果該Mapper接口沒有注冊過,則拋異常
      if (mapperProxyFactory == null) {
        throw new BindingException("Type " + type + " is not known to the
    MapperRegistry.");
     }
      try {
        //【使用代理工廠創建Mapper接口的代理對象】
        return mapperProxyFactory.newInstance(sqlSession);
     } catch (Exception e) {
        throw new BindingException("Error getting mapper instance. Cause: " + e,
    e);
     }
    }
    //MapperProxyFactory  --->此時生成代理對象 ====================>
    protected T newInstance(MapperProxy<T> mapperProxy) {
      //Mybatis底層是調用JDK的Proxy類來創建代理實例
      return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new
    Class[] { mapperInterface }, mapperProxy);
    }
    public T newInstance(SqlSession sqlSession) {
      final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession,
    mapperInterface, methodCache);
      return newInstance(mapperProxy);
    }

    代理對象執行邏輯:

    //MapperProxy   ====================>
    /**代理對象執行的方法,代理以后,所有Mapper的方法調用時,都會調用這個invoke方法*/
    public Object invoke(Object proxy, Method method, Object[] args) throws
    Throwable {
     try {
      if (Object.class.equals(method.getDeclaringClass())) {
       //如果是Object方法,則調用方法本身
       return method.invoke(this, args);
     } else {
       //調用接口方法:根據被調用接口的Method對象,從緩存中獲取MapperMethodInvoker對象
       //apper接口中的每一個方法都對應一個MapperMethodInvoker對象,而MapperMethodInvoker
    對象里面的MapperMethod保存著對應的SQL信息和返回類型以完成SQL調用 ...
       return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
     }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    }
    /**
    獲取緩存中MapperMethodInvoker,如果沒有則創建一個,而MapperMethodInvoker內部封裝這一
    個MethodHandler
    */
    private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
      try {
        return methodCache.computeIfAbsent(method, m -> {
          if (m.isDefault()) {
            //如果調用接口的是默認方法(default方法)
            try {
              if (privateLookupInMethod == null) {
                return new
    DefaultMethodInvoker(getMethodHandleJava8(method));
             } else {
                return new
    DefaultMethodInvoker(getMethodHandleJava9(method));
             }
           } catch (IllegalAccessException | InstantiationException |
    InvocationTargetException
                | NoSuchMethodException e) {
              throw new RuntimeException(e);
           }
         } else {
            //如果調用的普通方法(非default方法),則創建一個PlainMethodInvoker并放
    入緩存,其中MapperMethod保存對應接口方法的SQL以及入參和出參的數據類型等信息
            return new PlainMethodInvoker(new MapperMethod(mapperInterface,
    method, sqlSession.getConfiguration()));
         }
       });
     } catch (RuntimeException re) {
        Throwable cause = re.getCause();
    throw cause == null ? re : cause;
     }
    }
    // MapperProxy內部類: PainMethodInvoker  ====================>
    // 當cacheInvoker返回了PalinMethodInvoker實例之后,緊接著調用了這個實例的
    PlainMethodInvoker:invoke方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args, SqlSession
    sqlSession) throws Throwable {
     //Mybatis實現接口方法的核心: MapperMethod::execute方法:
     return mapperMethod.execute(sqlSession, args);
    }
    // MapperMethod  ====================>
    public Object execute(SqlSession sqlSession, Object[] args) {
      Object result;
      switch (command.getType()) {
        case INSERT: {
          // 將args進行解析,如果是多個參數則,則根據@Param注解指定名稱將參數轉換為Map,
    如果是封裝實體則不轉換
          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 {
            //解析參數,因為SqlSession::selectOne方法參數只能傳入一個,但是我們
    Mapper中可能傳入多個參數,
            //有可能是通過@Param注解指定參數名,所以這里需要將Mapper接口方法中的多個參
    數轉化為一個ParamMap,
            //也就是說如果是傳入的單個封裝實體,那么直接返回出來;如果傳入的是多個參數,
    實際上都轉換成了Map
            Object param = method.convertArgsToSqlCommandParam(args);
            //可以看到動態代理最后還是使用SqlSession操作數據庫的
            result = sqlSession.selectOne(command.getName(), param);
            if (method.returnsOptional()
              && (result == null ||
    !method.getReturnType().equals(result.getClass()))) {
              result = Optional.ofNullable(result);
           }
         }
          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;
    }
    // 此時我們發現: 回到了sqlsession中
    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.selectList(command.getName(), param, rowBounds);
     } else {
       result = sqlSession.selectList(command.getName(), param);
     }
     // ...
      return result;
    }

    MyBatis的SQL語句執行過程是什么

    3 查詢語句的執行過程分析

    3.1 selectOne方法分析

    // DefaultSqlSession類  ===============>
    // selectOne
    @Override
    public <T> T selectOne(String statement, Object parameter) {
      // //selectOne()會調用selectList()。
      List<T> list = this.selectList(statement, parameter);
      if (list.size() == 1) {
        return list.get(0);
     } else if (list.size() > 1) {
        throw new TooManyResultsException("Expected one result (or null) to be
    returned by selectOne(), but found: " + list.size());
     } else {
        return null;
     }
    }
    // selectList
    public <E> List<E> selectList(String statement, Object parameter, RowBounds
    rowBounds) {
      try {
        MappedStatement ms = configuration.getMappedStatement(statement);
        // CURD操作是交給Excetor去處理的
        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();
     }
    }

    3.2 sql獲取

    // CachingExecutor ===============>
    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds
    rowBounds, ResultHandler resultHandler) throws SQLException {
      // 獲取綁定的sql命令,比如"SELECT * FROM xxx"
      BoundSql boundSql = ms.getBoundSql(parameterObject); 
      CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
      return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }
    @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, boundSql);
          @SuppressWarnings("unchecked")
          List<E> list = (List<E>) tcm.getObject(cache, key);
          if (list == null) {
            list = delegate.query(ms, parameterObject, rowBounds,
    resultHandler, key, boundSql);
            tcm.putObject(cache, key, list); // issue #578 and #116
        }
          return list;
       }
     }
      return delegate.query(ms, parameterObject, rowBounds, resultHandler, key,
    boundSql);
    }
    //真正執行query操作的是SimplyExecutor代理來完成的,SimplyExecutor的父類BaseExecutor的
    query方法中:
    // BaseExecutor類:SimplyExecutor的父類 =================>
    @Override
    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++;
        //localCache是一級緩存,如果找不到就調用queryFromDatabase從數據庫中查找
        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();
       }
        deferredLoads.clear();
        if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
          clearLocalCache();
       }
    }
      return list;
    }
    //第一次,沒有緩存,所以會調用queryFromDatabase方法來執行查詢。
    private <E> List<E> queryFromDatabase(...) 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;
    }
    // SimpleExecutor類 ============================>
    public <E> List<E> doQuery(...) throws SQLException {
      Statement stmt = null;
      try {
        Configuration configuration = ms.getConfiguration();
        StatementHandler handler = configuration.newStatementHandler(....);
        // 1:SQL查詢參數的設置
        stmt = prepareStatement(handler, ms.getStatementLog());
        // StatementHandler封裝了Statement
        // 2:SQL查詢操作和結果集的封裝
        return handler.<E>query(stmt);
     } finally {
        closeStatement(stmt);
     }
    }

    3.3 參數設置

    // SimplyExecutor類 ============================>
    // 【1】 參數設置: prepareStatement
    private Statement prepareStatement(StatementHandler handler, Log statementLog)
    throws SQLException {
      Statement stmt;
      // 通過getConnection方法來獲取一個Connection,
      Connection connection = getConnection(statementLog);
      // 調用prepare方法來獲取一個Statement
      stmt = handler.prepare(connection, transaction.getTimeout());
     
      // 設置SQL查詢中的參數值 ***
      handler.parameterize(stmt);
      return stmt;
    }
    // RoutingStatementHandler ============================>
    // PreparedStatementHandler ============================>
    @Override
    public void parameterize(Statement statement) throws SQLException {
      parameterHandler.setParameters((PreparedStatement) statement);
    }
    // DefaultParameterHandler ============================> 此時參數設置成功
    @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)) {
              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 | SQLException e) {
              throw new TypeException("Could not set parameters for
    mapping.....");
           }
         }
       }
     }
    }

    3.4 SQL執行和結果集的封裝

    // RoutingStatementHandler ============================>
    @Override
    public <E> List<E> query(Statement statement) throws SQLException {
      return delegate.<E>query(statement);
    }
    // PreparedStatementHandler ============================>
    @Override
    public <E> List<E> query(Statement statement, ResultHandler resultHandler)
    throws SQLException {
      // 這里就到了熟悉的PreparedStatement了
      PreparedStatement ps = (PreparedStatement) statement;
      // 執行SQL查詢操作
      ps.execute();
      // 結果交給ResultHandler來處理
      return resultSetHandler.<E> handleResultSets(ps);
    }
    // DefaultResultSetHandler類(封裝返回值,將查詢結果封裝成Object對象)
    @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);
    }

    MyBatis的SQL語句執行過程是什么

    4 更新語句的執行過程分析

    • xecutor 的 update 方法分析

    • insert、update 和 delete 操作都會清空一二級緩存

    • doUpdate 方法

    • PreparedStatementHandler 的 update 方法

    • 默認是創建PreparedStatementHandler,然后執行prepareStatement方法。

    • 執行結果為受影響行數

    • 執行更新語句的SQL

    4.1 sqlsession增刪改方法分析

    // DefaultSqlSession ===============>
    @Override
     public int insert(...) {
      return update(statement, parameter);
    }
     @Override
     public int update(String statement) {
      return update(statement, null);
    }
     @Override
      public int delete(...) {
      return update(....);
    }
    // insert 、delete操作是通過調用update語句進行的相關邏輯
     @Override
     public int update(String statement, Object parameter) {
      try {
       dirty = true;
       MappedStatement ms = configuration.getMappedStatement(statement);
       // 增刪改 最終底層都是 update
       return executor.update(ms, wrapCollection(parameter));
       
     } catch (Exception e) {
       throw ExceptionFactory.wrapException("Error updating database. Cause: " +
    e, e);
     } finally {
       ErrorContext.instance().reset();
     }
    }

    4.2 sql獲取

    // CachingExecutor  ===============>
    @Override
    public int update(MappedStatement ms, Object parameterObject) throws
    SQLException {
      // 執行增刪改,清除緩存
      flushCacheIfRequired(ms);
      // 跳轉BaseExecutor
      return delegate.update(ms, parameterObject);
    }
    // BaseExecutor   ===============>
    @Override
    public int update(MappedStatement ms, Object parameter) throws SQLException {
      ErrorContext.instance().resource(ms.getResource()).activity("executing an
    update").object(ms.getId());
      if (closed) {
        throw new ExecutorException("Executor was closed.");
     }
      // 清除 LocalCache 一級緩存
      clearLocalCache();
      //執行 doUpdate
      return doUpdate(ms, parameter);
    }
    // SimpleExecutor  ===============>
    // doUpdate
    @Override
    public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
      Statement stmt = null;
      try {
        Configuration configuration = ms.getConfiguration();
        StatementHandler handler = configuration.newStatementHandler(...);
        // 【1】.獲取statement,并進行參數映射
        stmt = prepareStatement(handler, ms.getStatementLog());
        // 【2】.handler.update()方法執行具體sql指令
        return handler.update(stmt);
     } finally {
        closeStatement(stmt);
     }
    }

    4.3 參數設置

    // SimplyExecutor類 ============================>
    //【1】 prepareStatement
    private Statement prepareStatement(StatementHandler handler, Log statementLog)
    throws SQLException {
      Statement stmt;
      Connection connection = getConnection(statementLog);
      // 使用connection對象信息創建statement,并將超時時間綁定
      stmt = handler.prepare(connection, transaction.getTimeout());
      // parameterize方法設置sql執行時候需要的參數
      handler.parameterize(stmt);
      return stmt;
    }
    // RoutingStatementHandler ============================>
    // PreparedStatementHandler ============================>
    @Override
    public void parameterize(Statement statement) throws SQLException {
      parameterHandler.setParameters((PreparedStatement) statement);
    }
    // DefaultParameterHandler ============================> 此時參數設置成功
    @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)) {
              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 | SQLException e) {
              throw new TypeException("Could not set parameters for
    mapping.....");
           }
         }
       }
     }
    }

    4.4 SQL執行

    // RoutingStatementHandler ============================>
     @Override
     public int update(Statement statement) throws SQLException {
      return delegate.update(statement);
    }
    // PreparedStatementHandler ============================>
    @Override
    public int update(Statement statement) throws SQLException {
      // 這里就是底層JDBC的PreparedStatement 操作了
      PreparedStatement ps = (PreparedStatement) statement;
      // 執行SQL增刪改操作
      ps.execute();
      // 獲取影響的行數
      int rows = ps.getUpdateCount();
      Object parameterObject = boundSql.getParameterObject();
      KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
      keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
      // 返回影響的行數
      return rows;
    }

    到此,關于“MyBatis的SQL語句執行過程是什么”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    固安县| 凤阳县| 洛宁县| 谷城县| 锡林浩特市| 将乐县| 仁布县| 扬州市| 崇明县| 老河口市| 克什克腾旗| 扶绥县| 麻阳| 左权县| 松滋市| 津市市| 桐梓县| 斗六市| 杨浦区| 农安县| 柳河县| 威宁| 屯门区| 巴马| 达日县| 黄平县| 宁乡县| 安图县| 白沙| 峨眉山市| 鄢陵县| 东城区| 乐山市| 新密市| 定州市| 荆门市| 沙雅县| 庐江县| 太保市| 繁峙县| 新郑市|