IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 大数据 -> JDBC和DBUtils工具 -> 正文阅读

[大数据]JDBC和DBUtils工具

JDBC和DBUtils工具

什么是JDBC?

在Web开发中,不可避免的要使用数据库来存储和管理数据。为了在Java语言中提供对数据库访问的支持,SUN公司于1996年提供了一套访问数据库的标准Java类库,即JDBC

JDBC的全称是Java数据库连接Java Database Connectivity),它是一套用于执行SQL语句的Java API。应用程序可通过这套API连接到关系型数据库,并使用SQL语句来完成对数据库中数据的查询、更新、新增和删除的操作。

不同种类的数据库(如MySQL、Oracle等)在其内部处理数据的方式是不同的,如果直接使用数据库厂商提供的访问接口操作数据库,应用程序的可移植性就会变得很差。例如,用户当前在程序中使用的是MySQL提供的接口操作数据库,如果换成Oracle数据库,则需要重新使用Oracle数据库提供的接口,这样代码的改动量会非常大。有了JDBC后,这种情况就不复存在了,因为它要求各个数据库厂商按照统一的规范来提供数据库驱动,而在程序中是由JDBC和具体的数据库驱动联系,所以用户就不必直接与底层的数据库交互,这使得代码的通用性更强。

使用JDBC之前,根据自己使用的数据库厂商,要先从对应的官网中下载对应的驱动jar包,配置到项目中。

JDBC 是sun公司提供的一套接口 接口都有调用者和实现者,面向接口调用,面向接口写实现类,都属于面向接口编程 。

为什么要面向接口编程: 为了解耦合,降低程序的耦合度,提高程序的扩展力。

img

应用程序使用JDBC访问数据库的方式如下图所示。

image-20211015124047111

从上图中可以看出,JDBC在应用程序与数据库之间起到了一个桥梁作用,当应用程序使用JDBC访问特定的数据库时,需要通过不同数据库驱动与不同的数据库进行连接,连接后即可对该数据库进行相应的操作。

JDBC常用API

Driver接口

Driver接口是所有JDBC驱动程序必须实现的接口,该接口专门提供给数据库厂商使用。需要注意的是,在编写JDBC程序时,必须要把所使用的数据库驱动程序或类库加载到项目的classpath中(这里指MySQL驱动JAR包)。

DriverManager类

DriverManager类用于加载JDBC驱动并且创建与数据库的连接。在DriverManager类中,定义了两个比较重要的静态方法,如下表所示。

image-20211015124235110

Connection接口

Connection接口代表Java程序和数据库的连接,只有获得该连接对象后才能访问数据库,并操作数据表。在Connection接口中,定义了一系列方法,其常用方法如下表所示。

image-20211015124314819

Statement接口

Statement接口用于执行静态的SQL语句,并返回一个结果对象,该接口的对象通过Connection实例的createStatement()方法获得。利用该对象把静态的SQL语句发送到数据库编译执行,然后返回数据库的处理结果。在Statement接口中,提供了3个常用的执行SQL语句的方法,具体如下表所示。

image-20211015124406272

PreparedStatement接口

Statement接口封装了JDBC执行SQL语句的方法,可以完成Java程序执行SQL语句的操作。然而在实际开发过程中往往需要将程序中的变量作为SQL语句的查询条件,而使用Statement接口操作这些SQL语句会过于繁琐,并且存在安全方面的问题。针对这一问题,JDBC API 中提供了扩展的PreparedStatement接口。

PreparedStatement是Statement的子接口,用于执行预编译的SQL语句。该接口扩展了带有参数SQL语句的执行操作,应用该接口中的SQL语句可以使用占位符“?”来代替其参数,然后通过setXxx()方法为SQL语句的参数赋值。在PreparedStatement接口中,提供了一些常用方法,具体如下

image-20211015124558786

需要注意的是,表中的setDate()方法可以设置日期内容,但参数Date的类型是java.sql.Date,而不是java.util.Date。

在通过setXxx()方法为SQL语句中的参数赋值时,可以通过输入参数的已定义SQL类型兼容的方法(例如,如果参数具有SQL类型为Integer,那么应该使用setInt 方法),也可以通过setObject()方法设置多种类型的输入参数。具体如下所示:

image-20211015124708746

ResultSet接口

ResultSet接口用于保存JDBC执行查询时返回的结果集,该结果集封装在一个逻辑表格中。在ResultSet接口内部有一个指向表格数据行的游标(或指针),ResultSet对象初始化时,游标在表格的第一行之前,调用next()方法可将游标移动到下一行。如果下一行没有数据,则返回false。在应用程序中经常使用next()方法作为while循环的条件来迭代ResultSet结果集。

ResultSet主要用于存储结果集,可以通过next()方法由前向后逐个获取结果集中的数据。

ResultSet接口中的常用方法如下表所示。

image-20211015124758346

从表中可以看出,ResultSet接口中定义了大量的getXxx()方法,而采用哪种getXxx()方法取决于字段的数据类型。程序既可以通过字段的名称来获取指定数据,也可以通过字段的索引来获取指定的数据,字段的索引是从1开始编号的。例如,数据表的第一列字段名为id,字段类型为int,那么既可以使用getInt(1)字段索引的方式获取该列的值,也可以使用getInt(“id”)字段名称的方式获取该列的值。

实现一个JDBC程序步骤

使用JDBC之前,要先去对应厂商的官网下载驱动jar包。

JDBC的使用一般可以按照以下六个步骤进行。

(1)加载并注册数据库驱动。(注册驱动)

也就是要告诉Java程序将要连接的是哪个品牌的数据库,我这里使用的是mysql。

虽然使用DriverManager.registerDriver(new com.mysql.jdbc.Driver())方法也可以完成注册,但此方式会使数据库驱动被注册两次。这是因为Driver类的源码中,已经在静态代码块中完成了数据库驱动的注册。所以,为了避免数据库驱动被重复注册,只需要在程序中使用Class.forName()方法加载驱动类即可。

