文章目录
JDBC
什么是 JDBC
JDBC 规范定义接口,具体的实现由各大数据库厂商来实现。
JDBC 是 Java 访问数据库的标准规范,真正怎么操作数据库还需要具体的实现类,也就是数据库驱动。每个数据库厂商根据自家数据库的通信格式编写好自己数据库的驱动。所以我们只需要会调用 JDBC 接口中的方法即可,数据库驱动由数据库厂商提供。
使用 JDBC 的好处:
- 程序员如果要开发访问数据库的程序,只需要会调用 JDBC 接口中的方法即可,不用关注类是如何实现的。
- 使用同一套 Java 代码,进行少量的修改就可以访问其他 JDBC 支持的数据库
使用 JDBC 开发使用到的包:
会使用到的包 | 说明 |
---|---|
java.sql | 所有与 JDBC 访问数据库相关的接口和类 |
javax.sql | 数据库扩展包,提供数据库额外的功能。如:连接池 |
数据库的驱动 | 由各大数据库厂商提供,需要额外去下载,是对 JDBC 接口实现的类 |
JDBC 的核心 API
接口或类 | 作用 |
---|---|
DriverManager 类 | 1)管理和注册数据库驱动 2)得到数据库连接对象,程序中使用该类的主要功能是获取 Connection对象 3)public static synchronized Connection getConnection(String url,String user,String pass): 该方法获得 url 对应数据库的连接 |
Connection 接口 | 一个连接对象,可用于创建 Statement 和 PreparedStatement 对象 |
Statement 接口 | 一个 SQL 语句对象,用于将 SQL 语句发送给数据库服务器。 |
PreparedStatemen 接口 | 一个 SQL 语句对象,是 Statement 的子接口 |
ResultSet 接口 | 用于封装数据库查询的结果集,返回给客户端 Java 程序 |
快速实现步骤:
-
导入驱动jar包 mysql-connector-java-5.1.37-bin.jar
复制
mysql-connector-java-5.1.37-bin.jar
到项目的libs
目录下对
libs
目录:右键–> Add As Library -
注册驱动
-
获取数据库连接对象 Connection
-
定义sql
-
获取执行sql语句的对象 Statement
-
执行sql,接受返回结果
-
处理结果
-
释放资源
代码快速实现示例
public static void main(String[] args) throws Exception {
//1. 导入驱动jar包
//2.注册驱动
Class.forName("com.mysql.jdbc.Driver");
//3.获取数据库连接对象
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/db03", "root", "root");
//4.定义sql语句
String sql = "update account set balance = 500 where id = 1";
//5.获取执行sql的对象 Statement
Statement stmt = conn.createStatement();
//6.执行sql
int count = stmt.executeUpdate(sql);
//7.处理结果
System.out.println(count);
//8.释放资源
stmt.close();
conn.close();
}
DriverManager类-驱动管理对象
-
注册驱动:告诉程序该使用哪一个数据库驱动jar
方法:
static void registerDriver(Driver driver)
:注册与给定的驱动程序 DriverManager 。写代码使用:
Class.forName("com.mysql.jdbc.Driver");
通过查看源码发现:在com.mysql.jdbc.Driver类中存在静态代码块:
static { try { java.sql.DriverManager.registerDriver(new Driver()); } catch (SQLException E) { throw new RuntimeException("Can't register driver!"); } }
注意:mysql5 之后的驱动jar包可以省略注册驱动的步骤。
-
获取数据库连接
方法:
-
static Connection getConnection(String url, String user, String password)
:通过连接字符串,用户名,密码来得到数据库的连接对象
-
static Connection getConnection (String url, Properties info)
:通过连接字符串,属性对象来得到连接对象
参数:
- url:指定连接的路径
- 语法:jdbc:mysql://ip地址(域名):端口号/数据库名称
- 例子:
jdbc:mysql://localhost:3306/db3
- 细节:如果连接的是本机mysql服务器,并且mysql服务默认端口是3306,则url可以简写为:jdbc:mysql:///数据库名称
- user:用户名
- password:密码
-
-
乱码的处理
如果数据库出现乱码,可以指定参数: ?characterEncoding=utf8,表示让数据库以 UTF-8 编码来处理数据。
jdbc:mysql://localhost:3306/数据库?characterEncoding=utf8
-
案例:得到 MySQL 的数据库连接对象
// 方法1:使用用户名、密码、URL 得到连接对象 public static void method1() throws SQLException { String url = "jdbc:mysql://localhost:3306/db03"; // db03 是要连接的数据库名 //1) 使用用户名、密码、URL 得到连接对象 Connection connection = DriverManager.getConnection(url, "root", "root"); //com.mysql.jdbc.JDBC4Connection@68de145 System.out.println(connection); } // 方法2:使用属性文件和 url 得到连接对象 public static void method2() throws SQLException { //url 连接字符串 String url = "jdbc:mysql://localhost:3306/db03"; // db03 是要连接的数据库名 //属性对象 Properties info = new Properties(); //把用户名和密码放在 info 对象中 info.setProperty("user","root"); info.setProperty("password","root"); Connection connection = DriverManager.getConnection(url, info); //com.mysql.jdbc.JDBC4Connection@68de145 System.out.println(connection); }
Connection接口:数据库连接对象
功能:
-
获取执行sql 的对象
Statement createStatement()
PreparedStatement prepareStatement(String sql)
-
管理事务:
- 开启事务:
setAutoCommit(boolean autoCommit)
:调用该方法设置参数为false,即开启事务 - 提交事务:
commit()
- 回滚事务:
rollback()
- 开启事务:
Statement:执行sql的对象
JDBC 访问数据库的步骤
1) 注册和加载驱动(可以省略)
2) 获取连接
3) Connection 获取 Statement 对象
4) 使用 Statement 对象执行 SQL 语句
5) 返回结果集
6) 释放资源
Statement 作用:代表一条语句对象,用于发送 SQL 语句给服务器,用于执行静态 SQL 语句并返回它所生成结果的对象。
Statement 中的方法:
-
int executeUpdate(String sql)
:用于发送 DML 语句,增删改的操作,insert、update、delete(参数:SQL 语句;返回值:返回对数据库影响的行数) -
ResultSet executeQuery(String sql)
:用于发送 DQL 语句,执行查询的操作。select(参数:SQL 语句;返回值:查询的结果集) -
int executeUpdate(String sql)
:执行DML(insert、update、delete)语句、DDL(create,alter、drop)语句,返回值:影响的行数,可以通过这个影响的行数判断DML语句是否执行成功 (返回值>0 的则执行成功,反之,则失败)。
释放资源
-
需要释放的对象:ResultSet 结果集,Statement 语句,Connection 连接
-
释放原则:先开的后关,后开的先关。ResultSet -> Statement -> Connection
-
放在哪个代码块中:finally 块
执行 DML 语句
- account表 添加一条记录
- account表 修改记录
- account表 删除一条记录
public static void main(String[] args) throws Exception {
Statement stmt = null;
Connection conn = null;
try {
//1. 注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2. 定义sql
String sql = "insert into account values(null,'王五',3000)";
//3.获取Connection对象
conn = DriverManager.getConnection("jdbc:mysql:///db03", "root", "root");
//4.获取执行sql的对象 Statement
stmt = conn.createStatement();
//5.执行sql
int count = stmt.executeUpdate(sql);//影响的行数
//6.处理结果
System.out.println(count);
if(count > 0){
System.out.println("添加成功!");
}else{
System.out.println("添加失败!");
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}finally {
//stmt.close();
//7. 释放资源
//避免空指针异常
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
执行 DQL 操作
ResultSet 接口
作用:封装数据库查询的结果集,对结果集进行遍历,取出每一条记录
接口中的方法:
-
boolean next():1)游标向下移动 1 行; 2)返回 boolean 类型,如果还有下一条记录,返回 true,否则返回 false
-
数据类型 getXxx() :1) 通过字段名,参数是 String 类型。返回不同的类型; 2) 通过列号,参数是整数,从 1 开始。返回不同的类型
常用数据类型转换表
代码示例:查询所有的学员信息
public static void main(String[] args) throws SQLException {
//1) 得到连接对象
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/db03","root","root");
//2) 得到语句对象
Statement statement = connection.createStatement();
//3) 执行 SQL 语句得到结果集 ResultSet 对象
ResultSet rs = statement.executeQuery("select * from student");
//4) 循环遍历取出每一条记录
while(rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
boolean gender = rs.getBoolean("gender");
Date birthday = rs.getDate("birthday");
//5) 输出的控制台上
System.out.println("编号:" + id + ", 姓名:" + name + ", 性别:" + gender + ", 生日:" + birthday);
}
//6) 释放资源
rs.close();
statement.close();
connection.close();
}
关于 ResultSet 接口中的注意事项:
- 如果光标在第一行之前,使用 rs.getXX()获取列值,报错:Before start of result set
- 如果光标在最后一行之后,使用 rs.getXX()获取列值,报错:After end of result set
- 使用完毕以后要关闭结果集 ResultSet,再关闭 Statement,再关闭 Connection
小练习-数据库工具类 JdbcUtils
什么时候自己创建工具类?
如果一个功能经常要用到,我们建议把这个功能做成一个工具类,可以在不同的地方重用。
上面写的代码中出现了很多重复的代码,可以把这些公共代码抽取出来。
/**
* 访问数据库的工具类
*/
public class JdbcUtils {
//可以把几个字符串定义成常量:用户名,密码,URL,驱动类
private static final String USER = "root";
private static final String PWD = "root";
private static final String URL = "jdbc:mysql://localhost:3306/day24";
private static final String DRIVER= "com.mysql.jdbc.Driver";
/**
* 注册驱动
*/
static {
try {
Class.forName(DRIVER);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* 得到数据库的连接
*/
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(URL,USER,PWD);
}
/**
* 关闭所有打开的资源
*/
public static void close(Connection conn, Statement stmt) {
if (stmt!=null) {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn!=null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
/**
* 关闭所有打开的资源
*/
public static void close(Connection conn, Statement stmt, ResultSet rs) {
if (rs!=null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
close(conn, stmt);
}
}
PreparedStatement:执行sql的对象
如果经常需要反复执行一条结构相似的 SQL 语句, 例如如下两条 SQL 语句:
insert into student_table values(null,'张三、 1);
insert into student_table values(null,'李四、 2);
对于这两条 SQL 语句而言, 它们的结构基本相似, 只是执行插入时插入的值不同而己。 对于这种情况, 可以使用带占位符(?) 参数的 SQL 语句来代替它:
insert into student table values(null,?,?);
但 Statement 执行 SQL 语句时不允许使用问号占位符参数, 而且这个问号占位符参数必须获得值后才可以执行。 为了满足这种功能, JDBC 提供PreparedStatement 接口, 它是 Statement 接口的子接口,它可以预编译 SQL 语句, 预编译后的 SQL 语句被存储在 PreparedStatement 对象中, 然后可以使用该对象多次高效地执行该语句。 简而言之, 使用 PreparedStatement 比使用 Statement 的效率要高。
创建 PreparedStatement 对象使用 Connection 的 prepareStatement()方法, 该方法需要传入一个 SQL字符串, 该 SQL 字符串可以包含占位符参数。 如下代码所示:
// 创建一个 PreparedStatement 对象
pstmt = conn.prepareStatement("insert into student_table values(null,?,1)");
PreparedStatement 也提供了 execute()、 executeUpdate()、 executeQuery()三个方法来执行 SQL 语句,不过这三个方法无须参数, 因为 PreparedStatement 己存储了预编译的 SQL 语句。
使用 PreparedStatement 预编译 SQL 语句时, 该 SQL 语句可以带占位符参数, 因此在执行 SQL 语句之前必须为这些参数传入参数值,PreparedStatement 提供了一系列的 setXxx(int index , Xxx value)方法来传入参数值。
import java.util.*;
import java.io.*;
import java.sql.*;
public class PreparedStatementTest
{
private String driver;
private String url;
private String user;
private String pass;
public void initParam(String paramFile)throws Exception
{
// 使用Properties类来加载属性文件
Properties props = new Properties();
props.load(new FileInputStream(paramFile));
driver = props.getProperty("driver");
url = props.getProperty("url");
user = props.getProperty("user");
pass = props.getProperty("pass");
// 2. 注册驱动
Class.forName(driver);
}
public void insertUseStatement()throws Exception
{
long start = System.currentTimeMillis();
try(
// 获取数据库连接对象 Connection
// 获取数据库连接
Connection conn = DriverManager.getConnection(url, user, pass);
// 使用Connection来创建一个Statment对象
Statement stmt = conn.createStatement())
{
// 需要使用100条SQL语句来插入100条记录
for (int i = 0; i < 100 ; i++ )
{
stmt.executeUpdate("insert into student_table values(" + " null ,'姓名" + i + "' , 1)");
}
System.out.println("使用Statement费时:" + (System.currentTimeMillis() - start));
}
}
public void insertUsePrepare()throws Exception
{
long start = System.currentTimeMillis();
try(
// 3. 获取数据库连接对象 Connection
Connection conn = DriverManager.getConnection(url, user , pass);
// 4. 使用Connection来创建一个PreparedStatement对象, 获取执行sql语句的对象
PreparedStatement pstmt = conn.prepareStatement("insert into student_table values(null,?,1)"))
{
// 100次为PreparedStatement的参数设值,就可以插入100条记录
for (int i = 0; i < 100 ; i++ )
{
// 5. 给?赋值:
pstmt.setString(1 , "姓名" + i); // setXxx(参数1,参数2):(参数1:?的位置编号 从1开始; 参数2:?的值)。
// 6. 执行sql,接受返回结果,不需要传递sql语句
int counter = pstmt.executeUpdate();
}
System.out.println("使用PreparedStatement费时:" + (System.currentTimeMillis() - start));
}
// 9. 释放资源
}
public static void main(String[] args) throws Exception
{
// 1. 导入驱动jar包 mysql-connector-java-5.1.37-bin.jar
PreparedStatementTest pt = new PreparedStatementTest();
pt.initParam("mysql.ini");
pt.insertUseStatement();
pt.insertUsePrepare();
}
}
案例:将多条记录封装成集合 List,集合中每个元素是一个 JavaBean 实体类
需求: 查询所有的学生类,封装成 List返回
代码:
public static void main(String[] args) throws SQLException {
//创建一个集合
List<Student> students = new ArrayList<>();
Connection connection = JdbcUtils.getConnection();
PreparedStatement ps = connection.prepareStatement("select * from student");
//没有参数替换
ResultSet resultSet = ps.executeQuery();
while(resultSet.next()) {
//每次循环是一个学生对象
Student student = new Student();
//封装成一个学生对象
student.setId(resultSet.getInt("id"));
student.setName(resultSet.getString("name"));
student.setGender(resultSet.getBoolean("gender"));
student.setBirthday(resultSet.getDate("birthday"));
//把数据放到集合中
students.add(student);
}
//关闭连接
JdbcUtils.close(connection,ps,resultSet);
//使用数据
for (Student stu: students) {
System.out.println(stu);
}
}
执行 DML 操作
public class Demo11DML {
public static void main(String[] args) throws SQLException {
//insert();
//update();
delete();
}
//插入记录
private static void insert() throws SQLException {
Connection connection = JdbcUtils.getConnection();
PreparedStatement ps = connection.prepareStatement("insert into student values(null,?,?,?)");
ps.setString(1,"小白龙");
ps.setBoolean(2, true);
ps.setDate(3,java.sql.Date.valueOf("1999-11-11"));
int row = ps.executeUpdate();
System.out.println("插入了" + row + "条记录");
JdbcUtils.close(connection,ps);
}
//更新记录: 换名字和生日
private static void update() throws SQLException {
Connection connection = JdbcUtils.getConnection();
PreparedStatement ps = connection.prepareStatement("update student set name=?, birthday=? where id=?");
ps.setString(1,"黑熊怪");
ps.setDate(2,java.sql.Date.valueOf("1999-03-23"));
ps.setInt(3,5);
int row = ps.executeUpdate();
System.out.println("更新" + row + "条记录");
JdbcUtils.close(connection,ps);
}
//删除记录: 删除第 5 条记录
private static void delete() throws SQLException {
Connection connection = JdbcUtils.getConnection();
PreparedStatement ps = connection.prepareStatement("delete from student where id=?");
ps.setInt(1,5);
int row = ps.executeUpdate();
System.out.println("删除了" + row + "条记录");
JdbcUtils.close(connection,ps);
}
}
JDBC 事务的处理
事务是由一步或几步数据库操作序列组成的逻辑执行单元, 这系列操作要么全部执行, 要么全部放弃执行。 程序和事务是两个不同的概念。 一般而言, 一段程序中可能包含多个事务。
事务具备 4 个特性: 原子性( Atomicity)、 一致性( Consistency)、 隔离性 (Isolation) 和持续性( Durability)。 这 4 个特性也简称为 ACID 性。
- 原 子 性(Atomicity): 事务是应用中最小的执行单位, 就如原子是自然界的最小颗粒, 具有不可再分的特征一样, 事务是应用中不可再分的最小逻辑执行体。
- 一致性(Consistency): 事务执行的结果, 必须使数据库从一个一致性状态, 变到另一个一致性状态。 当数据库只包含事务成功提交的结果时, 数据库处于一致性状态。 如果系统运行发生中断, 某个事务尚未完成而被迫中断, 而该未完成的事务对数据库所做的修改已被写入数据库,此时, 数据库就处于一种不正确的状态。 比如银行在两个账户之间转账: 从 A 账户向 B 账户转入 1000 元, 系统先减少 A 账户的 1000 元, 然后再为 B 账户增加 1000 元。 如果全部执行成功,数据库处于于一致性状态; 如果仅执行完 A 账户金额的修改, 而没有增加 B 账户的金额, 则数据库就处于不一致性状态; 因此, 一致性是通过原子性来保证的。
- 隔 离 性 (Isolation): 各个事务的执行互不干扰, 任意一个事务的内部操作对其他并发的事务都是隔离的。 也就是说, 并发执行的事务之间不能看到对方的中间状态, 并发执行的事务之间不能互相影响。
- 持续性( Durability): 持续性也称为持久性(Persistence), 指事务一旦提交, 对数据所做的任何改变都要记录到永久存储器中, 通常就是保存进物理数据库。
Connection接口管理事务:
- 开启事务:
setAutoCommit(boolean autoCommit)
:调用该方法设置参数为false,即开启事务 - 提交事务:
commit()
- 回滚事务:
rollback()
public static void main(String[] args) {
Connection conn = null;
PreparedStatement pstmt1 = null;
PreparedStatement pstmt2 = null;
try {
//1.获取连接
conn = JDBCUtils.getConnection();
//开启事务
conn.setAutoCommit(false);
//2.定义sql
//2.1 张三 - 500
String sql1 = "update account set balance = balance - ? where id = ?";
//2.2 李四 + 500
String sql2 = "update account set balance = balance + ? where id = ?";
//3.获取执行sql对象
pstmt1 = conn.prepareStatement(sql1);
pstmt2 = conn.prepareStatement(sql2);
//4. 设置参数
pstmt1.setDouble(1,500);
pstmt1.setInt(2,1);
pstmt2.setDouble(1,500);
pstmt2.setInt(2,2);
//5.执行sql
pstmt1.executeUpdate();
// 手动制造异常
int i = 3/0;
pstmt2.executeUpdate();
//提交事务
conn.commit();
} catch (Exception e) {
//事务回滚
try {
if(conn != null) {
conn.rollback();
}
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}finally {
JDBCUtils.close(pstmt1,conn);
JDBCUtils.close(pstmt2,null);
}
}
JDBC Batch批量操作
使用JDBC操作数据库的时候,经常会执行一些批量操作。
例如,一次性给会员增加可用优惠券若干,我们可以执行以下SQL代码:
INSERT INTO coupons (user_id, type, expires) VALUES (123, 'DISCOUNT', '2030-12-31');
INSERT INTO coupons (user_id, type, expires) VALUES (234, 'DISCOUNT', '2030-12-31');
INSERT INTO coupons (user_id, type, expires) VALUES (345, 'DISCOUNT', '2030-12-31');
INSERT INTO coupons (user_id, type, expires) VALUES (456, 'DISCOUNT', '2030-12-31');
...
实际上执行JDBC时,因为只有占位符参数不同,所以SQL实际上是一样的:
for (var params : paramsList) {
PreparedStatement ps = conn.preparedStatement("INSERT INTO coupons (user_id, type, expires) VALUES (?,?,?)");
ps.setLong(params.get(0));
ps.setString(params.get(1));
ps.setString(params.get(2));
ps.executeUpdate();
}
通过一个循环来执行每个PreparedStatement
虽然可行,但是性能很低。SQL数据库对SQL语句相同,但只有参数不同的若干语句可以作为batch执行,即批量执行,这种操作有特别优化,速度远远快于循环执行每个SQL。
在JDBC代码中,我们可以利用SQL数据库的这一特性,把同一个SQL但参数不同的若干次操作合并为一个batch执行。我们以批量插入为例,示例代码如下:
try (PreparedStatement ps = conn.prepareStatement("INSERT INTO students (name, gender, grade, score) VALUES (?, ?, ?, ?)")) {
// 对同一个PreparedStatement反复设置参数并调用addBatch():
for (Student s : students) {
ps.setString(1, s.name);
ps.setBoolean(2, s.gender);
ps.setInt(3, s.grade);
ps.setInt(4, s.score);
ps.addBatch(); // 添加到batch
}
// 执行batch:
int[] ns = ps.executeBatch(); // 调用executeBatch()方法
for (int n : ns) {
System.out.println(n + " inserted."); // batch中每个SQL执行的结果数量
}
}
执行batch和执行一个SQL不同点在于,需要对同一个PreparedStatement
反复设置参数并调用addBatch()
,这样就相当于给一个SQL加上了多组参数,相当于变成了“多行”SQL。
第二个不同点是调用的不是executeUpdate()
,而是executeBatch()
,因为我们设置了多组参数,相应地,返回结果也是多个int
值,因此返回类型是int[]
,循环int[]
数组即可获取每组参数执行后影响的结果数量。
数据库连接池
我们在讲多线程的时候说过,创建线程是一个昂贵的操作,如果有大量的小任务需要执行,并且频繁地创建和销毁线程,实际上会消耗大量的系统资源,往往创建和消耗线程所耗费的时间比执行任务的时间还长,所以,为了提高效率,可以用线程池。
类似的,在执行JDBC的增删改查的操作时,如果每一次操作都来一次打开连接,操作,关闭连接,那么创建和销毁JDBC连接的开销就太大了。为了避免频繁地创建和销毁JDBC连接,我们可以通过连接池(Connection Pool)复用已经创建好的连接。
JDBC连接池有一个标准的接口javax.sql.DataSource
,注意这个类位于Java标准库中,但仅仅是接口。要使用JDBC连接池,我们必须选择一个JDBC连接池的实现。常用的JDBC连接池有:
- HikariCP
- C3P0
- BoneCP
- Druid
目前使用最广泛的是HikariCP。我们以HikariCP为例,要使用JDBC连接池,先添加HikariCP的依赖如下:
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>2.7.1</version>
</dependency>
紧接着,我们需要创建一个DataSource
实例,这个实例就是连接池:
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/test");
config.setUsername("root");
config.setPassword("password");
config.addDataSourceProperty("connectionTimeout", "1000"); // 连接超时:1秒
config.addDataSourceProperty("idleTimeout", "60000"); // 空闲超时:60秒
config.addDataSourceProperty("maximumPoolSize", "10"); // 最大连接数:10
DataSource ds = new HikariDataSource(config);
注意创建DataSource
也是一个非常昂贵的操作,所以通常DataSource
实例总是作为一个全局变量存储,并贯穿整个应用程序的生命周期。
有了连接池以后,我们如何使用它呢?和前面的代码类似,只是获取Connection
时,把DriverManage.getConnection()
改为ds.getConnection()
:
try (Connection conn = ds.getConnection()) { // 在此获取连接
...
} // 在此“关闭”连接
通过连接池获取连接时,并不需要指定JDBC的相关URL、用户名、口令等信息,因为这些信息已经存储在连接池内部了(创建HikariDataSource
时传入的HikariConfig
持有这些信息)。一开始,连接池内部并没有连接,所以,第一次调用ds.getConnection()
,会迫使连接池内部先创建一个Connection
,再返回给客户端使用。当我们调用conn.close()
方法时(在try(resource){...}
结束处),不是真正“关闭”连接,而是释放到连接池中,以便下次获取连接时能直接返回。
因此,连接池内部维护了若干个Connection
实例,如果调用ds.getConnection()
,就选择一个空闲连接,并标记它为“正在使用”然后返回,如果对Connection
调用close()
,那么就把连接再次标记为“空闲”从而等待下次调用。这样一来,我们就通过连接池维护了少量连接,但可以频繁地执行大量的SQL语句。
通常连接池提供了大量的参数可以配置,例如,维护的最小、最大活动连接数,指定一个连接在空闲一段时间后自动关闭等,需要根据应用程序的负载合理地配置这些参数。此外,大多数连接池都提供了详细的实时状态以便进行监控。