韩顺平循序渐进学Java零基础 第25章 JDBC和数据库连接池

Posted Spring-_-Bear

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了韩顺平循序渐进学Java零基础 第25章 JDBC和数据库连接池相关的知识,希望对你有一定的参考价值。

第25章 JDBC和数据库连接池

821. JDBC原理示意图

  • JDBC 为访问不同的数据库提供了统一的接口,为使用者屏蔽了使用细节。Java 程序员使用 JDBC API 可以连接任何提供了 JDBC 驱动程序的数据库系统,从而完成对数据库的各种操作

  • Java 设计者定义了操作数据库的接口规范,由各自数据库厂商具体实现。Java 程序员只需要面向这套接口编程即可。

822. JDBC模拟实现

823. JDBC快速入门

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

  • JDBC 程序编写步骤:

  1. 注册驱动:加载 Driver 类
  2. 获取连接:获得 Connection 对象
  3. 执行 SQL:获得 Statement 对象
  4. 释放资源
  • 数据库连接方式1:
package p823;

import com.mysql.jdbc.Driver;

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

/**
* 静态加载方式连接到数据库
* @author Spring-_-Bear
* @version 2021-11-08 20:56
*/
public class Jdbc01 
   public static void main(String[] args) throws SQLException 
       // 连接到的数据库:jdbc:mysql://主机IP地址:端口/db_name
       String url = "jdbc:mysql://localhost:3306/temp";
       // 设置用户名与密码
       Properties properties = new Properties();
       properties.setProperty("user", "springbear");
       properties.setProperty("password","123");
       
       // 加载驱动: new com.mysql.jdbc.Driver()
       Driver driver = new Driver();
       // 获得连接
       Connection connect = driver.connect(url, properties);

       // SQL 语句
       String insert = "INSERT INTO actor VALUES (NULL,'张三','男','1970-01-01','10086');";

       // 得到一个执行静态 SQL 语句并返回其生成的结果的对象
       Statement statement = connect.createStatement();
       // 返回受影响的行数
       int rows = statement.executeUpdate(insert);

       System.out.println("返回受影响的行数 = " + rows);

       statement.close();
       connect.close();
   

824. 数据库连接方式2

/**
* 方式1属于静态加载,灵活性差,依赖性强,考虑方式二使用反射机制 
*/
public void connect02() throws ClassNotFoundException, InstantiationException, IllegalAccessException, SQLException 
   // 连接到的数据库:jdbc:mysql://主机IP地址:端口/db_name
   String url = "jdbc:mysql://localhost:3306/temp";
   // 设置用户名与密码
   Properties properties = new Properties();
   properties.setProperty("user", "springbear");
   properties.setProperty("password","123");

   // 加载类信息
   Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");
   // 获得类实例
   Driver driver = (Driver)aClass.newInstance();
   // 获得连接
   Connection connect = driver.connect(url, properties);

825. 数据库连接方式3

/**
* 使用 DriverManager 替代 Driver,进行统一管理
*/
public void connect03() throws ClassNotFoundException, InstantiationException, IllegalAccessException, SQLException 
   // 加载类信息
   Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");
   // 获得类实例
   Driver driver = (Driver)aClass.newInstance();

   String url = "jdbc:mysql://localhost:3306/temp";
   String user = "springbear";
   String pwd = "123";

   // 注册驱动
   DriverManager.registerDriver(driver);
   // 获得连接
   Connection connection = DriverManager.getConnection(url, user, pwd);

826. 数据库连接方式4

/**
* 使用 Class.forName 自动完成注册驱动
*/
public void connect04() throws ClassNotFoundException, SQLException 
   // 加载类信息,在加载 Driver 的过程中自动完成注册
   Class.forName("com.mysql.jdbc.Driver");
   /*
    * static 
    *      try 
    *          DriverManager.registerDriver(new Driver());
    *       catch (SQLException var1) 
    *          throw new RuntimeException("Can't register driver!");
    *      
    * 
    */
   
   String url = "jdbc:mysql://localhost:3306/temp";
   String user = "springbear";
   String pwd = "123";

   // 获得连接
   Connection connection = DriverManager.getConnection(url, user, pwd);