//第一步:加载并注册驱动,这里有两种方式。
//第一种
Driver driver = new Driver();
DriverManager.registerDriver(driver);

//第二种  (比较常用)
Class.forName("com.mysql.jdbc.Driver")

(2)通过DriverManager获取数据库连接。(获取连接)

表示jvm和数据库之间的进程通道打开了,这属于进程之间的通信,使用之后一定要关闭。

//第二步:通过DriverManager获取数据库连接

String url = "jdbc:mysql://127.0.0.1:3306/databaseName";
String userName = "root";
String password = "123456";

Connection connection = DriverManager.getConnection(url,userName,password);

从上述代码可以看出,getConnection()方法中有3个参数,它们分别表示连接数据库的URL地址登录数据库的用户名密码。以MySQL数据库为例,其URL地址的书写格式如下:

image-20211017193814972

上面代码中,jdbc:mysql:是固定的写法,mysql指的是MySQL数据库。hostname指的是主机的名称(如果数据库在本机中,hostname可以为localhost或127.0.0.1;如果要连接的数据库在其他电脑上hostname为所要连接电脑的IP),port指的是连接数据库的端口号(MySQL端口号默认为3306),而databasename指的是MySQL中相应数据库的名称。

这里有个注意事项,如果使用的mysql数据库版本是8的话,url后面要加上

?useSSL=FALSE&serverTimezone=UTC,也就是

jdbc:mysql://127.0.0.1:3306/databaseName?useSSL=FALSE&serverTimezone=UTC

不加的话,程序会报异常错误。

(3)通过Connection对象获取Statement对象。(获取数据库操作对象(执行sql语句的对象))

Connection创建Statement的方式有如下三种:

  • createStatement():创建基本的Statement对象。
  • prepareStatement():创建PreparedStatement对象。
  • prepareCall():创建CallableStatement对象。

以创建基本的Statement对象为例,创建方式如下:

//第三步:通过Connection对象获取Statement对象
Statement statement = connection.createStatement();

(4)使用Statement执行SQL语句。(执行sql语句)

所有的Statement都有如下三种执行SQL语句的方法:

  • execute():可以执行任何SQL语句。
  • executeQuery():通常执行查询语句,执行后返回代表结果集的ResultSet对象。
  • executeUpdate():主要用于执行DML和DDL语句。执行DML语句,如 INSERT、UPDATE或DELETE时,返回受SQL语句影响的行数,执行DDL语句返回0。

以executeQuery()方法为例,其使用方式如下:

//第四步:执行SQL语句,获取结果集ResultSet
ResultSet resultSet = statement.executeQuery(sql);

(5)处理ResultSet查询结果集。(只有第四步执行的是select语句时才有第五步)

如果执行的SQL语句是查询语句,执行结果将返回一个ResultSet对象,该对象里保存了SQL语句查询的结果。程序可以通过操作该ResultSet对象来取出查询结果。

(6)关闭连接,释放资源。(java程序和数据库是进程之间的通信,使用之后一定要关闭)

每次操作数据库结束后都要关闭数据库连接,释放资源,包括关闭ResultSet、Statement和Connection等资源。

由于数据库资源非常宝贵,数据库允许的并发访问连接数量有限,因此,当数据库资源使用完毕后,一定要记得释放资源。为了保证资源的释放,在Java程序中,应该将最终必须要执行的操作放在finally代码块中。

example01:

数据库

create database mydb;
use mydb;

create table users(
	id int primary key auto_increment,
    name varchar(40),
    password varchar(40),
    email varchar(60),
    birthday date
)character set utf8 collate utf8_general_ci;


insert into users(name,password,email,birthday) values ('zs','123456','zs@sina.com','1978-12-12');
insert into users(name,password,email,birthday) values ('lisi','123456','lisi@sina.com','1943-03-12');
insert into users(name,password,email,birthday) values ('wangwu','123456','wangwu@sina.com','1999-09-12');

JDBC程序

package com.mcxfate.study.jdbc;


import java.sql.*;

/**
 * @Author: Mcxfate
 * 
 */
public class MyJDBC  {

   public static void main(String args[]) {

      Statement statement = null;
      ResultSet resultSet = null;
      Connection connection = null;

      //第一步:加载并注册驱动,这里有两种方式。
      //第一种
      //Driver driver = new Driver();
      //DriverManager.registerDriver(driver);


      try {
         //第二种 比较常用
         Class.forName("com.mysql.jdbc.Driver");


         //第二步:通过DriverManager获取数据库连接
         String url = "jdbc:mysql://127.0.0.1:3306/mydb?useSSL=FALSE&serverTimezone=UTC";
         String userName = "root";
         String password = "root";
         connection = DriverManager.getConnection(url,userName,password);

         //第三步:通过Connection对象获取Statement对象
         statement = connection.createStatement();

         //第四步:执行SQL语句,获取结果集ResultSet
         String sql = "select * from users";
         resultSet = statement.executeQuery(sql);

         //第五步:处理Result结果集
         System.out.println("id |  name  |   password  |  email   | birthday");
         while (resultSet.next()){
            int id = resultSet.getInt("id");
            String name = resultSet.getString("name");
            String pwd = resultSet.getString("password");
            String email = resultSet.getString("email");
            Date birthday = resultSet.getDate("birthday");
            System.out.println(id +"  |  "+name+"    | " + pwd  +" | " + email  + "  | " + birthday);
         }

      } catch (ClassNotFoundException | SQLException e) {

         e.printStackTrace();

      }finally {

         //第六步:关闭连接,释放资源
         if(resultSet != null){
            try {
               resultSet.close();
            } catch (SQLException e) {
               e.printStackTrace();
            }
            resultSet = null;
         }

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

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

      }


   }



}

