最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Mybatis兩級緩存可能導(dǎo)致的問題詳細(xì)講解

 更新時間:2025年07月16日 10:10:21   作者:趙丙雙  
MyBatis作為一款流行的持久層框架,在數(shù)據(jù)處理和緩存管理方面有著廣泛的應(yīng)用,其中,二級緩存作為其重要的緩存機制,對于提升應(yīng)用性能具有重要作用,但同時也存在一些潛在的問題,這篇文章主要介紹了Mybatis兩級緩存可能導(dǎo)致問題的相關(guān)資料,需要的朋友可以參考下

兩級緩存簡介

一級緩存 localCache

效果

一級緩存是 session 或者說事務(wù)級別的,只在同一事務(wù)內(nèi)有效,在以相同的參數(shù)執(zhí)行多次同一個查詢方法時,實際只會在第一次時進(jìn)行數(shù)據(jù)庫 select 查詢,后續(xù)會直接從緩存中返回。如下:

@GetMapping("/test1")
@Transactional(rollbackFor = Exception.class)
public String test1() {
    log.info("---------------------------------------------------------------------------");

    Teacher teacher1 = teacherMapper.selectByPrimaryKey("01");
    log.info("teacher1: {}, hashCode: {} \n", teacher1, System.identityHashCode(teacher1));

    Teacher teacher2 = teacherMapper.selectByPrimaryKey("01");
    log.info("teacher2: {}, hashCode: {} \n", teacher2, System.identityHashCode(teacher2));

    Student student1 = studentMapper.selectByPrimaryKey("01");
    log.info("student1: {}, hashCode: {} \n", student1, System.identityHashCode(student1));

    Student student2 = studentMapper.selectByPrimaryKey("01");
    log.info("student2: {}, hashCode: {} \n", student2, System.identityHashCode(student2));

    return "test1";
}

下圖中是調(diào)用了兩次的輸出,從第一次輸出中可以看出查詢 teacher、student 的 SQL 都只打印了一遍,說明分別只執(zhí)行了一次數(shù)據(jù)庫查詢。且兩個 teacher、student 的 hashCode 分別是一樣的,說明是同一個對象。第二次調(diào)用的輸出和第一次的相似,都重新執(zhí)行了一次數(shù)據(jù)庫查詢,說明一級緩存只在同一事務(wù)內(nèi)有效,不能跨事務(wù)。

如果事務(wù)中有 DML 語句的話,會清空所有的緩存。不管 DML 語句中的表是否與緩存中的表相同,都會無條件的清空所有緩存。

@GetMapping("/test2")
@Transactional(rollbackFor = Exception.class)
public String test2() {
    log.info("---------------------------------------------------------------------------");

    Teacher teacher1 = teacherMapper.selectByPrimaryKey("01");
    log.info("teacher1: {}, hashCode: {} \n", teacher1, System.identityHashCode(teacher1));

    Teacher teacher2 = teacherMapper.selectByPrimaryKey("01");
    log.info("teacher2: {}, hashCode: {} \n", teacher2, System.identityHashCode(teacher2));

    Student student1 = studentMapper.selectByPrimaryKey("01");
    log.info("student1: {}, hashCode: {} \n", student1, System.identityHashCode(student1));

    Student student2 = studentMapper.selectByPrimaryKey("01");
    log.info("student2: {}, hashCode: {} \n", student2, System.identityHashCode(student2));

    insertScore();
    log.info("insertScore\n");

    Teacher teacher3 = teacherMapper.selectByPrimaryKey("01");
    log.info("teacher3: {}, hashCode: {} \n", teacher3, System.identityHashCode(teacher3));

    Student student3 = studentMapper.selectByPrimaryKey("01");
    log.info("student3: {}, hashCode: {} \n", student3, System.identityHashCode(student3));

    return "test2";
}

private void insertScore() {
    Score score = new Score();
    score.setSId("08");
    score.setCId("01");
    score.setSScore(100);
    scoreMapper.insert(score);
}

