0
点赞
收藏
分享

微信扫一扫

MySQL学习笔记3——JDBC

东林梁 2022-02-14 阅读 67

目录

JDBC简介

Java数据库连接,(Java Database Connectivity,简称JDBC)是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数据库中数据的方法。

数据库驱动

什么是数据库驱动?类似声卡驱动、显卡驱动
我们的程序会通过数据库驱动,和数据库打交道。
在这里插入图片描述

JDBC

SUN 公司为了简化开发人员的(对数据库的统一)操作,提供了一个(Java操作数据库的)规范,JDBC。

这些规范的实现由具体的厂商去做。

对于开发人员来说,我们只需要掌握JDBC的接口操作即可。
在这里插入图片描述
java.sql

javax.sql

还需要导入数据库驱动包

第一个JDBC程序

1.创建测试数据库

CREATE DATABASE jdbcStudy CHARACTER SET utf8 COLLATE utf8_general_ci;

USE jdbcStudy;

CREATE TABLE `users`(
id INT PRIMARY KEY,
NAME VARCHAR(40),
PASSWORD VARCHAR(40),
email VARCHAR(60),
birthday DATE
);

INSERT INTO `users`(id,NAME,PASSWORD,email,birthday)
VALUES(1,'zhansan','123456','zs@sina.com','1980-12-04'),
(2,'lisi','123456','lisi@sina.com','1981-12-04'),
(3,'wangwu','123456','wangwu@sina.com','1979-12-04')

2.创建一个普通项目

3.导入数据库驱动
pom.xml

    <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.16</version>
    </dependency>

4.编写测试代码

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

public class JdbcDemo {
    public static void main(String[] args) throws Exception {
        //1. 加载驱动
        Class.forName("com.mysql.cj.jdbc.Driver");//固定写法 jdbc驱动版本8.0之前为com.mysql.jdbc.Driver

        //2. 用户信息和url
        //useUnicode=true&characterEncoding=utf8&&useSSL=true
        //useUnicode=true支持中文编码;characterEncoding=utf8中文字符集设置为utf-8;useSSL=true使用安全链接
        //jdbc驱动版本8.0之后要添加serverTimezone=UTC
        String url ="jdbc:mysql://localhost:3306/jdbcstudy?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&&useSSL=true";
        String name = "root";
        String password = "123456";

        //3. 连接成功,返回数据库对象  connection代表数据库
        Connection connection= DriverManager.getConnection(url,name,password);
        //4. 执行SQL的对象 statement 执行SQL的对象
        Statement statement = connection.createStatement();

        //5. 执行SQL的对象 去执行SQL   可能存在结果,查看返回结果
        String sql="SELECT * FROM users";
        ResultSet resultSet = statement.executeQuery(sql);//返回的结果集,结果集中封装了我们全部查询的结果
        while(resultSet.next()){
            System.out.println("ID="+resultSet.getObject("id"));
            System.out.println("姓名="+resultSet.getObject("NAME"));
            System.out.println("密码="+resultSet.getObject("PASSWORD"));
            System.out.println("邮箱="+resultSet.getObject("email"));
            System.out.println("生日="+resultSet.getObject("birthday"));
            System.out.println("==================================");
        }
        //6. 释放连接(从后往前释放)
        resultSet.close();
        statement.close();
        connection.close();
    }
}

JDBC中各对象详解

步骤总结:
1.加载驱动

2.连接数据库 DriverManager

3.获取执行SQL的对象 Statement

4.获得返回的结果集

5.释放连接

DriverManager

//DriverManager.registerDriver(new com.mysql.jdbc.Driver());
Class.forName("com.mysql.jdbc.Driver");//固定写法
Connection connection= DriverManager.getConnection(url,name,password);

//connection代表数据库
//数据库设置自动提交
//事务提交
//事务回滚
connection.rollback();
connection.commit();
connection.setAutoCommit();

URL

String url ="jdbc:mysql://localhost:3306/jdbcstudy?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&&useSSL=true";

//mysql 默认端口3306
//协议://主机地址:端口号/数据库名?参数1&参数2&参数3...

//Oracle   1521
//jdbc:oralce:thin:@localhost:1521:sid

statement执行SQL的对象 PrepareStatement 执行SQL的对象

String sql="SELECT * FROM users";//编写Sql

statement.executeQuery();//查询操作,返回ResultSet
statement.execute();//执行任何SQL
statement.executeUpdate();//更新,插入,删除,返回一个受影响的行数


