快速使用Java和MariaDB–创建和管理连接池
在上一篇文章我们直接用数据库打开连接,让Java应用程序和数据库进行交互。但是在多用户发送并发请求时这个方法就不太适用了,因为数据库只能打开有限数量的连接,从而有可能会导致其中一些请求失败。并且数据库连接是非常占用资源的,尤其是在高并发的情况下,如果每次都去建立数据库连接就会非常浪费资源,也会影响一个应用程序的延展性和健壮性,影响到程序的性能指标。针对这个问题,连接池出现了,连接池就是为了解决这个问题的。
连接池原理:
数据库连接池负责分配、管理、释放连接,它保证应用程序可以重复使用同一个连接而不需要每次都建立数据库连接,如果数据库连接时间超过设置的最长数据库连接时间会自动释放链接,为了避免因为没有释放链接而导致的数据库连接遗漏,因此,数据库连接池可以明显的提高数据库的连接性能。
数据库连接池在初始化的时候会放入一定数量的连接,这个连接是由最小连接数决定的,就算没有用到这些连接,这个连接也会放在连接池中。如果连接数超过最大连接数,那么会放入队列中等待释放链接再使用。
连接池的优点:
-
资源重用
-
更快的系统反应速度
-
新的分配方式
-
统一的连接管理,避免数据库连接泄露
HikariCP概述
HikariCP是由日本程序员开源的一个数据库连接池组件,代码非常轻量,并且速度非常的快。根据官方提供的数据,在i7,开启32个线程32个连接的情况下,进行随机数据库读写操作,HikariCP的速度是现在常用的C3P0数据库连接池的数百倍。
HikariCP特点
- 字节码精简 :优化代码(HikariCP利用了一个第三方的Java字节码修改类库Javassist来生成委托实现动态代理,动态代理的实现在ProxyFactory类),直到编译后的字节码最少,这样,CPU缓存可以加载更多的程序代码。
- 优化代理和拦截器:减少代码,例如HikariCP的Statement proxy只有100行代码,只有BoneCP的十分之一;
- 自定义数组类型(FastStatementList)代替ArrayList:避免每次get()调用都要进行range check,避免调用remove()时的从头到尾的扫描,相对与ArrayList极大地提升了性能,而其中的区别是,ArrayList在每次执行get(Index)方法时,都需要对List的范围进行检查,而FastStatementList不需要,在能确保范围的合法性的情况下,可以省去范围检查的开销。
- 自定义集合类型(ConcurrentBag):支持快速插入和删除,特别是在同一线程既添加又删除项时,提高并发读写的效率;
- 针对CPU的时间片算法进行优化:尽可能在一个时间片里面完成各种操作。
- 针对连接中断的情况:比其他CP响应时间上有了极好的优化,响应时间为5S,会抛出SqlException异常,并且后续的getConnection()可以正常进行
- 关于Connection的操作:另外在Java代码中,很多都是在使用完之后直接关闭连接,以前都是从头到尾遍历,来关闭对应的Connection,而HikariCP则是从尾部对Connection集合进行扫描,整体上来说,从尾部开始的性能更好一些。
接下来我们将通过一个 Maven 项目我们去学习如何使用 MariaDB JDBC 驱动程序中包含的连接池,并展示在与数据库交互时使用池的重要性。该项目是在上一个项目上进行修改,假如你还没有阅读上一篇文章,点击这里查看使用JDBC执行SQL语句。
1.在pom.xml文件中添加 HikariCP依赖代码如下:
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>3.4.5</version>
</dependency>
2.在pom.xml文件中添加日志文件依赖代码如下:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.8.0-beta4</version>
</dependency>
3.初始化数据库连接池和关闭数据库连接池
4.修改CRUD操作的方法,Create操作的代码实现如下:
//createData()方法具体实现
private static void createData(String name, int rating) throws SQLException {
System.out.println("Creating data... " );
int rowsInserted;
try(Connection connection=dataSource.getConnection()) {
try (PreparedStatement statement = connection.prepareStatement(
" INSERT INTO programming_language(name, rating)\n" + " VALUES (?, ?)\n")) {
statement.setString(1, name);
statement.setInt(2, rating);
rowsInserted = statement.executeUpdate();
}
}
System.out.println("Rows inserted: " + rowsInserted);
}
5.运行结果如下:
完整代码如下:
package com.example;
import com.zaxxer.hikari.HikariDataSource;
import java.sql.*;
public class Application {
private static HikariDataSource dataSource;
public static void main(String[] args) throws SQLException {
try {
//初始化数据库连接池
initDatabaseConnectionPool();
//插入数据
createData("Java", 10);
createData("JavaScript", 9);
createData("C++", 8);
//插入数据结束
//检索数据
readData();
//修改数据
updateData("C++", 7);
readData();
//删除数据
deleteData("C++");
deleteData("JavaScript");
deleteData("Java");
} finally {
//关闭数据连接池
closeDatabaseConnectionPool();
}
}
//createData()方法具体实现
private static void createData(String name, int rating) throws SQLException {
System.out.println("Creating data... " );
int rowsInserted;
try(Connection connection=dataSource.getConnection()) {
try (PreparedStatement statement = connection.prepareStatement(
" INSERT INTO programming_language(name, rating)\n" + " VALUES (?, ?)\n")) {
statement.setString(1, name);
statement.setInt(2, rating);
rowsInserted = statement.executeUpdate();
}
}
System.out.println("Rows inserted: " + rowsInserted);
}
//readData()方法具体实现
private static void readData() throws SQLException {
//申请一个数据库连接
Connection connection=dataSource.getConnection();
try (PreparedStatement statement = connection.prepareStatement(
" SELECT name, rating\n" + " FROM programming_language\n" + " ORDER BY rating DESC\n")) {
try (ResultSet resultSet = statement.executeQuery()) {
boolean empty = true;
while (resultSet.next()) {
String name = resultSet.getString("name");
int rating = resultSet.getInt("rating");
System.out.println("\t> " + name + ": " + rating);
empty = false;
}
if (empty) {
System.out.println("\t (no data)");
}
}
}
}
//updateData()方法具体实现
private static void updateData(String name, int newRating) throws SQLException {
//申请一个数据库连接
Connection connection=dataSource.getConnection();
try (PreparedStatement statement = connection.prepareStatement(
" UPDATE programming_language\n" + " SET rating = ?\n" + " WHERE name = ?\n")) {
statement.setInt(1, newRating);
statement.setString(2, name);
int rowsUpdated = statement.executeUpdate();
System.out.println("Rows updated: " + rowsUpdated);
}
}
//deleteData()方法具体实现
private static void deleteData(String nameExpression) throws SQLException {
//申请一个数据库连接
Connection connection=dataSource.getConnection();
try (PreparedStatement statement = connection.prepareStatement(" DELETE FROM programming_language\n" +
" WHERE name LIKE ?\n")) {
statement.setString(1, nameExpression);
int rowsDeleted = statement.executeUpdate();
System.out.println("Rows deleted: " + rowsDeleted);
}
}
//初始化数据库连接池
private static void initDatabaseConnectionPool() throws SQLException{
dataSource=new HikariDataSource();
dataSource.setJdbcUrl("jdbc:mariadb://localhost:3306/jdbc_demo");
dataSource.setUsername("user");
dataSource.setPassword("passWord");
}
//关闭数据库连接池
private static void closeDatabaseConnectionPool() {
dataSource.close();
}
}
如有不妥之处请留言指正。相互学习,共同进步!