前半部分的輸出與 test1 相同,當(dāng)插入 score 后再次查詢 teacher、student 時,打印了 SQL,且與上半部分的 hashCode 不相同,說明執(zhí)行 insertScore 時緩存被全部清空了。

開關(guān)

一級緩存在 mybatis 源碼中被稱為 localCache,springboot 可使用 mybatis.configuration.local-cache-scope 來控制其行為,默認(rèn)值是 session,也就是事務(wù)級別的緩存??蓪⑵渑渲脼?statement 以關(guān)閉 localCache 功能。

下面是將 mybatis.configuration.local-cache-scope 配置為 statement 后再執(zhí)行 test1 的輸出,每次都打印了 SQL,且 hashCode 都不一樣,說明緩存沒有起作用。

二級緩存

二級緩存是 namespace 級別的(或者說是 Mapper 級別的,如下 xml),與一級緩存類似,在以相同的參數(shù)執(zhí)行多次同一個查詢方法時,實際只會在第一次時進(jìn)行數(shù)據(jù)庫 select 查詢,后續(xù)會直接從緩存中返回。如果執(zhí)行同一個 namespace 中的 DML 語句(比如 delete、insert、update)的話,會清空 namespace 相關(guān)的所有 select 的緩存。

<?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="org.example.mybatis.mapper.StudentMapper">
    <select>
        ...
    </select>
    <delete>
        ...
    </delete>
    <insert>
        ...
    </insert>
    ...
</mapper>

二級緩存由 mybatis.configuration.cache-enabled 控制,默認(rèn)為 true。除此之外還需要在要開啟二級緩存的 Mapper.xml 中添加 <cache/> 表情才能開啟對應(yīng) Mapper 的二級緩存。

下面是在關(guān)閉一級緩存,且只開啟 StudentMapper.xml 二級緩存的情況下的測試:

application.properties

...
mybatis.configuration.local-cache-scope=statement
mybatis.configuration.cache-enabled=true

StudentMapper.xml

<?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="org.example.mybatis.mapper.StudentMapper">
  <resultMap id="BaseResultMap" type="org.example.mybatis.entity.Student">
    <!--@mbg.generated-->
    <!--@Table student-->
    <id column="s_id" jdbcType="VARCHAR" property="sId" />
    <result column="s_name" jdbcType="VARCHAR" property="sName" />
    <result column="s_birth" jdbcType="VARCHAR" property="sBirth" />
    <result column="s_sex" jdbcType="VARCHAR" property="sSex" />
  </resultMap>

  <cache readOnly="true"/>
  ...
</mapper>

這是執(zhí)行了兩次 test1 的輸出:

由于沒有開啟 TeacherMapper.xml 的二級緩存,所以每次查詢 teacher 都打印了 SQL,且 hashCode 不相同,說明 teacher 的緩存沒起作用。

第 ① 次查詢 student 打印了 SQL,直接查詢了數(shù)據(jù)庫,這是正常的,因為此時緩存中沒有數(shù)據(jù)。但第 ② 次查詢 student 也沒有走緩存,也直接查詢了數(shù)據(jù)庫,這是為啥?是因為二級緩存不是在執(zhí)行完 select 后立即填充的,是要等到事務(wù)提交之后才會填充緩存。

從最后幾行的輸出能看出最后兩次查詢 student 確實走了緩存,并且還打印了緩存命中率。這是因為第一次調(diào)用 test1 結(jié)束后事務(wù)提交了,數(shù)據(jù)被填充到了緩存里。

測試無事務(wù)時的效果

test3 是在 test1 的基礎(chǔ)上刪除了 @Transactional 注解

