JDBC和数据库连接池
Posted Spring-_-Bear
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JDBC和数据库连接池相关的知识,希望对你有一定的参考价值。
第25章 JDBC和数据库连接池
821. JDBC原理示意图
-
JDBC 为访问不同的数据库提供了统一的接口,为使用者屏蔽了使用细节。Java 程序员使用 JDBC API 可以连接任何提供了 JDBC 驱动程序的数据库系统,从而完成对数据库的各种操作
-
Java 设计者定义了操作数据库的接口规范,由各自数据库厂商具体实现。Java 程序员只需要面向这套接口编程即可。
822. JDBC模拟实现
823. JDBC快速入门
-
JDBC API 是一系列的接口,它统一和规范了应用程序与数据库的连接、执行 SQL 语句,并得到返回结果等各类操作,相关的接口和类在 java.sql 和 javax.sql 包中
-
JDBC 程序编写步骤:
- 注册驱动:加载 Driver 类
- 获取连接:获得 Connection 对象
- 执行 SQL:获得 Statement 对象
- 释放资源
- 数据库连接方式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 语句并返回其生成结果的对象
- 在建立连接之后,想要对数据库进行操作,一般有以下三种方式:
- Statement:存在 SQL 注入
- PreparedStatement:预处理
- 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
接口名 | 方法名 | 功能 |
---|---|---|
Connection | createStatement() | 创建执行静态 SQL 语句的对象 |
createPreparedStatement(sql) | 获得 SQL 语句预处理对象 | |
Statement | executeUpdate(sql) | 执行 DML 语句,返回受影响行数 |
executeQuery(sql) | 执行 DQL 语句,返回结果集 | |
execute(sql) | 执行任意 SQL 语句,返回布尔值 | |
PreparedStatement | executeUpdate(sql) | 执行 DML 语句,返回受影响行数 |
executeQuery(sql) | 执行 DQL 语句,返回结果集 | |
execute(sql) | 执行任意 SQL 语句,返回布尔值 | |
setXxx(index,value) | 设置 SQL 语句中的值 | |
setObject(index,value) | 设置 SQL 语句中的值 | |
ResultSet | next() | 向下移动一行,到表尾返回 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 只是一个接口,该接口具体实现留给第三方
连接池< 以上是关于JDBC和数据库连接池的主要内容,如果未能解决你的问题,请参考以下文章 |
---|