JDBC API 万字详解(通俗易懂)

news2024/11/24 8:51:55

目录

一、前言

二、JDBC API概述

三、获取连接的三种方式

        0.朝花夕拾 : 

        1.方式一 —— 通过new关键字 : 

        2.方式二 —— 通过反射机制 : 

        3.方式三 —— 通过DriverManager

                Δ方式三简化版

                Δ方式三优化版

四、 ResultSet

        1.简介 : 

        2.代码演示 : 

        3.底层实现 : 

五、SQL注入

        1.什么是SQL注入?

        2.SQL注入演示 : 

        3.PreparedStatement : 

                ①简介

                ②牛逼之处

                ③使用演示

六、总结 : 


一、前言

  • 第二节内容,up主要和大家分享一下JDBC——API方面的内容。
  • 注意事项——代码中的注释也很重要;不要眼高手低;点击文章的侧边栏目录或者文章开头的目录可以进行跳转。
  • 良工不示人以朴,所有文章都会适时补充完善。大家如果有问题都可以在评论区进行交流或者私信up。感谢阅读!

二、JDBC API概述

        JDBC API是一系列的接口,它统一和规范了应用程序与数据库的连接,执行SQL语句并得到返回结果等各类操作,相关类和接口在java.sqljavax.sql包下

                相关体系图如下(建议阅读完毕后返回来细品☕) : 


三、获取连接的三种方式

        0.朝花夕拾 : 

                上一小节内容中,我们提到了编写JDBC程序的核心四部曲,这里再来回顾一下——

  •         1° 注册驱动
  •         2° 获取连接
  •         3° 执行SQL
  •         4° 释放资源

                这里我们要重点再说一下第二个步骤——即获取数据库的连接

        1.方式一 —— 通过new关键字 : 

                这也是我们在第一小节中,演示第一个JDBC程序时用到的方法。即先通过com.mysql.cj.jdbc.Driver()来获取到Driver类对象,然后再通过Driver类中的connect方法来获取连接。connect方法的详细信息如下:

Connection connect(String url, Properties info) :需要传入一个包含数据库信息的url字符串对象,以及一个包含登录用户信息的Properties对象。

                这种方法有什么弊端?

                通过new的方法获取到Driver对象,Driver对象属于第三方,并且是静态加载,导致灵活性低,依赖性强

                up以JdbcConn类为演示类,来给大家演示一下第一种方式获取连接,其实就是把第一小节的程序演示再来一遍罢了(当然这里我们不会像第一小节讲那么细了)。
                代码如下 : 

package api.connection;

import com.mysql.cj.jdbc.Driver;
import org.testng.annotations.Test;

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

/**
 * @author : Cyan_RA9
 * @version : 21.0
 */
public class JdbcConn {
//演示JDBC连接数据库的三种方式
    //1.方式一 —— new关键字静态加载
    @Test
    public  void connection_1() throws SQLException {
        Driver driver = new Driver();

        String url = "jdbc:mysql://localhost:3306/jdbc_ex";
        Properties info = new Properties();
        info.setProperty("user","root");
        info.setProperty("password","RA9_Cyan");

        Connection connect = driver.connect(url, info);
        System.out.println("方式一获取到的连接 = " + connect);

        connect.close();
        System.out.println("--------------------------------------------------");

    }
}

                运行结果 : 

        2.方式二 —— 通过反射机制 : 

                提到了灵活性和依赖性,我们就不由得想到了反射机制。反射机制可以动态的加载和构建对象,属于动态加载,相比new关键字的方式具有更高的灵活性,同时也减低了依赖性。我们可以使用 Class.forName("com.mysql.cj.jdbc.Driver"); 来获取Driver类实例。

                up仍然以JdbcConn类为演示类代码如下 : 

package api.connection;

import com.mysql.cj.jdbc.Driver;
import org.testng.annotations.Test;

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

