一)数据库连接池简介
数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个。
数据库连接池原理:
在系统初始化的时候,将数据库连接作为对象存储在内存中,当用户需要访问数据库时,并非建立一个新的连接,而是从连接池中取出一个已建立的空闲连接对象。使用完毕后,用户也并非将连接关闭,而是将连接放回连接池中,以供下一个请求访问使用。而连接的建立、断开都由连接池自身来管理。
数据库访问步骤:
1)加载对应的数据库驱动程序。
2)通过JDBC的方式创建数据库连接。
3)开启数据库事务。
4)执行sql查询,并返回结果。
5)提交事务,并关闭数据库事务。
6)关闭数据库连接。
说明:在有了JDBC实际操作经验之后,会发现访问数据库最耗时的步骤是创建和关闭数据库连接这两个步骤,当用户量较多,频繁访问数据库时,性能开销是非常大的,所以可以在第1、2、6步骤的功能单独抽离处理。
Oracle或Mysql连接JDBC,可查看该文章:https://blog.csdn.net/p812438109/article/details/88640267
二)自定义数据库连接池
第一步:创建一个数据库连接池工具类,用LinkedList作为数据库连接池。
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.LinkedList;
/**
* 自定义数据库连接池
* @author ouyangjun
*/
public class ConnectionPool {
// 数据库驱动
private static String DATABASE_DRIVER = "";
// 数据库url
private static String DATABASE_URL = "";
// 数据库账号
private static String DATABASE_USER = "";
// 数据库密码
private static String DATABASE_PASSWORD = "";
// 数据库连接池
private final LinkedList<Connection> linkedList = new LinkedList<Connection>();
// 默认创建10个数据库连接
public ConnectionPool() {
this(10, false);
}
// 创建指定数量的数据库连接
public ConnectionPool(int size, boolean fair) {
if (size <= 0) {
throw new IndexOutOfBoundsException("Size: " + size);
}
// 创建数据库连接
addConnection(size);
}
}
第二步:新建一个创建数据库连接的方法,把创建的数据库连接放置到连接池中。
// 创建数据库连接,并把连接放置到连接池
private void createConnection(Integer size) {
try {
// 加载数据库驱动程序
Class.forName(DATABASE_DRIVER);
Connection conn = null;
try {
// 循环创建
for (int i = 0; i < size; i++) {
// 获取数据库连接,数据库URL,数据库用户名,数据库密码
conn = DriverManager.getConnection(DATABASE_URL, DATABASE_USER, DATABASE_PASSWORD);
// 把连接存放到连接池中
linkedList.add(conn);
}
} catch (Exception e) {
System.out.println("DatabaseConnectionPool.addConnection==>SQLException Fail!" + e);
}
} catch (ClassNotFoundException e) {
System.out.println("DatabaseConnectionPool.addConnection==>ClassNotFoundException Fail!" + e);
}
}
第三步:创建一个从连接池中获取连接的方法,获取数据库连接之后并移除该连接。
// 获取一个数据库连接,并从连接池中移除
public Connection getConnection() {
Connection conn = null;
if (!linkedList.isEmpty()) {
conn = linkedList.removeFirst(); // 获取第一个数据库连接池,并移除
}
return conn;
}
第四步:创建一个数据库连接回收到连接池的方法,当用户使用完连接之后,调用该方法把连接重新放置到连接池。
// 是否数据库连接,并重新存放到连接池
public void releaseConnection(Connection conn) {
if (conn != null) {
linkedList.add(conn); // 把连接重新放入到连接池中
}
}
总结:该方式能让人更容易了解数据库连接池实现原理。不足的地方就是,非线程安全,连接可能被使用完,导致无连接可用。
三)自定义阻塞、线程安全的数据库连接池
主要是在上面的实现方式上修改。
第一步:在数据库连接池工具类中,增加ReentrantLock锁和Condition。
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.LinkedList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* 自定义数据库连接池
* @author ouyangjun
*/
public class DatabaseConnectionPool {
final ReentrantLock lock; // 锁
private final Condition notEmpty; // 线程已空
private final Condition notFull; // 线程已满
// 数据库驱动
private static String DATABASE_DRIVER = "";
// 数据库url
private static String DATABASE_URL = "";
// 数据库账号
private static String DATABASE_USER = "";
// 数据库密码
private static String DATABASE_PASSWORD = "";
// 数据库连接池
private final LinkedList<Connection> linkedList = new LinkedList<Connection>();
private int size; // 数据库连接池大小
// 默认创建10个数据库连接
public DatabaseConnectionPool() {
this(10, false);
}
// 创建指定数量的数据库连接
public DatabaseConnectionPool(int size, boolean fair) {
if (size <= 0) {
throw new IndexOutOfBoundsException("Size: " + size);
}
this.size = size;
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition();
notFull = lock.newCondition();
// 创建数据库连接
createConnection(size);
}
}
第二步:初始化数据库连接方法没变化。
// 创建数据库连接,并把连接放置到连接池
private void createConnection(Integer size) {
try {
// 加载数据库驱动
Class.forName(DATABASE_DRIVER);
Connection conn = null;
try {
// 循环创建
for (int i = 0; i < size; i++) {
// 获取数据库连接
conn = DriverManager.getConnection(DATABASE_URL, DATABASE_USER, DATABASE_PASSWORD);
// 把连接存放到连接池中
linkedList.add(conn);
}
} catch (Exception e) {
System.out.println("DatabaseConnectionPool.addConnection==>SQLException Fail!" + e);
}
} catch (ClassNotFoundException e) {
System.out.println("DatabaseConnectionPool.addConnection==>ClassNotFoundException Fail!" + e);
}
}
第三步:获取数据库连接。当获取连接时,当前线程会被锁定,再判断是否有空闲连接,如果没有,就等待,否则就获取一个空闲连接返回,再解锁。
// 获取一个数据库连接,并从连接池中移除
public Connection getConnection() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly(); // 获取锁定, 除非当前线程是interrupted。
try {
if (linkedList.isEmpty()) { // 当连接池为空时, 就等待
System.out.println("==>notEmpty.await();");
notEmpty.await(); // 线程等待
}
Connection conn = linkedList.removeFirst(); // 获取第一个数据库连接池,并移除
notFull.signal(); // 唤醒连接池已满线程
return conn;
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock(); // 尝试释放此锁
}
return null;
}
第四步:回收数据库连接。当线程池已满时,先等待,否则把数据库连接放置到连接池中,再解锁。
// 是否数据库连接,并重新存放到连接池
public void releaseConnection(Connection conn) throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly(); // 获取锁定, 除非当前线程是interrupted
try {
while (size == linkedList.size()) {
System.out.println("==>notFull.await();");
notFull.await(); // 线程等待
}
linkedList.add(conn); // 把连接重新放入到连接池中
notEmpty.signal(); // 唤醒连接池已空线程
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock(); // 尝试释放此锁
}
}
第五步:main方法测试
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class JDBCTest {
public static void main(String[] args) throws InterruptedException {
DatabaseConnectionPool pool = new DatabaseConnectionPool(1, false);
// 测试
for (int i=0; i<5; i++) {
getCount(pool);
}
}
public static void getCount(DatabaseConnectionPool pool) throws InterruptedException {
Integer count = 0;
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = pool.getConnection();
ps = conn.prepareStatement("select count(0) as ct from tb_test");
rs = ps.executeQuery();
while (rs.next()) {
count = rs.getInt(1);
}
} catch (SQLException e) {
System.out.println("SQLException Error!" + e);
} finally {
try {
if (rs != null) {
rs.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (ps != null) {
ps.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
// 把连接重新放入到连接池中
pool.releaseConnection(conn);
}
System.out.println("count: " + count);
}
}
识别二维码关注个人微信公众号
本章完结,待续,欢迎转载!
本文说明:该文章属于原创,如需转载,请标明文章转载来源!