827. 数据库连接方式5

  • mysql-connector-java-5.1.37-bin.jar 驱动文件 5.1.6 之后无需 Class.forName(“com.mysql.jdbc.Driver”) 也可以直接获得连接。原因:从 jdk5 以后使用了 jdbc4,不再需要显式调用 Class.forName() 注册驱动,而是自动调用驱动 jar 包下的 META-INF\\services\\java.sql.Driver 文本中的类名称去注册
/**
* 在方式4的基础上使用配置文件进行连接,更加灵活
*/
public void connect05() throws IOException, ClassNotFoundException, SQLException 
   // 从配置文件中读取信息
   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);
   Connection connection = DriverManager.getConnection(url, user, password);

828. ResultSet底层

  • ResultSet:表示从数据库读取到的数据表的结果集。ResultSet 对象保持一个光标指向当前的数据行。最初,光标位于第一行之前。其有一个 next 方法将光标移动到下一行,并且由于在 ResultSet 对象中没有更多行时返回 false,因此常用 while 循环来遍历结果集
  • com.mysql.jdbc.JDBC42 ResultSet 类下有一个 RowData(接口) 类型的字段,rowData 中有一个 ArrayList 类型的集合 rows,rows 中又有 byte[] 类型的 internalRowData,数据真正存储的位置

829. SQL注入

  • Statement 对象,用于执行静态 SQL 语句并返回其生成结果的对象
  • 在建立连接之后,想要对数据库进行操作,一般有以下三种方式:
  1. Statement:存在 SQL 注入
  2. PreparedStatement:预处理
  3. CallableStatement:用于执行数据库存储过程
  • SQL 注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入的数据中注入非法的 SQL 语句段或命令,恶意攻击数据库
package p823;

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

/**
* @author Spring-_-Bear
* @version 2021-11-09 09:48
*/
public class SqlInjection 
   public static void main(String[] args) throws IOException, ClassNotFoundException, SQLException 
       // 获取用户想要查询的用户名和密码
       // Input userName = 1' or
       // Input pwd = or '1' = '1
       Scanner scanner = new Scanner(System.in);
       System.out.print("Input the name that you want to query:");
       String userName = scanner.nextLine();
       System.out.print("Input the password that you want to query:");
       String pwd = scanner.nextLine();

       // 加载配置文件
       Properties properties = new Properties();
       properties.load(new FileInputStream("config\\\\temp.properties"));

       // 加载驱动类信息,自动注册驱动
       Class.forName(properties.getProperty("driver"));
       // 获得连接
       Connection connection = DriverManager.getConnection(properties.getProperty("url"), properties);
       Statement statement = connection.createStatement();

       String select = "SELECT * FROM admin WHERE name='" + userName + "' AND pwd= '" + pwd + "'";
       ResultSet resultSet = statement.executeQuery(select);
       while (resultSet.next()) 
           userName = resultSet.getString(1);
           pwd = resultSet.getString(2);
           System.out.println(userName + "\\t" + pwd);
       

       resultSet.close();
       statement.close();
       connection.close();
   

830. Statement

831. 预处理查询

  • PreparedStatement 执行的 SQL 语句中的参数用问号(?)来表示,调用 PreparedStatement 对象的 setXxx() 方法来设置这些参数。setXxx() 方法有两个参数,第一个参数是要设置的 SQL 语句中的参数的索引,从 1 开始,第二个参数是设置 SQL 语句中的参数的值
  • 预处理的好处:
String select = "SELECT * FROM admin WHERE name = ? AND pwd= ?";
// SQL 语句预处理
PreparedStatement preparedStatement = connection.prepareStatement(select);
preparedStatement.setString(1, userName);
preparedStatement.setString(2, pwd);