public class JdbcConn {
//演示JDBC连接数据库的三种方式
    //2.方式二 —— 反射机制
    @Test
    public void connection_2() throws ClassNotFoundException, InstantiationException, IllegalAccessException, SQLException {
        Class<?> clazz = Class.forName("com.mysql.cj.jdbc.Driver");

        Driver driver = (Driver) clazz.newInstance();

        String url = "jdbc:mysql://localhost:3306/jdbc_ex";
        Properties info = new Properties();
        info.setProperty("user", "root");
        info.setProperty("password", "RA9_Cyan");

        Connection connect = driver.connect(url, info);
        System.out.println("方式二获取到的连接 = " + connect);
        System.out.println("--------------------------------------------------");
    }
}

                运行结果 : 

        3.方式三 —— 通过DriverManager

                在反射机制的基础上,使用DriverManager替代Driver,进行统一管理,具有更好的拓展性。并且,单独定义url, user, password也具有更高的灵活性。
                需要用到DriverManager类的两个方法,如下——

  1. static void registerDriver(Driver driver) : 根据传入的Driver类对象,注册Driver驱动。
  2. static Connection getConnection(String url, String user, String password) : 根据传入的数据库URL,获取数据库连接。

                up仍然以JdbcConn类为演示类代码如下 : 

package api.connection;

import com.mysql.cj.jdbc.Driver;
import org.testng.annotations.Test;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;

public class JdbcConn {
//演示JDBC连接数据库的三种方式
    //方式三 —— 通过DriverManager
    @Test
    public void connection_3() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, SQLException {
        //使用反射机制加载Driver类
        Class<?> clazz = Class.forName("com.mysql.cj.jdbc.Driver");
        Constructor<?> constructor = clazz.getConstructor();
        Driver driver = (Driver) constructor.newInstance();

        //创建url,user,password
        String url = "jdbc:mysql://localhost:3306/jdbc_ex";
        String user = "root";
        String password = "RA9_Cyan";

        //注册Driver驱动
        DriverManager.registerDriver(driver);

        //获取连接
        Connection connection = DriverManager.getConnection(url, user, password);
        System.out.println("方式三获取到的连接= " + connection);
    }
}

                运行结果 : 

                Δ方式三简化版

                PS_1 :
                其实,在方式三的基础上,可以进行简化——
                通过Class.forName()方法动态加载Driver类后,不需要接收Class对象,也不需要获取构造器对象再得到Driver类对象。
                不需要通过DriverManager类的registerDriver方法来注册Driver驱动,即不需要注册驱动,而是直接通过getConnection方法来获取连接。

                仍然以JdbcConn类为演示类,代码如下 : 

package api.connection;

import com.mysql.cj.jdbc.Driver;
import org.testng.annotations.Test;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;

public class JdbcConn {
//演示JDBC连接数据库的三种方式
    //方式三 —— DriverManager(简化版)
    @Test
    public void connection_3() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, SQLException {
        Class.forName("com.mysql.cj.jdbc.Driver");
        
        String url = "jdbc:mysql://localhost:3306/jdbc_ex";
        String user = "root";
        String password = "RA9_Cyan";

        Connection connection = DriverManager.getConnection(url, user, password);
        System.out.println("方式三简化后得到的连接 = " + connection);
    }
}

                运行结果 : 

                可以看到, 简化后,整个代码简洁了许多。
                但是,这时候可能就要有p小将(Personable小将,指风度翩翩的人)出来bb问了:👴把编写JDBC程序的核心四部曲背的比家谱都熟,第一步就是注册驱动,好家伙,隔你这儿直接给省略了?给👴爬!

                p哥先息怒,其实这里之所以能顺利获取连接,是因为jvm底层做了优化,当Driver类被动态加载时,会自动帮我们注册Driver驱动,我们查看com.mysql.cj.jdbc.Driver类的源码,可以找到一个静态代码块如下 : 

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

                答案很明显了——当Driver类被动态加载时,静态代码块被执行。而静态代码块里的try语句中,调用了DriverManager类的registerDriver方法,完成了“注册驱动”的操作
                还要说明一点,这种“简化版”的第三种方式,是实际开发中用到最多的。

                PS_2 : 

                其实,在上述“简化版”的第三种方式中,就连调用forName的语句都可以省略。MySQL 5.1.6及以上版本无需使用forName语句;从JDK1.5以后使用了JDBC4,不再需要显示调用Class.forName(...)注册驱动,而是自动调用驱动,根据jar包下META-INF\services\java.sql.Driver文本中的类名称去注册,如下图所示 :

                但是,就像我们上面说的那样,“简化版”的方式三是实际开发中用到最多的方式,因此还是建议大家写上,以更明确。

                Δ方式三优化版

                在简化版的基础上,我们可以将url, user,以及password中的各种信息,诸如端口,数据库,用户名和用户密码等保存到properties配置文件中,使得我们的操作更加快捷和灵活。

                up先在JdbcConn类本包下,创建一个mysql.properties文件,如下图所示 : 

                JdbcConn类代码如下 : 