执行结果如图:

image-20211017220743406

PreparedStatement对象

在上面的JDBC程序中,SQL语句的执行是通过Statement对象实现的。Statement对象每次执行SQL语句时,都会对其进行编译。当相同的SQL语句执行多次时,Statement对象就会使数据库频繁编译相同的SQL语句,从而降低数据库的访问效率。

为了解决上述问题,Statement提供了一个子类PreparedStatement。PreparedStatement对象可以对SQL语句进行预编译,预编译的信息会存储在PreparedStatement对象中。当相同的SQL语句再次执行时,程序会使用PreparedStatement对象中的数据,而不需要对SQL语句再次编译去查询数据库,这样就大大的提高了数据的访问效率。

example02:

package com.mcxfate.study.jdbc;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

/**
 * @Author: Mcxfate
 * 
 */
public class Example02 {

    public static void main(String[] args) {
        Connection connection = null;
        PreparedStatement preparedStatement = null;


        try {
            //加载数据库驱动
            Class.forName("com.mysql.jdbc.Driver");
            String url = "jdbc:mysql://localhost:3306/mydb?useSSL=FALSE&serverTimezone=UTC";
            String userName = "root";
            String password = "root";
            //获取连接
            connection = DriverManager.getConnection(url,userName,password);

            //获取操作对象
            String sql = "insert into users (name,password,email,birthday) " + " values (?,?,?,?)";
            preparedStatement = connection.prepareStatement(sql);

            //为SQL语句中的参数赋值
            preparedStatement.setString(1,"xiaoming");
            preparedStatement.setString(2,"123456");
            preparedStatement.setString(3,"xiaoyao@qq.com");
            preparedStatement.setString(4,"2021-05-12");

            //执行SQL
            int result = preparedStatement.executeUpdate();
            System.out.println(result);

        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        }finally {
            //释放资源
            if (preparedStatement != null) {
                try {
                    preparedStatement.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
                preparedStatement = null;
            }

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


        }

    }

}

ResultSet对象

ResultSet主要用于存储结果集,可以通过next()方法由前向后逐个获取结果集中的数据,如果想获取结果集中任意位置的数据,则需要在创建Statement对象时,设置两个ResultSet定义的常量,具体设置方式如下:

image-20211017225857298

在上述方式中,常量“Result.TYPE_SCROLL_INSENITIVE”表示结果集可滚动,常量“ResultSet.CONCUR_READ_ONLY”表示以只读形式打开结果集。

example03:

数据库表中的数据

image-20211018161532245

package com.mcxfate.study.jdbc;

import java.sql.*;

/**
 * @Author: Mcxfate
 * 
 */
public class Example03 {

    public static void main(String[] args) {
        Connection connection = null;
        Statement statement = null;

        try {
            Class.forName("com.mysql.jdbc.Driver");
            String url = "jdbc:mysql://localhost:3306/mydb?useSSL=FALSE&serverTimezone=UTC";
            String userName = "root";
            String password = "root";
            connection = DriverManager.getConnection(url,userName,password);

            String sql = "select * from users";
            //创建Statement对象并设置常量
            statement = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);

            //执行SQL语句
            ResultSet resultSet = statement.executeQuery(sql);

            System.out.println("第2条数据的name值为:");
            resultSet.absolute(2);     //将指针定位到结果集中第2行数据
            System.out.println(resultSet.getString("name"));
            System.out.println("第1条数据的name值为:");
            resultSet.beforeFirst();        //将指针定位到结果集中第1行数据之前
            resultSet.next();               //将指针向后滚动
            System.out.println(resultSet.getString("name"));
            System.out.println("第4条数据的name值为:");
            resultSet.afterLast();          //将指针定位到结果集中最后一条数据之后
            resultSet.previous();           //将指针向前滚动
            System.out.println(resultSet.getString("name"));


        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        }finally {
            //释放资源

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


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

        }
    }

}

运行结果

image-20211018161621789

实例1:JDBC完成数据的增删改查

1.创建JavaBean

package com.mcxfate.study.example;

import java.util.Date;

/**
 * @Author: Mcxfate
 * 
 */
//用户类
public class User {

    private int id;
    private String username;
    private String password;
    private String email;
    private Date birthday;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

}

2.创建工具类

package com.mcxfate.study.example;import java.sql.Connection;import java.sql.DriverManager;import java.sql.ResultSet;import java.sql.SQLException;import java.sql.Statement;/** * @Author: Mcxfate *  *///数据库相关操作的工具类public class JDBCUtils {    public static Connection getConnection() throws ClassNotFoundException, SQLException {        //注册数据库驱动        Class.forName("com.mysql.jdbc.Driver");        String url = "jdbc:mysql://localhost:3306/mydb?useSSL=FALSE&serverTimezone=UTC";        String username = "root";        String password = "root";        //获取连接对象        Connection connection = DriverManager.getConnection(url,username,password);        return connection;    }    //关闭数据库连接,释放资源    public static void release(Connection connection, Statement statement){        if (connection != null) {            try {                connection.close();            } catch (SQLException e) {                e.printStackTrace();            }            connection = null;        }        if (statement != null) {            try {                statement.close();            } catch (SQLException e) {                e.printStackTrace();            }            statement = null;        }    }    //重载release方法    public static void release(Connection connection, Statement statement, ResultSet resultSet){        if (resultSet != null) {            try {                resultSet.close();            } catch (SQLException e) {                e.printStackTrace();            }            resultSet = null;        }        release(connection,statement);    }}

3.创建DAO