// 执行 SQL 语句,得到结果集
ResultSet resultSet = preparedStatement.executeQuery();

832. 预处理DML

833. JDBC API

接口名方法名功能
ConnectioncreateStatement()创建执行静态 SQL 语句的对象
createPreparedStatement(sql)获得 SQL 语句预处理对象
StatementexecuteUpdate(sql)执行 DML 语句,返回受影响行数
executeQuery(sql)执行 DQL 语句,返回结果集
execute(sql)执行任意 SQL 语句,返回布尔值
PreparedStatementexecuteUpdate(sql)执行 DML 语句,返回受影响行数
executeQuery(sql)执行 DQL 语句,返回结果集
execute(sql)执行任意 SQL 语句,返回布尔值
setXxx(index,value)设置 SQL 语句中的值
setObject(index,value)设置 SQL 语句中的值
ResultSetnext()向下移动一行,到表尾返回 false
previous()向上移动一行,到表头返回 false
getXxx(index || colLabel)获得指定列的值
getObject(index || colLabel)获得指定列的值

834. JDBC Utils开发

package utils;


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

/**
* 数据库连接、资源关闭工具类
*
* @author Spring-_-Bear
* @version 2021-11-09 11:40
*/
public class JdbcUtils 
   private static String driver;
   private static String url;
   private static String user;
   private static String password;
   private static String path = "config\\\\temp.properties";

   /**
    * 读取文件信息,初始化字段
    */
   static 
       Properties properties = new Properties();
       try 
           properties.load(new FileInputStream(path));
           driver = properties.getProperty("driver");
           url = properties.getProperty("url");
           user = properties.getProperty("user");
           password = properties.getProperty("password");
        catch (IOException e) 
           // 实际开发中,会将此类编译异常转换成运行异常,由调用方自行处理,较为方便
           throw new RuntimeException(e);
       
   

   /**
    * 获得对数据库的连接
    *
    * @return 数据库连接对象
    */
   public static Connection getConnection() 
       try 
           return DriverManager.getConnection(url, user, password);
        catch (SQLException e) 
           // 实际开发中,会将此类编译异常转换成运行异常,由调用方自行处理,较为方便
           throw new RuntimeException(e);
       
   

   /**
    * 关闭对应资源
    *
    * @param resultSet  none
    * @param statement  none
    * @param connection none
    */
   public static void close(ResultSet resultSet, Statement statement, Connection connection) 
       try 
           if (resultSet != null) 
               resultSet.close();
           
           if (statement != null) 
               statement.close();
           
           if (connection != null) 
               connection.close();
           
        catch (SQLException e) 
           // 实际开发中,会将此类编译异常转换成运行异常,由调用方自行处理,较为方便
           throw new RuntimeException(e);
       
   

835. JDBC Utils DML

836. JDBC Utils 查询

837. 事务介绍

  • JDBC 程序中当一个 Connectioon 对象被创建时,默认情况下是自动提交事务:每次执行一条 SQL 语句时,如果执行成功,就会向数据库自动提交,而不能回滚
  • 可以调用 Connection 接口的 setAutoCommit(false) 方法取消自动提交事务
  • 在所有的 SQL 语句都执行成功后,调用 commit() 方法提交事务;在其中某个操作失败或出现异常时,调用 rollback() 方法回滚事务

838. 事务处理

public void transaction() 
   Connection connection = null;
   PreparedStatement preparedStatement = null;
   String sub = "UPDATE account SET balance = balance - 100 WHERE id = 1";
   String add = "UPDATE account SET balance = balance + 100 WHERE id = 2";

   try 
       // 获得连接
       connection = JdbcUtils.getConnection();
       // 关闭自动提交即开启事务
       connection.setAutoCommit(false);
       // 执行 SQL
       preparedStatement = connection.prepareStatement(add);
       preparedStatement.executeUpdate();
       int temp = 1 / 0;
       preparedStatement = connection.prepareStatement(sub);
       preparedStatement.executeUpdate();
       // 提交事务
       connection.commit();
       System.out.println("所有 SQL 操作成功,提交事务!");
    catch (SQLException | ArithmeticException e) 
       try 
           // 发生异常,撤销操作,事务回滚
           System.out.println("程序执行发生了异常,回滚所有操作!!!");
           connection.rollback();
        catch (SQLException ex) 
           ex.printStackTrace();
       
       e.printStackTrace();
    finally 
       JdbcUtils.close(null, preparedStatement, connection);
   

839. 批处理应用

  • 当需要成批插入或者更新记录时,可以采用 Java 的批处理更新机制,这一机制允许多条语句一次性提交给数据库批量处理。通常情况下比单独提交处理更有效率
方法名功能
addBatch()添加需要批量处理的 SQL 语句或参数
executeBatch()批量发送执行
clearBatch()清空批处理包
  • JDBC 连接 MySQL 时,如果需要使用批处理功能,需要在 url 中添加参数:
url = "jdbc:mysql://localhost:3306/temp?rewriteBatchedStatements=true"
  • 批处理往往和 PreparedStatement 一起搭配使用,既可以减少编译次数,又可以减少运行次数,效率大大提高
preparedStatement.addBatch();
if (i % 1000 == 0) 
   preparedStatement.executeBatch();
   preparedStatement.clearBatch();

840. 批处理源码分析

/**
* 第一次会创建 ArrayList<elementData>
* elementData => Object[] 会存放预处理后的 SQL 语句
* 当 elementDate 满后会按照 1.5 倍扩容
* 当达到指定的容量之后,就会发送给 MySQL 执行
* 批处理会减少发送 SQL 语句的网络开销,减少编译次数,从而提高效率
* @throws SQLException none
*/
public void addBatch() throws SQLException 
   synchronized(this.checkClosed().getConnectionMutex()) 
       if (this.batchedArgs == null) 
           this.batchedArgs = new ArrayList();
       

       for(int i = 0; i < this.parameterValues.length; ++i) 
           this.checkAllParametersSet(this.parameterValues[i], this.parameterStreams[i], i);
       

       this.batchedArgs.add(new com.mysql.jdbc.PreparedStatement.BatchParams(this.parameterValues, this.parameterStreams, this.isStream, this.streamLengths, this.isNull));
   

841. 传统链接弊端分析

  • 传统的 JDBC 数据库连接使用 DriverManager 来获取,每次向数据库建立连接的时候都需要将 Connection 加载到内存中,再验证 IP 地址、用户名、密码(耗时0.05s ~ 1s)是否正确。需要向数据库连接的时候,就向数据库请求一个连接,频繁的请求操作将占用过多的系统资源,容易造成服务器崩溃
  • 每一次数据库连接,使用完后都得及时断开,如果程序出现异常而导致未能正常关闭,将导致数据库内存泄漏,最终导致数据库崩溃重启
  • 传统获取连接的方式,不能控制创建连接的数量,如果连接过多,也可能导致内存泄漏,从而导致 MySQL 崩溃

842. 数据库连接池原理

  • 预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕归还给缓冲池(并不断开与数据库的连接)
  • 数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个
  • 当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列
  • JDBC 的数据库连接池使用 javax.sql.DataSource 来表示,DataSource 只是一个接口,该接口具体实现留给第三方
连接池<

以上是关于韩顺平循序渐进学Java零基础 第25章 JDBC和数据库连接池的主要内容,如果未能解决你的问题,请参考以下文章

韩顺平循序渐进学Java零基础 第27章 正则表达式

韩顺平循序渐进学Java零基础 第14章 集合

韩顺平循序渐进学Java零基础 第01-08章

韩顺平循序渐进学Java零基础 第01-08章

韩顺平循序渐进学Java零基础 第13章 常用类

韩顺平循序渐进学Java零基础 第23章 反射

(c)2006-2024 SYSTEM All Rights Reserved IT常识