package api.connection;

import com.mysql.cj.jdbc.Driver;
import org.testng.annotations.Test;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;

public class JdbcConn {
//演示JDBC连接数据库的三种方式
    //方式三 —— DriverManager
    @Test
    public void connection_3() throws ClassNotFoundException, SQLException, IOException {
        //通过Properties对象获取配置文件信息
        Properties properties = new Properties();
        properties.load(new FileInputStream("src/api/connection/mysql.properties"));

        //通过获取到的配置文件信息,得到对应的值
        String driver = properties.getProperty("driver");
        String url = properties.getProperty("url");
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");

        //注册驱动
        Class.forName(driver);

        //获取连接
        Connection connection = DriverManager.getConnection(url, user, password);
        System.out.println("方式三优化后得到的连接 = " + connection);
    }
}

四、 ResultSet

        1.简介 : 

        ResultSet表示数据结果集的数据表,通常通过DQL(Data Query Language)来生成。ResultSet对象保持一个光标,该光标指向其当前的数据行最初,光标位于第一行之前,next方法会使光标移动到下一行,并且当ResultSet对象中没有更多行时返回false,因此可以使用While循环来遍历结果集。

        默认的ResultSet对象不可更新,并且只有一个向前移动的光标。因此,默认只能从第一行到最后一行迭代一次。但是,可以手动生成可滚动/可更新的ResultSet对象。

        PS_1 : 若有需求让光标向上移动一行,可以使用previous()方法;如果再往上没有行可以返回时,返回false。

        PS_2 : 使用getXxx()方法返回获得的记录(一行数据)中指定的字段,需要传入要获取的字段的索引(从1开始);或者也可以直接传入字段名。

        PS_3 : 若有需求以对象的形式来接收返回的字段,可以使用getObject(...)方法,传入的实参与getXxx方法一致。

        2.代码演示 : 

                根据对ResultSet的描述,我们不难会联想到迭代器的执行原理。只不过相比迭代器来说,ResultSet的next方法是把两件事都干了——判断和移动指针。

                现有一张学生表如下 :

                根据ResultSet结果集的简介,当我们通过while循环遍历结果集时,一开始ResultSet保持的光标位置会指在学生表第一条记录的上面,如下图所示 : 

                现在我们通过JDBC的方式查询这张表,up以ResultSet_Demo类为演示类,代码如下 : 

package api.resultSet;

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

public class ResultSet_Demo {
    public static void main(String[] args) throws IOException, ClassNotFoundException, SQLException {
    //编写JDBC程序核心四部曲:
        Properties properties = new Properties();
        properties.load(new FileInputStream("src/api/connection/mysql.properties"));
        String driver = properties.getProperty("driver");
        String url = properties.getProperty("url");
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");

        //1.注册驱动
        Class.forName(driver);

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

        //3.执行SQL
        Statement statement = connection.createStatement();
        String sql = "SELECT * FROM stus;";

        ResultSet resultSet = statement.executeQuery(sql);
        /**
            注意 : 执行DQL(数据查询语句)要使用Statement类中的executeQuery方法。
         */
        while (resultSet.next()) {  //使用while循环来遍历结果集
            //获取当前光标指向的记录的第一个字段
            int id = resultSet.getInt(1);
            //获取第二个字段
            String name = resultSet.getString(2);
            //获取第三个字段
            String sex = resultSet.getString(3);
            //获取第四个字段
            double score = resultSet.getDouble(4);
            /*打印获取的字段*/
            System.out.println(String.format("%d\t%5s\t%s\t%.2f", id,name,sex,score));
        }

        //4.释放资源
        resultSet.close();      //结果集也需要关闭!
        statement.close();
        connection.close();
    }
}

                运行结果 : 

        3.底层实现 : 

                接下来,我们通过Debug的方式看一下ResultSet类的源码,看看它底层到底是如何实现的。

                在返回结果集的代码行设置断点,进入Debug,如下图所示 : 

                可以发现ResultSet对象其实是一个ResultSet接口的实现类(JDBC规定要实现的接口),如下图所示 : 

                在该实现类的众多成员中,存放数据的成员是rowData,如下图所示 : 

                rowData其实是一个实现了ResultsetRows接口的ResultsetRowsStatic类的对象。而ResultsetRowsStatic类的成员rows才是真正存放表中数据的地方,它是一个ArrayList类对象,其中存放了表所有行的数据。

                可以看到,仍然是我们熟悉的elementData数组(up之前出过ArrayList类的源码分析,大家有兴趣可以去看看)。现在elementData数组中有四个元素,对应我们要查询的学生表中共四条记录。

                继续,elementData数组中元素的类型实际是ByteArrayRow类型,而ByteArrayRow类中有包含一个成员internalRowData,是一个byte类型的数组,如下图所示 : 

                这个byte数组中又有四个元素,是对应了我们学生表中的四个字段(id,name,sex,score),此处存放的是字段的值对应的ASCII码值


五、SQL注入

        1.什么是SQL注入?

        Statement也是JDBC规范的接口之一。用于执行静态SQL语句并返回其生成的结果的对象。

        在建立连接后,需要对数据库进行访问,执行SQL语句,可以通过Statement, PreparedStatement(预处理), 或者CallableStatement(存储过程)三种途径。

        但是,使用Statement会存在SQL注入的风险。所谓SQL注入,指的是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的SQL语句段或命令,恶意攻击数据库

        防范SQL注入可以使用PreparedStatement来取代Statement

        2.SQL注入演示 : 

                举一个简单的SQL注入的栗子,输入用户的用户名为:1' OR,输入用户的密码为:OR '1'  = '1。因为我们在WHERE子句中确定name和password时,会使用单引号。那么当我们以上述的用户名和密码来登录时,就会造成如下效果 : 

        ...WHERE name = '1' OR' AND password = 'OR '1' = '1';

        ...WHERE name = '1' OR' AND password = 'OR '1' = '1';

                可以看到,由于输入的用户名和密码中恶意使用了单引号,使得原来的条件验证被改成了条件1 OR 条件2 OR 条件3的格式,并且这里的条件3 —— '1' = '1'是永真式。 

                up以用户表users来演示(表示可登录的用户),创建表的代码如下 : 

CREATE TABLE IF NOT EXISTS `users`(
		`name` VARCHAR(32) NOT NULL,
		`password` VARCHAR(32) NOT NULL
) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin ENGINE INNODB;

INSERT INTO users
		VALUES
		('Ice', '12345'),
		('Bob', 'bbbbb');
		
SELECT * FROM users;

                users表效果如下 : 

                测试SQL注入,如下: 

SELECT * FROM users
		WHERE `name` = '1' OR'
		AND password = 'OR '1' = '1';

                查询结果如下 :  

                如果登录程序以“能否查询到表中的内容”为判定管理员是否存在,那么SQL注入的方式就可以顺利侵入数据库
                接下来我们使用Java程序来演示一下SQL注入
                up以Sumulation类为演示类,代码如下:

package api.sql_injection;

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

public class Simulation {
    public static void main(String[] args) throws ClassNotFoundException, SQLException, IOException {
    //核心四部曲
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入要登录用户的用户名:");
        String name = scanner.nextLine();
        System.out.println("请输入要登录用户的密  码:");
        String password_ex = scanner.nextLine();

        Properties properties = new Properties();
        properties.load(new FileInputStream("src/api/connection/mysql.properties"));
        String driver = properties.getProperty("driver");
        String url = properties.getProperty("url");
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");

        //1.注册驱动
        Class.forName(driver);

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

        //3.执行SQL
        String sql = "SELECT * FROM users " +
                        "WHERE `name` = '" + name + "'" +
                        "AND password = '" + password_ex + "';";
        Statement statement = connection.createStatement();
        ResultSet resultSet = statement.executeQuery(sql);

        /**
         * 认为 ———— 只要查询到表中的内容,就说明当前管理员是存在的,判定登录成功。
         */
        if (resultSet.next()) {
            System.out.println("Log on successfully!");
        } else {
            System.out.println("Failed to log on!");
        }

        //4.释放资源
        resultSet.close();
        statement.close();
        connection.close();
        scanner.close();
    }
}

                运行结果 : 

        3.PreparedStatement : 

                ①简介

        PreparedStatement也是一个接口,并且是Statement接口的子接口,因此也可以使用Statement接口中的一些方法。 

        PreparedStatement执行的SQL语句中的参数用?来表示(?表示占位符),通过调用该类的setXxx方法来设置这些参数。如下图所示 : 

        可以看到,这些setXxx方法均有两个形参。其中,第一个形参均为int类型,代表了要设置的参数在对应SQL语句中存在的位置(从1开始)第二个形参便是具体要设置的值

        PS : 

        1>同Statement类似,调用executeQuery()方法来执行DQL(查),返回ResultSet对象;而调用executeUpdate()来执行DML(增,删,改),返回int类型的受影响的行数

        2>获取PreparedStatement时,直接传入要执行的SQL字符串,使两者关联;之后调用executeQuery和executeUpdate方法时,不再需要传入形参。

                ②牛逼之处

  • ----->不再需要使用+拼接SQL语句,减少了编程时的语法错误;
  • ----->有效解决了SQL注入的问题;
  • ----->大大减少了编译次数,执行效率较高。 

                ③使用演示

                up以Prepared_Demo类作为演示类,代码如下 : 

package api.sql_injection;

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

/**
 * @author : Cyan_RA9
 * @version : 21.0
 */
public class PreparedStatement_Demo {
    public static void main(String[] args) throws IOException, ClassNotFoundException, SQLException {
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入用户名:");
        String name = scanner.nextLine();
        System.out.println("请输入密  码:");
        String password_ex = scanner.nextLine();

        Properties properties = new Properties();
        properties.load(new FileInputStream("src/api/connection/mysql.properties"));
        String driver = properties.getProperty("driver");
        String url = properties.getProperty("url");
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");

    //JDBC核心四部曲
        //1.注册驱动
        Class.forName(driver);

        //2.获取连接
        Connection connection = DriverManager.getConnection(url, user, password);
        String sql = "SELECT * FROM users " +
                        "WHERE `name` = ? " +
                        "AND password = ? ;";
        PreparedStatement ps = connection.prepareStatement(sql);
        ps.setString(1, name);
        ps.setString(2, password_ex);

        //3.执行SQL
        ResultSet resultSet = ps.executeQuery();
        if (resultSet.next()) {
            System.out.println("Log on successfully!");
        } else {
            System.out.println("Failed to log on!");
        }

        //4.释放资源
        resultSet.close();
        ps.close();
        connection.close();
        scanner.close();
    }
}

                运行效果 : 
                我们先来测试一下输入正确的用户 :

                再来测试一下SQL注入,如下图所示 : 

                可以看到,使用PreparedStatement代替Statement后,SQL注入被成功拦截
                对于PreparedStatement执行DML的情况,很简单,大家可以自己去试试,改用executeUpdate方法,把ResultSet去掉,用int类型的变量做接收。非常容易,这里不做演示。


六、总结 : 

  • 🆗,以上就是JDBC 第二节的全部内容了。
  • 总结一下,我们在日常开发中最终要使用的JDBC连接方式,就是方式三(DriverManager)的简化版的优化版,以核心四部曲为框架,即——直接使用Class.forName(...)的反射形式动态加载Driver类,底层自动完成注册驱动的操作;使用DriverManager类的getConnection方法来获取连接(传入的参数从properties配置文件获得);使用PreparedStatement来执行SQL;释放资源。
  • 下一节内容——JDBC Utils,我们不见不散。感谢阅读!

        System.out.println("END------------------------------------------------------------------------------"); 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/624859.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

20分钟搞定 Stable Diffusion 模型在线服务部署

文章目录 AIGC之 AI 绘画20分钟搞定 Stable Diffusion 模型在线服务部署认识 Amazon SageMaker借助 Amazon SageMaker 进行环境搭建和模型推理1. 创建 jupyter notebook 运行环境2. 一键运行所有代码 关键代码分析如下1. 环境准备&#xff0c;代码模型下载2. 在Notebook中配置并…

Next 主题配置

当前用得最多的是next主题&#xff0c;那为什么用得多呢&#xff1f;当然是符合大多数人的审美。我使用的是next(v7.8.0)&#xff0c;下载地址&#xff1a;theme-next/hexo-theme-next 1 基本设置 1.1 主题设置 打开博客根目录 Blog 文件夹&#xff0c;右键 Git Bash&#xf…

制作投票链接怎么做微信上投票连接怎么样做投票链接

关于微信投票&#xff0c;我们现在用的最多的就是小程序投票&#xff0c;今天的网络投票&#xff0c;在这里会教大家如何用“活动星投票”小程序来进行投票。 我们现在要以“小手绘盛夏”为主题进行一次投票活动&#xff0c;我们可以在在微信小程序搜索&#xff0c;“活动星投票…

RuoYi-Cloud本地运行

启动nacos bin目录打开cmd&#xff0c;输入startup.cmd -m standalone&#xff0c;单机模式启动 http://localhost:8848/nacos/ 用户名密码都是nacos 启动redis 在 D:\Redis-x64-3.2.100 目录下 按shift鼠标右键&#xff0c;打开powerShell&#xff0c;输入 .\redis-server…

2023最新版本Activiti7系列-流程变量

流程变量 流程变量可以用将数据添加到流程的运行时状态中&#xff0c;或者更具体地说&#xff0c;变量作用域中。改变实体的各种API可以用来更新这些附加的变量。一般来说&#xff0c;一个变量由一个名称和一个值组成。名称用于在整个流程中识别变量。例如&#xff0c;如果一个…

在 Android 上恢复已删除的通话记录 - 安卓手机通话记录恢复技巧

有时&#xff0c;Android 用户会在内存空间用完时删除他们的通话记录。他们认为那些电话通话记录将不再需要了&#xff0c;但突然出于某些原因他们需要恢复那些已删除的通话记录。 恢复已删除的照片、视频、音乐、短信和通话记录等数据在以前是一件很难的事情。但是现在如果你…

# 蓝牙音频相关知识

蓝牙音频相关知识 文章目录 蓝牙音频相关知识1 音频源2 蓝牙音频编解码器3 一些标准4 蓝牙音频其他相关知识4.1 蓝牙版本4.2 ANC&#xff08;主动降噪&#xff09;4.3 音响相关参数4.4 音质评价4.5 HI-Fi声音特点4.6 耳机线材4.7 耳机分类4.8 IP防尘防水等级4.9 噪音与量化噪音…

如何用Python语言调取百度翻译的API

诸神缄默不语-个人CSDN博文目录 本文介绍如何用Python语言调用百度翻译的API服务。 理论上类似的写法也可以应用于其他语言。 http://api.fanyi.baidu.com/manage/developer可以注册开发者&#xff0c;或者APP ID和秘钥&#xff1b;然后还可以进行开发者认证&#xff0c;获得…

ajax-1--XML、AJAX简介、express框架使用、AJAX操作的基本步骤

一、XML&#xff08;可扩展标记语言&#xff09; XML与HTML类似&#xff0c;不同的是HTML中都是预定义标签&#xff0c;而XML中没有预定义标签&#xff0c;全都是自定义标签&#xff0c;用来表示一些数据。 比如有一个学生数据&#xff1a;name“孙悟空”;age18;gender“男”&a…

【Python实战】Python采集大学教务系统成绩单

前言 在现代教育中&#xff0c;教务系统已经成为了学校管理和教学工作的重要组成部分。然而&#xff0c;由于各种原因&#xff0c;教务系统的成绩单并不能下载的&#xff0c;这给我们带来了很多不必要的麻烦和困扰。因此&#xff0c;采集教务系统成绩单的项目具有非常重要的意义…

【轻量化网络系列(6)】EfficientNetV1论文超详细解读(翻译 +学习笔记+代码实现)

前言 EfficientNetV1是Google在2019年发布的文章&#xff0c;这篇论文最主要的创新点是Model Scaling。论文提出了compound scaling&#xff0c;混合缩放&#xff0c;把网络缩放的三种方式&#xff1a;深度、宽度、分辨率&#xff0c;组合起来按照一定规则缩放&#xff0c;从…

使用FPGA驱动GS2972(3G-SDI模式)外同步(HSYNC VSYNC DE)或内(BT1120)同步输出彩条调试

GS2972视频输出调试 一、外同步模式1.1 GS2972的硬件初始化1.2 GS2972的驱动时序1.3 GS2972的驱动RTL代码1.4 GS2972输出彩条1.5 GS2972驱动易出bug二、数据内嵌同步模式2.1 GS2972的硬件初始化2.2 GS2972的驱动时序2.3 GS2972的驱动RTL代码2.4 GS2972输出彩条2.5 GS2972驱动易…

IIC总线简介及IIC的通信过程与时序

1.IIC总线简介 IIC总线&#xff1a; IIC总线是Philips公司在八十年代初推出的一种串行、半双工总线&#xff0c;主要用于近距离、低速的芯片之间的通信&#xff1b;IIC总线有两根双向的信号线&#xff0c;一根数据线SDA用于收发数据&#xff0c;一根时钟线SCL用于通信双方时钟…

跳槽一次涨8k,5年跳了3次...

最近有人说&#xff0c;现在软件测试岗位初始工资太低了&#xff0c;有些刚刚入行的测试朋友说自己工资连5位数都没有…干了好几年也没怎么涨。 看看别人动辄月薪1-2万&#xff0c;其实我想说也没那么难。 说下如何高效地拿到3万的工资&#xff0c;总体来说&#xff0c;就靠跳…

墨天轮关系型分布式数据库榜单解读

分布式关系型数据库概述 作为数据库演进方向之一&#xff0c;分布式能力受到更多用户的关注。从技术架构演进来看&#xff0c;数据库正走过了从单机式、集中式到分布式的发展过程&#xff0c;目前是多种架构并存的阶段。分布式架构以其更好的存储与计算扩展能力&#xff0c;受…

四六位、不定长、计算题等验证码,一款工具全部搞定,简单方便还开箱即用,精度高达96%!!

四六位、不定长、计算题等验证码,第三方平台准确率低,自己不会神经网络训练,嫌麻烦,一款工具全部搞定,开箱即用!! 本文秉承着开箱即用的原则,真正做到一款工具识别秒杀所有单行文本验证码,这里的单行指的就是某张4位6位或者计算题或者文字的验证码,我是使用了4位+6位…

Vue2数据代理的理解

理解 最近在复习Vue2&#xff0c;对于数据代理有些模糊。查阅了一些资料。 这是官方解释https://v2.cn.vuejs.org/v2/api/#data数据代理&#xff1a;通过代理对象 操作&#xff08;读/写&#xff09;目标对象中的属性 这里出现了两个对象&#xff0c;一个叫代理对象&#xf…

VMware虚拟机安装Windows Server 2003

哈喽&#xff0c;各位小伙伴大家好&#xff0c;win server2003算是拖了比较久了&#xff0c;一直没发布&#xff0c;今天完成server2003的安装后server系列的安装教程就告一段落了。马上暑假了&#xff0c;想必新生开学之后&#xff0c;很多计算机网络专业的同学们要开始接触到…

全志V3S嵌入式驱动开发(sd卡驱动)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 之前开发过程中&#xff0c;整个linux的启动都是基于sd卡进行的。此外&#xff0c;我们有这么几点发现&#xff0c;首先&#xff0c;v3s本身是识别…

Python3+Selenium2完整的自动化测试实现之旅(五):自动化测试框架、Python面向对象以及POM设计模型简介

目录 前言 1 自动化测试框架概述 2 自动化测试框架需要的环境 3 自动化测试框架设计思想&#xff1a;Python面向对象 4 自动化测试框架设计思想&#xff1a;POM&#xff08;Page Object Model&#xff09;页面对象模型 【自动化测试工程师学习路线】 结语 前言 之前…