1. 数据库连接池概述
1.1概念:
其实就是一个容器(集合),存放数据库连接的容器。
当系统初始化好后,容器被创建,容器中会申请一些连接对象,当用户来访问数据库时,从容器中获取连接对象,用户访问完之后,会将连接对象归还给容器。
1.2为什么要有连接池呢?
- 假如没有连接池,那么我们在使用JDBC程序的时候,就会反复的创建连接,销毁连接,这样做比较消耗资源,并不能做到连接的反复利用。
- 这样做如果这个程序的用户人数很多,那么对性能的影响就会很大。
- 所以为了优化性能,就需要自己去创建一个连接池,然后每次从连接池里面获取连接
- 使用完了连接,放回连接池,做到连接的反复使用
1.3 为什么要有连接池呢?
1.3.1 应用程序直接获取链接的缺点
缺点:
- 用户每次请求都需要向数据库获得链接,而数据库创建连接通常需要消耗相对较大的资源,创建时间也较长
- 假设网站一天10万访问量,数据库服务器就需要创建10万次连接,极大的浪费数据库的资源,并且极易造成数据库服务器内存溢出、宕机
1.3.2 使用数据库连接池优化程序性能
2. 自己实现数据库连接池
2.1 包装设计模式
1、定义一个类,实现与被增强对象所实现的接口
2、定义一个变量,引用被增强对象
3、构造方法接收被增强对象
4、覆盖要被增强的方法
5、对于不想增强的方法,调用被增强对象的对应方法
2.2 编写连接池需实现javax.sql.DataSource接口。
DataSource接口中定义了两个重载的getConnection方法:
- Connection getConnection()
2.3 实现DataSource接口,并实现连接池功能的步骤:
- 在DataSource构造函数中批量创建与数据库的连接,并把创建的连接加入LinkedList对象中。
- 实现getConnection方法,让getConnection方法每次调用时,从LinkedList中取一个Connection返回给用户。
- 当用户使用完Connection,调用Connection.close()方法时,Collection对象应保证将自己返回到LinkedList中,而不要把conn还给数据库。
- Collection保证将自己返回到LinkedList中是此处编程的难点。
3. 手动实现数据库连接池
3.1 版本1(基本实现)
结构
数据库
MyConnectionPool1.java
// 连接池对象
public class MyConnectionPool1 {
// 得用一个数据结构去存放连接
static LinkedList<Connection> connectionPool;
static {
init(10);
}
// 往连接池里面放连接的方法
private static void init(int num) {
if (connectionPool == null) {
connectionPool = new LinkedList<>();
}
for (int i = 0; i < num; i++) {
// 添加一个连接
Connection connection = JDBCUtils.getConnection();
connectionPool.addFirst(connection);
}
}
// 获取连接 从头部存,从尾部取
public static Connection getConnection(){
// 动态扩容
if (connectionPool == null || connectionPool.size() < 5) {
init(10);
}
// 这里是取出连接
Connection connection = connectionPool.removeLast();
return connection;
}
// 返回连接,放回连接池
public static void releaseConnection(Connection connection){
connectionPool.addFirst(connection);
}
}
JDBCUtils.java
public class JDBCUtils {
private static String url;
private static String username;
private static String password;
private static String driverName;
static {
try {
// 加载配置文件
Properties properties = new Properties();
// 通过类加载器去获取
ClassLoader classLoader = JDBCUtils.class.getClassLoader();
InputStream inputStream = classLoader.getResourceAsStream("jdbc.properties");
properties.load(inputStream);
// 取值 赋值
url = properties.getProperty("url");
username = properties.getProperty("username");
password = properties.getProperty("password");
driverName = properties.getProperty("driverName");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
// 获取连接
public static Connection getConnection(){
Connection connection = null;
// 1. 注册驱动
try {
Class.forName(driverName); // = "new Driver()"
// DriverManager.registerDriver(new Driver());
// 2. 获取连接
connection = DriverManager.getConnection(url,username,password);
} catch (SQLException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return connection;
}
}
jdbc.properties
url=jdbc:mysql://localhost:3306/testdb1?useSSL=false&characterEncoding=utf-8
username=root
password=123456
driverName=com.mysql.jdbc.Driver
ConnectionPoolTest.java
public class ConnectionPoolTest {
@Test
public void testMyConnectionPoolV1() throws SQLException {
//获取连接
Connection connection = MyConnectionPool1.getConnection();
Statement statement = connection.createStatement();
// 执行sql
ResultSet resultSet = statement.executeQuery("select * from account");
while (resultSet.next()) {
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
BigDecimal money = resultSet.getBigDecimal("money");
System.out.println("id:" + id +", name:" + name + ",money:" + money);
}
// 返回连接
MyConnectionPool1.releaseConnection(connection);
}
结果:
3.2 版本2(实现DataSource接口)
版本1不足之处:功能不完整、动态扩容的方法不够智能
最重要的一点,是我们这个连接池使我们自己创建的,我们自己去获取连接的方法叫做 getConnection(), 别人创建一个连接池,这个方法可能叫别的名字,例如acquireConnection(),那么我们在使用的时候就没有一个统一的规范和标准。SUN公司就制定了连接池的标准,来定义这些方法,各个连接池的实现只需要实现这个标准即可。
javax.sql.Datasource
返回连接:
为什么Datasource这个接口里面没有给我们定义返回连接的方法呢?
1.因为即使你定义了,还是阻止不了很多开发者去执行完任务去关闭连接
2. 假如在Datasource接口里面定义了一个放回连接的方法,会出现使用的时候,可能先把连接关闭了,然后再把连接放回去,那么此时放回连接池里面的连接就成了死连接,下次就不可复用了
那么怎么才能去把这个连接放回去呢?如何去阻止用户手动关闭连接呢?
思路:
重写Close方法,是使用继承还是使用实现接口呢?
1.继承Connection的之类 需要知道Connection具体是哪个实现类 ,我们发现目前我们的实现类是JDBC4Connection
2.JDBC4Connection 是在Mysql的驱动包里面,假如换了一个驱动包的类型或者版本,那么这个对象的实例类型会不会变呢?
3.例如在 mysql-connector-java 8.X的版本里面,Connection的实现类叫JDBC42Connection, 所以我们采用继承JDBC4Connection的方法不太通用,不太合适
4.实现Connection接口 可以做到这样的事情
MyConnectionPool2.java
public class MyConnectionPool2 implements DataSource {
// 得用一个数据结构去存放连接
static LinkedList<Connection> connectionPool;
static {
init(10);
}
// 往连接池里面放连接的方法
private static void init(int num) {
if (connectionPool == null) {
connectionPool = new LinkedList<>();
}
for (int i = 0; i < num; i++) {
// 添加一个连接
Connection connection = JDBCUtils.getConnection();
connectionPool.addFirst(connection);
}
}
@Override
public Connection getConnection() throws SQLException {
Connection connection = connectionPool.removeLast();
return connection;
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return null;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
// 返回连接,放回连接池
public void releaseConnection(Connection connection){
connectionPool.addFirst(connection);
}
}
testMyConnectionPoolV2().java(测试类)
@Test
public void testMyConnectionPoolV2() throws SQLException {
//获取连接
MyConnectionPool2 connectionPool2 = new MyConnectionPool2();
Connection connection = connectionPool2.getConnection();
Statement statement = connection.createStatement();
// 执行sql
ResultSet resultSet = statement.executeQuery("select * from account");
while (resultSet.next()) {
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
BigDecimal money = resultSet.getBigDecimal("money");
System.out.println("id:" + id +", name:" + name + ",money:" + money);
}
connection.close();
connectionPool2.releaseConnection(connection);
结果:数据库同上,也能实现相同结果
3.3 版本3(自己实现了一个连接对象)
MyWrapperConnection.java(实现连接)
// 这个是自己实现了一个连接对象
public class MyWrapperConnection implements Connection {
// 在这个类里面去维护一个Connection对象,我们就直接调用Connection对象的方法
private Connection connection;
//申明一个连接池
private LinkedList<Connection> linkedList;
// 无参构造
public MyWrapperConnection() {
}
// 有参构造
public MyWrapperConnection(Connection connection, LinkedList<Connection> linkedList) {
this.connection = connection;
this.linkedList = linkedList;
}
@Override
public Statement createStatement() throws SQLException {
return connection.createStatement();
}
@Override
public PreparedStatement prepareStatement(String sql) throws SQLException {
return connection.prepareStatement(sql);
}
@Override
public CallableStatement prepareCall(String sql) throws SQLException {
return connection.prepareCall(sql);
}
@Override
public String nativeSQL(String sql) throws SQLException {
return connection.nativeSQL(sql);
}
@Override
public void setAutoCommit(boolean autoCommit) throws SQLException {
connection.setAutoCommit(autoCommit);
}
@Override
public boolean getAutoCommit() throws SQLException {
return false;
}
@Override
public void commit() throws SQLException {
}
@Override
public void rollback() throws SQLException {
}
// 这个方法要进行重写,自己实现
// 目的是要把这个关闭的方法里面变成放回连接池
@Override
public void close() throws SQLException {
// 这里传什么值呢?
linkedList.addFirst(this);
}
@Override
public boolean isClosed() throws SQLException {
return false;
}
@Override
public DatabaseMetaData getMetaData() throws SQLException {
return null;
}
@Override
public void setReadOnly(boolean readOnly) throws SQLException {
}
@Override
public boolean isReadOnly() throws SQLException {
return false;
}
@Override
public void setCatalog(String catalog) throws SQLException {
}
@Override
public String getCatalog() throws SQLException {
return null;
}
@Override
public void setTransactionIsolation(int level) throws SQLException {
}
@Override
public int getTransactionIsolation() throws SQLException {
return 0;
}
@Override
public SQLWarning getWarnings() throws SQLException {
return null;
}
@Override
public void clearWarnings() throws SQLException {
}
@Override
public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
return null;
}
@Override
public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
return null;
}
@Override
public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
return null;
}
@Override
public Map<String, Class<?>> getTypeMap() throws SQLException {
return null;
}
@Override
public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
}
@Override
public void setHoldability(int holdability) throws SQLException {
}
@Override
public int getHoldability() throws SQLException {
return 0;
}
@Override
public Savepoint setSavepoint() throws SQLException {
return null;
}
@Override
public Savepoint setSavepoint(String name) throws SQLException {
return null;
}
@Override
public void rollback(Savepoint savepoint) throws SQLException {
}
@Override
public void releaseSavepoint(Savepoint savepoint) throws SQLException {
}
@Override
public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
return null;
}
@Override
public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
return null;
}
@Override
public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
return null;
}
@Override
public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
return null;
}
@Override
public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
return null;
}
@Override
public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
return null;
}
@Override
public Clob createClob() throws SQLException {
return null;
}
@Override
public Blob createBlob() throws SQLException {
return null;
}
@Override
public NClob createNClob() throws SQLException {
return null;
}
@Override
public SQLXML createSQLXML() throws SQLException {
return null;
}
@Override
public boolean isValid(int timeout) throws SQLException {
return false;
}
@Override
public void setClientInfo(String name, String value) throws SQLClientInfoException {
}
@Override
public void setClientInfo(Properties properties) throws SQLClientInfoException {
}
@Override
public String getClientInfo(String name) throws SQLException {
return null;
}
@Override
public Properties getClientInfo() throws SQLException {
return null;
}
@Override
public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
return null;
}
@Override
public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
return null;
}
@Override
public void setSchema(String schema) throws SQLException {
}
@Override
public String getSchema() throws SQLException {
return null;
}
@Override
public void abort(Executor executor) throws SQLException {
}
@Override
public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException {
}
@Override
public int getNetworkTimeout() throws SQLException {
return 0;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
}
MyConnectionPool3.java(实现连接池)
public class MyConnectionPool3 implements DataSource {
// 得用一个数据结构去存放连接
static LinkedList<Connection> connectionPool;
static {
init(10);
}
// 往连接池里面放连接的方法
private static void init(int num) {
if (connectionPool == null) {
connectionPool = new LinkedList<>();
}
for (int i = 0; i < num; i++) {
// 添加一个连接
Connection connection = JDBCUtils.getConnection();
MyWrapperConnection myWrapperConnection = new MyWrapperConnection(connection, connectionPool);
connectionPool.addFirst(myWrapperConnection);
}
}
@Override
public Connection getConnection() throws SQLException {
Connection connection = connectionPool.removeLast();
return connection;
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return null;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
}
JDBCUtils.java(同上)
jdbc.properties(同上)
testMyConnectionPoolV3.java(测试类)
@Test
public void testMyConnectionPoolV3() throws SQLException {
//获取连接
MyConnectionPool3 myConnectionPool3 = new MyConnectionPool3();
Connection connection = myConnectionPool3.getConnection();
Statement statement = connection.createStatement();
// 执行sql
ResultSet resultSet = statement.executeQuery("select * from account");
while (resultSet.next()) {
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
BigDecimal money = resultSet.getBigDecimal("money");
System.out.println("id:" + id + ", name:" + name + ",money:" + money);
}
// 放回连接
connection.close();
}
结果:
总结:
自己手动实现这一系列连接池比较麻烦,并且很多功能自己都没有去实现,但是别人或者是别的组织已经考虑到了这个情况,所以网上有很多第三方的开源的数据库连接池供我们在实际工作中使用,有哪些呢?DBCP、C3p0、Druid有这三个可供选择。
4. 开源数据库连接池
现在很多WEB服务器(Weblogic, WebSphere, Tomcat)都提供了DataSoruce的实现,即连接池的实现。通常把DataSource的实现,按其英文含义称之为数据源,数据源中都包含了数据库连接池的实现。
实际应用时不需要编写连接数据库代码,直接从数据源获得数据库的连接。程序员编程时也应尽量使用这些数据源的实现,以提升程序的数据库访问性能。
4.1 DBCP
4.1.1 DBCP数据源简介
DBCP 是 Apache 软件基金组织下的开源连接池实现,使用DBCP数据源,应用程序应在系统中增加如下两个 jar 文件:
(1)Commons-dbcp.jar:连接池的实现
(2)Commons-pool.jar:连接池实现的依赖库
4.1.2 功能
Tomcat 的连接池正是采用该连接池来实现的。
该数据库连接池:
(1)既可以与应用服务器整合使用,
(2)也可由应用程序独立使用。
4.1.3 代码实现
第一步,导包:
https://mvnrepository.com/
<!--DBCP-->
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>commons-pool</groupId>
<artifactId>commons-pool</artifactId>
<version>1.6</version>
</dependency>
第二步 配置:
建立一个dbcp.properties文件,放在resources目录下
#连接设置
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/testdb1
username=root
password=123456
#<!-- 初始化连接 -->
initialSize=10
#最大连接数量
maxActive=50
#<!-- 最大空闲连接 -->
maxIdle=20
#<!-- 最小空闲连接 -->
minIdle=5
#<!-- 超时等待时间以毫秒为单位 6000毫秒/1000等于60秒 -->
maxWait=60000
#JDBC驱动建立连接时附带的连接属性属性的格式必须为这样:[属性名=property;]
#注意:"user" 与 "password" 两个属性会被明确地传递,因此这里不需要包含他们。
connectionProperties=useUnicode=true;characterEncoding=utf-8;useSSL=false
#指定由连接池所创建的连接的自动提交(auto-commit)状态。
defaultAutoCommit=true
#driver default 指定由连接池所创建的连接的只读(read-only)状态。
#如果没有设置该值,则“setReadOnly”方法将不被调用。(某些驱动并不支持只读模式,如:Informix)
defaultReadOnly=
#driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation)。
#可用值为下列之一:(详情可见javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
defaultTransactionIsolation=REPEATABLE_READ
第三步,创建DBCPUtils对象
这个工具类里面主要是提供我们获取连接的方法
DBCPUtils.java
public class DBCPUtils {
// 声明一个数据源对象
private static DataSource dataSource;
static {
// 加载配置文件
Properties properties = new Properties();
ClassLoader classLoader = DBCPUtils.class.getClassLoader();
InputStream stream = classLoader.getResourceAsStream("dbcp.properties");
try {
properties.load(stream);
} catch (IOException e) {
e.printStackTrace();
}
BasicDataSourceFactory basicDataSourceFactory = new BasicDataSourceFactory();
try {
// 给Datasource赋值
dataSource = basicDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
// 写一个方法 获取连接
public static Connection getConnection(){
Connection connection = null;
try {
connection = dataSource.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
return connection;
}
}
testDBCPDatasource()(测试类)
@Test
public void testDBCPDatasource() throws SQLException {
//获取连接 不是JDBC4Connection对象,而是Connection的一个实现类,是DBCP它自己实现的
Connection connection = DBCPUtils.getConnection();
Statement statement = connection.createStatement();
// 执行sql
ResultSet resultSet = statement.executeQuery("select * from account");
while (resultSet.next()) {
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
BigDecimal money = resultSet.getBigDecimal("money");
System.out.println("id:" + id + ", name:" + name + ",money:" + money);
}
// 放回连接
connection.close();
}
结果:
4.2 C3P0(数据库连接池技术)
4.2.1 代码实现
第一步 导包:
https://mvnrepository.com/
<!--c3p0-->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.4</version>
</dependency>
第二步 配置:
需要说明的是,这个配置文件的名字必须是 c3p0-config.xml,并且必须放在Resources目录下
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<default-config>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/28_jdbc</property>
<property name="user">root</property>
<property name="password">123456</property>
<property name="acquireIncrement">5</property>
<property name="initialPoolSize">10</property>
<property name="minPoolSize">5</property>
<property name="maxPoolSize">20</property>
</default-config>
</c3p0-config>
第三步 创建C3p0Utils对象
public class C3p0Utils {
// 首先声明一个数据源对象
private static DataSource dataSource;
static {
// 给Datasource对象去赋值
dataSource = new ComboPooledDataSource();
}
// 获取连接
public static Connection getConnection(){
Connection connection = null;
try {
connection = dataSource.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
return connection;
}
}
测试类:
testC3p0Datasource().java
@Test
public void testC3p0Datasource() throws SQLException {
//获取连接 不是JDBC4Connection对象,而是Connection的一个实现类,是C3p0它自己实现的
Connection connection = C3p0Utils.getConnection();
Statement statement = connection.createStatement();
// 执行sql
ResultSet resultSet = statement.executeQuery("select * from account");
while (resultSet.next()) {
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
BigDecimal money = resultSet.getBigDecimal("money");
System.out.println("id:" + id + ", name:" + name + ",money:" + money);
}
// 放回连接
connection.close();
}
结果:
4.3 Druid(数据库连接池实现技术(阿里提供))
4.3.1 步骤:
第一步 导包
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.19</version>
</dependency>
第二步 配置
首先在resouces里面创建一个配置文件
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/testdb1
username=root
password=123456
第三步 创建DruidUtils对象
public class DruidUtils {
private static DataSource dataSource;
static {
// 加载配置文件
Properties properties = new Properties();
ClassLoader classLoader = DruidUtils.class.getClassLoader();
InputStream stream = classLoader.getResourceAsStream("druid.properties");
try {
properties.load(stream);
} catch (IOException e) {
e.printStackTrace();
}
// 创建一个Druid数据源工厂对象
DruidDataSourceFactory dataSourceFactory = new DruidDataSourceFactory();
try {
// 通过工厂创建一个数据源并且去赋值
dataSource = dataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
// 获取连接
public static Connection getConnection(){
Connection connection = null;
try {
connection = dataSource.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
return connection;
}
}
testDruidDatasource()(测试类)
@Test
public void testDruidDatasource() throws SQLException {
//获取连接 不是JDBC4Connection对象
Connection connection = DruidUtils.getConnection();
Statement statement = connection.createStatement();
// 执行sql
ResultSet resultSet = statement.executeQuery("select * from account");
while (resultSet.next()) {
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
BigDecimal money = resultSet.getBigDecimal("money");
System.out.println("id:" + id + ", name:" + name + ",money:" + money);
}
// 放回连接
connection.close();
}
结果: