文章目录

  • 一、JDBC概述
  • 二、获取数据库连接
    • 2.1 Driver接口及实现类
      • 2.1.1 Driver接口介绍
      • 2.1.2 加载JDBC驱动(Driver实现类)
    • 2.2 数据库五种连接方式(迭代)
    • 2.3 URL
  • 三、PreparedStatement vs Statement
    • 3.1 使用Statement操作数据表的弊端及缺点
    • 3.2 PreparedStatement相比Statement的优点
  • 四、使用PreparedStatement实现CRUD操作
    • 4.1 操作和访问数据库
    • 4.2 使用PreparedStatement实现增、删、改操作(迭代)
    • 4.3 使用PreparedStatement实现查询操作(迭代)
      • 4.3.1 Java与SQL对应数据类型转换表
      • 4.3.2 ResultSet与ResultSetMetaData
      • 4.3.3 表的的查询操作(迭代)
    • 4.4 资源的释放
    • 4.5 JDBC API小结
  • 五、操作BLOB类型字段
    • 5.1 MySQL BLOB类型
    • 5.2 向数据表中插入大数据类型
    • 5.2 从数据表中读取大数据类型
  • 六、批量操作
    • 6.1 批量处理SQL语句
    • 6.2 高效的批量插入(迭代)
  • 七、数据库事务
    • 7.1 数据库事务介绍
    • 7.2 JDBC事务处理
    • 7.3 事务的ACID属性
      • 7.3.1 数据库的并发问题
      • 7.3.2 四种隔离级别
      • 7.3.3 在Java中设置隔离级别解决并发问题
  • 八、数据库连接池
    • 8.1 JDBC数据库连接池的必要性
    • 8.2 数据库连接池技术
      • 8.2.1 Druid(德鲁伊)数据库连接池
      • 8.3 使用德鲁伊连接池替换DriverManager获取连接
  • 九、Apache-DBUtils实现CRUD
    • 9.1 主要API介绍
      • 9.2 使用DbUtils替换JDBCUtils.java关闭资源
      • 9.2 使用QueryRunner类替换通用的增删改查
  • 十、Dao设计模式
    • 使用JUnit自动生成测试类

JDBC是java访问数据库的基石,JDO、Hibernate、MyBatis等都封装了JDBC。如果只单纯使用持久层框架,可以不用学习JDBC,但从程序员发展前景来看则必须要掌握的,框架会一直迭代更新,但原理是不变的,掌握JDBC,才能以不变应完变,这样才能走的长久

一、JDBC概述

JDBC:sun公司提供一套用于不局限某种特定的数据库操作的接口,不同的数据库厂商,需要针对这套接口,提供不同实现(实现类)。不同的实现的集合,即为不同数据库的驱动。所以java程序员只需要面向这套接口编程即可(即面向接口编程,java代码中不应该出现第三方代码)。

例如,在标准类库中定义了java.sql,javax.sql用来访问数据库的标准Java类库,使用这些类库可以以一种标准的方法、方便地访问数据库资源。而不同的数据库厂商,需要根据这一标准实现自己的数据库操作方式(驱动)

JDBC的目的是方便操作不同的数据库,进行数据的持久化

持久化(persistence): 把数据保存到可掉电式存储设备中以供之后使用,例如磁盘文件,XML文件,数据库

在这里插入图片描述

在没有JDBC时,由于不同的数据库的操作方式不同,这样每当切换数据库时,原有的Java代码就需要修改,可移植性很差,如下

在这里插入图片描述
有了JDBC,Java程序访问数据库时是这样的:

  • 创建标准类库中的Driver接口的实现类(某个数据库厂商提供的驱动)对象,赋给接口对象,实现上转型
  • 调用Driver接口的connect方法获取连接,由于多态和动态绑定,实际调用的是实现类的方法(某个数据库厂商驱动重写的connect方法)

在这里插入图片描述
JDBC程序编写步骤

在这里插入图片描述

补充:ODBC(Open Database Connectivity,开放式数据库连接),是微软在Windows平台下推出的。使用者在程序中只需要调用ODBC API,由 ODBC 驱动程序将调用转换成为对特定的数据库的调用请求。

二、获取数据库连接

2.1 Driver接口及实现类

2.1.1 Driver接口介绍

java.sql.Driver 接口是所有 JDBC 驱动程序需要实现的接口。这个接口是提供给数据库厂商使用的,不同数据库厂商提供不同的实现。

  • Oracle的驱动:oracle.jdbc.driver.OracleDriver
  • mySql的驱动: com.mysql.jdbc.Driver

java连接数据库需现在加载数据库驱动Driver(标准类库中的Driver接口的实现类),因此需要导入相关数据库的驱动jar包,这里已mysql为例,驱动下载地址

在这里插入图片描述
将上述jar包拷贝到Java工程的一个目录中,习惯上新建一个lib文件夹。

在这里插入图片描述
在这里插入图片描述

2.1.2 加载JDBC驱动(Driver实现类)

加载 JDBC 驱动需调用 Class 类的静态方法 forName(),向其传递要加载的 JDBC 驱动的类名

  • Class.forName(“com.mysql.jdbc.Driver”);

2.2 数据库五种连接方式(迭代)

连接方式一

// 方式一:
@Test
public void testConnection1() throws SQLException {//1.创建java.sql.Driver接口实现类的对象,并赋给接口对象driver,实现上转型Driver driver = new com.mysql.jdbc.Driver();// 2.提供url,指明具体操作的数据String url = "jdbc:mysql://localhost:3306/test";//3.提供Properties的对象,指明用户名和密码Properties info = new Properties();info.setProperty("user", "root");info.setProperty("password", "12345");//4.调用driver的connect(),获取连接Connection connect = driver.connect(url, info);System.out.println(connect);
}

方式一中显式出现了第三方数据库的API,数据库相关数据硬编码在代码中,耦合性大,每当切换数据库驱动或修改数据时,就需要修改源代码

连接方式二

// 方式二:对方式一的迭代:在如下的程序中不出现第三方的api,使得程序具有更好的可移植性
@Test
public void testConnection2() throws Exception {// 1.使用反射,加载Driver实现类对象:Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");// 2.创建驱动(实现类)对象Driver driver = (Driver) aClass.newInstance();String url = "jdbc:mysql://localhost:3306/test";String user= "root";String password = "12345";Properties info = new Properties();info.setProperty("user", user);info.setProperty("password", password);Connection connect = driver.connect(url, info);System.out.println(connect);
}

相较于方式一,这里仅仅使用反射实例化Driver,不在代码中体现第三方数据库的API。

连接方式三

// 方式三:使用DriverManager替换Driver
@Test
public void testConnection3() throws Exception {String url = "jdbc:mysql://localhost:3306/test";String user = "root";String password = "12345";Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");Driver driver = (Driver) aClass.newInstance();// 注册驱动DriverManager.registerDriver(driver);Connection connection = DriverManager.getConnection(url, user, password);System.out.println(connection);
}

相较于方式二,这里不需要直接去访问实现了 Driver 接口的类(即通过Driver获取连接),而是在驱动程序管理器类(java.sql.DriverManager)中注册驱动Driver,然后去调用这些Driver实现(即通过DriverManager获取数据库连接)。

连接方式四

// 方式四:可以只是加载驱动,不用显示的注册驱动。
@Test
public void testConnection4() throws Exception {String url = "jdbc:mysql://localhost:3306/test";String user = "root";String password = "12345";Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");Connection connection = DriverManager.getConnection(url, user, password);System.out.println(connection);
}

不必显式的创建Driver对象和注册驱动了。因为在Driver的源码中已经存在静态代码块(静态代码快回随着类的加载而加载),实现了Driver对象的创建以及对驱动的注册。
在这里插入图片描述

连接方式五(最终版)

上面四种方式,都是把数据库相关信息硬编码在代码中,这样每当需要修改数据库信息,就需要修改代码。

例如在打包项目部署时,如果修改的源码,就需要重新打包项目,项目大的情况下,是很费时的

下面采用的方式五,是把代码和数据的分离,使用配置文件的方式保存数据,在代码中加载配置文件,好处如下

  • 如果需要修改配置信息,直接在配置文件中修改,不需要深入代码
  • 如果修改了配置信息,省去重新编译的过程(即重新打包)。

在工程的src目录下创建配置文件:【jdbc.properties】

在这里插入图片描述

user=root
password=abc123
url=jdbc:mysql://localhost:3306/test
driverClass=com.mysql.jdbc.Driver
//方式五(final版):将数据库连接需要的4个基本信息声明在配置文件中,通过读取配置文件的方式,获取连接
/*
* 此种方式的好处?
* 1.实现了数据与代码的分离。实现了解耦
* 2.如果需要修改配置文件信息,可以避免程序重新打包。
*/
@Test
public void testConnection5() throws Exception {//1.加载配置文件InputStream is = ConnectionTest.class.getClassLoader().getResourceAsStream("jdbc.properties");Properties properties = new Properties();properties.load(is);//2.读取配置信息String classDriver = properties.getProperty("driverClass");String url = properties.getProperty("url");String user = properties.getProperty("user");String password = properties.getProperty("password");//3.加载驱动Class.forName(classDriver);//4.获取连接Connection connection = DriverManager.getConnection(url, user, password);System.out.println(connection);
}

相较于其他方式,方式五是通过系统类加载器的getResourceAsStream方法获取类路径下配置文件的输入流来加载配置文件信息的

2.3 URL

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

JDBC URL的标准由三部分组成,各部分间用冒号分隔。

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

举例:

在这里插入图片描述
几种常用数据库的 JDBC URL

MySQL的连接URL编写方式:jdbc:mysql://主机名称:mysql服务端口号/数据库名称?参数=值&参数=值

  • jdbc:mysql://localhost:3306/zhuo(连接到指定得数据库zhuo)
  • jdbc:mysql://localhost:3306/atguigu?useUnicode=true&characterEncoding=utf8(如果JDBC程序与服务器端的字符集不一致,会导致乱码,那么可以通过参数指定服务器端的字符集)
  • jdbc:mysql://localhost:3306/atguigu?user=root&password=123456(连接数据库时指定用户和密码)

Oracle 9i的连接URL编写方式:jdbc:oracle:thin:@主机名称:oracle服务端口号:数据库名称

  • jdbc:oracle:thin:@localhost:1521:zhuo

SQLServer的连接URL编写方式:jdbc:sqlserver://主机名称:sqlserver服务端口号:DatabaseName=数据库名称

  • jdbc:sqlserver://localhost:1433:DatabaseName=zhuo

三、PreparedStatement vs Statement

3.1 使用Statement操作数据表的弊端及缺点

通过调用 Connection 对象的 createStatement() 方法创建该对象。该对象用于执行静态的 SQL 语句,并且返回执行结果。
Statement 接口中定义了下列方法用于执行 SQL 语句:

  • int excuteUpdate(String sql):执行更新操作INSERT、UPDATE、DELETE
  • ResultSet executeQuery(String sql):执行查询操作SELECT

使用Statement操作数据表的弊端:

  • 问题一:存在拼串操作,繁琐
  • 问题二:存在SQL注入问题

SQL 注入是利用某些系统没有对用户输入的数据进行充分的检查,例如在用户登录的sql检索语句中,可以利用拼串来绕过用户登录检查

用户的登录检索语句
在这里插入图片描述使用拼串绕过检查

在这里插入图片描述

代码演示

public class StatementTest {// 使用Statement的弊端:需要拼写sql语句,并且存在SQL注入的问题public static void main(String[] args) {Scanner scan = new Scanner(System.in);System.out.print("用户名:");String userName = scan.nextLine();System.out.print("密   码:");String password = scan.nextLine();String sql = "SELECT user,password FROM user_table WHERE USER = '1' or ' AND PASSWORD = '='1' or '1' = '1'";User user = get(sql, User.class);if (user != null) {System.out.println("登陆成功!");} else {System.out.println("用户名或密码错误!");}}// 使用Statement实现对数据表的查询操作public static <T> T get(String sql, Class<T> clazz) {T t = null;Connection conn = null;Statement st = null;ResultSet rs = null;try {// 1.加载配置文件InputStream is = StatementTest.class.getClassLoader().getResourceAsStream("jdbc.properties");Properties pros = new Properties();pros.load(is);// 2.读取配置信息String user = pros.getProperty("user");String password = pros.getProperty("password");String url = pros.getProperty("url");String driverClass = pros.getProperty("driverClass");// 3.加载驱动Class.forName(driverClass);// 4.获取连接conn = DriverManager.getConnection(url, user, password);st = conn.createStatement();rs = st.executeQuery(sql);// 获取结果集的元数据ResultSetMetaData rsmd = rs.getMetaData();// 获取结果集的列数int columnCount = rsmd.getColumnCount();if (rs.next()) {t = clazz.newInstance();for (int i = 0; i < columnCount; i++) {// 1. 获取列的别名String columnName = rsmd.getColumnLabel(i + 1);// 2. 根据列名获取对应数据表中的数据Object columnVal = rs.getObject(columnName);// 3. 将数据表中得到的数据,封装进对象Field field = clazz.getDeclaredField(columnName);field.setAccessible(true);field.set(t, columnVal);}return t;}} catch (Exception e) {e.printStackTrace();} finally {// 关闭资源if (rs != null) {try {rs.close();} catch (SQLException e) {e.printStackTrace();}}if (st != null) {try {st.close();} catch (SQLException e) {e.printStackTrace();}}if (conn != null) {try {conn.close();} catch (SQLException e) {e.printStackTrace();}}}return null;}
}

Statement语句中,由于Statement是充当信使的,不能预编译,即每当有一条Statement语句,它便会送到数据库中执行,也就是说用户可以利用拼串来注入sql过滤条件来绕过检查。

使用Statement操作数据表的缺点:

不能操作Blog型数据

Statement操作数据库中,Statement填充数据只能通过拼串,而BLOB类型的数据无法使用字符串拼接写的(blog数据需要填充IO流)

批量操作性能差

Statement语句中,Statement是不能预编译的,即使是相同操作,但由于数据内容不一样,整个语句本身还是不能匹配,因此没有缓存语句的意义,批量操作性能差

事实上没有任何数据库会对普通语句编译后的执行代码缓存(即Statement送到数据库的sql语句)。这样每执行一次Statement语句(sql)都要对传入的语句编译一次。

3.2 PreparedStatement相比Statement的优点

PreparedStatement

  • 可以通过调用 Connection 对象的 preparedStatement(String sql)方法获取 PreparedStatement 对象
  • PreparedStatement 接口是 Statement 的子接口,它表示一条预编译过的 SQL 语句
  • PreparedStatement 对象所代表的 SQL 语句中的参数用问号(?)来表示,调用 PreparedStatement 对象的 setXxx() 方法来设置这些参数. setXxx() 方法有两个参数,第一个参数是要设置的 SQL 语句中的参数的索引(从 1 开始),第二个是设置的 SQL 语句中的参数的值

PreparedStatement 可以防止 SQL 注入

PreparedStatement语句是预编译语句,那为什么可以防止SQL注入呢?

因为PreparedStatement不再通过拼串的方式注入数据,而是通过占位符的方式来填充数据,这样当sql被预编译后,其sql语句就可以被确定下来,不管后面如何填充数据,最终的数据都会被认为是数据,而不是关键字(sql过滤条件等)

在这里插入图片描述

PreparedStatement可以实现更高效的批量操作

DBServer会对预编译语句提供性能优化。因为预编译语句有可能被重复调用,所以语句在被DBServer的编译器编译后的执行代码被缓存下来,那么下次调用时只要是相同的预编译语句就不需要编译,只要将参数直接传入编译过的语句执行代码中就会得到执行。

而批量操作执行的预编译语句是相同的,只是填充的数据不同,因此会被缓存下来,这样就可以实现更高效的批量操作

PreparedStatement可以操作Blog型数据

PreparedStatement是通过占位符来填充数据的,因此可以给单独的blog字段通过io流的方式写入数据

四、使用PreparedStatement实现CRUD操作

4.1 操作和访问数据库

数据库连接被用于向数据库服务器发送命令和 SQL 语句,并接受数据库服务器返回的结果。其实一个数据库连接就是一个Socket连接。

java.sql 包中有 3 个接口分别定义了对数据库的调用的不同方式:

  • Statement: 用于执行静态 SQL 语句并返回它所生成结果的对象(有sql注入风险,已被PrepatedStatement替代)。
  • PrepatedStatement: SQL 语句被预编译并存储在此对象中,可以使用此对象多次高效地执行该语句。
  • CallableStatement: 用于执行 SQL 存储过程

在这里插入图片描述

4.2 使用PreparedStatement实现增、删、改操作(迭代)

由于增删改操作基本是一样的,所有下面采用insert语句来演示增删改查的迭代

版本一

@Testpublic void testInsert()  {Connection connection = null;PreparedStatement preparedStatement = null;try {// 获取系统类加载器,调用系统类加载得器得getResourceAsStream方式加载类路径的文件获取输入流InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");// 获取配置文件信息Properties properties = new Properties();properties.load(is);String driverClass = properties.getProperty("driverClass");String url = properties.getProperty("url");String user = properties.getProperty("user");String password = properties.getProperty("password");// 加载驱动Class.forName(driverClass);// 调用驱动管理器获取数据库连接connection = DriverManager.getConnection(url, user, password);String sql = "insert into customers(name,email,birth) value(?,?,?)";// 预编译sqlpreparedStatement = connection.prepareStatement(sql);// 填充占位符preparedStatement.setString(1, "迪丽热巴");preparedStatement.setString(2, "stormzhuo@163.com");SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");Date date = simpleDateFormat.parse("1998-07-07");preparedStatement.setDate(3, new java.sql.Date(date.getTime()));// 执行sql语句preparedStatement.execute();} catch (Exception e) {e.printStackTrace();} finally {try {if (preparedStatement != null) {preparedStatement.close();}} catch (SQLException throwables) {throwables.printStackTrace();}try {if (connection != null) {connection.close();}} catch (SQLException throwables) {throwables.printStackTrace();}}}

版本一出现过多的冗余代码,例如对其他表进行增删改操作时,需要重复输入以上代码,因此需要把对其他表进行增删改的相同操作的代码抽取出来构造一个方法

版本二

通过版本一可以知道,无论对那个表进行增删改查,都需要先获取数据库连接,然后关闭资源,因此可以把这些相同的操作抽取成方法放在工具类中

JDBCUtils.java

public class JDBCUtils {// 获取数据库连接public static Connection getConnection() throws Exception {InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");Properties pros = new Properties();pros.load(is);String driverClass = pros.getProperty("driverClass");String url = pros.getProperty("url");String user = pros.getProperty("user");String password = pros.getProperty("password");Class.forName(driverClass);Connection conn = DriverManager.getConnection(url, user, password);return conn;}// 关闭资源public static void closeResource(Connection conn, Statement stat) {try {if (conn != null) {conn.close();}} catch (SQLException throwables) {throwables.printStackTrace();}try {if (stat != null) {stat.close();}} catch (SQLException throwables) {throwables.printStackTrace();}}
}

测试

@Test
public void testUpdate() {Connection conn = null;PreparedStatement ps = null;try {//1.获取数据库的连接conn = JDBCUtils.getConnection();//2.预编译sql语句,返回PreparedStatement的实例String sql = "update customers set name=? where id=?";ps = conn.prepareStatement(sql);//3.填充占位符ps.setObject(1, "仓老师");ps.setObject(2, 18);//4.执行ps.execute();} catch (Exception e) {e.printStackTrace();} finally {//5.资源的关闭JDBCUtils.closeResource(conn, ps);}
}

版本二经过优化后,减少了获取数据库连接和关闭资源的冗余代码

版本三(通用的增删改操作)

对数据库的增删改首先需要获取连接,然后预编译sql语句,最后填充占位符。这过程只有填充占位符的数量不同,而填充占位符可以使用可变形参来解决,因此可以这些操作抽取出来成为一个通用的增删改操作

//通用的增删改操作
public void update(String sql, Object ... args) {Connection conn = null;PreparedStatement ps = null;try {//1.获取数据库的连接conn = JDBCUtils.getConnection();//2.预编译sql语句,返回PreparedStatement的实例ps = conn.prepareStatement(sql);//3.填充占位符for (int i = 0; i < args.length; i++) {//小心参数声明错误!!数据库索引是从1开始的ps.setObject(i + 1, args[i]);}//4.执行ps.execute();} catch (Exception e) {e.printStackTrace();} finally {//5.资源的关闭JDBCUtils.closeResource(conn, ps);}
}

版本三是一个通用的增删改操作,即可以对任何一个表的任意字段进行操作。对于不同的字段填充问题,采用可变参数

4.3 使用PreparedStatement实现查询操作(迭代)

4.3.1 Java与SQL对应数据类型转换表

Java类型SQL类型
booleanBIT
byteTINYINT
shortSMALLINT
intINTEGER
longBIGINT
StringCHAR,VARCHAR,LONGVARCHAR
byte arrayBINARY , VAR BINARY
java.sql.DateDATE
java.sql.TimeTIME
java.sql.TimestampTIMESTAMP

4.3.2 ResultSet与ResultSetMetaData

ResultSet

  • 查询需要调用PreparedStatementexecuteQuery() 方法,查询结果是一个ResultSet 对象
  • ResultSet 对象以逻辑表格的形式封装了执行数据库操作的结果集,ResultSet 接口由数据库厂商提供实现类
  • ResultSet 返回的实际上就是一张数据表。有一个指针指向数据表的第一条记录的前面。
  • ResultSet 对象维护了一个指向当前数据行的游标,初始的时候,游标在第一行之前,可以通过 ResultSet 对象的 next() 方法移动到下一行。调用 next()方法检测下一行是否有效。若有效,该方法返回 true,且指针下移。相当于Iterator对象的 hasNext()next() 方法的结合体。
  • 当指针指向一行时, 可以通过调用 getXxx(int index)getXxx(int columnName) 获取每一列的值。
    • 例如: getInt(1), getString(“name”)(建议使用键的方式获取每一列的值,即见名之意)
    • 注意:Java与数据库交互涉及到的相关Java API中的索引都从1开始。

在这里插入图片描述
ResultSetMetaData

得到结果集ResultSet后, 如何知道该结果集中有哪些列 ? 列名是什么?

需要使用一个描述 ResultSet 的对象, 即 ResultSetMetaData(元数据)

关于ResultSetMetaData

  1. 如何获取 ResultSetMetaData: 调用 ResultSetgetMetaData() 方法即可
  2. 获取 ResultSet 中有多少列:调用 ResultSetMetaDatagetColumnCount() 方法
  3. 获取 ResultSet 每一列的列的列名或别名是什么:调用 ResultSetMetaDatagetColumnNamegetColumnLabel() 方法

在这里插入图片描述

ResultSetMetaData其他方法

  • getColumnTypeName(int column):检索指定列的数据库特定的类型名称。

  • getColumnDisplaySize(int column):指示指定列的最大标准宽度,以字符为单位。

  • isNullable(int column):指示指定列中的值是否可以为 null。

  • isAutoIncrement(int column):指示是否自动为指定列进行编号,这样这些列仍然是只读的。

4.3.3 表的的查询操作(迭代)

对单个表的的查询操作(迭代)

版本一

@Test
public void testQuery()  {Connection conn = null;PreparedStatement ps = null;ResultSet rs = null;try {conn = JDBCUtils.getConnection();String sql = "SELECT id,name,email,birth FROM customers WHERE id=?";ps = conn.prepareStatement(sql);ps.setObject(1, "2");rs = ps.executeQuery();if (rs.next()) {Integer id = rs.getInt(1);String name = rs.getString(2);String email = rs.getString(3);Date birth = rs.getDate(4);Customers customers = new Customers(id, name, email, birth);System.out.println(customers);}} catch (Exception e) {e.printStackTrace();} finally {JDBCUtils.closeResource(conn, ps, rs);}
}

版本一对单个表的查询不具有通用性,即如果改变了sql语句的查询字段,则结果集返回的字段也会改变,因此在结果集封装在实体类的代码也需要修改

版本二(单个表通用查询)

版本一查询操作包含很多冗余代码,即获取数据库连接,预编译sql,填充占位符,执行sql获取结果集,查询到的结果集封装在实体类中,因此可以把这些操作单独抽取出来成为一个对单个表的通用的查询方法

填充占位符可以通过可变形参来解决,但是把结果集封装在实体类较麻烦,因为对单个表的不同字段查询,返回的结果集是不同的。

对单个表的不同字段查询得到的结果集,可以通过getMetaData获取元数据ResultSetMetaData,再通过元数据获取结果集的包含的列数,列数知道了就可以遍历列数获取每一列的值

但是呢,每一列的值我们不知道对应的是那一列的列名,因此再遍历过程中还需要通过元数据获取当前列值对应的列名,最后通过反射把数据封装再实体类中

// 通用的针对于customers表的查询操作
public Customers customersForQuery(String sql, Object ... args)  {Connection conn = null;PreparedStatement ps = null;ResultSet rs = null;try {conn = JDBCUtils.getConnection();ps = conn.prepareStatement(sql);for (int i = 0; i < args.length; i++) {ps.setObject(i + 1, args[i]);}//执行,获取结果集rs = ps.executeQuery();//获取结果集的元数据ResultSetMetaData rsmd = rs.getMetaData();//获取列数if (rs.next()) {Customers customers = new Customers();//获取结果集的列数int columnCount = rsmd.getColumnCount();for (int i = 0; i < columnCount; i++) {//获取每个列的列值:通过ResultSetObject columnValue = rs.getObject(i + 1);//获取列的别名:getColumnLabel()String columnName = rsmd.getColumnLabel(i + 1);//通过反射,将对象指定名getColumnLabel的属性赋值为指定的值columnValueField field = Customers.class.getDeclaredField(columnName);field.setAccessible(true);field.set(customers, columnValue);}return customers;}} catch (Exception e) {e.printStackTrace();} finally {JDBCUtils.closeResource(conn, ps, rs);}return null;
}

测试

@Test
public void testQueryForCustomer() {String sql = "SELECT id,name,email,birth FROM customers WHERE id=?";Customers customers = customersForQuery(sql, 6);System.out.println(customers);sql = "SELECT email,birth FROM customers WHERE name=?";customers = customersForQuery(sql, "周杰伦");System.out.println(customers);
}

对不同表的的通用的查询操作(返回表中的一条记录)

对不同表的查询操作类似于对单个表的查询操作,主要在于封装的实体类不同,即查询不同的表,需要返回相应表的实体,因此可以使用泛型方法。

泛型方法返回的值是一个泛型实体,实际的类型由调用者决定。即需要在泛型方法中提供一个泛型型参,让调用方法者提供具体的实体类

// 针对于不同的表的通用的查询操作,返回表中的一条记录
public <T> T getInstance(Class<T> aClass, String sql, Object ... args) {Connection conn = null;PreparedStatement ps = null;ResultSet rs = null;try {conn = JDBCUtils.getConnection();ps = conn.prepareStatement(sql);for (int i = 0; i < args.length; i++) {ps.setObject(i + 1, args[i]);}rs = ps.executeQuery();// 获取结果集的元数据 :ResultSetMetaDataResultSetMetaData rsmd = rs.getMetaData();if (rs.next()) {// 通过Class实例获取对象,即反射T t = aClass.newInstance();// 通过ResultSetMetaData获取结果集中的列数int columnCount = rsmd.getColumnCount();// 处理结果集一行数据中的每一个列for (int i = 0; i < columnCount; i++) {// 获取列值Object columnValue = rs.getObject(i + 1);// 获取每个列的列名String columnLabel = rsmd.getColumnLabel(i + 1);// 给t对象指定的columnLabel属性,赋值为columValue:通过反射Field field = aClass.getDeclaredField(columnLabel);field.setAccessible(true);field.set(t, columnValue);}return t;}} catch (Exception e) {e.printStackTrace();} finally {JDBCUtils.closeResource(conn, ps, rs);}return null;
}

测试

@Test
public void testGetInstance() {String sql = "SELECT id,name,email,birth FROM customers WHERE id=?";Customers customers = getInstance(Customers.class, sql, 12);System.out.println(customers);sql = "SELECT order_id as orderId,order_name as orderName,order_date as orderDate FROM `order` WHERE order_id=?";Order order = getInstance(Order.class, sql, 1);System.out.println(order);
}

对不同表的的通用的查询操作(返回表中的多条记录)

如果返回多条记录,即查询返回的结果集包含多条记录,则需要把if( rs.next() )改成while( rs.next() ),最后把封装好的实体类添加到List集合中即可

public <T> List<T> getForList(Class<T> aClass, String sql, Object ... args)  {Connection conn = null;PreparedStatement ps = null;ResultSet rs = null;try {conn = JDBCUtils.getConnection();ps = conn.prepareStatement(sql);for (int i = 0; i < args.length; i++) {ps.setObject(i + 1, args[i]);}rs = ps.executeQuery();//创建集合对象List<T> list = new ArrayList<>();// 获取结果集的元数据 :ResultSetMetaDataResultSetMetaData rsmd = rs.getMetaData();while (rs.next()) {T t = aClass.newInstance();// 通过ResultSetMetaData获取结果集中的列数int columnCount = rsmd.getColumnCount();// 处理结果集一行数据中的每一个列:给t对象指定的属性赋值for (int i = 0; i < columnCount; i++) {// 获取列值Object columnValue = rs.getObject(i + 1);// 获取每个列的列名String columnLabel = rsmd.getColumnLabel(i + 1);// 给t对象指定的columnName属性,赋值为columValue:通过反射Field field = t.getClass().getDeclaredField(columnLabel);field.setAccessible(true);field.set(t, columnValue);}list.add(t);}return list;} catch (Exception e) {e.printStackTrace();} finally {JDBCUtils.closeResource(conn, ps, rs);}return null;
}

测试

@Test
public void testGetForList() {String sql = "SELECT id,name,email,birth FROM customers WHERE id<?";List<Customers> customersList = getForList(Customers.class, sql, 12);customersList.forEach(System.out::println);
}

4.4 资源的释放

目前需要释放的资源有三个ResultSet, Statement,Connection,因此在JDBCUtils可以写个通用的关闭资源的方法,如下

public static void closeResource(Connection conn, Statement stat, ResultSet rs) {try {if (conn != null) {conn.close();}} catch (SQLException throwables) {throwables.printStackTrace();}try {if (stat != null) {stat.close();}} catch (SQLException throwables) {throwables.printStackTrace();}try {if (rs != null) {rs.close();}} catch (SQLException throwables) {throwables.printStackTrace();}
}

数据库连接(Connection)是非常稀有的资源,用完后必须马上释放,如果Connection不能及时正确的关闭将导致系统宕机。Connection的使用原则是尽量晚创建,尽量早的释放。

可以在finally中关闭,保证及时其他代码出现异常,资源也一定能被关闭。

4.5 JDBC API小结

两种思想

  • 面向接口编程的思想

  • ORM思想(object relational mapping)

    • 一个数据表对应一个java类
    • 表中的一条记录对应java类的一个对象
    • 表中的一个字段对应java类的一个属性

sql是需要结合列名和表的属性名来写。注意起别名。

两种技术

  • JDBC结果集的元数据:ResultSetMetaData
    • 获取列数:getColumnCount()
    • 获取列的别名:getColumnLabel()
  • 通过反射,创建指定类的对象,获取指定的属性并赋值

五、操作BLOB类型字段

5.1 MySQL BLOB类型

MySQL中,BLOB是一个二进制大型对象,是一个可以存储大量数据的容器,它能容纳不同大小的数据。

插入BLOB类型的数据必须使用PreparedStatement,因为BLOB类型的数据无法使用字符串拼接写的。

MySQL的四种BLOB类型(除了在存储的最大信息量上不同外,他们是等同的),如下

在这里插入图片描述

需要注意的是:如果存储的文件过大,数据库的性能会下降。

如果在指定了相关的Blob类型以后,还报错:xxx too large,那么在mysql的安装目录下,找my.ini文件加上如下的配置参数: max_allowed_packet=16M。同时注意:修改了my.ini文件之后,需要重新启动mysql服务。

5.2 向数据表中插入大数据类型

@Test
public void testInsert()  {Connection conn = null;PreparedStatement ps = null;try {conn = JDBCUtils.getConnection();String sql = "INSERT INTO customers(name,email,birth,photo) values(?,?,?,?)";ps = conn.prepareStatement(sql);ps.setObject(1, "仓老师");ps.setObject(2, "stormzhuo@163.com");ps.setObject(3,"1998-07-07");// 操作Blob类型的变量FileInputStream fis = new FileInputStream(new File("thumb.png"));ps.setBlob(4, fis);int i = ps.executeUpdate();if (i > 0) {System.out.println("添加成功");} else {System.out.println("添加失败");}} catch (Exception e) {e.printStackTrace();}JDBCUtils.closeResource(conn, ps);
}

首先通过字节节点输入流读取文件数据,然后通过PreparedStatement的setBlog方法把文件数据写入Blog字段

5.2 从数据表中读取大数据类型

@Test
public void testQuery() {InputStream is = null;FileOutputStream fos = null;Connection conn = null;PreparedStatement ps = null;ResultSet rs = null;try {conn = JDBCUtils.getConnection();String sql = "SELECT id,name,email,birth,photo FROM customers WHERE id=?";ps = conn.prepareStatement(sql);ps.setObject(1, 16);rs = ps.executeQuery();if (rs.next()) {int id = rs.getInt("id");String name = rs.getString("name");String email = rs.getString("email");Date birth = rs.getDate("birth");Customers customers = new Customers(id, name, email, birth);System.out.println(customers);//读取Blob类型的字段Blob photo = rs.getBlob("photo");// 通过Blob的getBinaryStream获取字节输入流is = photo.getBinaryStream();// 创建字节节点输出流fos = new FileOutputStream("zhuyin.jpg");byte[] buffer = new byte[1024];int len;while ((len = is.read(buffer)) != -1) {fos.write(buffer, 0, len);}}} catch (Exception e) {e.printStackTrace();} finally {try {if (is != null) {is.close();}} catch (IOException e) {e.printStackTrace();}try {if (fos != null) {fos.close();}} catch (IOException e) {e.printStackTrace();}JDBCUtils.closeResource(conn, ps, rs);}
}

首先通过结果集ResultSetgetBlob方法获取Blog字段对象,然后调用BloggetBinaryStream方法获取字节输入流,最后创建字节节点输出流输出Blog数据到指定的文件

六、批量操作

6.1 批量处理SQL语句

批处理

批处理就是将一批一批SQL语句的处理,而不是一条一条语句进行处理。如当你有多条SQL语句要执行时,一次向服务器发送一条语句,这样虽然也可以达到效果,但是效率很差,而处理这个问题就可以使用批处理(即是一次向服务器发送多条SQL语句,然后让服务器一次性处理),批处理只针对更新语句(新增、删除、修改),所有批处理跟查询没有关系。

当需要成批插入或者更新记录时,可以采用Java的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理。通常情况下比单独提交处理更有效率

JDBC的批量处理语句包括下面三个方法:

  • addBatch(String):添加需要批量处理的SQL语句或是参数;
  • executeBatch():执行批量处理语句;
  • clearBatch():清空缓存的数据

通常我们会遇到两种批量执行SQL语句的情况:

  • 多条SQL语句的批量处理;
  • 一个SQL语句的批量传参;

6.2 高效的批量插入(迭代)

举例:向数据表中插入20000条数据

  • 数据库中提供一个goods表。创建如下:
CREATE TABLE goods(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(20)
);

准备工作

mysql服务器默认是关闭批处理的,我们需要通过一个参数,让mysql开启批处理的支持。

?rewriteBatchedStatements=true 写在配置文件的url后面

需要使用更新的mysql 驱动:mysql-connector-java-5.1.37-bin.jar

实现层次一:使用Statement

@Test
public void testInsert1() throws Exception {Connection conn = null;Statement statement = null;try {long start = System.currentTimeMillis();conn = JDBCUtils.getConnection();statement = conn.createStatement();for (int i = 1; i <= 20000; i++) {String sql = "INSERT INTO goods(name) values('name_" + i + "')";//1.“攒”sqlstatement.addBatch(sql);if (i % 500 == 0) {//2.执行statement.executeBatch();//3.清空statement.clearBatch();}}long end = System.currentTimeMillis();System.out.println("执行耗时:" + (end - start));} catch (Exception e) {e.printStackTrace();} finally {JDBCUtils.closeResource(conn, statement);}
}

执行耗时

在这里插入图片描述

使用Statement对象虽然也可以实现批处理,但是这种方式效率太低,因为它所有的SQL语句都是没有预编译的,而数据库对没用预编译的sql语句是不提供缓存的

实现层次二:使用PreparedStatement

@Test
public void testInsert3() {Connection conn = null;PreparedStatement ps = null;try {long start = System.currentTimeMillis();conn = JDBCUtils.getConnection();String sql = "INSERT INTO goods(name) values(?)";ps = conn.prepareStatement(sql);for (int i = 1; i <= 20000; i++) {ps.setObject(1, "name_" + i);//1.“攒”sqlps.addBatch();if (i % 500 == 0) {//2.执行ps.executeBatch();//3.清空ps.clearBatch();}}long end = System.currentTimeMillis();System.out.println("执行耗时:" + (end - start));} catch (Exception e) {e.printStackTrace();} finally {JDBCUtils.closeResource(conn, ps);}
}

执行耗时

在这里插入图片描述

PreparedStatement能够预编译sql语句,而数据库对预编译sql支持缓存,因此效率较高

实现层次三:PreparedStatement(优化)

层次二是攒到一定的sql数量后送到数据库中去执行,由于使用的是PreparedStatement,数据库会对相同的sql进行缓存,因此批量操作效率较高

那么可不可以攒完全部sql语句后再送去sql执行呢?

由于我们是通过取余条件来攒sql的,当满足条件便会执行批量sql语句,那么优化空间就是在执行批量sql语句时,阻止它提交数据。

DML(增删改操作)默认时提交数据的,即每一次执行excute方法都会向数据库中提交,因此需要在执行excute方法之前关闭自动提交,在赞完全部sql语句后在提交数据

@Test
public void testInsert4() throws Exception {Connection conn = null;PreparedStatement ps = null;try {long start = System.currentTimeMillis();conn = JDBCUtils.getConnection();//1.设置为不自动提交数据conn.setAutoCommit(false);String sql = "INSERT INTO goods(name) values(?)";ps = conn.prepareStatement(sql);for (int i = 1; i <= 20000; i++) {ps.setObject(1, "name_" + i);//1.“攒”sqlps.addBatch();if (i % 500 == 0) {//2.执行ps.executeBatch();//3.清空ps.clearBatch();}}//2.提交数据conn.commit();long end = System.currentTimeMillis();System.out.println("执行耗时:" + (end - start));} catch (Exception e) {e.printStackTrace();} finally {JDBCUtils.closeResource(conn, ps);}
}

执行耗时
在这里插入图片描述

七、数据库事务

7.1 数据库事务介绍

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

事务处理(事务操作): 保证所有事务都作为一个工作单元来执行,即使出现了故障,都不能改变这种执行方式。当在一个事务中执行多个操作时,要么所有的操作都被提交(commit),那么这些修改就永久地保存下来;要么数据库管理系统将放弃所作的所有修改,整个事务回滚(rollback)到最初状态。

为确保数据库中数据的一致性,数据的操纵应当是离散的成组的逻辑单元:当它全部完成时,数据的一致性可以保持,而当这个单元中的一部分操作失败,整个事务应全部视为错误,所有从起始点以后的操作应全部回退到开始状态。

7.2 JDBC事务处理

数据一旦提交,就不可回滚。那么数据什么时候意味着提交呢?

  • 当一个连接对象被创建时,默认情况下是自动提交事务:每次执行一个 SQL 语句时,如果执行成功,就会向数据库自动提交,而不能回滚。
  • 关闭数据库连接,数据就会自动的提交。如果多个操作,每个操作使用的是自己单独的连接,则无法保证事务。即同一个事务的多个操作必须在同一个连接下。

JDBC程序中为了让多个 SQL 语句作为一个事务执行,需要做如下几个事情

  • 调用 Connection 对象的 setAutoCommit(false); 以取消自动提交事务
  • 在所有的 SQL 语句都成功执行后,调用 commit(); 方法提交事务
  • 在出现异常时,调用 rollback();方法回滚事务

若此时 Connection 没有被关闭,还可能被重复使用,则需要恢复其自动提交状态 setAutoCommit(true)。尤其是在使用数据库连接池技术时,执行close()方法前,建议恢复自动提交状态。

【案例:用户AA向用户BB转账100】

我们可以使用前面的通用的增删改操作update方法,但是update执行完时是会关闭数据库连接的,要想支持事务,需要对update进行修改

//使用事务以后的通用的增删改操作
public int update(Connection conn, String sql, Object ... args)  {PreparedStatement ps = null;try {// 1.获取PreparedStatement的实例 (或:预编译sql语句)ps = conn.prepareStatement(sql);// 2.填充占位符for (int i = 0; i < args.length; i++) {ps.setObject(i + 1, args[i]);}// 3.执行sql语句return ps.executeUpdate();} catch (SQLException throwables) {throwables.printStackTrace();} finally {// 4.关闭资源JDBCUtils.closeResource(null, ps);}return 0;
}

下面进行模拟用户AA向BB转账100快,若转账成功,则AA会减少100,而BB会增加100

不使用事务的情况

@Test
public void testUpdate() {String sql = "UPDATE user_table SET balance=balance-100 WHERE user=?";int i = update(sql, "AA");// 模拟网络异常System.out.println(10 / 0);sql = "UPDATE user_table SET balance=balance+100 WHERE user=?";int i2 = update(sql, "BB");System.out.println("转账成功");
}

AA向BB转账100操作会执行两条sql语句,一条是AA减100的操作,另一条是BB加100操作。当AA减100的操作执行完时,我们模拟了网络异常,此时BB加100的操作不会执行

使用事务的情况

让多个 SQL 语句作为一个事务执行,首先需要保证多个SQL在同一连接下执行,因为数据库连接关闭时,数据会自动提交。

其次需要关闭数据自动提交,因为DML(insert,delete,update)默认是自动提交数据的,即执行增删改操作后,数据会自动提交到数据库中去

使用了事务之后,当在执行过程中出现异常,由于数据没用提交的数据库,因此可以回滚到原来的初始状态

@Test
public void testUpdateWithTransaction()  {Connection conn = null;try {// 1.获取数据库连接conn = JDBCUtils.getConnection();// 2.关闭数据自动提交,开启事务conn.setAutoCommit(false);// 3.进行数据库操作String sql = "UPDATE user_table SET balance=balance-100 WHERE user=?";int i1 = update(conn, sql, "AA");// 模拟网络异常System.out.println(10 / 0);sql = "UPDATE user_table SET balance=balance+100 WHERE user=?";int i2 = update(conn, sql, "BB");// 4.若没有异常,则提交事务conn.commit();System.out.println("转账成功");} catch (Exception e) {e.printStackTrace();try {//6.恢复每次DML操作的自动提交功能conn.rollback();} catch (SQLException throwables) {throwables.printStackTrace();}} finally {//7.关闭连接JDBCUtils.closeResource(conn, null);}
}

7.3 事务的ACID属性

  1. 原子性(Atomicity)
    原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。

  2. 一致性(Consistency)
    事务必须使数据库从一个一致性状态变换到另外一个一致性状态。

  3. 隔离性(Isolation)
    事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

  4. 持久性(Durability)
    持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响。

7.3.1 数据库的并发问题

对于同时运行的多个事务, 当这些事务访问数据库中相同的数据时, 如果没有采取必要的隔离机制, 就会导致各种并发问题:

  • 脏读: 对于两个事务 T1, T2, 若T1 读取了已经被 T2 更新但还没有被提交的字段。之后, 若 T2 回滚, T1读取的内容就是临时且无效的。
  • 不可重复读: 对于两个事务T1, T2, 若T1 读取了一个字段, 然后 T2 更新了该字段。之后, T1再次读取同一个字段, 值就不同了。
  • 幻读: 对于两个事务T1, T2, 若T1 读取了一个表, 然后 T2 在该表中插入了一些新的行。之后, 如果 T1 再次读取同一个表, 就会多出几行。

7.3.2 四种隔离级别

数据库事务的隔离性: 数据库系统必须具有隔离并发运行各个事务的能力, 使它们不会相互影响, 避免各种并发问题。

数据库提供的4种事务隔离级别来解决不同的并发问题:

隔离级别描述
READ UNCOMMITTED(读未提交数据)允许事务读取未被其他事物提交的变更.脏读,不可重复读和幻读的问题都会出现
READ COMMITED(读已提交数据)只允许事务读取已经被其它事务提交的变更.可以避免脏读,但不可重复读和幻读问题仍然可能出现
REPEATABLE READ(可重复读)确保事务可以多次从一个字段中读取相同的值.在这个事务持续期间,禁止其他事物对这个字段进行更新.可以避免脏读和不可重复读,但幻读的问题仍然存在.
SERIALIZABLE(串行化)确保事务可以从一个表中读取相同的行.在这个事务持续期间,禁止其他事务对该表执行插入,更新和删除操作.所有并发问题都可以避免,但性能十分低下.

一个事务与其他事务隔离的程度称为隔离级别。数据库规定了多种事务隔离级别, 不同隔离级别对应不同的干扰程度, 隔离级别越高, 数据一致性就越好, 但并发性越弱。

Oracle 支持的 2 种事务隔离级别:READ COMMITED, SERIALIZABLE。 Oracle 默认的事务隔离级别为: READ COMMITED

Mysql 支持 4 种事务隔离级别。Mysql 默认的事务隔离级别为: REPEATABLE READ。

7.3.3 在Java中设置隔离级别解决并发问题

READ UNCOMMITTED(读未提交数据)

三种并发问题都未解决,即脏读,不可重复读,幻读

下面通过一个查询事务和更新事务作为两个线程来演示脏读,即在一个用户在读表数据之前,另一个用户更新的表数据,但是没提交到数据库(即最终的数据库的数据是没改变的),此时读表用户读取的数据是更新表后的数据,但是更表用户的数据最后是没提交的数据库的,因此产生脏读。

在前面的查询语句中,当执行完语句时,数据库连接是会被关闭的,因此需要改成支持事务的,即不关闭连接。

public <T> T getInstance(Connection conn, Class<T> aClass, String sql, Object ... args) {PreparedStatement ps = null;ResultSet rs = null;try {ps = conn.prepareStatement(sql);for (int i = 0; i < args.length; i++) {ps.setObject(i + 1, args[i]);}rs = ps.executeQuery();ResultSetMetaData rsmd = rs.getMetaData();if (rs.next()) {T t = aClass.newInstance();int columnCount = rsmd.getColumnCount();for (int i = 0; i < columnCount; i++) {Object columnValue = rs.getObject(i + 1);String columnLabel = rsmd.getColumnLabel(i + 1);Field field = aClass.getDeclaredField(columnLabel);field.setAccessible(true);field.set(t, columnValue);}return t;}} catch (Exception throwables) {throwables.printStackTrace();} finally {JDBCUtils.closeResource(null, ps, rs);}return null;
}

测试代码

首先设置隔离级别为READ UNCOMMITTED,然后关闭数据自动提交

其次当执行sql语句后,不要关闭数据库连接,因为连接关闭会自动提交数据

最后对于更新事务,为了在查询事务能看到明显的脏读,需要在执行完更新事务后使用sleep将该线程阻塞挂起

@Test
public void testTransactionSelect() throws Exception {Connection conn = JDBCUtils.getConnection();conn.setTransactionIsolation(1);conn.setAutoCommit(false);String sql = "SELECT user, password, balance FROM user_table WHERE user=?";User user = getInstance(conn, User.class, sql, "CC");System.out.println(user);
}@Test
public void testTransactionUpdate() throws Exception {Connection conn = JDBCUtils.getConnection();conn.setTransactionIsolation(1);conn.setAutoCommit(false);String sql = "UPDATE user_table SET balance=? WHERE user=?";int cc = update(conn, sql, 5000, "CC");Thread.sleep(15000);System.out.println("更新成功");
}

首先执行select语句时,此时查询出来的balance为2000
在这里插入图片描述
之后执行update语句后在执行select语句时,此时查询出来的balance为5000
在这里插入图片描述
最后update操作完成后,再次查询出来的balance变为原来的2000
在这里插入图片描述

READ COMMITED(读已提交数据)

解决了脏读,但不可重复读和幻读未解决

只需要在上面代码修改隔离级别即可,如下

在这里插入图片描述

一般只需要解决脏读问题就行,而不可重复读和幻读问题的解决需要牺牲并发性能,因此后面两个级别REPEATABLE READ(可重复读),SERIALIZABLE(串行化)不作演示

八、数据库连接池

8.1 JDBC数据库连接池的必要性

在使用开发基于数据库的web程序时,传统的模式基本是按以下步骤:

  • 在主程序(如servlet、beans)中建立数据库连接
  • 进行sql操作
  • 断开数据库连接

这种模式开发,存在的问题:

  • 普通的JDBC数据库连接使用 DriverManager 来获取,每次向数据库建立连接的时候都要将 Connection 加载到内存中,再验证用户名和密码(得花费0.05s~1s的时间)。需要数据库连接的时候,就向数据库要求一个,执行完成后再断开连接。这样的方式将会消耗大量的资源和时间。数据库的连接资源并没有得到很好的重复利用。若同时有几百人甚至几千人在线,频繁的进行数据库连接操作将占用很多的系统资源,严重的甚至会造成服务器的崩溃。
  • 对于每一次数据库连接,使用完后都得断开。否则,如果程序出现异常而未能关闭,将会导致数据库系统中的内存泄漏,最终将导致重启数据库。
  • 这种开发不能控制被创建的连接对象数,系统资源会被毫无顾及的分配出去,如连接过多,也可能导致内存泄漏,服务器崩溃。

8.2 数据库连接池技术

为解决传统开发中的数据库连接问题,可以采用数据库连接池技术。

数据库连接池的基本思想:就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。

数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个

数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中,这些数据库连接的数量是由最小数据库连接数来设定的。无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量。连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。

在这里插入图片描述
工作原理:

在这里插入图片描述

JDBC 的数据库连接池使用 javax.sql.DataSource来表示,DataSource 只是一个接口,该接口通常由服务器(Weblogic, WebSphere, Tomcat)提供实现,也有一些开源组织提供实现:

  • DBCP是Apache提供的数据库连接池。tomcat 服务器自带dbcp数据库连接池。速度相对c3p0较快,但因自身存在BUG,Hibernate3已不再提供支持。
  • C3P0 是一个开源组织提供的一个数据库连接池,速度相对较慢,稳定性还可以。 hibernate官方推荐使用
  • Proxool 是sourceforge下的一个开源项目数据库连接池,有监控连接池状态的功能,稳定性较c3p0差一点
  • BoneCP 是一个开源组织提供的数据库连接池,速度快
  • Druid 是阿里提供的数据库连接池,据说是集DBCP 、C3P0 、Proxool 优点于一身的数据库连接池,但是速度不确定是否有BoneCP快

DataSource 通常被称为数据源,它包含连接池和连接池管理两个部分,习惯上也经常把 DataSource 称为连接池

DataSource用来取代DriverManager来获取Connection,获取速度快,同时可以大幅度提高数据库访问速度。

特别注意:

  • 数据源和数据库连接不同,数据源无需创建多个,它是产生数据库连接的工厂,因此整个应用只需要一个数据源即可
  • 当数据库访问结束后,程序还是像以前一样关闭数据库连接:conn.close();conn.close()并没有关闭数据库的物理连接,它仅仅把数据库连接释放,归还给了数据库连接池。

8.2.1 Druid(德鲁伊)数据库连接池

由于数据库连接池的使用都是一个套路,因此这里使用最常用的德鲁伊数据库连接池

Druid是阿里巴巴开源平台上一个数据库连接池实现,它结合了C3P0、DBCP、Proxool等DB池的优点,同时加入了日志监控,可以很好的监控DB池连接和SQL的执行情况,可以说是针对监控而生的DB连接池,可以说是目前最好的连接池之一。

详细配置参数:

配置缺省说明
name配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。 如果没有配置,将会生成一个名字,格式是:”DataSource-” + System.identityHashCode(this)
url连接数据库的url,不同数据库不一样。例如:mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto
username连接数据库的用户名
password连接数据库的密码。如果你不希望密码直接写在配置文件中,可以使用ConfigFilter。详细看这里:https://github.com/alibaba/druid/wiki/%E4%BD%BF%E7%94%A8ConfigFilter
driverClassName根据url自动识别 这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName(建议配置下)
initialSize0初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
maxActive8最大连接池数量
maxIdle8已经不再使用,配置了也没效果
minIdle最小连接池数量
maxWait获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
poolPreparedStatementsfalse是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
maxOpenPreparedStatements-1要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
validationQuery用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。
testOnBorrowtrue申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
testOnReturnfalse归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
testWhileIdlefalse建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
timeBetweenEvictionRunsMillis有两个含义: 1)Destroy线程会检测连接的间隔时间2)testWhileIdle的判断依据,详细看testWhileIdle属性的说明
numTestsPerEvictionRun不再使用,一个DruidDataSource只支持一个EvictionRun
minEvictableIdleTimeMillis
connectionInitSqls物理连接初始化的时候执行的sql
exceptionSorter根据dbType自动识别 当数据库抛出一些不可恢复的异常时,抛弃连接
filters属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有: 监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall
proxyFilters类型是List,如果同时配置了filters和proxyFilters,是组合关系,并非替换关系

在src下创建配置文件为:【druid.properties】

url=jdbc:mysql://localhost:3306/test
username=root
password=12345
driverClassName=com.mysql.jdbc.DriverinitialSize=10
maxActive=10

通过DruidDataSourceFactory工厂类的静态方法获取数据源,静态方法需要传入一个Properties参数,Properties是用来加载配置信息的,包含了连接数据库信息以及初始化连接池信息等

@Test
public void testGetConnection() throws Exception {Properties pro = new Properties();// 使用系统加载器的getResourceAsStream加载配置文件InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("druid.properties");pro.load(is);// 调用工厂类获取数据源DataSource dataSource = DruidDataSourceFactory.createDataSource(pro);Connection conn = dataSource.getConnection();System.out.println(conn);
}

8.3 使用德鲁伊连接池替换DriverManager获取连接

在前面,我们是通过DriverManager来获取数据库连接,最主要的缺点就是每次使用时都要自己造一个连接

使用连接池后,每次使用的连接都是从池里获取的,当不需要连接时,程序还是像以前一样关闭数据库连接:conn.close();conn.close()并没有关闭数据库的物理连接,它仅仅把数据库连接释放,归还给了数据库连接池。

使用德鲁伊连接池替换JDBCUtils.java获取数据库连接的操作,如下

//创建一个Druid数据库连接池
private static DataSource ds = null;static {Properties pros = new Properties();InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("druid.properties");try {pros.load(is);ds = DruidDataSourceFactory.createDataSource(pros);} catch (Exception e) {e.printStackTrace();}
}public static Connection getConnection() throws Exception {return ds.getConnection();
}

九、Apache-DBUtils实现CRUD

commons-dbutilsApache组织提供的一个开源 JDBC工具类库,它是对JDBC的简单封装,学习成本极低,并且使用dbutils能极大简化jdbc编码的工作量,同时也不会影响程序的性能。

9.1 主要API介绍

工具类:org.apache.commons.dbutils.DbUtils

DbUtils :提供如关闭连接、装载JDBC驱动程序等常规工作的工具类,里面的所有方法都是静态的。主要方法如下:

  • public static void close(…) throws java.sql.SQLException: DbUtils类提供了三个重载的关闭方法。这些方法检查所提供的参数是不是NULL,如果不是的话,它们就关闭Connection、Statement和ResultSet。
  • public static void closeQuietly(…): 这一类方法不仅能在Connection、Statement和ResultSet为NULL情况下避免关闭,还能隐藏一些在程序中抛出的SQLEeception。
  • public static void commitAndClose(Connection conn)throws SQLException: 用来提交连接的事务,然后关闭连接
  • public static void commitAndCloseQuietly(Connection conn): 用来提交连接,然后关闭连接,并且在关闭连接时不抛出SQL异常。
  • public static void rollback(Connection conn)throws SQLException:允许conn为null,因为方法内部做了判断
  • public static void rollbackAndClose(Connection conn)throws SQLException
  • rollbackAndCloseQuietly(Connection)
  • public static boolean loadDriver(java.lang.String driverClassName):这一方装载并注册JDBC驱动程序,如果成功就返回true。使用该方法,你不需要捕捉这个异常ClassNotFoundException。

org.apache.commons.dbutils.QueryRunner

该类简单化了SQL查询,它与ResultSetHandler组合在一起使用可以完成大部分的数据库操作,能够大大减少编码量。

QueryRunner类提供了两个构造器:

  • 默认的构造器
  • 需要一个 javax.sql.DataSource 来作参数的构造器

QueryRunner类的主要方法:

  • 更新
    • public int update(Connection conn, String sql, Object… params) throws SQLException:用来执行一个更新(插入、更新或删除)操作。
  • 插入
    • public T insert(Connection conn,String sql,ResultSetHandler rsh, Object… params) throws SQLException:只支持INSERT语句
  • 批处理
    • public int[] batch(Connection conn,String sql,Object[][] params)throws SQLException: INSERT, UPDATE, or DELETE语句
    • public T insertBatch(Connection conn,String sql,ResultSetHandler rsh,Object[][] params)throws SQLException:只支持INSERT语句
  • 查询
    • public Object query(Connection conn, String sql, ResultSetHandler rsh,Object… params) throws SQLException:执行一个查询操作,在这个查询中,对象数组中的每个元素值被用来作为查询语句的置换参数。该方法会自行处理 PreparedStatement 和 ResultSet 的创建和关闭。

org.apache.commons.dbutils.ResultSetHandler

该接口用于处理 java.sql.ResultSet,将数据按要求转换为另一种形式。

ResultSetHandler 接口提供了一个单独的方法:Object handle (java.sql.ResultSet .rs)

接口的主要实现类:

  • ArrayHandler:把结果集中的第一行数据转成对象数组。
  • ArrayListHandler:把结果集中的每一行数据都转成一个数组,再存放到List中。
  • BeanHandler:将结果集中的第一行数据封装到一个对应的JavaBean实例中。
  • BeanListHandler:将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里。
  • ColumnListHandler:将结果集中某一列的数据存放到List中。
  • KeyedHandler(name):将结果集中的每一行数据都封装到一个Map里,再把这些map再存到一个map里,其key为指定的key。
  • MapHandler:将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值。
  • MapListHandler:将结果集中的每一行数据都封装到一个Map里,然后再存放到List
  • ScalarHandler:查询单个值对象

9.2 使用DbUtils替换JDBCUtils.java关闭资源

在之前的JDBCUtils工具类关闭资源的操作中,可以使用DbUtuils工具类来替换,如下

public static void closeResource(Connection conn, Statement stat, ResultSet rs) {DbUtils.closeQuietly(conn);DbUtils.closeQuietly(stat);DbUtils.closeQuietly(rs);
}

使用了DbUtils工具类后,不用捕获异常以及判断资源是否为null,查看源码知道,其实之前我们做的它都帮我做了
在这里插入图片描述
在这里插入图片描述

9.2 使用QueryRunner类替换通用的增删改查

前面写过的通用的增删改查操作,可以使用QueryRunner相关方法来替换增删改查

使用QueryRunner类的update方法替换通用的增删改操作,如下

@Test
public void testCommonUpdate() {Connection conn = null;try {QueryRunner qr = new QueryRunner();conn = JDBCUtils.getConnection();String sql = "update `order` set order_name=? where order_id=?";qr.update(conn, sql, "BB" , "2");} catch (Exception e) {e.printStackTrace();} finally {JDBCUtils.closeResource(conn, null, null);}
}

通过查看源码可以知道,update核心代码跟我们自己编写的是一样的,区别在于健壮性比较好
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

使用QueryRunner类的query方法替换通用的查询操作,如下

/** BeanHander:是ResultSetHandler接口的实现类,用于封装表中的一条记录。*/
@Test
public void testGetInstance() {Connection conn = null;try {QueryRunner qr = new QueryRunner();conn = JDBCUtils.getConnection();String sql = "SELECT id,name,email,birth FROM customers WHERE id=?";BeanHandler<Customers> bh = new BeanHandler<>(Customers.class);Customers customers = qr.query(conn, sql, bh, 1);System.out.println(customers);sql = "SELECT order_id as orderId,order_name as orderName,order_date as orderDate FROM `order` WHERE order_id=?";BeanHandler<Order> bh1 = new BeanHandler<>(Order.class);Order order = qr.query(conn, sql, bh1, 1);System.out.println(order);} catch (Exception e) {e.printStackTrace();} finally {JDBCUtils.closeResource(conn ,null, null);}
}/** BeanListHandler:是ResultSetHandler接口的实现类,用于封装表中的多条记录构成的集合。*/
@Test
public void testGetForList() {Connection conn = null;try {QueryRunner qr = new QueryRunner();conn = JDBCUtils.getConnection();String sql = "SELECT id,name,email,birth FROM customers WHERE id<?";BeanListHandler<Customers> blh = new BeanListHandler<>(Customers.class);List<Customers> customersList = qr.query(conn, sql, blh, 12);customersList.forEach(System.out::println);} catch (Exception e) {e.printStackTrace();} finally {JDBCUtils.closeResource(conn, null, null);}
}

十、Dao设计模式

DAO(Data Access Object)称为数据访问对象,Dao设计模式可以在使用数据库的应用程序实现业务逻辑和数据访问逻辑的分离,从而使应用的维护变得简单。它通过将数据访问实现(通常使用JDBC技术)封装在Dao类中,提高应用程序的灵活性

在Dao模式有很多变体,这里介绍一种比较简单的形式,首先定义一个BaseDao抽象类并声明通用的增删改查方法,可以使用Apache-DBUtils

然后为每种实体的持久化操作定义一个接口,如CustomersDao接口负责Customers对象的持久化, UserDao接口负责User对象的持久化,最后定义这些接口的实现类,实现类继承BaseDao抽象类,因此可以使用BaseDao定义的通用的增删改查方法

BaseDao通用的查询操作,需要传入具体类的Class实例,出现了具体实体类名称,因此可以进一步优化
在这里插入图片描述
在这里插入图片描述

首先把BaseDao声明为泛型类

在这里插入图片描述
然后在子类继承BaseDao时不保留泛型,而是指定具体的类型

在这里插入图片描述而这个类型正是我们传入方法的参数,那么怎么获取这个类型呢?

可以使用反射,首先调用实现类的getClass方法获取Class实例,然后调用Class实例的getGenericSuperClass方法获取父类的泛型,最后调用泛型的getActualTypeArguments方法获取父类的泛型参数数组

这个类型存在与非静态方法参数中,因此调用方法之前这个类型就必须确认,因此可以把获取这个类型的操作声明在非静态代码块中

在这里插入图片描述

经过优化后,查询操作就不需要传入具体的类型,而是通过反射动态获取

在这里插入图片描述在这里插入图片描述

完整代码

BaseDao.java

// 定义一个用来被继承的对数据库进行基本操作的Dao
public abstract class BaseDao<T> {// 定义一个变量来接收泛型的类型private Class<T> aClass = null;// 获取T的Class对象,获取泛型的类型,泛型是在被子类继承时才确定{//获取当前BaseDAO的子类继承的父类中的泛型Type gs = this.getClass().getGenericSuperclass();ParameterizedType pt = (ParameterizedType) gs;//获取了父类的泛型参数Type[] ata = pt.getActualTypeArguments();//泛型的第一个参数aClass = (Class<T>) ata[0];}// 通用的增删改操作 (考虑上事务)public int update(Connection conn, String sql, Object... args) { // sql中占位符的个数与可变形参的长度相同!int i = 0;try {QueryRunner qr = new QueryRunner();i = qr.update(conn, sql, args);} catch (SQLException throwables) {throwables.printStackTrace();}return i;}// 通用的查询操作,用于返回数据表中的一条记录(考虑上事务)public T getInstance(Connection conn,  String sql, Object... args)  {T t = null;try {QueryRunner qr = new QueryRunner();BeanHandler<T> bh = new BeanHandler<>(aClass);t = qr.query(conn, sql, bh, args);} catch (SQLException throwables) {throwables.printStackTrace();}return t;}// 通用的查询操作,用于返回数据表中的多条记录构成的集合(考虑上事务)public  List<T> getForList(Connection conn,  String sql, Object... args)  {List<T> list = null;try {QueryRunner qr = new QueryRunner();BeanListHandler<T> blh = new BeanListHandler<>(aClass);list = qr.query(conn, sql, blh, args);} catch (SQLException throwables) {throwables.printStackTrace();}return list;}//用于查询特殊值的通用的方法public <T> T getValue(Connection conn, String sql, Object... args)  {Object obj = null;try {QueryRunner qr = new QueryRunner();ScalarHandler sh = new ScalarHandler();obj = qr.query(conn, sql, sh, args);} catch (SQLException throwables) {throwables.printStackTrace();}return (T) obj;}
}

下面给出为Customers对象的持久化的接口以及实现类

CustomersDao.java

public interface CustomersDao {// 将cust对象添加到数据库中void addCus(Connection conn, Customers customers);// 针对指定的id,删除表cust中的一条记录int deleteCusById(Connection conn, Integer id);// 针对内存中的cust对象,去修改数据表中指定的记录int updateCus(Connection conn, Customers customers);// 针对指定的id查询得到对应的Customer对象Customers getCusById(Connection conn, Integer id);// 查询表cust中的所有记录构成的集合List<Customers> getAllCus(Connection conn);// 返回数据表cust中的数据的条目数Long getCount(Connection conn);// 返回数据表cust中最大的生日Date getMaxBirth(Connection conn);
}

实现类

CustomersDaoImpl.java

public class CustomersDaoImpl extends BaseDao<Customers> implements CustomersDao {@Overridepublic void addCus(Connection conn, Customers customers) {String sql = "INSERT INTO customers(name,email,birth) value(?,?,?)";update(conn, sql, customers.getName(), customers.getEmail(), customers.getBirth());}@Overridepublic int deleteCusById(Connection conn, Integer id) {String sql = "DELETE FROM customers WHERE id=?";return update(conn, sql, id);}@Overridepublic int updateCus(Connection conn, Customers customers) {String sql = "UPDATE customers SET name=?,email=?,birth=? WHERE id=?";int i = update(conn, sql, customers.getName(), customers.getEmail(), customers.getBirth(), customers.getId());return i;}@Overridepublic Customers getCusById(Connection conn, Integer id) {String sql = "SELECT id,name,email,birth FROM customers WHERE id=?";return getInstance(conn,  sql, id);}@Overridepublic List<Customers> getAllCus(Connection conn) {String sql = "SELECT id,name,email,birth FROM customers";return getForList(conn,  sql);}@Overridepublic Long getCount(Connection conn) {String sql = "SELECT COUNT(*) FROM customers";return getValue(conn, sql);}@Overridepublic Date getMaxBirth(Connection conn) {String sql = "SELECT MAX(birth) FROM customers";return getValue(conn, sql);}
}

使用JUnit自动生成测试类

在项目下创建与src同级的test文件夹,右击test,执行如下操作

在这里插入图片描述
下载JUnitGenerator插件

在这里插入图片描述
右击要测试的类,执行如下操作
在这里插入图片描述在这里插入图片描述
在这里插入图片描述

测试类如下

class CustomersDaoImplTest {CustomersDao customersDao = new CustomersDaoImpl();@Testvoid testAddCus() {Connection conn = null;try {conn = JDBCUtils.getConnection();Customers customers = new Customers(1, "路飞", "lufei@163.com", new Date(13213123213L));customersDao.addCus(conn, customers);} catch (Exception e) {e.printStackTrace();} finally {JDBCUtils.closeResource(conn, null, null);}}@Testvoid deleteCusById()  {Connection conn = null;try {conn = JDBCUtils.getConnection();int i = customersDao.deleteCusById(conn, 1);System.out.println(i);} catch (Exception e) {e.printStackTrace();} finally {JDBCUtils.closeResource(conn, null, null);}}@Testvoid updateCus() {Connection conn = null;try {conn = JDBCUtils.getConnection();Customers customers = new Customers(2, "迪丽热巴", "dilireba@163.com", new Date(1312134213L));int i = customersDao.updateCus(conn, customers);System.out.println(i);} catch (Exception e) {e.printStackTrace();} finally {JDBCUtils.closeResource(conn, null, null);}}@Testvoid getCusById() throws Exception {Connection conn = null;try {conn = JDBCUtils.getConnection();Customers customers = customersDao.getCusById(conn, 2);System.out.println(customers);} catch (Exception e) {e.printStackTrace();} finally {JDBCUtils.closeResource(conn, null, null);}}@Testvoid getAllCus() {Connection conn = null;try {conn = JDBCUtils.getConnection();List<Customers> list = customersDao.getAllCus(conn);list.forEach(System.out::println);} catch (Exception e) {e.printStackTrace();} finally {JDBCUtils.closeResource(conn, null, null);}}@Testvoid getCount() {Connection conn = null;try {conn = JDBCUtils.getConnection();Long count = customersDao.getCount(conn);System.out.println(count);} catch (Exception e) {e.printStackTrace();} finally {JDBCUtils.closeResource(conn, null, null);}}@Testvoid getMaxBirth() {Connection conn = null;try {conn = JDBCUtils.getConnection();Date maxBirth = customersDao.getMaxBirth(conn);System.out.println(maxBirth);} catch (Exception e) {e.printStackTrace();} finally {JDBCUtils.closeResource(conn, null, null);}}
}
查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. 卸载软件包【与程序有区别】

    apt-get的卸载相关的命令有remove/purge/autoremove/clean/autoclean等。 具体来说&#xff1a; -删除已安装包&#xff08;不保留配置文件)。 -如软件包a&#xff0c;依赖软件包b&#xff0c;则执行该命令会删除a&#xff0c;而且不保留配置文件 apt-get purge / apt-get --p…...

    2024/4/20 7:34:07
  2. oracle rman 备份与还原

    完备脚本 #!/bin/bashexport ORACLE_SIDdb3 export ORACLE_BASE/u01/oracle export ORACLE_HOME/u01/oracle/product/11.2.0/dbhome_1$ORACLE_HOME/bin/rman target / << EOF run{ allocate channel c1 type disk; allocate channel c2 type disk; allocate channel c3 …...

    2024/4/13 20:32:10
  3. vue 由 clearTimeout无法清除定时器引发的vue 周期函数,事件代码执行顺序思考

    vue 由 clearTimeout无法清除定时器引发的vue 周期函数&#xff0c;事件代码执行顺序思考最近做个移动的项目&#xff0c;遇到需求&#xff1a;首页无操作20秒&#xff0c;自动退出登录。其他页面20秒无操作&#xff0c;自动跳转首页。 所谓的无操作&#xff0c;包括点击&…...

    2024/4/14 22:04:00
  4. Python爬虫之xpath解析4k美女图片

    先看看我爬的&#x1f447;&#x1f447;&#x1f447;这里我只爬了2022张&#xff0c;祝大家新年快乐&#xff01; 接下来我们进行操作&#xff0c;非常简单&#xff0c;讲解的非常详细&#xff0c;如果有帮助还请留下脚印~~ 首先第一步当然是导包啦&#xff0c;os呢&#xff…...

    2024/4/13 20:32:15
  5. 基于蝗虫算法优化最小二乘支持向量机lssvm实现预测matlab代码

    1 简介 本文提出一种基于蝗虫算法优化最小二乘支持向量机的数据预测方法。LSSVM 是一种新型机器学习算法&#xff0c;其在传统支持向量机 SVM 基础上&#xff0c;将二次规划问题中的不等式约束改为等式约束&#xff0c;极大地方便了求解过程&#xff0c;克服了数据集粗糙、数据…...

    2024/4/13 20:32:05
  6. 基于遗传算法优化最小二乘支持向量机lssvm实现数据预测matlab代码

    1 简介 本文提出一种基于最小二乘支持向量机的数据预测方法。​LSSVM 是一种新型机器学习算法&#xff0c;其在传统支持向量机 SVM 基础上&#xff0c;将二次规划问题中的不等式约束改为等式约束&#xff0c;极大地方便了求解过程&#xff0c;克服了数据集粗糙、数据集波动性大…...

    2024/4/27 21:21:29
  7. 完美卸载Apt

    【记得套用自己的文件名称】 升级说明 当发布Docker新版本时&#xff0c;可以使用sudo apt update && sudo apt upgrade命令来更新软件包。 如果要阻止更新Docker软件包&#xff0c;请将其标记为保留&#xff0c;运行以下命令&#xff1a; sudo apt-mark hold dock…...

    2024/4/19 5:10:29
  8. springboot整合SpringSecurity实现认证、授权功能简单入门案例

    目录一、SpringSecurity介绍二、springboot整合Spring Security三、自定义登录逻辑和权限四、自定义成功和失败处理器五、自定义403状态码错误页面六、注解配置一、SpringSecurity介绍 Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的…...

    2024/4/13 20:32:05
  9. Java 实现二叉树排序树第k个节点

    Java 实现二叉树排序树第k个节点 需要用到二叉搜搜索数的性质&#xff1a;中序遍历之后得到的值是递增的顺序 import java.util.ArrayList;public class Solution {/*** 使用二叉搜索树的一个特性&#xff1a;中序遍历之后数据的顺序递增* param proot* param k* return*/pub…...

    2024/4/15 12:53:07
  10. 基于海鸥算法优化最小二乘支持向量机lssvm实现预测matlab代码

    1 简介 本文提出一种基于海鸥算法优化最小二乘支持向量机的数据预测方法。LSSVM 是一种新型机器学习算法&#xff0c;其在传统支持向量机 SVM 基础上&#xff0c;将二次规划问题中的不等式约束改为等式约束&#xff0c;极大地方便了求解过程&#xff0c;克服了数据集粗糙、数据…...

    2024/4/17 8:06:10
  11. 《Web安全之机器学习入门》笔记:第七章 7.6朴素贝叶斯检测DGA域名

    DGA&#xff08;域名生成算法&#xff08;Domain Generation Algorithm&#xff09;是一种僵尸网络利用随机字符来生成C&C域名&#xff0c;从而逃避安全设备域名黑名单检测的技术手段。 1.白样本 使用alexa前1000域名&#xff08;679个样本&#xff09;作为白样本&#xff…...

    2024/4/13 20:32:10
  12. 支持加密的开源笔记Joplin

    今天既是腊月二十九&#xff0c;也是除夕&#xff0c;是牛年的最后一天&#xff0c;辞别旧岁&#xff0c;迎来新年。在新的一年&#xff0c;愿您平安健康&#xff0c;家人常伴&#xff1b;财运滚滚&#xff0c;大吉大利&#xff01; 什么是 Joplin &#xff1f; Joplin 是一个免…...

    2024/4/13 20:33:15
  13. exness:圣保罗暴雨引发山体滑坡和洪水,造成19人死亡

    巴西公共安全官员周日表示&#xff0c;自周五以来&#xff0c;圣保罗州大雨引发的山体滑坡和洪水已造成至少 19 人死亡&#xff0c;其中包括 7 名儿童。 据圣保罗州当局称&#xff0c;另有 9 人在雨中受伤&#xff0c;另有 4 人失踪&#xff0c;全州约有 500 个家庭无家可归。 …...

    2024/4/13 20:33:20
  14. 3.開啟兩個瀏覽器的問題

    可用MDI開啟兩個瀏覽器 但是 當使用 youtube 播放影片&#xff0c;會出現無法撥放的問題 1.直接用 import os os.system(‘start chrome --apphttps://www.google.com’) 2.詳細指令 os.system(start chrome --apphttps://www.google.com --window-size0,0 --window-position10…...

    2024/4/14 20:19:29
  15. 周游C语言教程6 - 循环语句

    周游C语言教程6 - 循环语句 这是周游C语言的第六篇教程&#xff0c;你将在这篇文章里认识循环语句。 循环 循环的意思就是你要不断做一件事&#xff0c;比如你要不断的上班上学&#xff0c;直到毕业退休。 循环在C语言中有3种形式&#xff0c;分别是 while(表达式) {代码块…...

    2024/4/13 20:33:05
  16. C++ 监视用户输入的数据(键盘钩子)代码及详解

    有关利用C设置键盘钩子的代码&#xff0c;基本上都是基于一个窗口程序&#xff0c;其实控制台窗口也能够实现&#xff0c;我们不需要太多的修改即可实现。 在SetWindowsHookEx&#xff08;&#xff09;函数中的第一个参数我们要设置成 WH_KEYBOARD_LL 而不是 WH_KEYBOARD 为…...

    2024/4/13 20:32:55
  17. 2022年全球市场非碳酸饮料瓶盖总体规模、主要生产商、主要地区、产品和应用细分研究报告

    本文研究全球市场、主要地区和主要国家非碳酸饮料瓶盖的销量、销售收入等&#xff0c;同时也重点分析全球范围内主要厂商&#xff08;品牌&#xff09;竞争态势&#xff0c;非碳酸饮料瓶盖销量、价格、收入和市场份额等。针对过去五年&#xff08;2017-2021&#xff09;年的历史…...

    2024/4/13 20:33:10
  18. 大学第一个爬虫的复杂内心戏

    艰辛的学习历程 你好&#xff0c;我是一名Python新手小白。 不过严格意义上说&#xff0c;不能算一名特别新的萌新&#xff0c;毕竟我在B站上也入门了4、5次…多少还是掌握了一点Python的基础&#xff0c;然而学了忘、忘了学&#xff0c;我对于python的基础基本是聊胜于无。 …...

    2024/4/13 20:33:25
  19. uniapp原生模块开发_用AndroidStudio开发原生uniapp模块---uniapp一次开发_小程序_Android_IOS_快应用多端通用工作笔记004

    首先去下载androidstudio去官网下载即可. 然后去下载uniapp的sdk的包,这里用离线sdk包就可以了 原生开发者支持 去上面这个地址下载,最新的android,SDK...

    2024/4/13 20:33:00
  20. Java、计算CD价值

    假设你用10000美元投资一张CD&#xff0c;年获利率为5.75%。一个月后&#xff0c;这张CD价值为 10000 10000 * 5.75 / 1200 10047.92 两个月之后&#xff0c;这张CD价值为 10047.92 10047.92 * 5.75 / 1200 10096.06 三个月之后&#xff0c;这张CD价值为 10096.06 10096.0…...

    2024/4/15 8:13:53

最新文章

  1. 提交链码-编辑前后端,调用链码功能

    一 . 链码介绍 1.什么链码&#xff1f; • 链码是一段用 Go、Node.js 或者 Java 实现了规定接口的程序。链码在安全的Docker容器中运行&#xff0c; 与背书节点的进程隔离。通过应用程序提交的交易&#xff0c;链码初始化和管理账本状态。• 链码通常处理网络成员协商达成的业…...

    2024/4/27 22:42:56
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. matlab 轨迹生成函数

    文章目录 jtrajctrajmstrajmtrajtpolylspbtrinterp用例参考链接jtraj 计算两个构型之间的关节空间轨迹 [q, qd, qdd] = jtraj(q0, qf, m)是关节空间轨迹q(MxN),其中关节坐标从q0(1xN)变化到qf(1xN)。使用五次(5阶)多项式,并默认速度和加速度为零边界条件。假设时间以m步从0…...

    2024/4/24 8:45:17
  4. 01背包问题 小明的背包

    2.小明的背包1 - 蓝桥云课 (lanqiao.cn) #include <bits/stdc.h> using namespace std; const int N1010;//开始写的105 开小了 样例过了但最后只过了很少一部分 int n,m; int v[N],w[N]; int f[N][N];int main() {cin>>n>>m;for(int i1;i<n;i){cin>&…...

    2024/4/22 8:36:15
  5. PostCss:详尽指南之安装和使用

    引言 在现代前端开发中&#xff0c;CSS预处理器如Sass、Less等已经成为提升开发效率、增强代码可维护性的重要工具。然而&#xff0c;随着Web技术的发展&#xff0c;CSS的功能也在不断扩展&#xff0c;一些新的CSS语法&#xff08;如变量、自定义属性、CSS Grid等&#xff09;以…...

    2024/4/26 1:51:33
  6. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/4/26 18:09:39
  7. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/4/26 20:12:18
  8. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/4/26 23:05:52
  9. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/4/27 4:00:35
  10. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/4/27 17:58:04
  11. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/4/27 14:22:49
  12. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/4/26 21:56:58
  13. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/4/27 9:01:45
  14. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/4/27 17:59:30
  15. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/4/25 18:39:16
  16. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/4/25 18:39:16
  17. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/4/26 19:03:37
  18. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/4/26 22:01:59
  19. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/4/25 18:39:14
  20. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/4/26 23:04:58
  21. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/4/25 2:10:52
  22. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/4/25 18:39:00
  23. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/4/26 19:46:12
  24. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/4/27 11:43:08
  25. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/4/27 8:32:30
  26. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  27. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  28. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  29. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  30. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  31. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  32. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  33. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  34. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  35. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  36. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  37. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  38. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  39. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  40. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  41. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  42. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  43. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  44. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  45. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57