Java学习(二十)---JDBC

发布时间:2024年01月18日

数据库存取技术

?? ?1. JDBC直接访问数据库

?? ?2. JDO(java Data Object)技术

?? ?3. 第三方O/R工具:?? ??? ?如hibernate、spring等


JDBC介绍

概述

JDBC(Java DataBase Connectivity:Java语言连接数据库):

  • sun公司定义了一套操作所有关系型数据库的规则(接口),各个数据库厂商去实现这套数据库,并提供数据库驱动jar包。我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类;
  • 一种用于执行SQL语句的Java API(Application Programming Interface,应用程序设计接口),可以为多种关系型数据库提供统一访问,一组用Java语言编写的类和接口组成的;
  • 应用程序可以通过这套API联系到关系型数据库,并使用SQL语句来完成对数据库中的数据的查询、新增、更新和删除等操作。
  • 目标是使Java程序员使用JDBC可以连接任何提供JDBC驱动程序的数据库系统

好处

  • ? ??? ?JDBC使得编程人员从复杂的驱动器调用命令和函数中解脱出来,可以致力于应用程序中的关键地方。
  • ? ??? ?JDBC支持不同的关系数据库,这使得程序的可移植性大大加强。
  • ? ??? ?JDBC API是面向对象的,可以让用户把常用的方法封装为—个类,以备后用

缺点

  • ? ??? ?1.使用JDBC,访问数据记录的速度会受到一定程度的影响;
  • ?? ??? ?2.JDBC结构中包含不同厂家的产品,这就给更改数据源带来了很大的麻烦。

JDBC的体系结构

JDBC的结构可划分为两层:一个是面向底层的JDBC Driver Interface(驱动程序管理器接口),另一个是面向程序员的JDBC API。

JDBC程序编写步骤

1. 加载数据库驱动:在Java应用程序中,首先需要加载适当的数据库驱动程序。

2. 连接数据库:使用Java程序中的getConnection()方法与数据库建立连接。

3. 创建操作对象:使用Java程序中的Statement对象或者PreparedStatement对象来执行SQL语句。

4. 执行SQL语句:使用Statement对象或者PreparedStatement对象来执行SQL语句,在执行SQL语句之前,需要对SQL语句进行预编译。

5. 处理查询结果:使用ResultSet对象来处理从数据库返回的查询结果。

6. 释放资源:使用Java程序中的close()方法释放资源(ResultSet对象、Statement对象、Connection对象)。

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


@SuppressWarnings({"all"})
public class PreparedStatement_ {
    public static void main(String[] args) throws Exception {

        Scanner scanner = new Scanner(System.in);

        //让用户输入管理员名和密码
        System.out.print("请输入管理员的名字: ");  //next(): 当接收到 空格或者 '就是表示结束
        String admin_name = scanner.nextLine(); // 说明,如果希望看到SQL注入,这里需要用nextLine
        System.out.print("请输入管理员的密码: ");
        String admin_pwd = scanner.nextLine();

        //通过Properties对象获取配置文件的信息

        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\mysql.properties"));
        //获取相关的值
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String driver = properties.getProperty("driver");
        String url = properties.getProperty("url");

        //1. 注册驱动
        Class.forName(driver);//建议写上

        //2. 得到连接
        Connection connection = DriverManager.getConnection(url, user, password);

        //3. 得到PreparedStatement
        //3.1 组织SqL , Sql 语句的 ? 就相当于占位符
        String sql = "select name , pwd  from admin where name =? and pwd = ?";

        //3.2 preparedStatement 对象实现了 PreparedStatement 接口的实现类的对象
        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        //3.3 给 ? 赋值
        preparedStatement.setString(1, admin_name);
        preparedStatement.setString(2, admin_pwd);

        //4. 执行 select 语句使用  executeQuery
        //   如果执行的是 dml(update, insert ,delete) executeUpdate()
        //   这里执行 executeQuery ,不要在写 sql

        ResultSet resultSet = preparedStatement.executeQuery(sql);
        if (resultSet.next()) { //如果查询到一条记录,则说明该管理存在
            System.out.println("恭喜, 登录成功");
        } else {
            System.out.println("对不起,登录失败");
        }

        //4.关闭连接
        resultSet.close();
        preparedStatement.close();
        connection.close();
    }
}

JDBC的核心类库

包含在?java.sql,javax.sql包;

Connection接口

特定数据库的连接(会话)。在连接上下文中执行SQL语句并返回结果。

(1)获取执行sql的对象:即获取Statement对象

Statement接?: 编译执?静态SQL指令

//Conection接口源码
Statement createStatement() throws SQLException;

创建Statement对象
Statement statement = connection.createStatement();

//释放资源
connection.close

PreparedStatement接?:继承了Statement接?,预编译动态SQL指令(解决SQL注?问题)

//Conection接口源码
PreparedStatement prepareStatement(String sql)throws SQLException;

//创建PreparedStatement对象
PreparedStatement preparedStatement = connection.prepareStatement(sql);

//释放资源
connection.close


CallableStatement接?:继承了PreparedStatement接?,可以调?存储过程

//Conection接口源码
CallableStatement prepareCall(String sql) throws SQLException;

创建callableStatement对象
CallableStatement callableStatement = connection.prepareCall(sql);

//释放资源
connection.close

(2)事务管理

事务:一组逻辑操作单元,使数据从一种状态变换到另一种状态。

JDBC程序中当一个Connection对象创建时,默认情况下是自动提交事务;? ?每次执行一个SQL语句时,若执行成功,则会向数据库自动提交,而不能回滚;

若将多个SQL语句作为一个整体执行,需要使用事务;

//事务的实现流程:

获取数据库连接
conn = JDBCUtils.getConnection();

开启事务(关闭事务?动提交)
conn.setAutoCommit(false);

进行数据库操作
xxx

若有异常,则回滚事务
connection.rollback();

若没有异常,则提交事务
conn.commit();
//操作转账的业务
//1. 得到连接
Connection connection = null;

//2. 组织一个sql
String sql = "update account set balance = balance - 100 where id = 1";
String sql2 = "update account set balance = balance + 100 where id = 2";

PreparedStatement preparedStatement = null;

//3. 创建PreparedStatement 对象
try {
	connection = JDBCUtils.getConnection(); // 在默认情况下,connection是默认自动提交
	
	//将 connection 设置为不自动提交
	connection.setAutoCommit(false); //开启了事务
	preparedStatement = connection.prepareStatement(sql);
	preparedStatement.executeUpdate(); // 执行第1条sql

	int i = 1 / 0; //抛出异常
	preparedStatement = connection.prepareStatement(sql2);
	preparedStatement.executeUpdate(); // 执行第3条sql

	//这里提交事务
	connection.commit();

} catch (SQLException e) {
	//这里我们可以进行回滚,即撤销执行的SQL
	//默认回滚到事务开始的状态.
	System.out.println("执行发生了异常,撤销执行的sql");
	try {
		connection.rollback();
	} catch (SQLException throwables) {
		throwables.printStackTrace();
	}
	e.printStackTrace();
} finally {
	//关闭资源
	JDBCUtils.close(null, preparedStatement, connection);
}

?Statement接口

Statement对象:

  • 用于执行静态SQL语句并返回其生成的结果的对象;
  • 通过调用 Connection 对象的 createStatement() 方法创建该对象;
//String sql = "insert into actor values(null, '刘德华', '男', '1970-11-11', '110')";
//String sql = "update actor set name='周星驰' where id = 1";
String sql = "delete from actor where id = 1";

//statement 用于执行静态 SQL 语句并返回其生成的结果的对象
Statement statement = connect.createStatement();

// 执行DML(insert\delete\update)语句,DDL(create\drop\alter)的SQL指令(返回值是受影响行数)
int row = statement.executeUpdate(sql);

// 执?DQL操作的SQL指令(返回结果集)
ResultSet rs = statement.executeQuery(sql);

