JDK中有常量池,线程池,数据库操作有连接池,那么这些池到底是什么?有什么作用?底层原理是啥?下面以连接池示例。
测试环境:JDK1.8 + MySQL8.0.16 + Spring Boot 2.1.3.RELEASE + Gradle 4.4.1
一. 从一个最原始的JDBC查询开始
- build.gralde 加入相关依赖
// 依赖列表
dependencies {
compile 'org.springframework.boot:spring-boot-starter-web'
compile 'mysql:mysql-connector-java:8.0.16'
testCompile 'org.springframework.boot:spring-boot-starter-test'
testCompile group: 'junit', name: 'junit', version: '4.12'
compileOnly 'org.projectlombok:lombok:1.18.4'
}
- application.yml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/zzxypm?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&serverTimezone=UTC
username: root
password: root
- 数据源配置参数
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @author wanghong
* @date 2019/08/18 14:58
**/
@Data
@Component
@ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceConfig {
private String driverClassName;
private String url;
private String username;
private String password;
}
- 原生JDBC操作工具类
package com.hong.util;
import com.hong.config.DataSourceConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author wanghong
* @date 2019/08/18 14:11
**/
@Component
public class JDBCUtil {
@Autowired
private DataSourceConfig dataSourceConfig;
/**
* 获取数据库连接
*
* @return
* @throws SQLException
*/
public Connection getConnection() throws SQLException {
try {
Class.forName(dataSourceConfig.getDriverClassName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return DriverManager.getConnection(dataSourceConfig.getUrl(), dataSourceConfig.getUsername(), dataSourceConfig.getPassword());
}
/**
* * 关闭资源
* * @param resultSet 查询返回的结果集,没有为空
* * @param statement
* * @param connection
*/
public void close(ResultSet resultSet, Statement statement, Connection connection) {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
/**
* 增加,删除,修改
*/
public void insertOrDeleteOrUpdate(String sql) {
try {
Connection connection = getConnection();
PreparedStatement pst = connection.prepareStatement(sql);
int execute = pst.executeUpdate();
System.out.println("执行语句:" + sql + "," + execute + "行数据受影响");
close(null, pst, connection);
} catch (SQLException e) {
System.out.println("异常提醒:" + e);
}
}
/**
* * 查询,返回结果集
*/
public List<Map<String, Object>> select(String sql) {
List<Map<String, Object>> returnResultToList = null;
try {
Connection connection = getConnection();
PreparedStatement pst = connection.prepareStatement(sql);
ResultSet resultSet = pst.executeQuery();
returnResultToList = returnResultToList(resultSet);
close(resultSet, pst, connection);
} catch (SQLException e) {
System.out.println("异常提醒:" + e);
}
return returnResultToList;
}
/**
* 数据返回集合
*
* @param resultSet
* @return
* @throws SQLException
*/
private List<Map<String, Object>> returnResultToList(ResultSet resultSet) {
List<Map<String, Object>> values = null;
try {
// 键: 存放列的别名, 值: 存放列的值.
values = new ArrayList<>();
// 存放字段名
List<String> columnName = new ArrayList<>();
ResultSetMetaData rsmd = resultSet.getMetaData();
for (int i = 0; i < rsmd.getColumnCount(); i++) {
// 字段名
columnName.add(rsmd.getColumnLabel(i + 1));
}
System.out.println("表字段为:");
System.out.println(columnName);
System.out.println("表数据为:");
Map<String, Object> map = null;
// 处理 ResultSet, 使用 while 循环
while (resultSet.next()) {
map = new HashMap<>();
for (String column : columnName) {
Object value = resultSet.getObject(column);
map.put(column, value);
System.out.print(value + "\t");
}
// 把一条记录的 Map 对象放入准备的 List 中
values.add(map);
System.out.println();
}
} catch (SQLException e) {
System.out.println("异常提醒:" + e);
}
return values;
}
}
@Service
public class JdbcService {
@Autowired
private JDBCUtil jdbcUtil;
/**
* 数据库操作
* @param sql
*/
public List<Map<String, Object>> execute(String sql){
List<Map<String, Object>> list = jdbcUtil.select(sql);
return list;
}
}
@Controller
public class HelloController {
@Autowired
private JdbcService jdbcService;
@GetMapping("/")
@ResponseBody
public Object Home() {
String sql = "SELECT * FROM t_user WHERE account = 'wanghong'";
return jdbcService.execute(sql);
}
}
好了,可以正常访问。下面模拟下并发查询。
二. 多线程并发操作
改造下service代码如下:
package com.hong.service;
import com.hong.util.JDBCUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
/**
* @author wanghong
* @date 2019/08/18 12:09
**/
@Service
public class JdbcService {
private static final int CONCURRENCY_LEVEL = 200; // 模拟并发请求
@Autowired
private JDBCUtil jdbcUtil;
/**
* 数据源连接压测
*/
public void benchmarkTest() {
String sql = "SELECT * FROM t_user WHERE account = 'wanghong'";
CountDownLatch cdl = new CountDownLatch(CONCURRENCY_LEVEL);
for (int i = 0; i < CONCURRENCY_LEVEL; i++) {
// 多线程模拟客户端并发查询请求
Thread thread = new Thread(() -> {
try {
// 代码在这里等待CountDownLatch计数为0,代表所有线程可以start,再同时抢占运行
cdl.await();
execute(sql);
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + "线程执行出现异常:" + e.getMessage());
}
});
thread.setName("jdbc-thread-" + i);
thread.start();
//线程启动后,倒记数器-1,表示又有一个线程就绪了
cdl.countDown();
}
}
/**
* 数据库操作
* @param sql
*/
public List<Map<String, Object>> execute(String sql){
List<Map<String, Object>> list = jdbcUtil.select(sql);
return list;
}
}
@Controller
public class HelloController {
@Autowired
private JdbcService jdbcService;
@GetMapping("/")
@ResponseBody
public Object Home() {
// String sql = "SELECT * FROM t_user WHERE account = 'wanghong'";
// return jdbcService.execute(sql);
jdbcService.benchmarkTest();
return null;
}
}
发送请求,然后控制台出现了如下异常信息:
java.sql.SQLNonTransientConnectionException: Data source rejected establishment of connection, message from server: "Too many connections"
下面看看如何使用池化技术解决数据库连接的问题。
三. 池?
- 池是什么?
- 池化技术有哪些共性?
- 什么样的资源需要池化?
四. 如何设计一个池?
-
设计实现
-
技术内幕 — AtomicInteger CAS
-
技术内幕 — Condition
-
代码落地
package com.hong.config;
import java.sql.Connection;
/**
* @author wanghong
* @date 2019/08/18 16:21
* 数据库连接池定义
**/
public interface ConnectionPool {
/**
* 初始化
* @param initSize 初始化连接数
* @param maxActive 最大连接数
* @param idleCount 空闲连接数
* @param waitTime 获取连接超时时间
*/
void init(int initSize ,int maxActive ,int idleCount,long waitTime);
/**
* 获取连接
* @return
*/
Connection get();
/**
* 回收资源
* @param conn
*/
void recycle(Connection conn);
}
package com.hong.config;
import com.hong.util.JDBCUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author wanghong
* @date 2019/08/18 16:29
* 连接池组件实现
**/
@Component
public class ConnectionPoolImpl implements ConnectionPool {
@Autowired
private JDBCUtil jdbcUtil;
private int initSize;
private int maxActive;
private int idleCount;
private long waitTime;
// 当前活动的线程数
private AtomicInteger activeSize = new AtomicInteger();
BlockingQueue<Connection> idle;
BlockingQueue<Connection> busy;
@Override
public void init(int initSize, int maxActive, int idleCount, long waitTime) {
// 省略参数检查
this.initSize = initSize;
this.maxActive = maxActive;
this.idleCount = idleCount;
this.waitTime = waitTime;
idle = new LinkedBlockingDeque<>();
busy = new LinkedBlockingDeque<>();
initConnection(this.initSize);
}
/**
* 初始化数据库连接
*
* @param initSize
*/
private void initConnection(int initSize) {
for (int i = 0; i < initSize; i++) {
if (activeSize.get() < maxActive){
// 双重检查
if (activeSize.incrementAndGet() <= maxActive){
try {
Connection conn = jdbcUtil.getConnection();
idle.offer(conn);
} catch (SQLException e) {
e.printStackTrace();
}
}else {
activeSize.decrementAndGet();
}
}
}
}
@Override
public Connection get() {
long start = System.currentTimeMillis();
// 1. 从空闲组获取
Connection conn = idle.poll();
if (conn != null){
System.out.println("从空闲组获得连接");
busy.offer(conn);
return conn;
}
// 2. 空闲组没有,没有限制
if (activeSize.get() < maxActive){
if (activeSize.incrementAndGet() <= maxActive){
System.out.println("创建一个新的连接");
try{
conn = jdbcUtil.getConnection();
busy.offer(conn);
return conn;
}catch (Exception e){
System.out.println("获取连接异常:" + e);
}
}else {
activeSize.decrementAndGet();
}
}
// 3.连接全忙,等待连接归还
long timeout = waitTime - (System.currentTimeMillis() - start);
try {
conn = idle.poll(timeout, TimeUnit.MILLISECONDS);
if (conn == null){
throw new TimeoutException("等待获取连接超时,等待时间为:" + timeout + "ms");
}
} catch (InterruptedException | TimeoutException e) {
e.printStackTrace();
Thread.interrupted();
}
return conn;
}
@Override
public void recycle(Connection conn) {
if (conn == null){
return;
}
// 从busy挪到idle
boolean removed = busy.remove(conn);
if (removed){
// 空闲连接太多,释放掉
if (idleCount < idle.size()){
jdbcUtil.close(null,null,conn);
activeSize.decrementAndGet();
return;
}
boolean offerd = idle.offer(conn);
// 没有放入成功
if (!offerd){
jdbcUtil.close(null,null,conn);
activeSize.decrementAndGet();
}
}else {
// 不在组中,关闭释放连接,防止连接泄露
jdbcUtil.close(null,null,conn);
activeSize.decrementAndGet();
}
}
}
package com.hong.util;
import com.hong.config.ConnectionPool;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
/**
* @author wanghong
* @date 2019/08/18 17:20
**/
@Component
public class JDBCDao {
@Autowired
private ConnectionPool connectionPool;
@Autowired
private JDBCUtil jdbcUtil;
/**
* 注解@PostConstruct与@PreDestroy
*
* PostConstruct 用于在依赖关系注入完成之后需要执行的方法上,以执行任何初始化。
*/
@PostConstruct
private void init(){
connectionPool.init(10,100,10,5000);
}
public List<Map<String, Object>> select(String sql) {
List<Map<String, Object>> returnResultToList = null;
try {
// 从连接池中获取连接
Connection connection = connectionPool.get();
PreparedStatement pst = connection.prepareStatement(sql);
ResultSet resultSet = pst.executeQuery();
returnResultToList = jdbcUtil.returnResultToList(resultSet);
// 使用完,将连接放回连接池
connectionPool.recycle(connection);
} catch (SQLException e) {
System.out.println("异常提醒:" + e);
}
return returnResultToList;
}
}
JdbcService.java
/**
* 数据库操作
* @param sql
*/
public List<Map<String, Object>> execute(String sql){
// List<Map<String, Object>> list = jdbcUtil.select(sql);
List<Map<String, Object>> list = jdbcDao.select(sql);
return list;
}
请求测试,没有再报之前的异常了。