久久精品人人爽,华人av在线,亚洲性视频网站,欧美专区一二三

Mybatis中怎么利用 mapper實(shí)現(xiàn)動(dòng)態(tài)代理

共計(jì) 18067 個(gè)字符,預(yù)計(jì)需要花費(fèi) 46 分鐘才能閱讀完成。

自動(dòng)寫代碼機(jī)器人,免費(fèi)開通

Mybatis 中怎么利用 mapper 實(shí)現(xiàn)動(dòng)態(tài)代理,很多新手對此不是很清楚,為了幫助大家解決這個(gè)難題,下面丸趣 TV 小編將為大家詳細(xì)講解,有這方面需求的人可以來學(xué)習(xí)下,希望你能有所收獲。

前言

在開始動(dòng)態(tài)代理的原理講解以前,我們先看一下集成 mybatis 以后 dao 層不使用動(dòng)態(tài)代理以及使用動(dòng)態(tài)代理的兩種實(shí)現(xiàn)方式,通過對比我們自己實(shí)現(xiàn) dao 層接口以及 mybatis 動(dòng)態(tài)代理可以更加直觀的展現(xiàn)出 mybatis 動(dòng)態(tài)代理替我們所做的工作,有利于我們理解動(dòng)態(tài)代理的過程,講解完以后我們再進(jìn)行動(dòng)態(tài)代理的原理解析,此講解基于 mybatis 的環(huán)境已經(jīng)搭建完成,并且已經(jīng)實(shí)現(xiàn)了基本的用戶類編寫以及用戶類的 Dao 接口的聲明,下面是 Dao 層的接口代碼

public interface UserDao {
 /*
  查詢所有用戶信息
 */
 List User  findAll();
 /**
 *  保存用戶
 * @param user
 */
 void save(User user);
 /**
 *  更新用戶
 * @return
 */
 void update(User user);
 /**
 *  刪除用戶
 */
 void delete(Integer userId);
 /**
 *  查找一個(gè)用戶
 * @param userId
 * @return
 */
 User findOne(Integer userId);
 /**
 *  根據(jù)名字模糊查詢
 * @param name
 * @return
 */
 List User  findByName(String name);
 /**
 *  根據(jù)組合對象進(jìn)行模糊查詢
 * @param vo
 * @return
 */
 List User  findByQueryVo(QueryVo vo);
}

一、Mybatis dao 層兩種實(shí)現(xiàn)方式的對比

1.dao 層不使用動(dòng)態(tài)代理

dao 層不使用動(dòng)態(tài)代理的話,就需要我們自己實(shí)現(xiàn) dao 層的接口,為了簡便起見,我只是實(shí)現(xiàn)了 Dao 接口中的 findAll 方法,以此方法為例子來展現(xiàn)我們自己實(shí)現(xiàn) Dao 的方式的情況,讓我們來看代碼:

public class UserDaoImpl implements UserDao{
 private SqlSessionFactory factory;
 public UserDaoImpl(SqlSessionFactory factory){
 this.factory = factory;
 }
 public List User  findAll() {
 //1. 獲取 sqlSession 對象
 SqlSession sqlSession = factory.openSession();
 //2. 調(diào)用 selectList 方法
 List User  list = sqlSession.selectList( com.example.dao.UserDao.findAll 
 //3. 關(guān)閉流
 sqlSession.close();
 return list;
 }
 public void save(User user) { }
 public void update(User user) { }
 public void delete(Integer userId) { }
 public User findOne(Integer userId) {
 return null;
 }
 public List User  findByName(String name) {
 return null;
 }
 public List User  findByQueryVo(QueryVo vo) {
 return null;
 }

這里的關(guān)鍵代碼 List User list = sqlSession.selectList(com.example.dao.UserDao.findAll),需要我們自己手動(dòng)調(diào)用 SqlSession 里面的方法,基于動(dòng)態(tài)代理的方式最后的目標(biāo)也是成功的調(diào)用到這里。

注意:如果是添加,更新或者刪除操作的話需要在方法中增加事務(wù)的提交。

2.dao 層使用 Mybatis 的動(dòng)態(tài)代理

使用動(dòng)態(tài)代理的話 Dao 層的接口聲明完成以后只需要在使用的時(shí)候通過 SqlSession 對象的 getMapper 方法獲取對應(yīng) Dao 接口的代理對象,關(guān)鍵代碼如下:

//3. 獲取 SqlSession 對象
SqlSession session = factory.openSession();
//4. 獲取 dao 的代理對象
UserDao mapper = session.getMapper(UserDao.class);
//5. 執(zhí)行查詢所有的方法
List User  list = mapper.findAll();

獲取到 dao 層的代理對象以后通過代理對象調(diào)用查詢方法就可以實(shí)現(xiàn)查詢所有用戶列表的功能。

二、Mybatis 動(dòng)態(tài)代理實(shí)現(xiàn)方式的原理解析

動(dòng)態(tài)代理中最重要的類:SqlSession、MapperProxy、MapperMethod,下面開始從入口方法到調(diào)用結(jié)束的過程分析。

1. 調(diào)用方法的開始:

//4. 獲取 dao 的代理對象

UserDao mapper = session.getMapper(UserDao.class); 因?yàn)?SqlSesseion 為接口,所以我們通過 Debug 方式發(fā)現(xiàn)這里使用的實(shí)現(xiàn)類為 DefaultSqlSession。

2. 找到 DeaultSqlSession 中的 getMapper 方法,發(fā)現(xiàn)這里沒有做其他的動(dòng)作,只是將工作繼續(xù)拋到了 Configuration 類中,Configuration 為類不是接口,可以直接進(jìn)入該類的 getMapper 方法中

@Override
 public  T  T getMapper(Class T  type) { return configuration. T getMapper(type, this);
 }

3. 找到 Configuration 類的 getMapper 方法,這里也是將工作繼續(xù)交到 MapperRegistry 的 getMapper 的方法中,所以我們繼續(xù)向下進(jìn)行。

