zoukankan      html  css  js  c++  java
  • MyBatis系列目录--5. MyBatis一级缓存和二级缓存(redis实现)

    转载请注明出处哈:http://carlosfu.iteye.com/blog/2238662


    0. 相关知识:
    查询缓存:绝大数系统主要是读多写少。
    缓存作用:减轻数据库压力,提供访问速度。

      

    1. 一级缓存测试用例

    (1) 默认开启,不需要有什么配置

    (2) 示意图


     

    (3) 测试代码

    Java代码  收藏代码
    1. package com.sohu.tv.cache;  
    2. import org.apache.ibatis.session.SqlSession;  
    3. import org.junit.After;  
    4. import org.junit.Before;  
    5. import org.junit.Test;  
    6. import com.sohu.tv.bean.Player;  
    7. import com.sohu.tv.mapper.PlayerDao;  
    8. import com.sohu.tv.test.mapper.BaseTest;  
    9. /** 
    10.  * 一级缓存测试 
    11.  *  
    12.  * @author leifu 
    13.  * @Date 2015-8-3 
    14.  * @Time 下午9:51:00 
    15.  */  
    16. public class FirstCacheTest extends BaseTest {  
    17.     private SqlSession sqlSession;  
    18.     private SqlSession sqlSessionAnother;  
    19.   
    20.       
    21.     @Before  
    22.     public void before() {  
    23.         sqlSession = sessionFactory.openSession(false);  
    24.         sqlSessionAnother = sessionFactory.openSession(false);  
    25.     }  
    26.     @After  
    27.     public void after() {  
    28.         sqlSession.close();  
    29.         sqlSessionAnother.close();  
    30.     }  
    31.     @Test  
    32.     public void test1() throws Exception {  
    33.         PlayerDao playerDao = sqlSession.getMapper(PlayerDao.class);  
    34.         Player player = playerDao.getPlayerById(1);  
    35.         System.out.println(player);  
    36.           
    37.         playerDao = sqlSession.getMapper(PlayerDao.class);  
    38.         player = playerDao.getPlayerById(1);  
    39.         System.out.println(player);  
    40.           
    41.         playerDao = sqlSessionAnother.getMapper(PlayerDao.class);  
    42.         player = playerDao.getPlayerById(1);  
    43.         System.out.println(player);  
    44.           
    45.     }  
    46.       
    47.     @Test  
    48.     public void test2() throws Exception {  
    49.         PlayerDao playerDao = sqlSession.getMapper(PlayerDao.class);  
    50.         Player player = playerDao.getPlayerById(1);  
    51.         System.out.println(player);  
    52.           
    53.         //1. session清除或者提交  
    54. //        sqlSession1.commit();  
    55. //        sqlSession.clearCache();  
    56.           
    57.         //2. 增删改查  
    58. //        playerDao.savePlayer(new Player(-1, "abcd", 13));  
    59. //        playerDao.updatePlayer(new Player(4, "abcd", 13));  
    60.         playerDao.deletePlayer(4);  
    61.           
    62.         player = playerDao.getPlayerById(1);  
    63.         System.out.println(player);  
    64.           
    65.     }  
    66.       
    67.       
    68. }  

    2、二级缓存(自带 PerpetualCache)

    (0) 示意图



     

    (1) 二级缓存需要开启

    总配置文件中,二级缓存也是开启的,不需要设置

    Xml代码  收藏代码
    1. <setting name="cacheEnabled" value="true"/>  

    mapper级别的cache需要开启,在对应的mapper.xml写入

    Xml代码  收藏代码
    1. <!--开启本mapper的二级缓存-->  
    2. <cache/>  

    (2) 实体类在二级缓存中需要进行序列化,所以所有实体类需要实现Serializable 

    (3) 示例:

    Java代码  收藏代码
    1. package com.sohu.tv.cache;  
    2. import org.apache.ibatis.session.SqlSession;  
    3. import org.junit.After;  
    4. import org.junit.Before;  
    5. import org.junit.Test;  
    6. import com.sohu.tv.bean.Player;  
    7. import com.sohu.tv.mapper.PlayerDao;  
    8. import com.sohu.tv.test.mapper.BaseTest;  
    9. /** 
    10.  * 二级缓存测试 
    11.  *  
    12.  * @author leifu 
    13.  * @Date 2015-8-3 
    14.  * @Time 下午10:10:34 
    15.  */  
    16. public class SecondCacheTest extends BaseTest {  
    17.     private SqlSession sqlSession1 = sessionFactory.openSession();  
    18.       
    19.     private SqlSession sqlSession2 = sessionFactory.openSession();  
    20.       
    21.     private SqlSession sqlSession3 = sessionFactory.openSession();  
    22.       
    23.     private PlayerDao playerDao1;  
    24.       
    25.     private PlayerDao playerDao2;  
    26.       
    27.     private PlayerDao playerDao3;  
    28.       
    29.     @Before  
    30.     public void before() {  
    31.         sqlSession1 = sessionFactory.openSession(false);  
    32.         sqlSession2 = sessionFactory.openSession(false);  
    33.         sqlSession3 = sessionFactory.openSession(false);  
    34.           
    35.         playerDao1 = sqlSession1.getMapper(PlayerDao.class);  
    36.         playerDao2 = sqlSession2.getMapper(PlayerDao.class);  
    37.         playerDao3 = sqlSession3.getMapper(PlayerDao.class);  
    38.     }  
    39.     @After  
    40.     public void after() {  
    41.         sqlSession1.close();  
    42.         sqlSession2.close();  
    43.         sqlSession3.close();  
    44.     }  
    45.        
    46.     @Test  
    47.     public void test1() throws Exception {  
    48.         int targetId = 1;  
    49.           
    50.         //session1 查询并提交  
    51.         Player player1 = playerDao1.getPlayerById(targetId);  
    52.         System.out.println("player1: " + player1);  
    53.         sqlSession1.commit();  
    54.           
    55.         //session2 命中后,更新并提交清空缓存  
    56.         Player player2 = playerDao2.getPlayerById(targetId);  
    57.         System.out.println("player2: " + player2);  
    58.         player2.setAge(15);  
    59.         playerDao2.update(player2);  
    60.         sqlSession2.commit();  
    61.           
    62.         //session3 不命中  
    63.         Player player3 = playerDao3.getPlayerById(targetId);  
    64.         System.out.println("player3: " + player3);  
    65.     }  
    66.       
    67.     @Test  
    68.     public void test2() throws Exception {  
    69.         int one = 1;  
    70.         int two = 2;  
    71.           
    72.         //session1 查询并提交  
    73.         Player player1 = playerDao1.getPlayerById(one);  
    74.         playerDao1.getPlayerById(two);  
    75.         System.out.println("player1: " + player1);  
    76.         sqlSession1.commit();  
    77.           
    78.         //session2 命中后,更新并提交清空缓存  
    79.         Player player2 = playerDao2.getPlayerById(one);  
    80.         System.out.println("player2: " + player2);  
    81.         player2.setAge(15);  
    82.         playerDao2.updatePlayer(player2);  
    83.         sqlSession2.commit();  
    84.           
    85.         //session3 不命中  
    86.         Player player3 = playerDao3.getPlayerById(two);  
    87.         System.out.println("player3: " + player3);  
    88.     }  
    89.       
    90.       
    91. }  

    (4) 重要日志:

    Java代码  收藏代码
    1. 22:24:37.191 [main] DEBUG com.sohu.tv.mapper.PlayerDao - Cache Hit Ratio [com.sohu.tv.mapper.PlayerDao]: 0.0  
    2. 22:24:37.196 [main] DEBUG o.a.i.t.jdbc.JdbcTransaction - Opening JDBC Connection  
    3. 22:24:37.460 [main] DEBUG o.a.i.d.pooled.PooledDataSource - Created connection 1695520324.  
    4. 22:24:37.460 [main] DEBUG o.a.i.t.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@650f9644]  
    5. 22:24:37.463 [main] DEBUG c.s.t.mapper.PlayerDao.getPlayerById - ==> Preparing: select id,name,age from players where id=?   
    6. 22:24:37.520 [main] DEBUG c.s.t.mapper.PlayerDao.getPlayerById - ==> Parameters: 1(Integer)  
    7. 22:24:37.541 [main] DEBUG c.s.t.mapper.PlayerDao.getPlayerById - <== Total: 1  
    8. player1: Player [id=1, name=kaka, age=60]  
    9. 22:24:37.549 [main] DEBUG o.a.i.t.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@650f9644]  
    10. 22:24:37.549 [main] DEBUG o.a.i.t.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@650f9644]  
    11. 22:24:37.549 [main] DEBUG o.a.i.d.pooled.PooledDataSource - Returned connection 1695520324 to pool.  
    12. 22:29:13.203 [main] DEBUG com.sohu.tv.mapper.PlayerDao - Cache Hit Ratio [com.sohu.tv.mapper.PlayerDao]: 0.5  
    13. player3: Player [id=1, name=kaka, age=60]  
    14. 22:29:13.204 [main] DEBUG o.a.i.t.jdbc.JdbcTransaction - Opening JDBC Connection  
    15. 22:29:13.204 [main] DEBUG o.a.i.d.pooled.PooledDataSource - Checked out connection 1695520324 from pool.  
    16. 22:29:13.204 [main] DEBUG o.a.i.t.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@650f9644]  
    17. 22:29:13.205 [main] DEBUG c.s.tv.mapper.PlayerDao.updatePlayer - ==> Preparing: update players set name=?,age=? where id=?   
    18. 22:29:13.207 [main] DEBUG c.s.tv.mapper.PlayerDao.updatePlayer - ==> Parameters: kaka(String), 60(Integer), 1(Integer)  
    19. 22:29:13.208 [main] DEBUG c.s.tv.mapper.PlayerDao.updatePlayer - <== Updates: 1  
    20. 22:29:13.210 [main] DEBUG o.a.i.t.jdbc.JdbcTransaction - Committing JDBC Connection [com.mysql.jdbc.JDBC4Connection@650f9644]  
    21. 22:29:13.210 [main] DEBUG o.a.i.t.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@650f9644]  
    22. 22:29:13.211 [main] DEBUG o.a.i.t.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@650f9644]  
    23. 22:29:13.211 [main] DEBUG o.a.i.d.pooled.PooledDataSource - Returned connection 1695520324 to pool.  
    24. 22:29:13.211 [main] DEBUG com.sohu.tv.mapper.PlayerDao - Cache Hit Ratio [com.sohu.tv.mapper.PlayerDao]: 0.3333333333333333  
    25. 22:29:13.211 [main] DEBUG o.a.i.t.jdbc.JdbcTransaction - Opening JDBC Connection  
    26. 22:29:13.212 [main] DEBUG o.a.i.d.pooled.PooledDataSource - Checked out connection 1695520324 from pool.  
    27. 22:29:13.212 [main] DEBUG o.a.i.t.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@650f9644]  
    28. 22:29:13.212 [main] DEBUG c.s.t.mapper.PlayerDao.getPlayerById - ==> Preparing: select id,name,age from players where id=?   
    29. 22:29:13.213 [main] DEBUG c.s.t.mapper.PlayerDao.getPlayerById - ==> Parameters: 1(Integer)  
    30. 22:29:13.214 [main] DEBUG c.s.t.mapper.PlayerDao.getPlayerById - <== Total: 1  
    31. player2: Player [id=1, name=kaka, age=60]  
    32. 22:29:13.215 [main] DEBUG o.a.i.t.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@650f9644]  
    33. 22:29:13.216 [main] DEBUG o.a.i.t.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@650f9644]  
    34. 22:29:13.216 [main] DEBUG o.a.i.d.pooled.PooledDataSource - Returned connection 1695520324 to pool.  
     

    3、二级缓存(Redis版)

    (1) redis使用一个简单的单点实例作为数据源:

    引入jedis pom依赖:

    Xml代码  收藏代码
    1. <jedis.version>2.8.0</jedis.version>  
    2. <protostuff.version>1.0.8</protostuff.version>  
    3. <dependency>  
    4.     <groupId>redis.clients</groupId>  
    5.     <artifactId>jedis</artifactId>  
    6.     <version>${jedis.version}</version>  
    7. </dependency>  
    8. <dependency>  
    9.     <groupId>com.dyuproject.protostuff</groupId>  
    10.     <artifactId>protostuff-runtime</artifactId>  
    11.     <version>${protostuff.version}</version>  
    12. </dependency>  
    13.   
    14. <dependency>  
    15.     <groupId>com.dyuproject.protostuff</groupId>  
    16.     <artifactId>protostuff-core</artifactId>  
    17.     <version>${protostuff.version}</version>  
    18. </dependency>  

    jedis获取工具(使用jedispool)

    Java代码  收藏代码
    1. package com.sohu.tv.redis;  
    2. import org.apache.commons.pool2.impl.GenericObjectPoolConfig;  
    3. import org.slf4j.Logger;  
    4. import org.slf4j.LoggerFactory;  
    5. import redis.clients.jedis.JedisPool;  
    6. /** 
    7.  * jedisPool获取工具 
    8.  *  
    9.  * @author leifu 
    10.  * @Date 2015年8月4日 
    11.  * @Time 上午9:01:45 
    12.  */  
    13. public class RedisStandAloneUtil {  
    14.     private final static Logger logger = LoggerFactory.getLogger(RedisStandAloneUtil.class);  
    15.     /** 
    16.      * jedis连接池 
    17.      */  
    18.     private static JedisPool jedisPool;  
    19.        
    20.     /** 
    21.      * redis-host 
    22.      */  
    23.     private final static String REDIS_HOST = "10.10.xx.xx";  
    24.        
    25.     /** 
    26.      * redis-port 
    27.      */  
    28.     private final static int REDIS_PORT = 6384;  
    29.        
    30.     static {  
    31.         try {  
    32.             jedisPool = new JedisPool(new GenericObjectPoolConfig(), REDIS_HOST, REDIS_PORT);  
    33.         } catch (Exception e) {  
    34.             logger.error(e.getMessage(), e);  
    35.         }  
    36.     }  
    37.     public static JedisPool getJedisPool() {  
    38.         return jedisPool;  
    39.     }  
    40.         
    41.     public static void main(String[] args) {  
    42.         System.out.println(RedisStandAloneUtil.getJedisPool().getResource().info());  
    43.     }  
    44. }  

    (2) 如果自己实现mybatis的二级缓存,需要实现org.apache.ibatis.cache.Cache接口,已经实现的有如下:


    序列化相关工具代码:

    Java代码  收藏代码
    1. package com.sohu.tv.redis.serializable;  
    2.   
    3.   
    4.   
    5. import com.dyuproject.protostuff.LinkedBuffer;  
    6. import com.dyuproject.protostuff.ProtostuffIOUtil;  
    7. import com.dyuproject.protostuff.Schema;  
    8. import com.dyuproject.protostuff.runtime.RuntimeSchema;  
    9.   
    10. import java.util.concurrent.ConcurrentHashMap;  
    11.   
    12. public class ProtostuffSerializer {  
    13.   
    14.     private static ConcurrentHashMap<Class<?>, Schema<?>> cachedSchema = new ConcurrentHashMap<Class<?>, Schema<?>>();  
    15.   
    16.     public <T> byte[] serialize(final T source) {  
    17.         VO<T> vo = new VO<T>(source);  
    18.   
    19.         final LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);  
    20.         try {  
    21.             final Schema<VO> schema = getSchema(VO.class);  
    22.             return serializeInternal(vo, schema, buffer);  
    23.         } catch (final Exception e) {  
    24.             throw new IllegalStateException(e.getMessage(), e);  
    25.         } finally {  
    26.             buffer.clear();  
    27.         }  
    28.     }  
    29.   
    30.     public <T> T deserialize(final byte[] bytes) {  
    31.         try {  
    32.             Schema<VO> schema = getSchema(VO.class);  
    33.             VO vo = deserializeInternal(bytes, schema.newMessage(), schema);  
    34.             if (vo != null && vo.getValue() != null) {  
    35.                 return (T) vo.getValue();  
    36.             }  
    37.         } catch (final Exception e) {  
    38.             throw new IllegalStateException(e.getMessage(), e);  
    39.         }  
    40.         return null;  
    41.     }  
    42.   
    43.     private <T> byte[] serializeInternal(final T source, final Schema<T> schema, final LinkedBuffer buffer) {  
    44.         return ProtostuffIOUtil.toByteArray(source, schema, buffer);  
    45.     }  
    46.   
    47.     private <T> T deserializeInternal(final byte[] bytes, final T result, final Schema<T> schema) {  
    48.         ProtostuffIOUtil.mergeFrom(bytes, result, schema);  
    49.         return result;  
    50.     }  
    51.   
    52.     private static <T> Schema<T> getSchema(Class<T> clazz) {  
    53.         @SuppressWarnings("unchecked")  
    54.         Schema<T> schema = (Schema<T>) cachedSchema.get(clazz);  
    55.         if (schema == null) {  
    56.             schema = RuntimeSchema.createFrom(clazz);  
    57.             cachedSchema.put(clazz, schema);  
    58.         }  
    59.         return schema;  
    60.     }  
    61.   
    62. }  
    Java代码  收藏代码
    1. package com.sohu.tv.redis.serializable;  
    2.   
    3.   
    4. import java.io.Serializable;  
    5.   
    6. public class VO<T> implements Serializable {  
    7.   
    8.     private T value;  
    9.   
    10.     public VO(T value) {  
    11.         this.value = value;  
    12.     }  
    13.   
    14.     public VO() {  
    15.     }  
    16.   
    17.     public T getValue() {  
    18.         return value;  
    19.     }  
    20.   
    21.     @Override  
    22.     public String toString() {  
    23.         return "VO{" +  
    24.                 "value=" + value +  
    25.                 '}';  
    26.     }  
    27. }  

     
     

    Redis需要自己来实现,代码如下:

    Java代码  收藏代码
    1. package com.sohu.tv.redis;  
    2. import java.util.concurrent.locks.ReadWriteLock;  
    3. import java.util.concurrent.locks.ReentrantReadWriteLock;  
    4. import org.apache.ibatis.cache.Cache;  
    5. import org.slf4j.Logger;  
    6. import org.slf4j.LoggerFactory;  
    7. import redis.clients.jedis.Jedis;  
    8. import redis.clients.jedis.serializable.ProtostuffSerializer;  
    9. /** 
    10.  * mybatis redis实现 
    11.  *  
    12.  * @author leifu 
    13.  * @Date 2015年8月4日 
    14.  * @Time 上午9:12:37 
    15.  */  
    16. public class MybatisRedisCache implements Cache {  
    17.     private static Logger logger = LoggerFactory.getLogger(MybatisRedisCache.class);  
    18.     private String id;  
    19.     private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();  
    20.     private final ProtostuffSerializer protostuffSerializer = new ProtostuffSerializer();  
    21.     public MybatisRedisCache(final String id) {  
    22.         if (logger.isInfoEnabled()) {  
    23.             logger.info("============ MybatisRedisCache id {} ============", id);  
    24.         }  
    25.         if (id == null) {    
    26.             throw new IllegalArgumentException("Cache instances require an ID");    
    27.         }    
    28.         this.id = id;    
    29.     }   
    30.        
    31.     @Override  
    32.     public String getId() {  
    33.         return this.id;  
    34.     }  
    35.     @Override  
    36.     public int getSize() {  
    37.         Jedis jedis = null;  
    38.         int size = -1;  
    39.         try {  
    40.             jedis = RedisStandAloneUtil.getJedisPool().getResource();  
    41.             size = Integer.valueOf(jedis.dbSize().toString());  
    42.         } catch (Exception e) {  
    43.             logger.error(e.getMessage(), e);  
    44.         } finally {  
    45.             if (jedis != null) {  
    46.                 jedis.close();  
    47.             }  
    48.         }  
    49.         return size;  
    50.     }  
    51.     @Override  
    52.     public void putObject(Object key, Object value) {  
    53.         if (logger.isInfoEnabled()) {  
    54.             logger.info("============ putObject key: {}, value: {} ============", key, value);  
    55.         }  
    56.         Jedis jedis = null;  
    57.         try {  
    58.             jedis = RedisStandAloneUtil.getJedisPool().getResource();  
    59.             byte[] byteKey = protostuffSerializer.serialize(key);  
    60.             byte[] byteValue = protostuffSerializer.serialize(value);  
    61.             jedis.set(byteKey, byteValue);  
    62.         } catch (Exception e) {  
    63.             logger.error(e.getMessage(), e);  
    64.         } finally {  
    65.             if (jedis != null) {  
    66.                 jedis.close();  
    67.             }  
    68.         }  
    69.     }  
    70.     @Override  
    71.     public Object getObject(Object key) {  
    72.         if (logger.isInfoEnabled()) {  
    73.             logger.info("============ getObject key: {}============", key);  
    74.         }  
    75.         Object object = null;  
    76.         Jedis jedis = null;  
    77.         try {  
    78.             jedis = RedisStandAloneUtil.getJedisPool().getResource();  
    79.             byte[] bytes = jedis.get(protostuffSerializer.serialize(key));  
    80.             if (bytes != null) {  
    81.                 object = protostuffSerializer.deserialize(bytes);  
    82.             }  
    83.         } catch (Exception e) {  
    84.             logger.error(e.getMessage(), e);  
    85.         } finally {  
    86.             if (jedis != null) {  
    87.                 jedis.close();  
    88.             }  
    89.         }  
    90.         return object;  
    91.     }  
    92.     @Override  
    93.     public Object removeObject(Object key) {  
    94.         if (logger.isInfoEnabled()) {  
    95.             logger.info("============ removeObject key: {}============", key);  
    96.         }  
    97.         String result = "success";  
    98.         Jedis jedis = null;  
    99.         try {  
    100.             jedis = RedisStandAloneUtil.getJedisPool().getResource();  
    101.             jedis.del(String.valueOf(key));  
    102.         } catch (Exception e) {  
    103.             logger.error(e.getMessage(), e);  
    104.         } finally {  
    105.             if (jedis != null) {  
    106.                 jedis.close();  
    107.             }  
    108.         }  
    109.         return result;  
    110.     }  
    111.     @Override  
    112.     public void clear() {  
    113.         if (logger.isInfoEnabled()) {  
    114.             logger.info("============ start clear cache ============");  
    115.         }  
    116.         String result = "fail";  
    117.         Jedis jedis = null;  
    118.         try {  
    119.             jedis = RedisStandAloneUtil.getJedisPool().getResource();  
    120.             result = jedis.flushAll();  
    121.         } catch (Exception e) {  
    122.             logger.error(e.getMessage(), e);  
    123.         } finally {  
    124.             if (jedis != null) {  
    125.                 jedis.close();  
    126.             }  
    127.         }  
    128.         if (logger.isInfoEnabled()) {  
    129.             logger.info("============ end clear cache result is {}============", result);  
    130.         }  
    131.     }  
    132.     @Override  
    133.     public ReadWriteLock getReadWriteLock() {  
    134.         return readWriteLock;  
    135.     }  
    136. }  

    (3) mapper配置中加入自定义redis二级缓存:

    Xml代码  收藏代码
    1. <cache type="com.sohu.tv.redis.MybatisRedisCache"/>  

    (4) 单元测试同第二节

  • 相关阅读:
    Android数据存储之IO
    UVA
    【源代码剖析】tornado-memcached-sessions —— Tornado session 支持的实现(二)
    杀毒与免杀技术具体解释之二:特征码定位-工具及原理
    C++ 訪问控制权限图解
    新辰:传统行业进军互联网 怎样颠覆网络获得新生?
    【二】注入框架RoboGuice使用:(Your First View Injection)
    POJ 1741 Tree 树形DP(分治)
    技术单词
    活动合集 | 2017微软技术暨生态大会 英雄讲师召集令
  • 原文地址:https://www.cnblogs.com/dogdogwang/p/7144844.html
Copyright © 2011-2022 走看看