数据库连接池

自定义连接池、DBCP、C3P0、DRUID


写在前面

百度百科关于数据库连接池的介绍 数据库连接池主要解决的就是连接性能的问题,因为频繁地建立和关闭连接是一件相当耗费性能的事情,尤其是在访问量比较大的时候,所以如何合理分配、管理数据库连接就变得尤为重要,下面列举了四种数据库连接池技术,包括自定义连接池、使用C3P0、DBCP、DRUID创建数据库连接池。


自定义连接池

创建自定义连接池的方法也很简单,只需要将连接放到集合(容器)中,当用户需要连接数据库时,不是直接创建连接而是获取连接池中已经创建好连接,这在一定程度上可以提高性能。当然,在实际的项目中通常不会使用自定义的连接池,这里仅仅作为演示。首先要创建一个properties文件,设置连接参数,包括驱动(driver)、数据库连接地址(url)、用户名(username)、密码(password),例如:

properties文件
1
2
3
4
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql:///test?useUnicode=true&useSSL=false&serverTimezone=GMT%2B8&character=utf8
username=mackvord
password=12345678

最新版本的MySQL驱动,其注册地址是com.mysql.cj.jdbc.Driver而不是com.mysql.jdbc.Driver,并且必须要设置useSSL和serverTimezone两个参数,否则会报错(警告)

JdbcUtils工具类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
package com.jdbc.utils;

import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;