@GetMapping("/test3")
public String test3() {
    log.info("---------------------------------------------------------------------------");

    Teacher teacher1 = teacherMapper.selectByPrimaryKey("01");
    log.info("teacher1: {}, hashCode: {} \n", teacher1, System.identityHashCode(teacher1));

    Teacher teacher2 = teacherMapper.selectByPrimaryKey("01");
    log.info("teacher2: {}, hashCode: {} \n", teacher2, System.identityHashCode(teacher2));

    Student student1 = studentMapper.selectByPrimaryKey("01");
    log.info("student1: {}, hashCode: {} \n", student1, System.identityHashCode(student1));

    Student student2 = studentMapper.selectByPrimaryKey("01");
    log.info("student2: {}, hashCode: {} \n", student2, System.identityHashCode(student2));

    return "test3";
}

teacher 的緩存還是沒起作用。

只有第一次查詢 student 時直接查詢了數(shù)據(jù)庫,其他三次都命中了緩存。

兩級緩存可能導(dǎo)致的問題

分布式環(huán)境下查詢到過期數(shù)據(jù)

假設(shè)支付服務(wù) A 有兩個實例 A1、A2,負(fù)載均衡采用輪訓(xùn)策略,第一次查詢余額訪問 A1 返回 100000,第二次消費 100 訪問 A2 返回余額 99900,第三次查詢余額訪問 A1 返回的還是 100000。如下的模擬

application.properties

...
mybatis.configuration.local-cache-scope=statement
mybatis.configuration.cache-enabled=true

AccountMapper.xml

<?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="org.example.mybatis.mapper.AccountMapper">
  ...
  <cache readOnly="true"/>

  <update id="pay">
    update account
    set balance = balance - #{amount}
    where id = #{id}
  </update>
</mapper>
@GetMapping("/balance")
public Long queryBalance() {
    return accountMapper.selectByPrimaryKey(1).getBalance();
}

@GetMapping("/pay")
public Long pay() {
    accountMapper.pay(1, 100);
    return accountMapper.selectByPrimaryKey(1).getBalance();
}

分別在 8080、8081 啟動兩個實例,如下輸出:

要解決這個問題很簡單,就是不使用緩存,比如 mybatis.configuration.cache-enabled=false 或者將 AccountMapper.xml 中的 <cache/> 標(biāo)簽刪除。

事務(wù)隔離級別失效

讀已提交失效

在開發(fā)中經(jīng)常有這種場景:先判斷是否存在,如果不存在再插入。這種判斷再插入的操作不是原子的,多線程會有問題,所以需要加鎖保證操作的安全性。在讀多寫少的場景中,會使用 double check 來盡可能的減少用鎖的使用,偽代碼如下:

def doubleCheck(id) {
    o = select(id);
    if (o == null) {
        lock.lock();
        try {
            o = select(id);
            if (o == null) {
                o = create(id);
            }
        } finally {
            lock.unlock();
        }
    }
    return o;
}

創(chuàng)建 Account 的測試

application.properties

還原成默認(rèn)值,且刪除 AccountMapper.xml 中的 <cache/> 標(biāo)簽,用以關(guān)閉 AccountMapper 的二級緩存。

...
mybatis.configuration.local-cache-scope=session
mybatis.configuration.cache-enabled=true

注意這里使用的隔離級別為讀已提交

@PutMapping("/accounts/{id}")
// double check 需要使用讀已提交隔離級別才能讀到最新數(shù)據(jù)
@Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED)
public Account createAccount(@PathVariable("id") Integer id) throws InterruptedException {
    Account account = accountMapper.selectByPrimaryKey(id);

    // 等待多個請求到達(dá)
    TimeUnit.SECONDS.sleep(5);

    // 如果賬戶不存在,需要加分布式鎖后進(jìn)行 double check,防止并發(fā)問題
    if (account == null) {
        RLock lock = redissonClient.getLock("lock:account:create:" + id);
        boolean locked = lock.tryLock(10, TimeUnit.SECONDS);
        if (locked) {
            try {
                account = accountMapper.selectByPrimaryKey(id);
                if (account == null) {
                    // 創(chuàng)建賬戶
                    account = createAccount0(id);
                }
            } finally {
                lock.unlock();
            }
        }
    }
    return account;
}