//可以执行任意的sql
boolean execute(String sql);

//释放资源
statement.close

缺点:

  • 必要情况下,需要拼串。
  • 存在SQL注入的问题;?? ?是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的SQL语句段或命令,攻击数据库。
-- SQL
-- 输入用户名 为 1' or
-- 输入万能密码 为 or '1'= '1

//4. 组织SqL
String sql = "select name , pwd  from admin where name ='" + admin_name + "' and pwd = '" + admin_pwd + "'";  //admin_name和admin_pwd为拼接到SQL中的变量

//使?字符串拼接变量的形式来设置SQL语句中的数据,可能会导致因变量值的改变引起SQL指令的原意发?改变,这就被称为SQL注?。
//SQL注?问题是需要避免的

当admin_name为1' or ;admin_pwd为or '1'= '1,SQL指令为:select name , pwd  from admin where name ='1' or ' and pwd = ' or '1'= '1';  //此时SQL中的条件则是?个恒等式(sql指令发生变化)

PreparedStatement(预处理)

基本介绍

  • ? ? ? ? PreparedStatement 对象:通过调用 Connection 对象的 preparedStatement(String sql) 方法获得;
  • ?? ??? ?预编译SQL,需要先写SQL,但是不执行
  • ?? ??? ?SQL 语句被预编译并存储在此对象中,可以使用此对象多次高效地执行该语句;

?? ?执行的Sql 语句中的参数用( ?)表示, ?相当于占位符

  • ?? ??? ?通过调用preparedStatement对象的setXxx()方法来设置这些参数
  • ?? ??? ?setXxx()方法有2个参数:第1个:设置参数的索引(从1开始);? ? ?第2个:设置SQL语句中的参数的值;

相较于Statetment的好处

  • ?? ?1.不用拼串,避免了sql注入问题,减少语法错误
  • ?? ?2.预编译sql语句,当执行批量操作时,效率要高于Statement
  • ?? ?3.方便来操作Blob类型的数据
  • ? ? 4.大大减少编译次数,效率较高
//得到PreparedStatement
//1 组织SqL , Sql 语句的 ? 就相当于占位符
String sql = "select name , pwd  from admin where name =? and pwd = ?";

//2 preparedStatement 对象实现了 PreparedStatement 接口的实现类的对象
//在获取PreparedStatement对象时,将SQL语句发送给mysql服务器进行检查,编译;若sql模板一样,则只需要进行一次检查、编译;
PreparedStatement preparedStatement = connection.prepareStatement(sql);

//3 给 ? 赋值
preparedStatement.setString(1, admin_name);
preparedStatement.setString(2, admin_pwd);

//4. 执行 select 语句使用  executeQuery(); 如果执行的是 dml(update, insert ,delete) executeUpdate(),无需传递SQL语句;

ResultSet resultSet = preparedStatement.executeQuery();

int row = preparedStatement.executeUpdate();


//释放资源
preparedStatement.close

其他操作

操作Blob类型的数据:

写入
	setBlob(int index,InputStream is)

读取
	Blob blob = getBlob(String columnLabel);

InputStream is = blob.getBinaryStream()

批处理:

使用批处理功能

  • ?? ?url参数后加参数:? ? ? ? ?rewriteBatchedStatements=true
  • ?? ?一般与PreparedStatement一起搭配使用,可减少编译次数,减少运行次数,提高效率

java 的批量更新机制

  • ?? ?使用mysql更新的驱动
  • ?? ?操作完,一次性提交

应用场景

  • ?? ?1.多条SQL语句的批量处理
  • ?? ?2.一个SQL语句的批量传参:把同一个SQL但参数不同的若干次操作合并为一个batch执行;
Connection connection = JDBCUtils.getConnection();

// 1.设置为不自动提交数据
connection.setAutocommit(false);

//实际上执行JDBC时,因为只有占位符参数不同,SQL实际上是一样的
String sql = "insert into admin2 values(null, ?, ?)";

PreparedStatement preparedStatement = connection.prepareStatement(sql);