package com.mcxfate.study.example;import java.sql.Connection;import java.sql.ResultSet;import java.sql.SQLException;import java.sql.Statement;import java.text.SimpleDateFormat;import java.util.ArrayList;/** * @Author: Mcxfate *  *///封装了对users表的添加、查询、删除和更新等操作的Daopublic class UserDao {    //添加用户的操作    public boolean insert(User user){        Connection connection = null;        Statement statement = null;        //ResultSet resultSet = null;        try {            //获取连接对象            connection = JDBCUtils.getConnection();            //获取操作对象            statement = connection.createStatement();            //时间对象转化为字符串            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");            String birthday = simpleDateFormat.format(user.getBirthday());            //发送sql语句            String sql = "insert into users(id,name,password,email,birthday)" +                    "values (" + user.getId()+", '"+ user.getUsername()+"', '"+                    user.getPassword()+"','"+                    user.getEmail()+"', '"+                    birthday+"' )";            System.out.println(sql);            int num = statement.executeUpdate(sql);            if(num > 0 ){                return true;            }            return false;        } catch (Exception e) {            e.printStackTrace();        }finally {            JDBCUtils.release(connection,statement);        }        return false;    }    //查询所有的user对象    public ArrayList<User> findAll(){        Connection connection = null;        Statement statement = null;        ResultSet resultSet = null;        ArrayList<User> list = new ArrayList<User>();        try {            connection = JDBCUtils.getConnection();            statement = connection.createStatement();            String sql = "select * from users";            resultSet = statement.executeQuery(sql);            //处理结果集            while (resultSet.next()){                User user = new User();                user.setId(resultSet.getInt("id"));                user.setUsername(resultSet.getString("name"));                user.setPassword(resultSet.getString("password"));                user.setEmail(resultSet.getString("email"));                user.setBirthday(resultSet.getDate("birthday"));                list.add(user);            }            return list;        } catch (ClassNotFoundException e) {            e.printStackTrace();        } catch (SQLException e) {            e.printStackTrace();        } finally {            JDBCUtils.release(connection,statement,resultSet);        }        return null;    }    //根据id查找指定的user    public User find(int id){        Connection connection = null;        Statement statement = null;        ResultSet resultSet = null;        try {            connection = JDBCUtils.getConnection();            statement = connection.createStatement();            String sql = "select * from users where id = "+id;            resultSet = statement.executeQuery(sql);            //处理结果集            while (resultSet.next()){                User user = new User();                user.setId(resultSet.getInt("id"));                user.setUsername(resultSet.getString("name"));                user.setPassword(resultSet.getString("password"));                user.setEmail(resultSet.getString("email"));                user.setBirthday(resultSet.getDate("birthday"));                return  user;            }            return null;        } catch (ClassNotFoundException e) {            e.printStackTrace();        } catch (SQLException e) {            e.printStackTrace();        } finally {            JDBCUtils.release(connection,statement,resultSet);        }        return null;    }    //删除用户    public boolean delete(int id){        Connection connection = null;        Statement statement = null;        //ResultSet resultSet = null;        try {            connection = JDBCUtils.getConnection();            statement = connection.createStatement();            String sql = "delete from users where id = "+ id;            int num = statement.executeUpdate(sql);            if(num > 0){                return true;            }            return false;        } catch (ClassNotFoundException e) {            e.printStackTrace();        } catch (SQLException e) {            e.printStackTrace();        } finally {            JDBCUtils.release(connection,statement);        }        return false;    }    //修改用户    public boolean update(User user){        Connection connection = null;        Statement statement = null;        //ResultSet resultSet = null;        try {            connection = JDBCUtils.getConnection();            statement = connection.createStatement();            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");            String birthday = sdf.format(user.getBirthday());            String sql = "update users set name = '" + user.getUsername()+"',password = '"+                    user.getPassword()+"',email = '"+user.getEmail()+"',birthday = '"+                    birthday+"' where id = "+user.getId();            System.out.println(sql);            int num = statement.executeUpdate(sql);            if(num > 0){                return true;            }            return false;        } catch (ClassNotFoundException e) {            e.printStackTrace();        } catch (SQLException e) {            e.printStackTrace();        } finally {            JDBCUtils.release(connection,statement);        }        return false;    }}

4.创建测试类

package com.mcxfate.study.example;

import java.util.ArrayList;
import java.util.Date;

/**
 * @Author: Mcxfate
 * 
 */
public class DemoTest {

    public static void main(String[] args) {

        //创建一个数据操作Dao对象
        UserDao userDao = new UserDao();


        //向users表插入一个用户的信息
        User user = new User();
//        user.setId(10);
//        user.setUsername("test01");
//        user.setPassword("test01");
//        user.setEmail("test01@qq.com");
//        user.setBirthday(new Date());
        boolean result = userDao.insert(user);
//        if(result){
//            System.out.println("插入成功!");
//        }else{
//            System.out.println("插入失败!");
//        }





        //查询所有的user对象
        ArrayList<User> list = userDao.findAll();
        for (User users : list) {
            System.out.println(users.getId()+"-"+users.getUsername()+"-"+users.getPassword()+"-"+users.getEmail()+"-"+users.getBirthday());
        }

        //通过id查询指定的用户信息
        user = userDao.find(1);
        System.out.println(user.getId()+"-"+user.getUsername()+"-"+user.getPassword()+"-"+user.getEmail()+"-"+user.getBirthday());


        //修改user的数据
        user.setId(1);
        user.setUsername("test02");
        user.setPassword("test02");
        user.setEmail("test02@qq.com");
        user.setBirthday(new Date());
        result = userDao.update(user);
        if(result){
            System.out.println("修改成功!");
        }else{
            System.out.println("修改失败!");
        }


        //删除user用户
        result = userDao.delete(3);
        if(result){
            System.out.println("删除成功!");
        }else{
            System.out.println("删除失败!");
        }

    }

}

插入测试结果:

image-20211025163853836

image-20211025163930041

查询所有用户测试结果:

image-20211025164158830

image-20211025164209010

通过id查找指定用户测试图:

image-20211025164351718

image-20211025164411895

修改用户数据测试图:

image-20211025164527504

image-20211025164610389

删除用户测试图:

image-20211025164654625

image-20211025164726074

数据库连接池

什么是数据库连接池?

在JDBC编程中,每次创建和断开Connection对象都会消耗一定得时间和IO资源。这是因为在Java程序与数据库连接之间建立连接时,数据库端要验证用户名和密码,并且要为这个连接分配资源,程序则要把代表连接的java.sql.Connection对象等加载到内存中,所以建立数据库连接的开销很大,尤其是大量的并发访问时。频繁地创建、断开数据库连接势必会影响数据库的访问效率,甚至导致数据库崩溃。

为了避免频繁的创建数据库连接,数据库连接池技术应运而生。数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用现有的数据库连接,而不是重新建立。

image-20211015224226510

从上图中可以看出,数据库连接池在初始化时将创建一定数量的数据库连接对象放到连接池中,当应用程序访问数据库时并不是直接创建Connection,而是向连接池 “申请” 一个Connection对象。如果连接池中有空闲的Connection对象,则其返回,否则创建新的Connection对象。使用完毕后,连接池会将该Connection回收,并交付其他的线程使用,以减少创建和断开数据库连接的次数,提高数据库的访问数据。

DataSource接口

为了获取数据库连接对象(Connection),JDBC提供了javax.sql.DataSource接口,它负责与数据库建立连接,并定义了返回值为Connection对象的方法,具体如下。

  • Connection getConnection()
  • Connection getConnection(String username, String password)

上述两个重载的方法都能用来获取 Connection 对象。

不同的是,第1个方法是通过无参的方式建立与数据库的连接,第2个方法是通过传入登录信息的方 式建立与数据库的连接。

接口通常都会有其实现类,javax salDatasource接口也不例外,通常习惯性地把实现了javax.sal DataSource 接口的类称为数据源,顾名思义,数据源即数据的来源。在数据源中存储了所有建立数据库连接的信息。就像通过指定文件名称可以在文件系统中找到文件一样,通过提供正确的数据源名称,也可以找到相应的数据库连接。

数据源中包含数据库连接池。如果数据是水,数据库就是水库,数据源就是连按到水库的管道,终端用户看到的数据集是管道里流出来的水。一些开源组织提供了数据源的独立实现,常用的有 DBCP 数据源C3P0 数据源

DBCP数据源

**DBCP是数据库连接池(DataBase Connection Pool)**的简称,是Apache组织下的开源连接池实现,也是Tomcat服务器使用的连接池组件。单独使用DBCP数据源时,需要在应用程序中导入两个JAR包,具体如下。

  • commons-dbcp.jar

    ? commons-dbcp.jiar 包是 DBCP 数据源的实现包,包含所有操作数据库连接信息和数据库连接池初始化信息的方法,并实现了 DataSource 接口的 getConnection()方法。

  • commons-pool.jar
    commons-poolijar 包是 DBCP 数据库连接池实现包的依赖包,为 commons-dbcp.jar 包中
    的方法提供了支持。可以这么说,没有该依赖包,commons-dbcp.jar 包中的很多方法就没有办
    法实现。

    这两个 JAR 包可以在 Apache 官网地址“http://commons.apache.org/proper/”中查询下载到。其中,commons-dbcp.jar 中包含两个核心的类,分别是 Basic Data SourceFactory
    BasicDataSource, 它们都包含获取 DBCP 数据源对象的方法。

    BasicDataSource 是 Datasource 接口的实现类,主要包括设置数据源对象的方法,该类的
    常用方法介绍如表所示。

    方法名称功能描述
    void setDriverClassName(String driverClassName)设置连接数据库的驱动名称
    void setUrl(String url)设置连接数据库的路径
    void setUsername(String username)设置数据库的登录账户
    void setPassword(String password)设置数据库的登录密码
    void setInitialSize(int initialSize)设置数据库连接池初始化的连接项目
    void setMaxActive(int maxldle)设置数据库连接池最大活跃的连接数目
    void setMinldle(int minldle)设置数据库连接池最小闲置的连接数目
    Connection getConnection()从连接池中获取一个数据库连接

在表中,列举了 BasicDetasource 对象的常用方法,其中,setDriverClassName、 setUrl、setUsername、setPassword等方法都是设置数据库连接信息的方法,setInitialSize、setMaxActive、setMinldle等方法都是设置数据库连接池初始化值的方法,getConnection方法表示从 DBCP 数据源中获取一个数据库连接。

BasicDataSourceFactory 是创建 BasicDataSource 对象的工厂类,它包含一个返回值为BasicDataSource 对象的方法 createDataSource(),该方法通过读取配置文件的信息生成数据源
对象并返回给调用者。这种把数据库的连接信息和数据源的初始化信息提取出来写进配置文件的
方式,让代码看起来更加简洁,思路也更加清晰。当使用 DBCP 数据源时,首先要创建数据源对象,数据源对象的创建方式有两种,具体如下。

1.通过 BasicDatasource 类直接创建数据源对象
在使用 BasicDataSource 类创建一个数据源对象时,需要手动给数据源对象设置属性值,然后获取数据库连接对象。

example04:
package com.mcxfate.study.jdbc;

import org.apache.commons.dbcp2.BasicDataSource;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;

/**
 * @Author: Mcxfate
 * 
 */
public class Example04 {

    public static DataSource dataSource = null;

    //静态代码块
    static {
        // 获取DBCP数据源实现类对象
        BasicDataSource basicDataSource = new BasicDataSource();
        // 设置连接数据库需要的配置信息
        basicDataSource.setDriverClassName("com.mysql.jdbc.Driver");
        basicDataSource.setUrl("jdbc:mysql://localhost:3306/mydb?useSSL=FALSE&serverTimezone=UTC");
        basicDataSource.setUsername("root");
        basicDataSource.setPassword("root");
        //设置连接池的参数
        basicDataSource.setInitialSize(5);
        basicDataSource.setMaxTotal(5);
        dataSource = basicDataSource;
    }