ResultSet 查询的结果集,封装了所有的查询结果

获得指定的数据类型

ResultSet resultSet = statement.executeQuery(sql);//返回的结果集,结果集中封装了我们全部查询的结果

resultSet.getObject();//在不知道列类型下使用

//如果知道则使用指定类型
resultSet.getString();
resultSet.getInt();
resultSet.getFloat();
resultSet.getDate();
...       

遍历,指针

        resultSet.next(); //移动到下一个
        resultSet.afterLast();//移动到最后
        resultSet.beforeFirst();//移动到最前面
        resultSet.previous();//移动到前一个
        resultSet.absolute(row);//移动到指定行

释放内存

//6. 释放连接(从后往前释放) 
        resultSet.close();
        statement.close();
        connection.close();//connection连接最占用资源

statement对象

JDBC中的statement对象用于向数据库发送SQL语句,想完成对数据库的增删改查,只需要通过这个对象向数据库发送增删改查语句即可。

Statement对象的executeUpdate方法,用于向数据库发送增、删、改的sq|语句, executeUpdate执行完后, 将会返回一个整数(即增删改语句导致了数据库几行数据发生了变化)。

Statement.executeQuery方法用于向数据库发生查询语句,executeQuery方法返回代表查询结果的ResultSet对象。

CRUD操作-create
使用executeUpdate(String sql)方法完成数据添加操作,示例操作:

 Statement statement = connection.createStatement();
        String sql = "insert into user(...) values(...)";
        int num = statement.executeUpdate(sql);
        if(num>0){
            System.out.println("插入成功");
        }

CRUD操作-delete
使用executeUpdate(String sql)方法完成数据删除操作,示例操作:

Statement statement = connection.createStatement();
        String sql = "delete from user where id =1";
        int num = statement.executeUpdate(sql);
        if(num>0){
            System.out.println("删除成功");
        }

CURD操作-update
使用executeUpdate(String sql)方法完成数据修改操作,示例操作:

Statement statement = connection.createStatement();
        String sql = "update user set name ='' where name = ''";
        int num = statement.executeUpdate(sql);
        if(num>0){
            System.out.println("修改成功");
        }

CURD操作-read
使用executeUpdate(String sql)方法完成数据查询操作,示例操作:

Statement statement = connection.createStatement();
        String sql = "select * from  user where id =1";
        ResultSet rs= statement.executeQuery(sql);
        if(rs.next()){
            System.out.println("");
        }

包装成工具类

首先配置文件db.properties写好信息

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbcstudy?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&&useSSL=true
username=root
password=123456

工具类

import java.io.FileInputStream;
import java.sql.*;
import java.util.Properties;

public class JdbcUtils {