System.out.println("开始执行");

 // 对同一个PreparedStatement反复设置参数并调用addBatch():
for (int i = 0; i < 5000; i++) {// 5000执行
	preparedStatement.setString(1, "jack" + i);
	preparedStatement.setString(2, "666");
	// 将sql 语句加入到批处理包中 -> 看源码

	// 1.“攒”sql
	preparedStatement.addBatch();

	// 当有1000条记录时,在批量执行
	if ((i + 1) % 1000 == 0) {// 满1000条sql

    //2. 执行batch:
    int[] ns = preparedStatement.executeBatch();

    //循环int[]数组即可获取每组参数执行后影响的结果数量。

	// 3.清空批处理包
	preparedStatement.clearBatch();

	}
}

// 2. 提交数据
connection.commit();

// 关闭连接
JDBCUtils.close(null, preparedStatement, connection);

CallableStatement接口

用于执行 SQL 存储过程。


ResultSet接口

基本介绍

  • ?? ?表示数据库结果集的数据表,通常通过执行查询数据库的语句生成;
  • ?? ?ResultSet对象保持一个光标指向其当前的数据行:最初,光标位于第一行之前;
  • ?? ?next方法将光标指向下一行,并且由于在ResultSet对象中没有更多行时返回false,因此可在while循环中使用循环遍历结果集

细节

  • ?? ?查询需要调用PreparedStatement 的 executeQuery() 方法,查询结果是一个ResultSet 对象
  • ?? ?ResultSet 对象以逻辑表格的形式封装了执行数据库操作的结果集;
  • ?? ?ResultSet 接口由数据库厂商提供实现;
  • ?? ?ResultSet 返回的实际上就是一张数据表,?有一个指针指向数据表的第一条记录的前面;
  • ?? ?当指针指向一行时, 可以通过调用 getXxx(int index) 或 getXxx(int columnName) 获取每一列的值

缺点

  • 结果集和connection是关联的,即关闭连接就不能使用结果集;
  • 结果集不利于数据管理【只能使用一次】;
  • 使用返回信息也不方便;
ResultSet resultSet = statement.executeQuery(sql);

//5. 使用while取出数据
while (resultSet.next()) { // 游标向下移动一行,判断当前行是否是最后一行末尾(是否有数据),如果是,返回false,否则返回true

	int id = resultSet.getInt(1); //获取该行的第1列

	//int id1 = resultSet.getInt("id"); 通过列名来获取值, 推荐

	String name = resultSet.getString(2);//获取该行的第2列
	String sex = resultSet.getString(3);
	Date date = resultSet.getDate(4);

	System.out.println(id + "\t" + name + "\t" + sex + "\t" + date);
}

//释放资源
resultSet.close

ResultSet底层结构:

ResultSet对象里面有一个rowData。数据实际在elementData中,因为查询的结果只有两行,所以elementData数组中只有两个,它们是以字节数组的形式出现的。其中internalRowData中出现了三个字节数组,则代表有三列。


DriverManager类 (驱动管理器)

介绍

java.sql.Driver 接口:

  • 所有 JDBC 驱动程序需要实现的接口;在程序中不需要直接去访问实现了 Driver 接口的类,而是由驱动程序管理器类(java.sql.DriverManager)去调用这些Driver实现。
  • Oracle的驱动:oracle.jdbc.driver.OracleDriver
  • mySql的驱动: com.mysql.jdbc.Driver

DriverManager类:负责管理JDBC驱动程序。使用JDBC驱动程序之前,必须先将驱动程序加载并注册后才可以使用,同时提供方法来建立与数据库的连接。

JDBC驱动包

?针对MySQL数据库,需要去Mysql官网下载JDBC的驱动包,驱动版本要与Mysql版本一一对应。Mysql驱动下载地址

导入jar包

//1.导入驱动jar包
// 在项目下创建一个文件夹比如 libs,将 mysql.jar 拷贝到该目录下,点击 add to project ..加入到项目中
//(在同一个项目中只需要导入一次驱动jar包即可)