public Account createAccount0(Integer id) {
    Account account = new Account();
    account.setId(id);
    account.setBalance(0L);
    accountMapper.insertSelective(account);
    
    // 操作其他表
    return account;
}

同時發(fā)起兩個 Put 請求 http://localhost:8080/accounts/2。一個正常返回,另一個在 insert 時報錯 Duplicate entry ‘2’ for key ‘account.PRIMARY’,說明讀已提交的隔離級別沒起作用,第二個請求沒有讀到最新的數(shù)據(jù)。

一級緩存實際起到了類似可重復(fù)讀的效果。

兩個請求(線程分別為 nio-8080-exec-3、nio-8080-exec-4)執(zhí)行了 3 次(第一個請求 1 次,第二個請求 2 次) accountMapper.selectByPrimaryKey(id),但每個線程都只打印了 1 次 SQL,說明第二個請求的第 2 次查詢走了緩存,導(dǎo)致沒有查詢到第一個請求插入的最新數(shù)據(jù),才導(dǎo)致的后來的報錯。

解決辦法

  1. 最簡單辦法就是修改 mybatis.configuration.local-cache-scope=statement,直接關(guān)閉一級緩存。

  2. 直接去掉 @Transactional 注解肯定能解決問題,但如果 createAccount0 方法中操作多張表的話,如果部分失敗事務(wù)將無法回滾。

  3. 不能直接去掉 @Transactional 注解,但可以縮小事務(wù)的范圍,將兩次查詢放到事務(wù)外,只將 createAccount0 方法放到事務(wù)內(nèi)。

    @Lazy
    @Autowired
    private TestController self;
    
    @PutMapping("/accounts/{id}")
    public Account createAccount(@PathVariable("id") Integer id) throws InterruptedException {
        Account account = accountMapper.selectByPrimaryKey(id);
    
        // 等待多個請求到達(dá)
        TimeUnit.SECONDS.sleep(5);
    
        // 如果賬戶不存在,需要加分布式鎖后進(jìn)行 double check,防止并發(fā)問題
        if (account == null) {
            RLock lock = redissonClient.getLock("lock:account:create:" + id);
            boolean locked = lock.tryLock(10, TimeUnit.SECONDS);
            if (locked) {
                try {
                    account = accountMapper.selectByPrimaryKey(id);
                    if (account == null) {
                        // 創(chuàng)建賬戶
                        account = self.createAccount0(id);
                    }
                } finally {
                    lock.unlock();
                }
            }
        }
        return account;
    }
    
    @Transactional(rollbackFor = Exception.class)
    public Account createAccount0(Integer id) {
        Account account = new Account();
        account.setId(id);
        account.setBalance(0L);
        accountMapper.insertSelective(account);
    
        // 操作其他表
        return account;
    }
    
  4. 如果外層有其他事務(wù)的話,由于一級緩存只有在同一個事務(wù)中才會生效,所以可以將兩個 accountMapper.selectByPrimaryKey(id) 拆分到不同的事務(wù)中,propagation 必須是 Propagation.REQUIRES_NEW。

    @Lazy
    @Autowired
    private TestController self;
    
    @PutMapping("/accounts/{id}")
    public Account createAccount(@PathVariable("id") Integer id) throws InterruptedException {
        Account account = self.getAccount0(id);
    
        // 等待多個請求到達(dá)
        TimeUnit.SECONDS.sleep(5);
    
        // 如果賬戶不存在,需要加分布式鎖后進(jìn)行 double check,防止并發(fā)問題
        if (account == null) {
            RLock lock = redissonClient.getLock("lock:account:create:" + id);
            boolean locked = lock.tryLock(10, TimeUnit.SECONDS);
            if (locked) {
                try {
                    account = self.getAccount0(id);
                    if (account == null) {
                        // 創(chuàng)建賬戶
                        //
                        account = self.createAccount0(id);
                    }
                } finally {
                    lock.unlock();
                }
            }
        }
        return account;
    }
    
    // 讀已提交 REQUIRES_NEW
    @Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRES_NEW)
    public Account getAccount0(Integer id) {
        return accountMapper.selectByPrimaryKey(id);
    }
    

