本篇内容包括:数据库连接池概述、JDBC 连接池原理、JDBC 连接池 Demo(addBatch demo、获取主键 demo、查看数据库的元数据 demo等)以及其他类型数据库连接池的介绍(比如 Druid)。
一、数据库连接池
数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个;释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏。这项技术能明显提高对数据库操作的性能。
连接池基本的思想是在系统初始化的时候,将数据库连接作为对象存储在内存中,当用户需要访问数据库时,并非建立一个新的连接,而是从连接池中取出一个已建立的空闲连接对象。使用完毕后,用户也并非将连接关闭,而是将连接放回连接池中,以供下一个请求访问使用。而连接的建立、断开都由连接池自身来管理。同时,还可以通过设置连接池的参数来控制连接池中的初始连接数、连接的上下限数以及每个连接的最大使用次数、最大空闲时间等等。也可以通过其自身的管理机制来监视数据库连接的数量、使用情况等。
连接池优点:
- 减少连接创建时间:虽然与其它数据库相比 GBase 提供了较为快速连接功能,但是创建新的 JDBC 连接仍会招致网络和 JDBC 驱动的开销。如果这类连接是“循环”使用的,使用该方式这些花销就可避免
- 简化的编程模式:当使用连接池时,每一个单独的线程能够像创建了一个自己的 JDBC 连接一样操作,允许用户直接使用JDBC编程技术。
- 受控的资源使用:如果用户不使用连接池,而是每当线程需要时创建一个新的连接,那么用户的应用程序的资源使用会产生非常大的浪费并且可能会导致高负载下的异常发生。连接池能够使性能最大化,同时还能将资源利用控制在一定的水平之下,如果超过该水平,应用程序将崩溃而不仅仅是变慢。
二、JDBC 连接池原理
JDBC 连接池的基本原理:
- 建立数据库连接池对象(服务器启动)。
- 按照事先指定的参数创建初始数量的数据库连接(即:空闲连接数)。
- 对于一个数据库的访问请求,直接从连接池中得到一个连接,如果数据库连接池对象中有空闲连接则直接使用、若没有空闲的链接,且连接数没有达到最大(即:最大活跃连接数),创建一个新的数据库连接来处理该请求,如果没有空闲连接并且达到最大活跃值则进行等待其它的链接释放再进行该请求的处理。
- 存取数据库。
- 关闭数据库,释放所有数据库连接(此时福安比数据库连接,并非真正的关闭,而是将其放入空闲队列中。如果实际空闲连接数大于初始空闲连接数则释放连接)。
- 释放数据库连接池对象(在服务器停止、维护期间,真正的释放数据库连接池对象,并释放所有资源)。
三、JDBC 连接池 Demo
1、添加 Maven 依赖
代码语言:javascript复制 <dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
2、添加一个配置文件 src/resources/jdbc.properties
代码语言:javascript复制# 配置数据库的连接信息
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/databaseName
username=userName
password=password
3、添加 DbUtils2 类
代码语言:javascript复制public class DbUtils2 {
private static String driver;
private static String url;
private static String username;
private static String password;
/**
* 声明数据源,用于配置数据库的链接信息
*/
private static BasicDataSource dataSource;
static {
Properties prop = new Properties();
InputStream ips = DbUtils2.class.getClassLoader().getResourceAsStream("jdbc.properties");
try {
//加载配置文件
prop.load(ips);
//获取我们在配置文件中配置的driver等信息
driver = prop.getProperty("driver");
url = prop.getProperty("url");
username = prop.getProperty("username");
password = prop.getProperty("password");
//创建数据源
dataSource = new BasicDataSource();
//设置数据库的连接信息:
dataSource.setDriverClassName(driver);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
//设置初始连接数量
dataSource.setInitialSize(3);
//设置最大连接数量
dataSource.setMaxActive(3);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
//关闭资源
ips.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static Connection getConn() throws Exception {
// 从连接池中获取连接
Connection conn = dataSource.getConnection();
return conn;
}
public static void close(Connection conn, Statement stat, ResultSet rs) {
try {
if (rs != null) {
rs.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (stat != null) {
stat.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (conn != null) {
//打开自动提交
conn.setAutoCommit(true);
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
4、使用 PrepareStatement 来实现登陆验证(可以避免SQL注入):
代码语言:javascript复制 public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
System.out.println("请输入用户名");
String username = scan.nextLine();
Scanner scr = new Scanner(System.in);
System.out.println("请输入密码");
String password = scr.nextLine();
boolean b = login(username, password);
if (b) {
System.out.println("登陆成功");
} else {
System.out.println("登录失败");
}
}
private static boolean login(String username, String password) {
String sql = "select count(*) from table_user where username=? and password=?";
Connection conn = null;
PreparedStatement stat = null;
ResultSet rs = null;
try {
conn = DbUtils2.getConn();
stat = conn.prepareStatement(sql);
stat.setString(1, username);
stat.setString(2, password);
rs = stat.executeQuery();
while (rs.next()) {
//得到查询的数量
int count = rs.getInt(1);
if (count > 0) {
return true;
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
DbUtils2.close(conn, stat, rs);
}
return false;
}
5、addBatch 批量操作 demo(使用Statement对象)
代码语言:javascript复制 @Test
public void testAddBatch01() {
String sql1 = "insert into table_user values(null,'悟空','aaa')";
String sql2 = "insert into table_user values(null,'八戒','bbb')";
String sql3 = "insert into table_user values(null,'沙僧','ccc')";
Connection conn = null;
Statement stat = null;
ResultSet rs = null;
try {
conn = DbUtils2.getConn();
stat = conn.createStatement();
stat.addBatch(sql1);
stat.addBatch(sql2);
stat.addBatch(sql3);
//执行批量操作
stat.executeBatch();
} catch (Exception e) {
e.printStackTrace();
} finally {
DbUtils2.close(conn, stat, rs);
}
}
6、addBatch 批量操作 demo(使用PrepareStatement对象)
代码语言:javascript复制 @Test
public void testAddBatch02() {
String sql = "insert into table_user values(null,?,?)";
Connection conn = null;
PreparedStatement stat = null;
ResultSet rs = null;
try {
conn = DbUtils2.getConn();
stat = conn.prepareStatement(sql);
stat.setString(1, "刘备");
stat.setString(2, "aaa");
//添加到批量处理
stat.addBatch();
stat.setString(1, "关羽");
stat.setString(2, "bbb");
stat.addBatch();
stat.setString(1, "张飞");
stat.setString(2, "ccc");
stat.addBatch();
stat.executeBatch();
} catch (Exception e) {
e.printStackTrace();
} finally {
DbUtils2.close(conn, stat, rs);
}
}
7、addBatch 批量操作 demo(避免内存溢出)
代码语言:javascript复制 @Test
public void testAddBatch03() {
String sql = "insert into table_user values(null,?,?)";
Connection conn = null;
PreparedStatement stat = null;
ResultSet rs = null;
try {
conn = DbUtils2.getConn();
stat = conn.prepareStatement(sql);
for (int i = 0; i < 100; i ) {
stat.setString(1, "name" i);
stat.setString(2, "admin");
stat.addBatch();
//下面写法可以避免内存溢出
if (i % 20 == 0) {
stat.executeBatch();
//清除批处理内容
stat.clearBatch();
}
}
stat.executeBatch();
} catch (Exception e) {
e.printStackTrace();
} finally {
DbUtils2.close(conn, stat, rs);
}
}
8、获取主键 demo
代码语言:javascript复制 @Test
public void testPrimaryKey() {
Connection conn = null;
Statement stat = null;
ResultSet rs = null;
try {
conn = DbUtils2.getConn();
stat = conn.createStatement();
String sql = "insert into table_user values(null,'小明','abc')";
//执行SQL并且制定需要获取自增主键值
stat.executeUpdate(sql, Statement.RETURN_GENERATED_KEYS);
//获取返回的主键值
rs = stat.getGeneratedKeys();
while (rs.next()) {
int id = rs.getInt(1);
System.out.println("自增主键" id);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
DbUtils2.close(conn, stat, rs);
}
}
9、查看数据库的元数据 demo
代码语言:javascript复制 @Test
public void testMetadata() {
Connection conn = null;
Statement stat = null;
ResultSet rs = null;
try {
conn = DbUtils2.getConn();
stat = conn.createStatement();
//得到数据库的元数据
DatabaseMetaData dbData = conn.getMetaData();
System.out.println("驱动版本:" dbData.getDriverMajorVersion());
System.out.println("用户名:" dbData.getUserName());
System.out.println("链接地址:" dbData.getURL());
System.out.println("数据库名称:" dbData.getDatabaseProductName());
//获取表相关的元数据
rs = stat.executeQuery("select * from table_user");
ResultSetMetaData rsData = rs.getMetaData();
//得到表字段的数量
int count = rsData.getColumnCount();
for (int i = 0; i < count; i ) {
String name = rsData.getColumnName(i 1);
String type = rsData.getColumnTypeName(i 1);
System.out.println(name ":" type);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
DbUtils2.close(conn, stat, rs);
}
}
四、其他类型数据库连接池
在 Java 程序中,开源的数据库连接池有以下几种 :
- C3P0:是一个开放源代码的 JDBC 连接池,它在 lib 目录中与 Hibernate 一起发布,包括了实现 jdbc3 和 jdbc2 扩展规范说明的 Connection 和 Statement 池的 DataSources 对象
- Proxool:是一个 Java SQL Driver 动程序,提供了对选择的其它类型的驱动程序的连接池封装。可以非常简单的移植到现存的代码中,完全可配置,快速、成熟、健壮。可以透明地为现存的JDBC驱动程序增加连接池功能。
- Jakarta DBCP:DBCP 是一个依赖 Jakarta commons-pool 对象池机制的数据库连接池。DBCP 可以直接的在应用程序中使用
- DDConnectionBroker:是一个简单、轻量级的数据库连接池
- DBPool:是一个高效、易配置的数据库连接池。它除了支持连接池应有的功能之外,还包括了一个对象池,使用户能够开发一个满足自己需求的数据库连接池
- XAPool:是一个 XA 数据库连接池。它实现了
javax.sql.XADataSource
并提供了连接池工具 - rimrose:是一个 Java 开发的数据库连接池。当前支持的容器包括 Tomcat4&5、Resin3 与J Boss3。它同样也有一个独立的版本,可以在应用程序中使用而不必运行在容器中。Primrose 通过一个 Web 接口来控制 SQL 处理的追踪、配置,以及动态池管理。在重负荷的情况下可进行连接请求队列处理
- SmartPool:是一个连接池组件,它模仿应用服务器对象池的特性。SmartPool 能够解决一些临界问题如连接泄漏(connection leaks)、连接阻塞、打开的 JDBC 对象(如Statements、PreparedStatements)等
- MiniConnectionPoolManager:是一个轻量级 JDBC 数据库连接池。它只需要 Java1.5(或更高)并且没有依赖第三方包
- BoneCP:是一个快速、开源的数据库连接池。帮用户管理数据连接,让应用程序能更快速地访问数据库。比C3P0/DBCP连接池速度快25倍
- Druid(推荐):Druid 不仅是一个数据库连接池,还包含一个 ProxyDriver、一系列内置的 JDBC 组件库、一个 SQL Parser,Druid 支持所有JDBC兼容的数据库,包括 Oracle、MySql、Derby、Postgresql、SQL Server、H2 等。 Druid 针对 Oracle 和 MySql 做了特别优化,比如:
- Oracle 的 PS Cache 内存占用优化
- MySql 的 ping检测优化
- Druid 提供了 MySql、Oracle、Postgresql、SQL-92 的 SQL 的完整支持,这是一个手写的高性能 SQL Parser,支持 Visitor 模式,使得分析 SQL 的抽象语法树很方便。
- 简单 SQL 语句用时 10 微秒以内,复杂 SQL 用时 30 微秒。
- 通过 Druid 提供的 SQL Parser 可以在 JDBC 层拦截 SQL 做相应处理,比如说分库分表、审计等。Druid 防御SQL注入攻击的 WallFilter,就是通过 Druid 的 SQL Parser 分析语义实现的
这些池底层都是对 driver-class-name=com.mysql.jdbc.Driver 对这个驱动进行了封装