 public  T  T getMapper(Class T  type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession);
 }

4. 找到 MapperRegistry 的 getMapper 的方法, 看到這里發(fā)現(xiàn)和以前不一樣了,通過 MapperProxyFactory 的命名方式我們知道這里將通過這個(gè)工廠生成我們所關(guān)注的 MapperProxy 的代理類,然后我們通過 mapperProxyFactory.newInstance(sqlSession); 進(jìn)入 MapperProxyFactory 的 newInstance 方法中

public  T  T getMapper(Class T  type, SqlSession sqlSession) { final MapperProxyFactory T  mapperProxyFactory = (MapperProxyFactory T) 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);
 }
 }

5. 找到 MapperProxyFactory 的 newIntance 方法,通過參數(shù)類型 SqlSession 可以得知,上面的調(diào)用先進(jìn)入第二個(gè) newInstance 方法中并創(chuàng)建我們所需要重點(diǎn)關(guān)注的 MapperProxy 對象,第二個(gè)方法中再調(diào)用第一個(gè) newInstance 方法并將 MapperProxy 對象傳入進(jìn)去,根據(jù)該對象創(chuàng)建代理類并返回。這里已經(jīng)得到需要的代理類了,但是我們的代理類所做的工作還得繼續(xù)向下看 MapperProxy 類。

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

6. 找到 MapperProxy 類,發(fā)現(xiàn)其確實(shí)實(shí)現(xiàn)了 JDK 動(dòng)態(tài)代理必須實(shí)現(xiàn)的接口 InvocationHandler,所以我們重點(diǎn)關(guān)注 invoke() 方法,這里看到在 invoke 方法里先獲取 MapperMethod 類,然后調(diào)用 mapperMethod.execute(),所以我們繼續(xù)查看 MapperMethod 類的 execute 方法。