讀未提交失效

同樣的由于一級緩存的存在,讀未提交也讀不到最新的未提交數(shù)據(jù)。

讀未提交 查詢 Account 的測試

application.properties

還原成默認(rèn)值,且刪除 AccountMapper.xml 中的 <cache/> 標(biāo)簽,用以關(guān)閉 AccountMapper 的二級緩存。

...
mybatis.configuration.local-cache-scope=session
mybatis.configuration.cache-enabled=true
@GetMapping("/accounts/{id}")
// 讀未提交
@Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_UNCOMMITTED)
public Account getAccount(@PathVariable("id") Integer id) throws InterruptedException {
    Account account = accountMapper.selectByPrimaryKey(id);
    log.info("account1: {}\n", account);

    // 若不存在,則等待幾秒再查
    if (account == null) {
        TimeUnit.SECONDS.sleep(10);
    }
    account = accountMapper.selectByPrimaryKey(id);
    log.info("account2: {}\n", account);
    return account;
}

@PutMapping("/accounts/{id}")
@Transactional(rollbackFor = Exception.class)
public Account createAccount(@PathVariable("id") Integer id) throws InterruptedException {

    Account account = new Account();
    account.setId(id);
    account.setBalance(0L);
    accountMapper.insertSelective(account);
    log.info("insert account: {}\n", account);

    // 延遲提交事務(wù)
    TimeUnit.SECONDS.sleep(15);

    // 操作其他表
    return account;
}

先請求 getAccount 再請求 createAccount,從輸出中可以看出,在使用讀未提交的情況下,account2 依舊為 null,走了緩存,導(dǎo)致讀未提交失效。

解決辦法

  1. 最簡單辦法就是修改 mybatis.configuration.local-cache-scope=statement,直接關(guān)閉一級緩存。

  2. 由于一級緩存只有在同一個事務(wù)中才會生效,所以可以將兩個 accountMapper.selectByPrimaryKey(id) 拆分到不同的事務(wù)中,propagation 必須是 Propagation.REQUIRES_NEW。

    @Lazy
    @Autowired
    private TestController self;
    
    @GetMapping("/accounts/{id}")
    public Account getAccount(@PathVariable("id") Integer id) throws InterruptedException {
        Account account = self.getAccount0(id);
        log.info("account1: {}\n", account);
    
        // 若不存在,則等待幾秒再查
        if (account == null) {
            TimeUnit.SECONDS.sleep(10);
        }
        account = self.getAccount0(id);
        log.info("account2: {}\n", account);
        return account;
    }
    
    // 讀未提交 REQUIRES_NEW
    @Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_UNCOMMITTED, propagation = Propagation.REQUIRES_NEW)
    public Account getAccount0(Integer id) {
        return accountMapper.selectByPrimaryKey(id);
    }
    

總結(jié)

一級緩存是事務(wù)級別的,實際起到了類似可重復(fù)讀的效果,而且比可重復(fù)讀的性能更好,因為多次查詢的話不會請求數(shù)據(jù)庫了。在事務(wù)隔離級別是可重復(fù)讀時使用一級緩存能提高性能。但就因為其類似可重復(fù)讀的效果會導(dǎo)致其他的隔離級別失效。要解決失效的問題,最簡單方式就是關(guān)閉一級緩存,但這樣會損失性能。另一個解決辦法是將需要使用其他隔離級別的方法使用 propagation = Propagation.REQUIRES_NEW 拆分到新的事務(wù)中。如果是讀已提交的話可通過縮小事務(wù)范圍的方式解決。

一級緩存是事務(wù)級別的,緩存的生命周期較短,但二級緩存是 namespace (Mapper)級別的,生命周期可能很長,在分布式、多實例環(huán)境中很容易查詢到過期的數(shù)據(jù),導(dǎo)致其他問題。我個人建議在分布式、多實例環(huán)境中應(yīng)該設(shè)置 mybatis.configuration.cache-enabled=false 來關(guān)閉二級緩存,從根源上杜絕這種問題。

