本文主要讲解JDBC中的连接池的基础,适合新手和入门的小白。
Table of Contents
我们以往连接数据库执行SQL的都必须要进行如下操作:
new一个驱动对象:
使用加载驱动程序类来注册驱动程序:
DBUtils中的工具类来获取连接:
通常情况下,我们获取一个连接,也仅仅只是执行一个SQL语句,然后就不用了(甚至有时候还忘记关闭),如果项目太大,连接数太多,数据库就会无法承载起这么多的连接而出错!于是,我们需要连接池来管理我们的连接!
连接池
诞生背景:
- “获得连接”或“释放资源”是非常消耗系统资源的两个过程
- 如果有很多个用户都在连接和释放操作,必然会浪费掉很多资源
解决方案:
- 事先创建好一个放置“连接”容器,成为连接池
- 通过连接池预先同数据库建立一些连接,放在内存中,应用程序需要建立数据库连接时直接到连接池中申请一个就行,用完后再放回去。
- 当用户A连接完毕后,连接池回收该连接,用户B又可以继续使用该连接
特性:
- Java为数据库连接池提供了公共的接口:javax.sql.DataSource,各个厂商需要让自己的连接池实现这个接口。
- 这样应用程序可以方便的切换不同厂商的连接池!常见的连接池:DBCP、C3P0。
JDBC原生技术实现连接池
自己实现线程池的步骤:
- 编写连接池需实现java.sql.DataSource接口
- 创建批量的Connection用LinkedList保存【既然是个池,当然用集合保存、、LinkedList底层是链表,对增删性能较好】
- 实现getConnetion(),让getConnection()每次调用,都是在LinkedList中取一个Connection返回给用户
- 调用Connection.close()方法,Connction返回给LinkedList
实现第一个JDBC原生技术连接池
核心:实现DataBase提供的接口
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.LinkedList;
import java.util.logging.Logger;
import javax.sql.DataSource;
import utils.JDBCUtils;
/**
* 使用JDBC原生技术,实现一个连接池工具类
*
* @author KYLE
*
*/
public class ConnectPoolWithJDBC implements DataSource{
//1.创建1个容器用于存储Connection对象
private static LinkedList<Connection> pool = new LinkedList<Connection>();
//2.创建5个连接放到容器中去
static{
for (int i = 0; i < 5; i++) {
Connection conn = JDBCUtils.getConnection();
pool.add(conn);
}
}
/**
* 重写获取连接的方法
*/
@Override
public Connection getConnection() throws SQLException {
Connection conn = null;
//3.使用前先判断
if(pool.size()==0){
//4.池子里面没有,我们再创建一些
for (int i = 0; i < 5; i++) {
conn = JDBCUtils.getConnection();
pool.add(conn);
}
}
//5.从池子里面获取一个连接对象Connection
conn = pool.remove(0);
return conn;
}
/**
* 归还连接对象到连接池中去
*/
public void backConnection(Connection conn){
pool.add(conn);
}
@Override
public PrintWriter getLogWriter() throws SQLException {
// TODO Auto-generated method stub
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
// TODO Auto-generated method stub
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
// TODO Auto-generated method stub
}
@Override
public int getLoginTimeout() throws SQLException {
// TODO Auto-generated method stub
return 0;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
// TODO Auto-generated method stub
return null;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
// TODO Auto-generated method stub
return false;
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
// TODO Auto-generated method stub
return null;
}
}
使用该连接池
以向数据库中添加为例,演示连接池的用法:
public void testAddUser() {
Connection conn = null;
PreparedStatement pstmt = null;
// 1.创建自定义连接池对象
ConnectPoolWithJDBC dataSource = new ConnectPoolWithJDBC();
try {
// 2.从池子中获取连接
conn = dataSource.getConnection();
String sql = "insert into person values(?,?,?)";
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, 15);
pstmt.setString(2, "hor");
pstmt.setInt(3, 33);
int rows = pstmt.executeUpdate();
if (rows > 0) {
System.out.println("添加成功!");
} else {
System.out.println("添加失败!");
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
dataSource.backConnection(conn);
}
}
DBCP连接池
由于自己写的连接池,在很多地方有缺陷,性能不好,为此,我们需要学习大佬们写的连接池。它的本质还是对JDBC底层连接池技术的封装。
DBCP连接池导包
- DBCP(DataBase connection pool),数据库连接池。是 apache 上的一个 java 连接池项目,也是 tomcat 使用的连接池组件。
- DBCP也是一个开源的连接池,是Apache Common成员之一,在企业开发中也比较常见,tomcat内置的连接池。
导入dbcp和pool的jar包,其中mysql-connector驱动包也不能少,dbutils包可选。
DBCP相关类
- 实现类数据源的规范接口:javax.sql.DataSource
- 创建连接池的类是:org.apache.commons.dbcp.BasicDataSource;
BasicDataSource类的成员属性:
分类 | 属性 | 描述 |
必须项 | driverClassName | 数据库驱动名称 |
url | 数据库的地址 | |
username | 用户名 | |
password | 密码 | |
基本项(扩展) | maxActive | 最大连接数量 |
minIdle | 最小空闲连接 | |
maxIdle | 最大空闲连接 | |
initialSize | 初始化连接 |
创建连接池
DBCP创建一个连接池,并连接数据库(方式一)
import java.sql.Connection;
import java.sql.SQLException;
import org.apache.commons.dbcp.BasicDataSource;
public class DBCPPools {
public static void main(String[] args) {
BasicDataSource bds = new BasicDataSource(); //使用该类来创建连接池
//连接数据库的4个最基本信息,通过对象方法setXXX设置进来
bds.setDriverClassName("com.mysql.jdbc.Driver");
bds.setUrl("jdbc:mysql://localhost:3306/mydb");
bds.setUsername("root");
bds.setPassword("kyle");
try {
Connection con = bds.getConnection();
System.out.println(con);
} catch(SQLException e) {
e.printStackTrace();
throw new RuntimeException("数据库连接失败!");
}
}
}
//表明连接成功:
//jdbc:mysql://localhost:3306/mydb, UserName=root@localhost, MySQL-AB JDBC Driver
DBCP创建一个连接池,并连接数据库(使用properties)
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.commons.dbcp.BasicDataSourceFactory;
/**
* 专门为 DBCP连接池 包封装的创建、连接方式
*
* 核心:
* 加载文件流:本类反射.getClassLoader().getResourceAsStream("配置文件");
*
* @author KYLE
*
*/
public class DBCPPools {
private static DataSource dataSource;
static{
try {
//1.加载找properties文件输入流
InputStream is = DBCPPools.class.getClassLoader().getResourceAsStream("configMySQL.properties");
//2.加载输入流
Properties props = new Properties();
props.load(is);
//3.创建数据源
dataSource = BasicDataSourceFactory.createDataSource(props);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static DataSource getDataSource(){
return dataSource;
}
public static Connection getConnection(){
try {
return dataSource.getConnection();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
使用连接池
class TestFunction {
// 定义2个方法,实现数据表的添加,数据表查询
// QueryRunner类对象,写在类成员位置,需要导入dbutils相关jar包
private static QueryRunner qr = new QueryRunner(DBCPPools.getDataSource());
public static void main(String[] args) {
select();
//insert();
}
// 数据表查询
public static void select() {
String sql = "SELECT * FROM person";
try {
List<Object[]> list = qr.query(sql, new ArrayListHandler());
for (Object[] objs : list) {
for (Object obj : objs) {
System.out.print(obj + "\t");
}
System.out.println();
}
} catch (SQLException ex) {
throw new RuntimeException("数据查询失败");
}
}
// 数据表添加数据
public static void insert() {
String sql = "INSERT INTO person (p_id,p_name,p_age)VALUES(?,?,?)";
Object[] params = {100, "aoc", 12};
try {
int row = qr.update(sql, params);
System.out.println(row);
} catch (SQLException ex) {
throw new RuntimeException("数据添加失败");
}
}
}
C3P0连接池
C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。目前使用它的开源项目有Hibernate,Spring等。
【DBCP连接池的使用】
第一步:引入DBCP连接池的jar包:c3p0-0.9.2.1.jar
第二步:新建配置文件(在src目录下),文件名为c3p0-config.xml(此文件名是固定的),
第三步:编辑该配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<!-- 使用默认的配置读取连接池对象 -->
<default-config>
<!-- 连接参数 -->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<!-- 指定字符集utf8,否则插入中文乱码 -->
<property name="jdbcUrl">jdbc:mysql://localhost:3306/kdb</property>
<!-- jdbc:mysql://localhost:3306/db_library?useSSL=false&serverTimezone=UTC&characterEncording=utf-8&allowPublicKeyRetrieval=true -->
<property name="user">root</property>
<property name="password">hackyle</property>
<!-- 连接池参数 -->
<!--初始化申请的连接数量-->
<property name="initialPoolSize">5</property>
<!--最大的连接数量,可以根据数据库类型不同设置最优数量,超过最大连接数量就会报错-->
<property name="maxPoolSize">20</property>
<!--超时时间(申请连接超过3秒还没成功则报错)-->
<property name="checkoutTimeout">3000</property>
</default-config>
</c3p0-config>
创建连接池
package util; //连接池工具类一般放在util包下
import java.sql.Connection;
import com.mchange.v2.c3p0.ComboPooledDataSource;
public final class MyC3P0Pool {
//使用单例模式创建数据库连接池
private static MyC3P0Pool instance;
private static ComboPooledDataSource dataSource;
//单例模式!!!
private MyC3P0Pool () { //构造函数
dataSource=new ComboPooledDataSource();
//防止通过反射进行实例化而破坏单例
if(instance!=null) {
throw new RuntimeException("Object has been instanced!!!");
}
}
/**
* 功能:建立一个连接池(注意:有且仅有一个连接池)
* 特性:
* 支持多线程
* 使用单件模式,确保有且仅有一个连接池实例
*
* @return ConnectionManager
*/
public static final MyC3P0Pool getInstance() {
if(instance==null) {
synchronized (MyC3P0Pool.class) {
try {
instance=new MyC3P0Pool();
}catch (Exception e) {
e.printStackTrace();
}
}
}
return instance;
}
/**
* 功能:从连接池中获取一个连接
*
* @return
*/
public synchronized final Connection getConnection() {
Connection conn=null;
try {
conn=dataSource.getConnection();
}catch (Exception e) {
e.printStackTrace();
}
return conn;
}
}
使用连接池
import java.sql.Connection;
public class TestUse {
public static void main(String[] args) {
Connection conn = MyC3P0Pool.getInstance().getConnection();
if(conn != null) {
System.out.println("连接成功:" + conn);
} else {
System.out.println("连接失败");
}
conn.close(); //用完后返还给连接池
}
//输出:连接成功:com.mchange.v2.c3p0.impl.NewProxyConnection@3a4afd8d
}