public class JdbcUtils {

private static String driver ;
private static String url ;
private static String username ;
private static String password ;

/**
* 静态代码块加载配置文件
*/
static {
// 方法1
// 1.获取当前类的类加载器
ClassLoader classLoader = JdbcUtils.class.getClassLoader() ;
// 2.加载src资源
InputStream is = classLoader.getResourceAsStream("db.properties") ;
// 3.创建properties对象
Properties props = new Properties() ;
// 4.加载指定的流
try {
props.load(is) ;
} catch (IOException e) {
e.printStackTrace();
}
// 5.使用getProperty(key)方法获取key对应的value值
driver = props.getProperty("driver") ;
url = props.getProperty("url") ;
username = props.getProperty("username") ;
password = props.getProperty("password") ;

/*// 方法2
ResourceBundle bundle = ResourceBundle.getBundle("db");
driver = bundle.getString("driver") ;
url = bundle.getString("url") ;
username = bundle.getString("username") ;
password = bundle.getString("password") ;*/
}

/**
* 获取数据库连接的方法
*
* @return
*/
public static Connection getConnection() {
Connection conn = null ;
try {
Class.forName(driver) ;
conn = DriverManager.getConnection(url, username, password) ;

} catch (Exception e) {
e.printStackTrace();
}
return conn ;
}

/**
* 释放资源的方法
*
* @param conn 数据库连接对象
* @param ps 执行SQL语句的对象
* @param rs 结果集
*/
public static void release(Connection conn, PreparedStatement ps, ResultSet rs) {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (ps != null) {
try {
ps.close() ;
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
MyDataSource类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package com.jdbc.datasource;

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 testjunit.com.JdbcUtils;

public class MyDataSource implements DataSource {

/**
* 1.创建数据库连接池对象
*/
private static LinkedList<Connection> pool = new LinkedList<Connection>() ;

// 2.创建连接池,假设创建五个连接
static {
for (int x = 0 ; x < 5 ; x++) {
Connection conn = JdbcUtils.getConnection() ;
//放入连接池中的连接已经被装饰过
MyConnection mc = new MyConnection(conn, pool) ;
pool.add(mc) ;
}
}

/**
* 获取数据库连接池中的连接的方法
*/
@Override
public Connection getConnection() throws SQLException {
Connection conn = null ;
// 3.使用连接池之前先判断
if (pool.size() == 0) {
for (int x = 0 ; x < 5 ; x++) {
conn = JdbcUtils.getConnection() ;
// 放入连接池中的连接已经被装饰过
MyConnection mc = new MyConnection(conn, pool) ;
pool.add(mc) ;
}
}
// 4.获取一个连接对象并返回
conn = pool.remove(0) ;
return conn ;
}

/**
* 将连接返还到连接池中的方法
* @param conn 调用此的对象的连接
*/
public void backConnection(Connection conn) {
pool.add(conn) ;
}

// 此处省略部分没有实现的方法
}

上面的代码在创建连接池的时候,没有直接将连接添加到连接池中,而是通过一个装饰类MyConnection包装后再添加到连接池中,这样做的主要原因是当用户操作完成后会调用release()方法释放资源,而release()方法中又调用了close(),如果每个用户在执行完操作后直接将连接关闭,那么连接池中的连接就会被消耗殆尽,所以理想的情况应该是用户在执行完操作后将连接归还到连接池中,为了实现这一操作,可以定义一个新的类MyConnection实现Connection接口并重写close()方法。

MyConnection类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package com.jdbc.datasource;

import java.sql.Array;
import java.sql.Blob;
import java.sql.CallableStatement;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.NClob;
import java.sql.PreparedStatement;
import java.sql.SQLClientInfoException;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.SQLXML;
import java.sql.Savepoint;
import java.sql.Statement;
import java.sql.Struct;
import java.util.LinkedList;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executor;

public class MyConnection implements Connection {

// 1.声明变量
private Connection conn ;
private LinkedList<Connection> pool ;

// 2.编写构造方法,利用多态的特性
public MyConnection(Connection conn, LinkedList<Connection> pool) {
this.conn = conn ;
this.pool = pool ;
}

/**
* 3.增强后的close()方法
*/
@Override
public void close() throws SQLException {
pool.add(conn) ;
}

/**
* 重写后的PreparedStatement()方法
*/
@Override
public PreparedStatement prepareStatement(String sql) throws SQLException {
return conn.prepareStatement(sql) ;
}

//因为太长了,此处省略一些没有实现的方法
}

下面是测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package com.jdbc.utils.test;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.junit.Test;
import com.jdbc.datasource.MyDataSource;
import testjunit.com.JdbcUtils;

public class MyDataSourceTest {

@Test
public void testAddCategory() {
Connection conn = null ;
PreparedStatement pstt = null ;
// 1.创建数据库连接池对象
DataSource mds = new MyDataSource() ;
try {
// 2.获取数据库连接
conn = mds.getConnection() ;
// 3.编写SQL语句
String sql = "update category set cname = ? where cid = ?" ;
// 4.创建执行SQL语句的对象
pstt = conn.prepareStatement(sql) ;
// 设置参数
pstt.setString(1, "鞋靴") ;
pstt.setString(2, "c007") ;
// 执行更新操作并返回操作的行数
int rows = pstt.executeUpdate() ;
if (rows > 0) {
System.out.println("Update Success !") ;
} else {
System.out.println("Update failed !") ;
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 调用此方法将会把连接归还到连接池中而不是直接关闭连接
JdbcUtils.release(conn, pstt, null) ;
}
}
}

通过上面的代码可以发现,在创建自定义数据库连接池的时候,核心的代码并不多,但是有相当多的方法需要重写,而这些方法大部分暂时还用不到,所以在实际中并不会这么做,为了更高效地创建连接池,通常会使用下面的三种方式。


C3P0

C3P0官网,要使用C3P0创建数据库连接池,需要用到相关的jar包以及配置xml文件,jar包下载地址,关于xml文件的配置官网上的文档有相关的说明,文档地址,下面是我的配置的一个简单的xml文件,内容如下:

xml配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>

<default-config>
<property name="driverClass">com.mysql.cj.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql:///test?useSSL=false&amp;serverTimezone=GMT%2B8</property>
<property name="user">root</property>
<property name="password">12345678</property>
<property name="initialPoolSize">5</property>
<property name="maxPoolSize">20</property>
</default-config>

<named-config name="mydb">
<property name="driverClass">com.mysql.cj.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql:///test</property>
<property name="user">mackvord</property>
<property name="password">12345678</property>
</named-config>

</c3p0-config>

上面的xml文件包括一个默认的配置和一个自定义的配置,创建连接池的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package com.jdbc.utils.test;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import org.junit.Test;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import testjunit.com.JdbcUtils;

public class C3p0Test {

@Test
public void testAddCategory() {
Connection conn = null ;
PreparedStatement pstt = null ;
// 1.创建数据库连接池对象,不写参数加载的是默认的配置
ComboPooledDataSource cpds = new ComboPooledDataSource() ;
//ComboPooledDataSource cpds = new ComboPooledDataSource("mydb") ;
try {
// 2.获取数据库连接
conn = cpds.getConnection() ;
// 3.编写SQL语句
String sql = "update category set cname = ? where cid = ?" ;
// 4.创建执行SQL语句的对象
pstt = conn.prepareStatement(sql) ;
// 设置参数
pstt.setString(1, "c007") ;
pstt.setString(2, "影音") ;
// 执行更新操作并返回操作的行数
int rows = pstt.executeUpdate() ;
if (rows > 0) {
System.out.println("Update Successful !") ;
} else {
System.out.println("Update failed !") ;
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtils.release(conn, pstt, null) ;
}
}
}

可以发现,使用C3P0创建连接池相比于使用原生的方法创建连接池要方便得多。

DBCP

DBCP相比于C3P0可能稍逊一筹,但功能也很强大,由apache开发,dbcp2 jar下载地址pool2 jar下载地址,在使用DBCP前需要先创建properties文件,所以我们可以创建一个工具类来加载资源文件。

DBCP工具类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.jdbc.utils;

import java.io.InputStream;
import java.sql.Connection;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.commons.dbcp2.BasicDataSourceFactory;

/**
* DBCP数据库连接池
* 需要导入的包:commons-logging、commons-dbcp2、commons-pool2
*/
public class DbcpUtils {

// 1.声明一个DataSource对象
private static DataSource ds ;
static {
try {
// 2.加载properties文件输入流
InputStream is = DbcpUtils.class.getClassLoader().getResourceAsStream("db.properties") ;
// 3.加载输入流
Properties pts = new Properties() ;
pts.load(is) ;
// 4.创建数据源
ds = BasicDataSourceFactory.createDataSource(pts) ;
} catch (Exception e) {
throw new RuntimeException() ;
}
}

/**
* 获取数据源的方法
* @return
*/
public static DataSource getDataSource() {
return ds ;
}

/**
* 获取连接的方法
*/
public static Connection getConnection() {
try {
return ds.getConnection() ;
} catch (Exception e) {
throw new RuntimeException() ;
}
}
}

下面是测试代码:

DbcpTest.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.jdbc.utils.test;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import org.junit.Test;
import com.jdbc.utils.DbcpUtils;
import testjunit.com.JdbcUtils;

/**
* 测试DBCP数据库连接池
*/
public class DbcpTest {

@Test
public void testUpdateByCid() {
Connection conn = null ;
PreparedStatement pstt = null ;
try {
conn = DbcpUtils.getConnection() ;
String sql = "update category set cname = ? where cid = ?" ;
pstt = conn.prepareStatement(sql) ;
pstt.setString(1, "美妆") ;
pstt.setString(2, "c004") ;
int rows = pstt.executeUpdate() ;
if (rows > 0) {
System.out.println("Update Successful !") ;
} else {
System.out.println("Update Failed !") ;
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtils.release(conn, pstt, null) ;
}
}
}

DRUID

druid官网,DRUID是一个开源的分布式数据存储程序。


如果您觉得我的文章对您有帮助,请随意赞赏,您的支持将鼓励我继续创作!
0%