到此這篇關(guān)于Mybatis兩級緩存可能導(dǎo)致問題的文章就介紹到這了,更多相關(guān)Mybatis兩級緩存問題內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • java獲取指定開始時間與結(jié)束時間之間的所有日期

    java獲取指定開始時間與結(jié)束時間之間的所有日期

    這篇文章主要為大家詳細(xì)介紹了java獲取指定開始時間與結(jié)束時間之間的所有日期,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-05-05
  • SpringBoot Redis批量存取數(shù)據(jù)的操作

    SpringBoot Redis批量存取數(shù)據(jù)的操作

    這篇文章主要介紹了SpringBoot Redis批量存取數(shù)據(jù)的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • java實現(xiàn)攝像頭截圖功能

    java實現(xiàn)攝像頭截圖功能

    這篇文章主要為大家詳細(xì)介紹了java實現(xiàn)攝像頭截圖功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-10-10
  • HashSet工作原理_動力節(jié)點Java學(xué)院整理

    HashSet工作原理_動力節(jié)點Java學(xué)院整理

    HashSet 底層采用 HashMap 來保存所有元素,因此 HashSet 的實現(xiàn)比較簡單。接下來通過本文給大家介紹HashSet工作原理_動力節(jié)點Java學(xué)院整理,需要的朋友可以參考下
    2017-04-04
  • 深入淺出講解Java集合之Collection接口

    深入淺出講解Java集合之Collection接口

    這篇文章主要介紹了深入淺出講解Java集合之Collection接口,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-09-09
  • java刪除數(shù)組中的某一個元素的方法

    java刪除數(shù)組中的某一個元素的方法

    下面小編就為大家?guī)硪黄猨ava刪除數(shù)組中的某一個元素的方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-01-01
  • 詳解java動態(tài)代理的2種實現(xiàn)方式

    詳解java動態(tài)代理的2種實現(xiàn)方式

    目前Java開發(fā)包中包含了對動態(tài)代理的支持,但是其實現(xiàn)只支持對接口的的實現(xiàn)。這篇文章主要介紹了詳解java動態(tài)代理的2種實現(xiàn)方式 ,有興趣的可以了解一下。
    2016-11-11
  • 淺談shiro的SecurityManager類結(jié)構(gòu)

    淺談shiro的SecurityManager類結(jié)構(gòu)

    下面小編就為大家?guī)硪黄獪\談shiro的SecurityManager類結(jié)構(gòu)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-07-07
  • Java生成pdf文件或jpg圖片的案例講解

    Java生成pdf文件或jpg圖片的案例講解

    這篇文章主要介紹了Java生成pdf文件或jpg圖片的案例講解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-01-01
  • Spring Boot開啟虛擬線程ScopedValue上下文傳遞的使用方式

    Spring Boot開啟虛擬線程ScopedValue上下文傳遞的使用方式

    本文給大家介紹了ScopedValue在Java虛擬線程中的應(yīng)用,解決了傳統(tǒng)ThreadLocal在大量短生命周期線程場景下的問題,本文結(jié)合實例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧
    2026-02-02

最新評論

扎兰屯市| 哈尔滨市| 扎兰屯市| 灵台县| 丹寨县| 桃江县| 明溪县| 凤城市| 龙南县| 广东省| 金华市| 资中县| 阳新县| 宜城市| 安吉县| 文登市| 丹凤县| 迁西县| 东辽县| 甘南县| 饶河县| 基隆市| 调兵山市| 丰镇市| 和平县| 金昌市| 遂川县| 利辛县| 米泉市| 平安县| 永福县| 盐源县| 凭祥市| 阿拉善盟| 桃江县| 涟源市| 屯留县| 科技| 三原县| 罗山县| 惠水县|