    public static void main(String[] args) throws SQLException {
        //获取数据库连接对象
        Connection connection = dataSource.getConnection();
        //获取数据库连接信息
        DatabaseMetaData metaData = connection.getMetaData();
        //打印数据库连接信息
        System.out.println(metaData.getURL()+"   "+"username = "+metaData.getUserName()+"    "+ metaData.getDriverName());

    }

}

运行结果如图:

image-20211018230009076

2.通过读取配置文件创建数据源对象

除了使用BasicDataSource 直接创建数据源对象外,还可以使用 BasicDataSourceFactory工厂类读取配置文件,创建数据源对象,然后获取数据库连接对象。

example05:
#配置文件 src/dbcpconfig.properties

#连接设置
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mydb?useSSL=FALSE&serverTimezone=UTC
username=root
password=root

#初始化连接
initialSize=5
#最大连接数量
maxActive=10
#最大空闲数量
maxIdle=10
package com.mcxfate.study.jdbc;

import org.apache.commons.dbcp2.BasicDataSourceFactory;
import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.Properties;

/**
 * @Author: Mcxfate
 *
 */
public class Example08 {

    public static DataSource ds = null;

    //静态代码块
    static{
        //创建一个配置文件对象
        Properties prop = new Properties();

        try {
            //通过类加载器找到文件路径,读取配置文件
            InputStream in = new Example08().getClass().getClassLoader().getResourceAsStream("dbcpconfig.properties");
            //把文件以输入流的形式加载到配置对象中
            prop.load(in);
            //创建数据源对象
            ds = BasicDataSourceFactory.createDataSource(prop);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    public static void main(String[] args) throws SQLException {
        //获取数据库连接对象
        Connection conn = ds.getConnection();
        //获取数据库连接信息
        DatabaseMetaData metaData  = conn.getMetaData();
        //打印数据库连接信息
        System.out.println(metaData.getURL()+"  userName:"+metaData.getUserName()+"  password="+metaData.getDriverName());

    }


}

image-20211022224012015

C3P0数据源

C3P0是目前最流行的开源数据库连接池之一,它实现了DataSource数据源接口,支持JDBC2和JDBC3的标准规范,易于扩展并且性能优越,著名的开源框架Hibernate和 Spring都支持该数据源。在使用C3P0数据源开发时,需要了解C3P0中DataSource接口的实现类ComboPooledDataSource,它是C3P0的核心类,提供了数据源对象的相关方法,该类的常用方法介绍如下表所示。

image-20211015231622238

当使用C3P0数据源时,首先需要创建数据源对象,创建数据源对象可以使用 Combo-PooledDatasource 类,该类有两个构造方法,分别是ComboPooledDataSource()Combo-PooledDataSource(String configName)

1.通过ComboPooledDataSource()构造方法创建数据源对象

example06:
package com.mcxfate.study.jdbc;import com.mchange.v2.c3p0.ComboPooledDataSource;import javax.sql.DataSource;import java.sql.SQLException;/** * @Author: Mcxfate *  */public class Example09 {    public static DataSource ds = null;    //初始化C3P0数据源    static{        ComboPooledDataSource cpds = new ComboPooledDataSource();        //设置连接数据库需要的配置信息        try {            cpds.setDriverClass("com.mysql.jdbc.Driver");            cpds.setJdbcUrl("jdbc:mysql://localhost:3306/mydb?useSSL=FALSE&serverTimezone=UTC");            cpds.setUser("root");            cpds.setPassword("root");            //设置连接池的参数            cpds.setInitialPoolSize(5);            cpds.setMaxPoolSize(15);            ds = cpds;        } catch (Exception e) {            throw new ExceptionInInitializerError(e);        }    }    public static void main(String[] args) throws SQLException {        //获取数据库连接对象        System.out.println(ds.getConnection());    }}

image-20211022232504851

2.通过读取配置文件创建数据源对象

使用Combo-PooledDataSource(String configName)构造方法读取c3p0-config.xml配置文件,从而创建数据源对象,然后获取数据库连接对象。

example07:

c3p0-config.xml 文件配置

<?xml version="1.0" encoding="UTF-8" ?><c3p0-config>    <!-- 使用默认的配置读取连接池对象 -->    <default-config>        <!--  连接参数 -->        <property name="driverClass">com.mysql.jdbc.Driver</property>        <property name="jdbcUrl">jdbc:mysql://localhost:3306/mydb</property>        <property name="user">root</property>        <property name="password">root</property>        <!-- 连接池参数 -->        <!--初始化的申请的连接数量-->        <property name="initialPoolSize">5</property>        <!--最大的连接数量-->        <property name="maxPoolSize">100</property>        <property name="minPoolSize">10</property>        <!--连接超时时间-->        <property name="checkoutTimeout">3000</property>        <property name="maxIdleTime">30</property>        <property name="maxStatements">200</property>    </default-config>    <!--  自定义配置文件  -->    <named-config name="otherc3p0">        <!--  连接参数 -->        <property name="driverClass">com.mysql.jdbc.Driver</property>        <property name="jdbcUrl">jdbc:mysql://localhost:3306/mydb</property>        <property name="user">root</property>        <property name="password">root</property>        <!-- 连接池参数 -->        <property name="initialPoolSize">5</property>        <property name="maxPoolSize">8</property>        <property name="checkoutTimeout">1000</property>    </named-config></c3p0-config>
package com.mcxfate.study.jdbc;

import com.mchange.v2.c3p0.ComboPooledDataSource;

import javax.sql.DataSource;
import java.sql.SQLException;

/**
 * @Author: Mcxfate
 *  
 */
public class Example10 {

    public static DataSource ds = null;

    static {

        //使用src/c3p0-config.xml 配置文件中的named-config节点中name属性的值
        ComboPooledDataSource cpds = new ComboPooledDataSource("otherc3p0");
        ds = cpds;
    }

    public static void main(String[] args) throws SQLException {
        //打印连接对象信息
        System.out.println(ds.getConnection());
    }

}

image-20211023224728348

DBUtils工具

为了更加简单地使用JDBC,Apache组织提供了一个DBUtils工具,它是操作数据库的一个组件,实现了对JDBC的简单封装,可以在不影响性能的情况下极大地简化JDBC的编码工作量。

DBUtils 工具可以在"http://commons.apache.org/proper/commons-dbutils/index.html"下载到。

DBUtils工具的核心是org.apache.commons.dbutils.QueryRunner类org.apache.commons.dbutils.ResultSetHandler接口

QueryRunner 类

QueryRunner类简化了执行SQL语句的代码,它与ResultSetHandler组合在一起就能完成大部分的数据库操作,大大的减少了编码量。

QueryRunner类提供了带有一个参数的构造方法,该方法以javax.sql.DataSource作为参数传递到QueryRunner的构造方法中来获取Connection对象。针对不同的数据库操作,QueryRunner 类提供了几种常见的方法,具体如下。

  • query (String sql, ResultSetHandler rsh, Object… params)方法该方法用于执行查询操作,它可以从提供给构造方法的数据源 DataSource 或使用的setDataSource() 方法中获得连接。
  • update(String sql, Object… params)方法,该方法用于执行插入、更新或者删除操作,其中,参数 params 表示SQL 语句中的置换参数。
  • update(String sql)方法,该方法用来执行插入、更新或者删除操作,它不需要置换参数。

ResultSetHandler接口

ResultSetHandler 接口用于处理ResultSet结果集,它可以将结果集中的数据转为不同的形式。根据结果集中数据类型的不同,ResultSetHandler提供了几种常见的实现类,具体如下:

  • BeanHandler:将结果集中的第一行数据封装到一个对应的JavaBean实例中。
  • BeanListHandler:将结果集中的每一行数据都封装到一个对应的JavaBean实例中,并存放到List里。
  • ScalarHandler:将结果集中某一条记录的其中某一列的数据存储成Object对象。

在 ResultSetHandler接口中,提供了一个单独的方法 handle (java.sal.Resuliset rs)如果上述实现类没有提供想要的功能,可以通过自定义一个实现 ResultSetrandler 接口的类然后通过重写handle(方法,实现结果集的处理。

ResultSetHandler实现类

1.BeanHandler 和 BeanListHandler

BeanHandler 和 BeanListHandler实现类是将结果集中的数据封装到对应的JavaBean实例中,这也是实际开发中最常用的结果集处理方法。

example08:

(1)在数据库中创建一个数据表user

use mydb;
create table user(
	id int primary key auto_increment,
    name varchar(20) not null,
    password varchar(20) not null
);

insert into user(name,password) values ('zhangsan','123456');
insert into user(name,password) values ('lisi','123456');
insert into user(name,password) values ('wangwu','123456');

(2)将下载的DBUtils工具的JAR包添加到项目的lib目录中。

commons-dbutils-1.6.jar

(3)编写测试类

package com.mcxfate.study.jdbc;

import java.sql.*;
import com.mcxfate.study.example.JDBCUtils;
import org.apache.commons.dbutils.ResultSetHandler;
public class BaseDao {
    // 优化查询
    public static Object query(String sql, ResultSetHandler<?> rsh,
                               Object... params) throws SQLException {
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {

            // 使用JDBCUtils工具类获取连接
            conn = JDBCUtils.getConnection();
            // 预编译sql
            pstmt = conn.prepareStatement(sql);
            // 将参数设置进去
            for (int i = 0; params != null && i < params.length; i++)
            {
                pstmt.setObject(i + 1, params[i]);
            }
            // 发送sql
            rs = pstmt.executeQuery();
            // 让调用者去实现对结果集的处理
            Object obj = rsh.handle(rs);
            return obj;
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            // 使用JDBCUtils工具类释放资源
            JDBCUtils.release(conn,pstmt,rs);

        }
        return rs;
    }
}

(4)创建实体类User

package com.mcxfate.study.entity;

/**
 * @Author: Mcxfate
 * 
 */
public class User {

    private  int id;
    private String name;
    private String password;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

(5)创建测试类

测试BeanHandler类

package com.mcxfate.study.utils;

import com.mcxfate.study.entity.User;
import com.mcxfate.study.jdbc.BaseDao;
import org.apache.commons.dbutils.handlers.BeanHandler;

import java.sql.SQLException;

/**
 * @Author: Mcxfate
 * 
 */
public class ResultSetTest1 {

    public static void testBeanHandler() throws SQLException {

        BaseDao baseDao = new BaseDao();
        String sql = "select * from user where id = ?";
        User user = (User) baseDao.query(sql,new BeanHandler(User.class),2);
        System.out.println("id为2的User对象的name值为:"+user.getName());
		System.out.println(user);
    }

    public static void main(String[] args) throws SQLException {
        testBeanHandler();
    }

}

image-20211028153301955

测试BeanListHandler类

package com.mcxfate.study.utils;

import com.mcxfate.study.entity.User;
import com.mcxfate.study.jdbc.BaseDao;
import org.apache.commons.dbutils.handlers.BeanListHandler;

import java.sql.SQLException;
import java.util.ArrayList;

/**
 * @Author: Mcxfate
 * 
 */
public class ResultSetTest2 {

    public static void testBeanListHandler() throws SQLException {

        BaseDao baseDao = new BaseDao();
        String sql = "select * from user ";
        ArrayList<User> list = (ArrayList<User>) baseDao.query(sql,new BeanListHandler(User.class));

        for (int i = 0; i < list.size(); i++) {
            System.out.println("第"+(i+1)+"条数据的username值为:"+list.get(i).getName());
        }

    }

    public static void main(String[] args) throws SQLException {
        testBeanListHandler();
    }

}

image-20211028153409411

2.ScalarHandler

在使用DBUtils工具操作数据库时,如果需要输出结果集中一行数据的指定字段值,可以使用ScalarHandler类。

example09:
package com.mcxfate.study.utils;

import com.mcxfate.study.jdbc.BaseDao;
import org.apache.commons.dbutils.handlers.ScalarHandler;

import java.sql.SQLException;

/**
 * @Author: Mcxfate
 * 
 */
public class ResultSetTest03 {

    public static void testScalarHandler() throws SQLException {

        BaseDao baseDao = new BaseDao();
        String sql = "select * from user where id = ?";
        Object arr = (Object) baseDao.query(sql,new ScalarHandler<>("name"),2);
        System.out.println(arr);

    }

    public static void main(String[] args) throws SQLException {
        testScalarHandler();
    }


}

image-20211028153657207

实例2:使用DBUtils实现增删改查

1.创建C3p0Utils类

package com.mcxfate.study.utils;

import com.mchange.v2.c3p0.ComboPooledDataSource;

import javax.sql.DataSource;

/**
 * @Author: Mcxfate
 * 
 */
public class C3p0Utils {

    private static DataSource ds;

    static {
        ds = new ComboPooledDataSource();
    }

    public static DataSource getDataSource(){

        return ds;

    }

}

2.创建DBUtilsDao类

package com.mcxfate.study.utils;

import com.mcxfate.study.entity.User;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;

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

/**
 * @Author: Mcxfate
 * 
 */
public class DBUtilsDao {

    //查询所有,返回list集合
    public List findAll() throws SQLException {

        //创建QueryRunner对象
        QueryRunner runner = new QueryRunner(C3p0Utils.getDataSource());

        //写sql语句
        String sql = "select * from user";

        //调用方法
        List list = runner.query(sql,new BeanListHandler<>(User.class));

        return list;

    }


    //单个查询,返回对象
    public User find(int id) throws SQLException {

        //创建QueryRunner对象
        QueryRunner runner = new QueryRunner(C3p0Utils.getDataSource());

        //写sql语句
        String sql = "select * from user where id = ?";

        //调用方法
        User user = (User) runner.query(sql,new BeanHandler<>(User.class),new Object[]{id});

        return user;
    }

    //添加用户的操作
    public Boolean insert(User user) throws SQLException {
        //创建QueryRunner对象
        QueryRunner runner = new QueryRunner(C3p0Utils.getDataSource());

        //sql语句
        String sql = "insert into user (name,password) values (?,?)";

        //调用方法
        int num  = runner.update(sql,new Object[]{user.getName(),user.getPassword()});

        if(num > 0){
            return true;
        }
        return false;
    }


    //修改用户的操作
    public boolean update(User user) throws SQLException {

        //创建QueryRunner对象
        QueryRunner runner = new QueryRunner(C3p0Utils.getDataSource());

        //写SQL语句
        String sql = "update user set name =?,password = ? where id = ?";

        //调用方法
        int num = runner.update(sql,new Object[]{user.getName(),user.getPassword(),user.getId()});
        if (num > 0)
            return true;

        return false;

    }


    //删除用户的操作
    public boolean delete(int id) throws SQLException {

        //创建QueryRunner对象
        QueryRunner runner = new QueryRunner(C3p0Utils.getDataSource());

        //写SQL语句
        String sql = "delete from user where id = ?";

        //调用方法
        int num = runner.update(sql,id);
        if(num > 0)
            return true;
        return false;

    }


}

上面的代码中,查询一条数据用的是能够处理一行数据的BeanHandler类,查询所有数据时用的是能处理所有行数据的BeanListHandler类。

3.测试DBUtilsDao类中的增删改查操作

package com.mcxfate.study.utils;

import com.mcxfate.study.entity.User;

import java.sql.SQLException;

/**
 * @Author: Mcxfate
 * 
 */
public class DBUtilsDaoTest01 {

    private static DBUtilsDao dao = new DBUtilsDao();

    public static void testInsert() throws SQLException {

        User user = new User();
        user.setName("test111");
        user.setPassword("666666");

        boolean result = dao.insert(user);
    }


    public static void testUpdate() throws SQLException {
        User user = new User();
        user.setName("test0002");
        user.setPassword("200000");
        user.setId(1);

        boolean result = dao.update(user);
        if(result){
            System.out.println("修改成功!!");
        }else{
            System.out.println("修改失败!!");
        }
    }

    public static void testDelete() throws SQLException {

        boolean result = dao.delete(1);
        if(result){
            System.out.println("删除成功!!");
        }else{
            System.out.println("删除失败!!");
        }

    }

    public static void testFind() throws SQLException {
        User user = dao.find(2);
        System.out.println(user.getId()+"---"+user.getName()+"----"+user.getPassword());

    }

    public static void main(String args[]) throws SQLException {

        //testInsert();
        //testUpdate();
        //testDelete();
        testFind();
    }



}
  大数据 最新文章
实现Kafka至少消费一次
亚马逊云科技:还在苦于ETL?Zero ETL的时代
初探MapReduce
【SpringBoot框架篇】32.基于注解+redis实现
Elasticsearch:如何减少 Elasticsearch 集
Go redis操作
Redis面试题
专题五 Redis高并发场景
基于GBase8s和Calcite的多数据源查询
Redis——底层数据结构原理
上一篇文章      下一篇文章      查看所有文章
加:2021-10-29 13:06:54  更:2021-10-29 13:06:59 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 4:36:51-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码