public class MapperProxy T  implements InvocationHandler, Serializable {
 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
 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;
 }
 @UsesJava7
 private Object invokeDefaultMethod(Object proxy, Method method, Object[] args)
 throws Throwable {
 final Constructor MethodHandles.Lookup  constructor = MethodHandles.Lookup.class
 .getDeclaredConstructor(Class.class, int.class);
 if (!constructor.isAccessible()) { constructor.setAccessible(true);
 }
 final Class ?  declaringClass = method.getDeclaringClass();
 return constructor
 .newInstance(declaringClass,
 MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
 | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC)
 .unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
 }
 /**
 * Backport of java.lang.reflect.Method#isDefault()
 */
 private boolean isDefaultMethod(Method method) { return ((method.getModifiers()
   (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC)
   method.getDeclaringClass().isInterface();
 }
}

7. 找到類 MapperMethod 類的 execute 方法,發(fā)現(xiàn) execute 中通過調(diào)用本類中的其他方法獲取并封裝返回結(jié)果,我們來看一下 MapperMethod 整個(gè)類。

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

8. MapperMethod 類是整個(gè)代理機(jī)制的核心類,對 SqlSession 中的操作進(jìn)行了封裝使用。

該類里有兩個(gè)內(nèi)部類 SqlCommand 和 MethodSignature。SqlCommand 用來封裝 CRUD 操作,也就是我們在 xml 中配置的操作的節(jié)點(diǎn)。每個(gè)節(jié)點(diǎn)都會(huì)生成一個(gè) MappedStatement 類。

MethodSignature 用來封裝方法的參數(shù)以及返回類型,在 execute 的方法中我們發(fā)現(xiàn)在這里又回到了 SqlSession 中的接口調(diào)用,和我們自己實(shí)現(xiàn) UerDao 接口的方式中直接用 SqlSession 對象調(diào)用 DefaultSqlSession 的實(shí)現(xiàn)類的方法是一樣的,經(jīng)過一大圈的代理又回到了原地,這就是整個(gè)動(dòng)態(tài)代理的實(shí)現(xiàn)過程了。

public class MapperMethod {
 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 Object rowCountResult(int rowCount) {
 final Object result;
 if (method.returnsVoid()) {
 result = null;
 } else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) {
 result = rowCount;
 } else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) { result = (long)rowCount;
 } else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) {
 result = rowCount   0;
 } else { throw new BindingException( Mapper method   + command.getName() +   has an unsupported return type:   + method.getReturnType());
 }
 return result;
 }
 private void executeWithResultHandler(SqlSession sqlSession, Object[] args) { MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(command.getName());
 if (void.class.equals(ms.getResultMaps().get(0).getType())) { throw new BindingException( method   + command.getName() 
 +   needs either a @ResultMap annotation, a @ResultType annotation,  
 +   or a resultType attribute in XML so a ResultHandler can be used as a parameter. 
 }
 Object param = method.convertArgsToSqlCommandParam(args);
 if (method.hasRowBounds()) { RowBounds rowBounds = method.extractRowBounds(args);
 sqlSession.select(command.getName(), param, rowBounds, method.extractResultHandler(args));
 } else { sqlSession.select(command.getName(), param, method.extractResultHandler(args));
 }
 }
 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;
 }
 private  T  Cursor T  executeForCursor(SqlSession sqlSession, Object[] args) {
 Cursor T  result;
 Object param = method.convertArgsToSqlCommandParam(args);
 if (method.hasRowBounds()) { RowBounds rowBounds = method.extractRowBounds(args);
 result = sqlSession. T selectCursor(command.getName(), param, rowBounds);
 } else { result = sqlSession. T selectCursor(command.getName(), param);
 }
 return result;
 }
 private  E  Object convertToDeclaredCollection(Configuration config, List E  list) { Object collection = config.getObjectFactory().create(method.getReturnType());
 MetaObject metaObject = config.newMetaObject(collection);
 metaObject.addAll(list);
 return collection;
 }
 @SuppressWarnings(unchecked)
 private  E  Object convertToArray(List E  list) { Class ?  arrayComponentType = method.getReturnType().getComponentType();
 Object array = Array.newInstance(arrayComponentType, list.size());
 if (arrayComponentType.isPrimitive()) { for (int i = 0; i   list.size(); i++) { Array.set(array, i, list.get(i));
 }
 return array;
 } else { return list.toArray((E[])array);
 }
 }
 private  K, V  Map K, V  executeForMap(SqlSession sqlSession, Object[] args) {
 Map K, V  result;
 Object param = method.convertArgsToSqlCommandParam(args);
 if (method.hasRowBounds()) { RowBounds rowBounds = method.extractRowBounds(args);
 result = sqlSession. K, V selectMap(command.getName(), param, method.getMapKey(), rowBounds);
 } else { result = sqlSession. K, V selectMap(command.getName(), param, method.getMapKey());
 }
 return result;
 }
 public static class ParamMap V  extends HashMap String, V  {
 private static final long serialVersionUID = -2212268410512043556L;
 @Override
 public V get(Object key) { if (!super.containsKey(key)) { throw new BindingException( Parameter   + key +   not found. Available parameters are   + keySet());
 }
 return super.get(key);
 }
 }
 public static class SqlCommand {
 private final String name;
 private final SqlCommandType type;
 public SqlCommand(Configuration configuration, Class ?  mapperInterface, Method method) { final String methodName = method.getName();
 final Class ?  declaringClass = method.getDeclaringClass();
 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);
 }
 }
 }
 public String getName() {
 return name;
 }
 public SqlCommandType getType() {
 return type;
 }
 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;
 }
 for (Class ?  superInterface : mapperInterface.getInterfaces()) { if (declaringClass.isAssignableFrom(superInterface)) {
 MappedStatement ms = resolveMappedStatement(superInterface, methodName,
 declaringClass, configuration);
 if (ms != null) {
 return ms;
 }
 }
 }
 return null;
 }
 }
 public static class MethodSignature {
 private final boolean returnsMany;
 private final boolean returnsMap;
 private final boolean returnsVoid;
 private final boolean returnsCursor;
 private final Class ?  returnType;
 private final String mapKey;
 private final Integer resultHandlerIndex;
 private final Integer rowBoundsIndex;
 private final ParamNameResolver paramNameResolver;
 public MethodSignature(Configuration configuration, Class ?  mapperInterface, Method method) { Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
 if (resolvedReturnType instanceof Class ?) { this.returnType = (Class ?) resolvedReturnType;
 } else if (resolvedReturnType instanceof ParameterizedType) { this.returnType = (Class ?) ((ParameterizedType) resolvedReturnType).getRawType();
 } else { this.returnType = method.getReturnType();
 }
 this.returnsVoid = void.class.equals(this.returnType);
 this.returnsMany = (configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray());
 this.returnsCursor = Cursor.class.equals(this.returnType);
 this.mapKey = getMapKey(method);
 this.returnsMap = (this.mapKey != null);
 this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
 this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
 this.paramNameResolver = new ParamNameResolver(configuration, method);
 }
 public Object convertArgsToSqlCommandParam(Object[] args) { return paramNameResolver.getNamedParams(args);
 }
 public boolean hasRowBounds() {
 return rowBoundsIndex != null;
 }
 public RowBounds extractRowBounds(Object[] args) { return hasRowBounds() ? (RowBounds) args[rowBoundsIndex] : null;
 }
 public boolean hasResultHandler() {
 return resultHandlerIndex != null;
 }
 public ResultHandler extractResultHandler(Object[] args) { return hasResultHandler() ? (ResultHandler) args[resultHandlerIndex] : null;
 }
 public String getMapKey() {
 return mapKey;
 }
 public Class ?  getReturnType() {
 return returnType;
 }
 public boolean returnsMany() {
 return returnsMany;
 }
 public boolean returnsMap() {
 return returnsMap;
 }
 public boolean returnsVoid() {
 return returnsVoid;
 }
 public boolean returnsCursor() {
 return returnsCursor;
 }
 private Integer getUniqueParamIndex(Method method, Class ?  paramType) {
 Integer index = null;
 final Class ? [] argTypes = method.getParameterTypes();
 for (int i = 0; i   argTypes.length; i++) { if (paramType.isAssignableFrom(argTypes[i])) { if (index == null) {
 index = i;
 } else { throw new BindingException(method.getName() +   cannot have multiple   + paramType.getSimpleName() +   parameters 
 }
 }
 }
 return index;
 }
 private String getMapKey(Method method) {
 String mapKey = null;
 if (Map.class.isAssignableFrom(method.getReturnType())) { final MapKey mapKeyAnnotation = method.getAnnotation(MapKey.class);
 if (mapKeyAnnotation != null) { mapKey = mapKeyAnnotation.value();
 }
 }
 return mapKey;
 }
 }

看完上述內(nèi)容是否對您有幫助呢?如果還想對相關(guān)知識(shí)有進(jìn)一步的了解或閱讀更多相關(guān)文章,請關(guān)注丸趣 TV 行業(yè)資訊頻道,感謝您對丸趣 TV 的支持。

向 AI 問一下細(xì)節(jié)

正文完
 
丸趣
版權(quán)聲明:本站原創(chuàng)文章,由 丸趣 2023-12-04發(fā)表,共計(jì)18067字。
轉(zhuǎn)載說明:除特殊說明外本站除技術(shù)相關(guān)以外文章皆由網(wǎng)絡(luò)搜集發(fā)布,轉(zhuǎn)載請注明出處。
評(píng)論(沒有評(píng)論)
主站蜘蛛池模板: 乌海市| 沁源县| 乐平市| 高唐县| 宝清县| 罗城| 阿鲁科尔沁旗| 东辽县| 清水河县| 新安县| 筠连县| 同德县| 民乐县| 湘阴县| 绥棱县| 遵义县| 新安县| 拉萨市| 乌鲁木齐县| 达拉特旗| 元朗区| 建昌县| 武汉市| 靖西县| 武威市| 高密市| 四平市| 大新县| 梧州市| 沽源县| 德江县| 新源县| 凤阳县| 海南省| 正镶白旗| 珲春市| 长海县| 云霄县| 九龙坡区| 奈曼旗| 郑州市|