    private static String driver = null;
    private static String url = null;
    private static String username = null;
    private static String password = null;
    static {
        try{
            FileInputStream in = new FileInputStream("src/main/resources/db.properties");
            Properties properties = new Properties();
            properties.load(in);
            driver=properties.getProperty("driver");
            url=properties.getProperty("url");
            username=properties.getProperty("username");
            password=properties.getProperty("password");

            //1.驱动只用加载一次
            Class.forName(driver);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //2.获取连接
    public static Connection getConnection() throws Exception{
        return DriverManager.getConnection(url, username, password);
    }
    //3.释放资源
    public static void release(Connection conn, Statement st, ResultSet rs) throws SQLException {

        if (rs != null) {
            rs.close();
        }
        if (st != null){
            st.close();
        }
        if(conn != null){
            conn.close();
        }

    }
}

使用工具类(插入数据为例)(exectueUpdate可以进行增删改三个操作)

import com.cheng.utils.JdbcUtils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;

import static com.cheng.utils.JdbcUtils.getConnection;

public class TestInsert {
    public static void main(String[] args){
        Connection conn =null;
        Statement st = null;
        ResultSet rs =null;

        try {
            conn = getConnection();//获取连接
            st = conn.createStatement();//获取SQL执行对象
            String sql = "INSERT INTO users(id,`NAME`,`PASSWORD`,`email`,`birthday`)" +
                    "VALUES(5,'你好','123456','233223@qq.com','2020-01-01')";

            int i = st.executeUpdate(sql);
            if(i>0){
                System.out.println("插入成功");
            }
            JdbcUtils.release(conn,st,rs);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

使用工具类(查询数据为例)

import com.cheng.utils.JdbcUtils;

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

import static com.cheng.utils.JdbcUtils.getConnection;

public class TestQuery {
    public static void main(String[] args) throws SQLException {
        Connection conn =null;
        Statement st = null;
        ResultSet rs =null;

        try {
            conn = getConnection();//获取连接
            st = conn.createStatement();//获取SQL执行对象
            String sql = "select * from users";
            rs=st.executeQuery(sql);//查询完毕返回结果集

            while (rs.next()){
                System.out.println(rs.getString("NAME"));
            }
            JdbcUtils.release(conn,st,rs);
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            JdbcUtils.release(conn,st,rs);
        }
    }
}

SQL注入问题

sql存在漏洞,会被攻击导致数据泄露 SQL会被拼接 or

原理,例如密码查询正常是要匹配密码,但是加个or 1=1后就会认为密码查询通过,因为or是左右两边有一个成立都认为是成立,又因为1=1恒成立,所以服务器被欺骗,认为密码查询通过

示例

import com.cheng.utils.JdbcUtils;

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

import static com.cheng.utils.JdbcUtils.getConnection;

public class SqlInjection {
    public static void main(String[] args) {
        // 正常查询
        login("你好","123456");

        System.out.println("=================");

        // SQL注入
        login("' OR '1=1","' OR '1=1");
    }
    public static void login(String name,String password){
        Connection conn =null;
        Statement st = null;
        ResultSet rs =null;
        try {
            conn = getConnection();//获取连接
            st = conn.createStatement();//获取SQL执行对象

            // 正常查询:SELECT * FROM users WHERE `NAME`='你好'  AND `PASSWORD`='123456'
            // SQL注入:SELECT * FROM users WHERE `NAME`='' OR '1=1' AND `PASSWORD`='' OR '1=1'
            // 即为SELECT * FROM users  这样就能获取整个表所有信息
            String sql = "SELECT * FROM users WHERE `NAME`='"+ name +"'  AND `PASSWORD`='"+ password +"'" ;

            rs=st.executeQuery(sql);//查询完毕返回结果集
            while (rs.next()){
                System.out.println(rs.getString("NAME"));
            }
                JdbcUtils.release(conn,st,rs);
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                try {
                    JdbcUtils.release(conn,st,rs);
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
    }
}

PreparedStatement对象

PreparedStatement 可以防止SQL注入 ,效率更高。

public class Test {
    public static void main(String[] args) {
        Connection connection= null;
        PreparedStatement pstm=null;
        try {
            connection = JdbcUtils.getConnection();
            //PreparedStatement防止SQL注入本质:把传递进来的参数当作字符
            //假设其中存在转义字符,比如说引号,会被直接转义
            
            // 区别:
            // 1.使用问号占位符代替参数
            String sql = "insert into users(id, `NAME`, `PASSWORD`, `email`,`birthday`) values(?, ?, ?, ?, ?)";
            // 2.预编译sql,先写sql然后不执行
            pstm = connection.prepareStatement(sql);
            // 手动赋值
            pstm.setInt(1,4);// 1代表第一个问号
            pstm.setString(2,"张三");
            pstm.setString(3,"123123");
            pstm.setString(4,"123333@qq.com");
            pstm.setDate(5,new java.sql.Date(new Date().getTime()));// 注意要转换成sql的Date
            //执行
            int i = pstm.executeUpdate();
            if (i>0){
                System.out.println("插入成功");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            try {
                JdbcUtils.release(connection,pstm,null);
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }
}

输出:

防止SQL注入本质,传递字符 带有“ ”,转义字符会被转义
改进示例:

import com.cheng.utils.JdbcUtils;

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

public class Test {
    public static void main(String[] args) {
        // 正常查询
        login("你好","123456");

        System.out.println("=================");
        // SQL注入
        login("'' OR 1=1","'' OR 1=1");
    }
    public static void login(String username,String password) {
        Connection connection = null;
        PreparedStatement pstm = null;
        ResultSet rs = null;
        try {
            connection = JdbcUtils.getConnection();
            // 区别:
            // 1.使用问号占位符代替参数
            String sql = "SELECT * FROM users WHERE `NAME`=? AND `PASSWORD`=?";
            // 2.预编译sql,先写sql然后不执行
            pstm = connection.prepareStatement(sql);
            // 手动赋值
            pstm.setString(1, username);// 1代表第一个问号
            pstm.setString(2, password);

            rs = pstm.executeQuery();//注意!!!!这里不像st.executeQuery(sql);那样,括号里不要写sql,否则报错。因为PreparedStatement是预编译的,PreparedStatement对象中已包含SQL查询
            //查询完毕返回结果集
            while (rs.next()) {
                System.out.println(rs.getString("NAME"));
            }
            JdbcUtils.release(connection, pstm, rs);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                JdbcUtils.release(connection, pstm, rs);
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }
}

注意!!!!rs = pstm.executeQuery();不像rs = st.executeQuery(sql);那样,括号里不要写sql,否则报错。因为PreparedStatement是预编译的,PreparedStatement对象中已包含SQL查询

输出:(SQL注入失效)

使用IDEA连接数据库

连接数据库
在这里插入图片描述

添加表
在这里插入图片描述

修改数据表:
双击数据表,修改,提交
在这里插入图片描述

编写查询
在这里插入图片描述

JDBC操作事务

要么都成功,要么都失败

ACID原则

  • 原子性:要么全部完成,要么都不完成
  • 一致性:结果总数不变
  • 隔离性:多个进程互不干扰
  • 持久性:一旦提交不可逆,持久化到数据库了

隔离性的问题:

  • 脏读: 一个事务读取了另一个没有提交的事务
  • 不可重复读:在同一个事务内,重复读取表中的数据,表发生了改变
  • 虚读(幻读):在一个事务内,读取到了别人插入的数据,导致前后读出来的结果不一致

代码实现

  1. 开启事务conn.setAutoCommit(false);
  2. 一组业务执行完毕,提交事务
  3. 可以在catch语句中显示的定义回滚,但是默认失败会回滚

创建示例数据库:
在这里插入图片描述

public class JdbcTransaction{
    public static void main(String[] args) {

        Connection conn =null;
        PreparedStatement ps = null;
        ResultSet rs = null;

        try {
            conn = JdbcUtils.getConnection();
            //关闭数据库的自动提交功能, 开启事务
            conn.setAutoCommit(false);
            //自动开启事务
            String sql = "update account set money = money-100 where id = 1";
            ps =conn.prepareStatement(sql);
            ps.executeUpdate();
            String sql2 = "update account set money = money-100 where id = 2";
            ps=conn.prepareStatement(sql2);
            ps.executeUpdate();

            //业务完毕,提交事务
            conn.commit();
            System.out.println("操作成功");
        } catch (Exception e) {
            try {
                //如果失败,则默认回滚
                conn.rollback();//如果失败,回滚
                System.out.println("失败");
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
            e.printStackTrace();
        }finally {
            try {
                JdbcUtils.release(conn,ps,rs);
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }
}

数据库连接池

传统连接方式:数据库连接–执行完毕–释放

服务器频繁重复连接–释放会十分浪费资源

池化技术:准备一些预先的资源,过来就连接预先准备好的

  • 常用连接数:10
  • 最小连接数:10(一般跟常用连接数相等)
  • 最大连接数 : 100(业务最高承载上限)
  • 若大于最大连接数,排队等待,
  • 等待超时:100ms(100ms后等待的链接断开连接)

编写连接池:实现一个接口DateSource

常见的开源数据源实现(拿来即用)
以下这些是市面上常见的DateSource接口的实现类

  • DBCP
  • C3P0
  • Druid: 阿里巴巴

使用了这些数据库连接池之后,我们在项目开发中就不需要编写连接数据库的代码了

DBCP

需要用到的JAR包

(使用maven导入)
pom.xml

    <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-dbcp2 -->
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-dbcp2</artifactId>
      <version>2.7.0</version>
    </dependency>

DBCP配置文件

dbcp-config.properties

#连接设置
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbcstudy?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&&useSSL=true
username=root
password=123456

#<!-- 初始化连接 -->
initialSize=10

#最大连接数量
maxActive=50

#<!-- 最大空闲连接 -->
maxIdle=20

#<!-- 最小空闲连接 -->
minIdle=5

#<!-- 超时等待时间以毫秒为单位 6000毫秒/1000等于60-->
maxWait=60000
#JDBC驱动建立连接时附带的连接属性属性的格式必须为这样:【属性名=property;】
#注意:"user""password" 两个属性会被明确地传递,因此这里不需要包含他们。
connectionProperties=useUnicode=true;characterEncoding=UTF8

#指定由连接池所创建的连接的自动提交(auto-commit)状态。
defaultAutoCommit=true

#driver default 指定由连接池所创建的连接的只读(read-only)状态。
#如果没有设置该值,则“setReadOnly”方法将不被调用。(某些驱动并不支持只读模式,如:Informix)
defaultReadOnly=

#driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation)。
#可用值为下列之一:(详情可见javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
defaultTransactionIsolation=READ_UNCOMMITTED

工具类

import org.apache.commons.dbcp2.BasicDataSourceFactory;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.sql.*;
import java.util.Properties;

public class Dbcp {
    private static DataSource dataSource = null;
    static {
        try{
            FileInputStream in = new FileInputStream("src/main/resources/dbcpconfig.properties");
            Properties properties = new Properties();
            properties.load(in);

            // 1.创建数据源 工厂模式——>创建
            dataSource = BasicDataSourceFactory.createDataSource(properties);


        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 2.获取连接
    public static Connection getConnection() throws Exception{
        return dataSource.getConnection();  // 从数据源中获取连接
    }
    // 3.释放资源
    public static void release(Connection conn, Statement st, ResultSet rs) throws SQLException {

        if (rs != null) {
            rs.close();
        }
        if (st != null){
            st.close();
        }
        if(conn != null){
            conn.close();
        }

    }
}

测试代码

import com.cheng.utils.Dbcp;
import java.sql.*;

public class DbcpTest {
    public static void main(String[] args) throws SQLException {
        Connection conn =null;
        PreparedStatement ps = null;
        ResultSet rs =null;
        try {
            conn = Dbcp.getConnection();
            String sql = "select * from users";
            ps=conn.prepareStatement(sql);
            rs=ps.executeQuery();
            while (rs.next()){
                System.out.println(rs.getString("NAME"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            Dbcp.release(conn,ps,rs);
        }
    }
}

C3P0

需要用到的JAR包

(使用maven导入)
pom.xml

    <!-- https://mvnrepository.com/artifact/com.mchange/c3p0 -->
    <dependency>
      <groupId>com.mchange</groupId>
      <artifactId>c3p0</artifactId>
      <version>0.9.5.4</version>
    </dependency>

C3P0配置文件

c3p0-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
    <!-- 如果在代码中"ComboPooledDataSource ds=new ComboPooledDataSource();"这样写就表示使用的是c3p0的缺省(默认)-->
    <!-- c3p0的缺省(默认)配置-->
    <default-config>
        <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbcstudy?serverTimezone=UTC&amp;useUnicode=true&amp;characterEncoding=utf8&amp;&amp;useSSL=true</property>
        <property name="user">root</property>
        <property name="password">123456</property>

        <property name="acquireIncrement">5</property>
        <property name="initialPoolSize">10</property>
        <property name="minPoolSize">5</property>
        <property name="maxPoolSize">20</property>
    </default-config>
    <!-- 如果在代码中"ComboPooledDataSource ds=new ComboPooledDataSource("s1");"这样写就表示使用的是s1配置参数)-->
    <named-config name="s1">
        
    </named-config>
    
</c3p0-config>

另外,可以使用代码配置参数

// 可以使用代码配置参数
	dataSource = new ComboPooledDataSource();
	dataSource.setDriverClass("");
	dataSource.setJdbcUrl("");
	dataSource.setUser("");
	dataSource.setPassword("");
	dataSource.setMaxPoolSize(100);

工具类

import com.mchange.v2.c3p0.ComboPooledDataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;


public class C3p0 {
    private static ComboPooledDataSource dataSource = null;
    static {
        try{
            // 创建数据源
            dataSource = new ComboPooledDataSource();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 2.获取连接 因为getConnection()是接口的方法 所以下面跟DBCP一样都不用变
    public static Connection getConnection() throws Exception{
        return dataSource.getConnection();  // 从数据源中获取连接
    }
    // 3.释放资源
    public static void release(Connection conn, Statement st, ResultSet rs) throws SQLException {

        if (rs != null) {
            rs.close();
        }
        if (st != null){
            st.close();
        }
        if(conn != null){
            conn.close();
        }
    }
}

测试代码

import com.cheng.utils.C3p0;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class C3p0Test {
    public static void main(String[] args) throws SQLException {
        Connection conn =null;
        PreparedStatement ps = null;
        ResultSet rs =null;
        try {
            conn = C3p0.getConnection();
            String sql = "select * from users";
            ps=conn.prepareStatement(sql);
            rs=ps.executeQuery();
            while (rs.next()){
                System.out.println(rs.getString("NAME"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            C3p0.release(conn,ps,rs);
        }
    }
}

结论

无论使用什么数据源,本质是不变的,DateSource接口不会变,方法就不会变,因为这些开源数据源都是DateSource接口的实现。

举报

相关推荐

0 条评论