如果是maven项目就更简单了,直接在pom配置文件中添加依赖:

<dependencies>
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
		<mysql.version>8.0.28</mysql.version>
	</dependency>
</dependencies>		

注册驱动

在Driver类中的静态初始化块中,注册驱动:DriverManager.registerDriver(new?Driver());

package com.mysql.jdbc;

import java.sql.DriverManager;
import java.sql.SQLException;

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }

    static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}

在我们的应?程序中?动注册驱动的代码也可以省略;【Class.forName(“com.mysql.cj.jdbc.Driver”);】

如果我们没有?动注册驱动,驱动管理器在获取连接的时候发现没有注册驱动 则读取 驱动jar/META-INF/servicesjava.sql.Driver?件中配置的驱动类路径进?注册 不推荐


JDBC URL

JDBC URL:用于标识一个被注册的驱动程序,驱动程序管理器通过这个 URL 选择正确的驱动程序,从而建立到数据库的连接。

JDBC URL的标准由三部分组成,各部分间用冒号分隔。jdbc:子协议:子名称;

  • 协议:JDBC URL中的协议总是jdbc
  • 子协议:子协议用于标识一个数据库驱动程序
  • 子名称:一种标识数据库的方法。子名称可以依不同的子协议而变化,用子名称的目的是为了定位数据库提供足够的信息。包含主机名(对应服务端的ip地址),端口号,数据库名
常见的数据库URL

jdbc:oracle:thin:@localhost:1521:testdb
jdbc:microsoft:sqlserver//localhost:1433; DatabaseName=testdb
jdbc:mysql://localhost:3306/testdb

注册连接实例:

连接方式1

静态加载Driver类;

//1. 注册驱动
Driver driver = new Driver(); //创建driver对象

//获取数据库连接
String url = "jdbc:mysql://localhost:3306/db_01?serverTimezone=UTC";
//将 用户名和密码放入到Properties 对象
Properties properties = new Properties();
properties.setProperty("user", "root");// 用户
properties.setProperty("password", "root123"); //密码

//3. 得到连接
Connection connect = driver.connect(url, properties);

mysql驱动是8.0版本,因此在url后边加了时区serverTimezone=UTC。

连接方式2

使用反射加载Driver类 , 动态加载,更加的灵活,减少依赖性;

//1. 注册驱动
//使用反射加载Driver类 , 动态加载,更加的灵活,减少依赖性
Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");
Driver driver = (Driver) aClass.newInstance();

//获取数据库连接
String url = "jdbc:mysql://localhost:3306/db_01";
//将 用户名和密码放入到Properties 对象
Properties properties = new Properties();
properties.setProperty("user", "root");// 用户
properties.setProperty("password", "root123"); //密码
		
//3. 得到连接
Connection connect = driver.connect(url, properties);

8.0版本对应的驱动类全类名是:com.mysql.cj.jdbc.Driver

5.7版本对应的全类名是:com.mysql.jdbc.Driver

连接方式3

使用DriverManager 替代 driver 进行统一管理;? ? 便于应对需要连接多个数据库的场景;

//1. 注册驱动
//使用反射加载Driver
Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");
Driver driver = (Driver) aClass.newInstance();

//获取数据库连接
//创建url 和 user 和 password
String url = "jdbc:mysql://localhost:3306/hsp_db02";
String user = "root";
String password = "hsp";
DriverManager.registerDriver(driver);//注册Driver驱动
		
//3. 得到连接
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println("第三种方式=" + connection);

这种方式没有创建properties对象,而且最后是使用DriverManager来获取Connection连接的。

连接方式4

使用Class.forName 自动完成注册驱动,简化代码;

//1. 注册驱动
Class.forName("com.mysql.jdbc.Driver");
//创建url 和 user 和 password
String url = "jdbc:mysql://localhost:3306/hsp_db02";
String user = "root";
String password = "hsp";

//3. 得到连接
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println("第4种方式~ " + connection);
连接方式5--推荐

在方式4的基础上改进,增加配置文件,让连接mysql更加灵活;

