数据库连接池&DBUtils_agg

数据库连接池

示例1

 抽取工具类

package com.atguigu.util;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import javax.sql.DataSource;

import com.mchange.v2.c3p0.ComboPooledDataSource;

/**
 * JDBC 的工具类
 * 
 * 其中包含: 获取数据库连接, 关闭数据库资源等方法.
 */
public class JDBCTools {
    
    //处理数据库事务的
    //提交事务
    public static void commit(Connection connection){
        if(connection != null){
            try {
                connection.commit();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
    
    //回滚事务
    public static void rollback(Connection connection){
        if(connection != null){
            try {
                connection.rollback();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
    
    //开始事务
    public static void beginTx(Connection connection){
        if(connection != null){
            try {
                connection.setAutoCommit(false);
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
    
    private static DataSource dataSource = null;

    //数据库连接池应只被初始化一次. 
    static{
        dataSource = new ComboPooledDataSource("helloc3p0");
    }
    
    public static Connection getConnection() throws Exception {
        return dataSource.getConnection();
    }

    public static void releaseDB(ResultSet resultSet, Statement statement,
            Connection connection) {

        if (resultSet != null) {
            try {
                resultSet.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if (statement != null) {
            try {
                statement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if (connection != null) {
            try {
                //数据库连接池的 Connection 对象进行 close 时
                //并不是真的进行关闭, 而是把该数据库连接会归还到数据库连接池中. 
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

}
View Code

 

连接池DBCP&C3P0

package com.atguigu.jdbc;

import java.beans.PropertyVetoException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

import javax.sql.DataSource;

import org.apache.commons.dbcp.BasicDataSource;
import org.apache.commons.dbcp.BasicDataSourceFactory;
import org.junit.Test;

import com.mchange.v2.c3p0.ComboPooledDataSource;

public class TestJDBC9 {
    
    /**
     * C3P0连接池
     * @throws PropertyVetoException
     * @throws SQLException
     */
    @Test
    public void testC3p0() throws PropertyVetoException, SQLException {
        ComboPooledDataSource cpds = new ComboPooledDataSource();
        cpds.setDriverClass( "com.mysql.jdbc.Driver" ); //loads the jdbc driver            
        cpds.setJdbcUrl( "jdbc:mysql:///hm_study" );
        cpds.setUser("root");                                  
        cpds.setPassword("tiger");   
        
        System.out.println(cpds.getConnection()); 
    }
    
    /**C3P0连接池
     * 1. 创建 c3p0-config.xml 文件, 参考帮助文档中 Appendix B: Configuation Files 的内容
     * 2. 创建 ComboPooledDataSource 实例;
     *         DataSource dataSource = new ComboPooledDataSource("helloc3p0");  
     * 3. 从 DataSource 实例中获取数据库连接. 
     */
    @Test
    public void testC3p02() throws Exception{
        DataSource dataSource = 
                new ComboPooledDataSource("helloc3p0");  
        
        System.out.println(dataSource.getConnection()); 
        
        ComboPooledDataSource comboPooledDataSource = 
                (ComboPooledDataSource) dataSource;
        System.out.println(comboPooledDataSource.getMaxStatements()); 
    }
    
    
    /**DBCP连接池
     * 使用 DBCP 数据库连接池
     * 1. 加入 jar 包(2 个jar 包). 依赖于 Commons Pool
     * 2. 创建数据库连接池
     * 3. 为数据源实例指定必须的属性
     * 4. 从数据源中获取数据库连接
     * 测试最大连接数量
     * @throws SQLException 
     */
    @Test
    public void testDBCP() throws SQLException{
        final BasicDataSource dataSource = new BasicDataSource();
        
        //2. 为数据源实例指定必须的属性
        dataSource.setUsername("root");
        dataSource.setPassword("tiger");
        dataSource.setUrl("jdbc:mysql:///hm_study");
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        
        //3. 指定数据源的一些可选的属性.
        //1). 指定数据库连接池中初始化连接数的个数
        dataSource.setInitialSize(5);
        
        //2). 指定最大的连接数: 同一时刻可以同时向数据库申请的连接数
        dataSource.setMaxActive(5);
        
        //3). 指定小连接数: 在数据库连接池中保存的最少的空闲连接的数量 
        dataSource.setMinIdle(2);
        
        //4).等待数据库连接池分配连接的最长时间. 单位为毫秒. 超出该时间将抛出异常. 
        dataSource.setMaxWait(1000 * 5);
        
        //4. 从数据源中获取数据库连接
        Connection connection = dataSource.getConnection();
        System.out.println(connection.getClass()); 
        
        connection = dataSource.getConnection();
        System.out.println(connection.getClass()); 
        
        connection = dataSource.getConnection();
        System.out.println(connection.getClass()); 
        
        connection = dataSource.getConnection();
        System.out.println(connection.getClass()); 
        
        Connection connection2 = dataSource.getConnection();
        System.out.println(">" + connection2.getClass()); 
        
        new Thread(){
            public void run() {
                Connection conn;
                try {
                    conn = dataSource.getConnection();
                    System.out.println(conn.getClass()); 
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            };
        }.start();
        
        try {
            Thread.sleep(5500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        connection2.close();
    }
    
    
    /**DBCP连接池
     * 1. 加载 dbcp 的 properties 配置文件: 配置文件中的键需要来自 BasicDataSource的属性.
     * 2. 调用 BasicDataSourceFactory 的 createDataSource 方法创建 DataSource实例
     * 3. 从 DataSource 实例中获取数据库连接. 
     */
    @Test
    public void testDBCP2() throws Exception{
        Properties properties = new Properties();
        InputStream inStream = getClass().getClassLoader().getResourceAsStream("dbcp.properties");
        properties.load(inStream);
        
        DataSource dataSource = BasicDataSourceFactory.createDataSource(properties);
        
        System.out.println(dataSource.getConnection()); 
        
        BasicDataSource basicDataSource = (BasicDataSource) dataSource;
        
        System.out.println(basicDataSource.getMaxWait()); 
    }
    
}
View Code

 

c3p0-config.xml

c3p0-config.xml

 

dbcp.properties

username=root
password=1230
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql:///hm_study

initialSize=10
maxActive=50
minIdle=5
maxWait=5000
dbcp.properties

 

 

 示例2

批量插入数据

package com.atguigu.jdbc;

import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;

import org.junit.Test;

import com.atguigu.util.JDBCTools;

public class TestJDBC10 {
    
    /**
     * 批量插入数据
     */
    @Test
    public void testBatch(){
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        String sql = null;
        
        try {
            connection = JDBCTools.getConnection();
            JDBCTools.beginTx(connection);
            sql = "INSERT INTO customers VALUES(?,?,?)";
            preparedStatement = connection.prepareStatement(sql);
            Date date = new Date(new java.util.Date().getTime());
            
            long begin = System.currentTimeMillis();
            for(int i = 0; i < 100000; i++){
                preparedStatement.setInt(1, i + 1);
                preparedStatement.setString(2, "name_" + i);
                preparedStatement.setDate(3, date);
                
                //"积攒" SQL 
                preparedStatement.addBatch();
                
                //当 "积攒" 到一定程度, 就统一的执行一次. 并且清空先前 "积攒" 的 SQL
                if((i + 1) % 300 == 0){
                    preparedStatement.executeBatch();
                    preparedStatement.clearBatch();
                }
            }
            
            //若总条数不是批量数值的整数倍, 则还需要再额外的执行一次. 
            if(100000 % 300 != 0){
                preparedStatement.executeBatch();
                preparedStatement.clearBatch();
            }
            
            long end = System.currentTimeMillis();
            
            System.out.println("Time: " + (end - begin)); //569
            
            JDBCTools.commit(connection);
        } catch (Exception e) {
            e.printStackTrace();
            JDBCTools.rollback(connection);
        } finally{
            JDBCTools.releaseDB(null, preparedStatement, connection);
        }
    }
}
View Code

 

 

 DBUtils

 示例3

 利用DBUtils执行CRUD操作,对其中的数据记录进行封装

package com.atguigu.jdbc;

import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.apache.commons.dbutils.QueryLoader;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.ResultSetHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.MapHandler;
import org.apache.commons.dbutils.handlers.MapListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import org.junit.Test;

import com.atguigu.domain.User;
import com.atguigu.util.JDBCTools;

/**
 * 测试DBUtils工具类
 * 
 * @author Dell
 *
 */
public class TestJDBC11 {
    /**
     * 测试 QueryRunner 类的 update 方法 该方法可用于 INSERT, UPDATE 和 DELETE
     */
    @Test
    public void test1() {
        // 创建QueryRunner 核心类对象
        QueryRunner queryRunner = new QueryRunner();
        String sql = "insert into user(username,password) values(?,?)";
        Connection conn = null;
        try {
            conn = JDBCTools.getConnection();
            queryRunner.update(conn, sql, "time", "computer");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCTools.releaseDB(null, null, conn);
        }
    }

    /**
     * 推演(可忽略) 测试 QueryRunner 的 query 方法
     */
    @SuppressWarnings("all")
    @Test
    public void test2() {
        String sql = "select id,username,password from user";
        Connection conn = null;
        QueryRunner queryRunner = new QueryRunner();

        try {
            conn = JDBCTools.getConnection();

            Object object = queryRunner.query(conn, sql, new ResultSetHandler() {
                @Override
                public Object handle(ResultSet rs) throws SQLException {
                    List<User> users = new ArrayList<>();
                    while (rs.next()) {
                        int id = rs.getInt(1);
                        String username = rs.getString(2);
                        String password = rs.getString(3);
                        User user = new User(id, username, password);
                        users.add(user);
                    }
                    return users;
                }
            });

            System.out.println(object);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCTools.releaseDB(null, null, conn);
        }
    }

    /**
     * 查询多条记录 BeanListHandler 测试 ResultSetHandler 的 BeanListHandler 实现类
     * BeanListHandler: 把结果集转为一个 Bean 的 List. 该 Bean 的类型在创建 BeanListHandler 对象时传入:
     * 
     * new BeanListHandler<>(Customer.class)
     */
    @Test
    public void test3() {
        String sql = "select id,username,password from user";
        QueryRunner queryRunner = new QueryRunner();
        Connection conn = null;
        try {
            conn = JDBCTools.getConnection();
            List<User> list = queryRunner.query(conn, sql, new BeanListHandler<>(User.class));
            for (User user : list) {
                System.out.println(user);
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCTools.releaseDB(null, null, conn);
        }
    }

    /**
     * MapHandler 查询一条记录,并把数据表中的字段和值,放入Map集合中
     */
    @Test
    public void test4() {
        String sql = "select id,username,password from user where id=?";
        QueryRunner queryRunner = new QueryRunner();
        Connection conn = null;
        try {
            conn = JDBCTools.getConnection();
            Map<String, Object> map = queryRunner.query(conn, sql, new MapHandler(), 1);
            System.out.println(map);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCTools.releaseDB(null, null, conn);
        }
    }

    /**
     * MapListHandler 查询多条记录,并把数据表中的字段和值封装Map集合对象,然后放入List集合中
     */
    @Test
    public void test5() {
        String sql = "select id,username,password from user";
        QueryRunner queryRunner = new QueryRunner();
        Connection conn = null;

        try {
            conn = JDBCTools.getConnection();
            List<Map<String, Object>> list = queryRunner.query(conn, sql, new MapListHandler());
            for (Map<String, Object> map : list) {
                System.out.println(map);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCTools.releaseDB(null, null, conn);
        }
    }

    /**
     * ScalarHandler 查询一条记录中某个字段的值
     */
    @Test
    public void test6() {
        String sql = "select username from user where id=?";
        QueryRunner queryRunner = new QueryRunner();
        Connection conn = null;
        try {
            conn = JDBCTools.getConnection();
            Object username = queryRunner.query(conn, sql, new ScalarHandler(), 2);
            System.out.println(username);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCTools.releaseDB(null, null, conn);
        }
    }

    /**
     * 加载SQL脚本文件,执行其中的SQL语句
     * 
     * QueryLoader可以用来加载存放着 SQL 语句的资源文件. 使用该类可以把 SQL 语句外置化到一个资源文件中. 以提供更好的解耦
     * @throws IOException 
     */
    @Test
    public void test7() throws IOException {
        // / 代表类路径的根目录.
        Map<String, String> sqls = QueryLoader.instance().load("/sql.properties");
        String updateSql = sqls.get("UPDATE_USER");
        System.out.println(updateSql);
    }
    
    
    
    /**
     * 1. ResultSetHandler 的作用: QueryRunner 的 query 方法的返回值最终取决于 query 方法的
     * ResultHandler 参数的 hanlde 方法的返回值.
     * 
     * 2. BeanListHandler: 把结果集转为一个 Bean 的 List, 并返回. Bean 的类型在 创建 BeanListHanlder
     * 对象时以 Class 对象的方式传入. 可以适应列的别名来映射 JavaBean 的属性名: String sql = "SELECT id, name
     * customerName, email, birth " + "FROM customers WHERE id = ?";
     * 
     * BeanListHandler(Class<T> type)
     * 
     * 3. BeanHandler: 把结果集转为一个 Bean, 并返回. Bean 的类型在创建 BeanHandler 对象时以 Class
     * 对象的方式传入 BeanHandler(Class<T> type)
     * 
     * 4. MapHandler: 把结果集转为一个 Map 对象, 并返回. 若结果集中有多条记录, 仅返回 第一条记录对应的 Map 对象. Map 的键:
     * 列名(而非列的别名), 值: 列的值
     * 
     * 5. MapListHandler: 把结果集转为一个 Map 对象的集合, 并返回. Map 的键: 列名(而非列的别名), 值: 列的值
     * 
     * 6. ScalarHandler: 可以返回指定列的一个值或返回一个统计函数的值.
     */
}
View Code

 

 示例4

利用DBUtils抽取通用的DAO

package com.atguigu.dao;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;

import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;

import com.atguigu.jdbc.ReflectionUtils;

/**
 * 使用 QueryRunner 提供其具体的实现
 * @param <T>: 子类需传入的泛型类型. 
 */
public class JdbcDaoImpl<T> implements DAO<T> {

    private QueryRunner queryRunner = null;
    private Class<T> type;
    
    public JdbcDaoImpl() {
        queryRunner = new QueryRunner();
        type = ReflectionUtils.getSuperGenericType(getClass());
    }
    
    /**
     * 批量处理的方法
     * @param connection
     * @param sql
     * @param args: 填充占位符的 Object [] 类型的可变参数.
     * @throws SQLException 
     */
    @Override
    public void batch(Connection connection, String sql, Object[]... args) throws SQLException {
        queryRunner.batch(connection, sql, args);
    }
    
    /**
     * 返回数据表中某一个字段的值
     * @param connection
     * @param sql
     * @param args
     * @return
     * @throws SQLException 
     */
    @Override
    public <E> E getForValue(Connection connection, String sql, Object... args) throws SQLException {
        return (E) queryRunner.query(connection, sql, new ScalarHandler(), args);
    }
    
    /**
     * 返回 T 的一个集合
     * @param connection
     * @param sql
     * @param args
     * @return
     * @throws SQLException 
     */
    @Override 
    public List<T> getForList(Connection connection, String sql, Object... args) 
            throws SQLException {
        return queryRunner.query(connection, sql, new BeanListHandler<>(type), args);
    }

    /**
     * 返回一个 T 的对象
     * @param connection
     * @param sql
     * @param args
     * @return
     * @throws SQLException 
     */
    @Override
    public T get(Connection connection, String sql, Object... args) throws SQLException { 
        return queryRunner.query(connection, sql, new BeanHandler<>(type), args);
    }
    
    /**
     * INSRET, UPDATE, DELETE
     * @param connection: 数据库连接
     * @param sql: SQL 语句
     * @param args: 填充占位符的可变参数.
     * @throws SQLException 
     */
    @Override
    public void update(Connection connection, String sql, Object... args) throws SQLException {
        queryRunner.update(connection, sql, args);
    }
    
}
View Code

 

 ReflectionUtils

package com.atguigu.jdbc;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

/**
 * 反射的 Utils 函数集合
 * 提供访问私有变量, 获取泛型类型 Class, 提取集合中元素属性等 Utils 函数
 * @author Administrator
 *
 */
public class ReflectionUtils {

    
    /**
     * 通过反射, 获得定义 Class 时声明的父类的泛型参数的类型
     * 如: public EmployeeDao extends BaseDao<Employee, String>
     * @param clazz
     * @param index
     * @return
     */
    @SuppressWarnings("unchecked")
    public static Class getSuperClassGenricType(Class clazz, int index){
        Type genType = clazz.getGenericSuperclass();
        
        if(!(genType instanceof ParameterizedType)){
            return Object.class;
        }
        
        Type [] params = ((ParameterizedType)genType).getActualTypeArguments();
        
        if(index >= params.length || index < 0){
            return Object.class;
        }
        
        if(!(params[index] instanceof Class)){
            return Object.class;
        }
        
        return (Class) params[index];
    }
    
    /**
     * 通过反射, 获得 Class 定义中声明的父类的泛型参数类型
     * 如: public EmployeeDao extends BaseDao<Employee, String>
     * @param <T>
     * @param clazz
     * @return
     */
    @SuppressWarnings("unchecked")
    public static<T> Class<T> getSuperGenericType(Class clazz){
        return getSuperClassGenricType(clazz, 0);
    }
    
    /**
     * 循环向上转型, 获取对象的 DeclaredMethod
     * @param object
     * @param methodName
     * @param parameterTypes
     * @return
     */
    public static Method getDeclaredMethod(Object object, String methodName, Class<?>[] parameterTypes){
        
        for(Class<?> superClass = object.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()){
            try {
                //superClass.getMethod(methodName, parameterTypes);
                return superClass.getDeclaredMethod(methodName, parameterTypes);
            } catch (NoSuchMethodException e) {
                //Method 不在当前类定义, 继续向上转型
            }
            //..
        }
        
        return null;
    }
    
    /**
     * 使 filed 变为可访问
     * @param field
     */
    public static void makeAccessible(Field field){
        if(!Modifier.isPublic(field.getModifiers())){
            field.setAccessible(true);
        }
    }
    
    /**
     * 循环向上转型, 获取对象的 DeclaredField
     * @param object
     * @param filedName
     * @return
     */
    public static Field getDeclaredField(Object object, String filedName){
        
        for(Class<?> superClass = object.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()){
            try {
                return superClass.getDeclaredField(filedName);
            } catch (NoSuchFieldException e) {
                //Field 不在当前类定义, 继续向上转型
            }
        }
        return null;
    }
    
    /**
     * 直接调用对象方法, 而忽略修饰符(private, protected)
     * @param object
     * @param methodName
     * @param parameterTypes
     * @param parameters
     * @return
     * @throws InvocationTargetException 
     * @throws IllegalArgumentException 
     */
    public static Object invokeMethod(Object object, String methodName, Class<?> [] parameterTypes,
            Object [] parameters) throws InvocationTargetException{
        
        Method method = getDeclaredMethod(object, methodName, parameterTypes);
        
        if(method == null){
            throw new IllegalArgumentException("Could not find method [" + methodName + "] on target [" + object + "]");
        }
        
        method.setAccessible(true);
        
        try {
            return method.invoke(object, parameters);
        } catch(IllegalAccessException e) {
            System.out.println("不可能抛出的异常");
        } 
        
        return null;
    }
    
    /**
     * 直接设置对象属性值, 忽略 private/protected 修饰符, 也不经过 setter
     * @param object
     * @param fieldName
     * @param value
     */
    public static void setFieldValue(Object object, String fieldName, Object value){
        Field field = getDeclaredField(object, fieldName);
        
        if (field == null)
            throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + object + "]");
        
        makeAccessible(field);
        
        try {
            field.set(object, value);
        } catch (IllegalAccessException e) {
            System.out.println("不可能抛出的异常");
        }
    }
    
    /**
     * 直接读取对象的属性值, 忽略 private/protected 修饰符, 也不经过 getter
     * @param object
     * @param fieldName
     * @return
     */
    public static Object getFieldValue(Object object, String fieldName){
        Field field = getDeclaredField(object, fieldName);
        
        if (field == null)
            throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + object + "]");
        
        makeAccessible(field);
        
        Object result = null;
        
        try {
            result = field.get(object);
        } catch (IllegalAccessException e) {
            System.out.println("不可能抛出的异常");
        }
        
        return result;
    }
}
View Code

 

 示例5(忽略)

调用存储过程

    /**
     * 如何使用 JDBC 调用存储在数据库中的函数或存储过程
     */
    @Test
    public void test1() {

        Connection connection = null;
        CallableStatement callableStatement = null;

        try {
            connection = JDBCTools.getConnection();

            // 1. 通过 Connection 对象的 prepareCall()
            // 方法创建一个 CallableStatement 对象的实例.
            // 在使用 Connection 对象的 preparedCall() 方法时,
            // 需要传入一个 String 类型的字符串, 该字符串用于指明如何调用存储过程.
            String sql = "{?= call sum_salary(?, ?)}";
            callableStatement = connection.prepareCall(sql);

            // 2. 通过 CallableStatement 对象的 
            //reisterOutParameter() 方法注册 OUT 参数.
            callableStatement.registerOutParameter(1, Types.NUMERIC);
            callableStatement.registerOutParameter(3, Types.NUMERIC);
            
            // 3. 通过 CallableStatement 对象的 setXxx() 方法设定 IN 或 IN OUT 参数. 若想将参数默认值设为
            // null, 可以使用 setNull() 方法.
            callableStatement.setInt(2, 80);
            
            // 4. 通过 CallableStatement 对象的 execute() 方法执行存储过程
            callableStatement.execute();
            
            // 5. 如果所调用的是带返回参数的存储过程, 
            //还需要通过 CallableStatement 对象的 getXxx() 方法获取其返回值.
            double sumSalary = callableStatement.getDouble(1);
            long empCount = callableStatement.getLong(3);
            
            System.out.println(sumSalary);
            System.out.println(empCount);
            
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCTools.releaseDB(null, callableStatement, connection);
        }


    }
View Code

 

 

 

zwy

posted @ 2019-04-15 16:16  zwyk  阅读(54)  评论(0)    收藏  举报