// 通过Properties对象获取配置文件的信息
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));

// 获取相关的值
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driver = properties.getProperty("driver");
String url = properties.getProperty("url");
Class.forName(driver);// 建议写上

// 3. 得到连接
Connection connection = DriverManager.getConnection(url, user, password);

System.out.println("方式5 "+connection);
}


//配置文件mysql.properties
user = root
password=root123
url=jdbc:mysql:// localhost:3306/db_01?rewriteBatchedStatements=true
driver=com.mysql.jdbc.Driver

SQLException类

有关数据库操作的异常。


JDBC工具类

创建JDBCUtils类,用于实现连接逻辑,交互配置文件等等

因为实际使用场景中,几乎数据库连接信息都不会写死在代码中,采用了配置文件的形式,使得灵活配置;

对于配置文件的实现:

  1. 创建Properties对象
  2. 创建流
  3. 加载流
  4. 通过Properties对象读取文件中的内容
  5. 关闭资源
配置文件mysql.properties
user=root
password=root123
url=jdbc:mysql://localhost:3306/db_01?rewriteBatchedStatements=true
driver=com.mysql.jdbc.Driver

//rewriteBatchedStatements表示使用PreparedStatement的批处理功能

JDBCUtils工具类详解:

  1. 在JdbcUtils的静态代码static代码块中读取db.properties中的相关配置,并加载驱动
  2. 每次使用JDBC之前都要获取getConnection连接,而获取连接的代码都是固定的,因此可以提取成一个公共方法getConnection;用户代码可以通过调用JdbcUtils.getConnection()来获取连接。
  3. 使用JDBCUtils工具类的优点,在我们有大量使用mysql的数据库的情况下,可以通过更改jdbc.properties配置文件就可以灵活修改数据库的配置,而不是寻找代码然后在一次次更改代码中的数据
  4. 查询操作中可以通过调用JdbcUtils.close(rs, stmt, conn)来关闭释放资源,而更新操作中可以通过调用JdbcUtils.close(stmt, conn)来释放资源

JDBCUtils.java代码示例:
?

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

//工具类,完成 mysql的连接和关闭资源

public class JDBCUtils {
    //定义相关的属性(4个), 因为只需要一份,因此,我们做出static
    private static String user; //用户名
    private static String password; //密码
    private static String url; //url
    private static String driver; //驱动名

    //在static代码块去初始化
    static {
        try {
            // 1.创建Properties对象
            Properties properties = new Properties();

            // 2.创建并加载流到Properties中
            properties.load(new FileInputStream("src\\mysql.properties"));

            //3.读取相关的属性值
            user = properties.getProperty("user");
            password = properties.getProperty("password");
            url = properties.getProperty("url");
            driver = properties.getProperty("driver");

        } catch (IOException e) {
            //在实际开发中,我们可以这样处理
            //1. 将编译异常转成 运行异常
            //2. 调用者,可以选择捕获该异常,也可以选择默认处理该异常,比较方便.
            throw new RuntimeException(e);
        }finally{
         //5.关闭资源
        if(properties !=null){
            try{
                properties.close();
                } catch(IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    //连接数据库, 返回Connection对象
    public static Connection getConnection() {
        try {
            return DriverManager.getConnection(url, user, password);
        } catch (SQLException e) {
            //1. 将编译异常转成 运行异常
            //2. 调用者,可以选择捕获该异常,也可以选择默认处理该异常,比较方便.
            throw new RuntimeException(e);
        }
    }

    //关闭相关资源
    /*
        1. ResultSet 结果集
        2. Statement 或者 PreparedStatement
        3. Connection
        4. 如果需要关闭资源,就传入对象,否则传入 null
     */
    public static void close(Connection connection, PreparedStatement ps) {

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

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

    public static void close(Connection connection, PreparedStatement ps, ResultSet rs) {
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        close(connection, ps);
    }
}

其他

数据类型转换

文章来源:https://blog.csdn.net/hahaha2221/article/details/135544278
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。