第25章 JDBC和数据库连接池
821. JDBC原理示意图
-
JDBC 为访问不同的数据库提供了统一的接口,为使用者屏蔽了使用细节。Java 程序员使用 JDBC API 可以连接任何提供了 JDBC 驱动程序的数据库系统,从而完成对数据库的各种操作
-
Java 设计者定义了操作数据库的接口规范,由各自数据库厂商具体实现。Java 程序员只需要面向这套接口编程即可。
822. JDBC模拟实现
823. JDBC快速入门
-
JDBC API 是一系列的接口,它统一和规范了应用程序与数据库的连接、执行 SQL 语句,并得到返回结果等各类操作,相关的接口和类在 java.sql 和 javax.sql 包中
-
JDBC 程序编写步骤:
- 注册驱动:加载 Driver 类
- 获取连接:获得 Connection 对象
- 执行 SQL:获得 Statement 对象
- 释放资源
-
数据库连接方式1:
package p823;
import com.mysql.jdbc.Driver;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
/**
* 静态加载方式连接到数据库
* @author Spring-_-Bear
* @version 2021-11-08 20:56
*/
public class Jdbc01 {
public static void main(String[] args) throws SQLException {
// 连接到的数据库:jdbc:mysql://主机IP地址:端口/db_name
String url = "jdbc:mysql://localhost:3306/temp";
// 设置用户名与密码
Properties properties = new Properties();
properties.setProperty("user", "springbear");
properties.setProperty("password","123");
// 加载驱动: new com.mysql.jdbc.Driver()
Driver driver = new Driver();
// 获得连接
Connection connect = driver.connect(url, properties);
// SQL 语句
String insert = "INSERT INTO actor VALUES (NULL,'张三','男','1970-01-01','10086');";
// 得到一个执行静态 SQL 语句并返回其生成的结果的对象
Statement statement = connect.createStatement();
// 返回受影响的行数
int rows = statement.executeUpdate(insert);
System.out.println("返回受影响的行数 = " + rows);
statement.close();
connect.close();
}
}
824. 数据库连接方式2
/**
* 方式1属于静态加载,灵活性差,依赖性强,考虑方式二使用反射机制
*/
public void connect02() throws ClassNotFoundException, InstantiationException, IllegalAccessException, SQLException {
// 连接到的数据库:jdbc:mysql://主机IP地址:端口/db_name
String url = "jdbc:mysql://localhost:3306/temp";
// 设置用户名与密码
Properties properties = new Properties();
properties.setProperty("user", "springbear");
properties.setProperty("password","123");
// 加载类信息
Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");
// 获得类实例
Driver driver = (Driver)aClass.newInstance();
// 获得连接
Connection connect = driver.connect(url, properties);
}
825. 数据库连接方式3
/**
* 使用 DriverManager 替代 Driver,进行统一管理
*/
public void connect03() throws ClassNotFoundException, InstantiationException, IllegalAccessException, SQLException {
// 加载类信息
Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");
// 获得类实例
Driver driver = (Driver)aClass.newInstance();
String url = "jdbc:mysql://localhost:3306/temp";
String user = "springbear";
String pwd = "123";
// 注册驱动
DriverManager.registerDriver(driver);
// 获得连接
Connection connection = DriverManager.getConnection(url, user, pwd);
}
826. 数据库连接方式4
/**
* 使用 Class.forName 自动完成注册驱动
*/
public void connect04() throws ClassNotFoundException, SQLException {
// 加载类信息,在加载 Driver 的过程中自动完成注册
Class.forName("com.mysql.jdbc.Driver");
/*
* static {
* try {
* DriverManager.registerDriver(new Driver());
* } catch (SQLException var1) {
* throw new RuntimeException("Can't register driver!");
* }
* }
*/
String url = "jdbc:mysql://localhost:3306/temp";
String user = "springbear";
String pwd = "123";
// 获得连接
Connection connection = DriverManager.getConnection(url, user, pwd);
}
827. 数据库连接方式5
- mysql-connector-java-5.1.37-bin.jar 驱动文件 5.1.6 之后无需 Class.forName(“com.mysql.jdbc.Driver”) 也可以直接获得连接。原因:从 jdk5 以后使用了 jdbc4,不再需要显式调用 Class.forName() 注册驱动,而是自动调用驱动 jar 包下的 META-INF\services\java.sql.Driver 文本中的类名称去注册
/**
* 在方式4的基础上使用配置文件进行连接,更加灵活
*/
public void connect05() throws IOException, ClassNotFoundException, SQLException {
// 从配置文件中读取信息
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driver = properties.getProperty("driver");
String url = properties.getProperty("url");
// 加载类信息,自动注册驱动,获得连接
Class.forName(driver);
Connection connection = DriverManager.getConnection(url, user, password);
}
828. ResultSet底层
- ResultSet:表示从数据库读取到的数据表的结果集。ResultSet 对象保持一个光标指向当前的数据行。最初,光标位于第一行之前。其有一个 next 方法将光标移动到下一行,并且由于在 ResultSet 对象中没有更多行时返回 false,因此常用 while 循环来遍历结果集
- com.mysql.jdbc.JDBC42 ResultSet 类下有一个 RowData(接口) 类型的字段,rowData 中有一个 ArrayList 类型的集合 rows,rows 中又有 byte[] 类型的 internalRowData,数据真正存储的位置
829. SQL注入
- Statement 对象,用于执行静态 SQL 语句并返回其生成结果的对象
- 在建立连接之后,想要对数据库进行操作,一般有以下三种方式:
- Statement:存在 SQL 注入
- PreparedStatement:预处理
- CallableStatement:用于执行数据库存储过程
- SQL 注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入的数据中注入非法的 SQL 语句段或命令,恶意攻击数据库
package p823;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;
import java.util.Scanner;
/**
* @author Spring-_-Bear
* @version 2021-11-09 09:48
*/
public class SqlInjection {
public static void main(String[] args) throws IOException, ClassNotFoundException, SQLException {
// 获取用户想要查询的用户名和密码
// Input userName = 1' or
// Input pwd = or '1' = '1
Scanner scanner = new Scanner(System.in);
System.out.print("Input the name that you want to query:");
String userName = scanner.nextLine();
System.out.print("Input the password that you want to query:");
String pwd = scanner.nextLine();
// 加载配置文件
Properties properties = new Properties();
properties.load(new FileInputStream("config\\temp.properties"));
// 加载驱动类信息,自动注册驱动
Class.forName(properties.getProperty("driver"));
// 获得连接
Connection connection = DriverManager.getConnection(properties.getProperty("url"), properties);
Statement statement = connection.createStatement();
String select = "SELECT * FROM admin WHERE name='" + userName + "' AND pwd= '" + pwd + "'";
ResultSet resultSet = statement.executeQuery(select);
while (resultSet.next()) {
userName = resultSet.getString(1);
pwd = resultSet.getString(2);
System.out.println(userName + "\t" + pwd);
}
resultSet.close();
statement.close();
connection.close();
}
}
830. Statement
831. 预处理查询
- PreparedStatement 执行的 SQL 语句中的参数用问号(?)来表示,调用 PreparedStatement 对象的 setXxx() 方法来设置这些参数。setXxx() 方法有两个参数,第一个参数是要设置的 SQL 语句中的参数的索引,从 1 开始,第二个参数是设置 SQL 语句中的参数的值
- 预处理的好处:
- 不再使用 + 拼接 SQL 语句,减少语法错误
- 有效解决了 SQL 注入问题
- 大大减少了编译次数,提高了效率
String select = "SELECT * FROM admin WHERE name = ? AND pwd= ?";
// SQL 语句预处理
PreparedStatement preparedStatement = connection.prepareStatement(select);
preparedStatement.setString(1, userName);
preparedStatement.setString(2, pwd);
// 执行 SQL 语句,得到结果集
ResultSet resultSet = preparedStatement.executeQuery();
832. 预处理DML
833. JDBC API
接口名 | 方法名 | 功能 |
---|---|---|
Connection | createStatement() | 创建执行静态 SQL 语句的对象 |
createPreparedStatement(sql) | 获得 SQL 语句预处理对象 | |
Statement | executeUpdate(sql) | 执行 DML 语句,返回受影响行数 |
executeQuery(sql) | 执行 DQL 语句,返回结果集 | |
execute(sql) | 执行任意 SQL 语句,返回布尔值 | |
PreparedStatement | executeUpdate(sql) | 执行 DML 语句,返回受影响行数 |
executeQuery(sql) | 执行 DQL 语句,返回结果集 | |
execute(sql) | 执行任意 SQL 语句,返回布尔值 | |
setXxx(index,value) | 设置 SQL 语句中的值 | |
setObject(index,value) | 设置 SQL 语句中的值 | |
ResultSet | next() | 向下移动一行,到表尾返回 false |
previous() | 向上移动一行,到表头返回 false | |
getXxx(index || colLabel) | 获得指定列的值 | |
getObject(index || colLabel) | 获得指定列的值 |
834. JDBC Utils开发
package utils;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;
/**
* 数据库连接、资源关闭工具类
*
* @author Spring-_-Bear
* @version 2021-11-09 11:40
*/
public class JdbcUtils {
private static String driver;
private static String url;
private static String user;
private static String password;
private static String path = "config\\temp.properties";
/**
* 读取文件信息,初始化字段
*/
static {
Properties properties = new Properties();
try {
properties.load(new FileInputStream(path));
driver = properties.getProperty("driver");
url = properties.getProperty("url");
user = properties.getProperty("user");
password = properties.getProperty("password");
} catch (IOException e) {
// 实际开发中,会将此类编译异常转换成运行异常,由调用方自行处理,较为方便
throw new RuntimeException(e);
}
}
/**
* 获得对数据库的连接
*
* @return 数据库连接对象
*/
public static Connection getConnection() {
try {
return DriverManager.getConnection(url, user, password);
} catch (SQLException e) {
// 实际开发中,会将此类编译异常转换成运行异常,由调用方自行处理,较为方便
throw new RuntimeException(e);
}
}
/**
* 关闭对应资源
*
* @param resultSet none
* @param statement none
* @param connection none
*/
public static void close(ResultSet resultSet, Statement statement, Connection connection) {
try {
if (resultSet != null) {
resultSet.close();
}
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
// 实际开发中,会将此类编译异常转换成运行异常,由调用方自行处理,较为方便
throw new RuntimeException(e);
}
}
}
835. JDBC Utils DML
836. JDBC Utils 查询
837. 事务介绍
- JDBC 程序中当一个 Connectioon 对象被创建时,默认情况下是自动提交事务:每次执行一条 SQL 语句时,如果执行成功,就会向数据库自动提交,而不能回滚
- 可以调用 Connection 接口的 setAutoCommit(false) 方法取消自动提交事务
- 在所有的 SQL 语句都执行成功后,调用 commit() 方法提交事务;在其中某个操作失败或出现异常时,调用 rollback() 方法回滚事务
838. 事务处理
public void transaction() {
Connection connection = null;
PreparedStatement preparedStatement = null;
String sub = "UPDATE account SET balance = balance - 100 WHERE id = 1";
String add = "UPDATE account SET balance = balance + 100 WHERE id = 2";
try {
// 获得连接
connection = JdbcUtils.getConnection();
// 关闭自动提交即开启事务
connection.setAutoCommit(false);
// 执行 SQL
preparedStatement = connection.prepareStatement(add);
preparedStatement.executeUpdate();
int temp = 1 / 0;
preparedStatement = connection.prepareStatement(sub);
preparedStatement.executeUpdate();
// 提交事务
connection.commit();
System.out.println("所有 SQL 操作成功,提交事务!");
} catch (SQLException | ArithmeticException e) {
try {
// 发生异常,撤销操作,事务回滚
System.out.println("程序执行发生了异常,回滚所有操作!!!");
connection.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
e.printStackTrace();
} finally {
JdbcUtils.close(null, preparedStatement, connection);
}
}
839. 批处理应用
- 当需要成批插入或者更新记录时,可以采用 Java 的批处理更新机制,这一机制允许多条语句一次性提交给数据库批量处理。通常情况下比单独提交处理更有效率
方法名 | 功能 |
---|---|
addBatch() | 添加需要批量处理的 SQL 语句或参数 |
executeBatch() | 批量发送执行 |
clearBatch() | 清空批处理包 |
- JDBC 连接 MySQL 时,如果需要使用批处理功能,需要在 url 中添加参数:
url = "jdbc:mysql://localhost:3306/temp?rewriteBatchedStatements=true"
- 批处理往往和 PreparedStatement 一起搭配使用,既可以减少编译次数,又可以减少运行次数,效率大大提高
preparedStatement.addBatch();
if (i % 1000 == 0) {
preparedStatement.executeBatch();
preparedStatement.clearBatch();
}
840. 批处理源码分析
/**
* 第一次会创建 ArrayList<elementData>
* elementData => Object[] 会存放预处理后的 SQL 语句
* 当 elementDate 满后会按照 1.5 倍扩容
* 当达到指定的容量之后,就会发送给 MySQL 执行
* 批处理会减少发送 SQL 语句的网络开销,减少编译次数,从而提高效率
* @throws SQLException none
*/
public void addBatch() throws SQLException {
synchronized(this.checkClosed().getConnectionMutex()) {
if (this.batchedArgs == null) {
this.batchedArgs = new ArrayList();
}
for(int i = 0; i < this.parameterValues.length; ++i) {
this.checkAllParametersSet(this.parameterValues[i], this.parameterStreams[i], i);
}
this.batchedArgs.add(new com.mysql.jdbc.PreparedStatement.BatchParams(this.parameterValues, this.parameterStreams, this.isStream, this.streamLengths, this.isNull));
}
}
841. 传统链接弊端分析
- 传统的 JDBC 数据库连接使用 DriverManager 来获取,每次向数据库建立连接的时候都需要将 Connection 加载到内存中,再验证 IP 地址、用户名、密码(耗时0.05s ~ 1s)是否正确。需要向数据库连接的时候,就向数据库请求一个连接,频繁的请求操作将占用过多的系统资源,容易造成服务器崩溃
- 每一次数据库连接,使用完后都得及时断开,如果程序出现异常而导致未能正常关闭,将导致数据库内存泄漏,最终导致数据库崩溃重启
- 传统获取连接的方式,不能控制创建连接的数量,如果连接过多,也可能导致内存泄漏,从而导致 MySQL 崩溃
842. 数据库连接池原理
- 预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕归还给缓冲池(并不断开与数据库的连接)
- 数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个
- 当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列
- JDBC 的数据库连接池使用 javax.sql.DataSource 来表示,DataSource 只是一个接口,该接口具体实现留给第三方
连接池 | 特点 |
---|---|
C3P0 | 速度相对较慢,稳定性不错(hibernate、spring底层均采用) |
Druid | 阿里巴巴提供的数据库连接池,集 DBCP、C3P0、Proxool 优点于一身 |
Proxool | 有监控连接池状态的功能,稳定性较 C3P0 略差 |
BoneCP | 速度快 |
DBCP | 速度较 C3P0 快,但不稳定 |
843. C3P0方式1
// 创建数据源对象
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
// 设置相关信息
comboPooledDataSource.setDriverClass(driver);
comboPooledDataSource.setJdbcUrl(url);
comboPooledDataSource.setUser(user);
comboPooledDataSource.setPassword(pwd);
comboPooledDataSource.setInitialPoolSize(10);
comboPooledDataSource.setMaxPoolSize(50);
// 获得连接
Connection connection = comboPooledDataSource.getConnection();
connection.close();
844. C3P0方式2
// 将 c3p0-config.xml 文件拷贝到工程 src 目录下
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource("spring_bear");
Connection connection = comboPooledDataSource.getConnection();
connection.close();
845. 德鲁伊连接池
// 加载配置文件
Properties properties = new Properties();
properties.load(new FileInputStream("config\\druid.properties"));
// 创建一个的连接池
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
// 获得连接
Connection connection = dataSource.getConnection();
connection.close();
846. 德鲁伊工具类
package utils;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
/**
* Druid 连接池
*
* @author Spring-_-Bear
* @version 2021-11-09 22:17
*/
public class JdbcUtilsByDruid {
static DataSource dataSource;
static String path = "config\\druid.properties";
static {
Properties properties = new Properties();
try {
properties.load(new FileInputStream(path));
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 从 Druid 连接池中返回一个数据库连接
*
* @return Connection
* @throws SQLException none
*/
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
/**
* 关闭连接,将连接归还给连接池
*
* @param resultSet none
* @param statement none
* @param connection none
*/
public static void close(ResultSet resultSet, Statement statement, Connection connection) {
try {
if (resultSet != null) {
resultSet.close();
}
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
847. ApDBUtils引出
- 关闭 connection 后,resultSet 结果集无法继续使用;然而很多时候我们希望关闭 connection 连接后仍然可以继续使用查询到的数据,resultSet 存储查询结果的方式不利于数据管理;从 resultSet 结果集中获取数据时操作方法不够明确,getXxx() 方法容易出错,含义模糊
- 定义一个类与数据库表的字段一一对应,这样的类一般称作 JavaBean 或 PoJo 或 Domain
- 将返回的结果集的字段值封装到自定义的类的对象中,将若干个这样的对象放进集合中,就可以直接访问集合从而获得数据库表的查询结果
848. 土办法完成封装
849. ApDBUtils查询
- commons-dbutils 是 Apache 组织提供的一个开源 JDBC 工具类,它是对 JDBC 的封装,使用 dbutils 能极大简化 JDBC 编码量
- QueryRunner 类封装了 SQL 的执行,是线程安全的 ,可以实现增、删、改、查和批处理
- RessultSetHandler 接口用于处理 java.sql.ResultSet,将查询到的数据转换为另一种形式
接口名 | 功能 |
---|---|
ArrayHandler | 将结果集中的第一行数据转换成对象数组 |
ArrayListHandler | 将结果集中的每一行都转换成一个数组,再存放到 List 中 |
BeanHandler | 将结果集中的第一行数据封装到一个对应的 JavaBean 实例中 |
BeanListHandler | 将结果集中的每一行数据封装到对应的 JavaBean 实例中,存放到 List |
ColumnListHandler | 将结果集中的某一列数据存放到 List 中 |
KeyedHandler(name) | 将每行数据封装到 Map 中,再将 map 存入另一个 map 中 |
MapHandler | 将结果集的第一行数据封装到 Map 中,key 是列名,value 是对应值 |
MapListHandler | 将结果集中的每一行数据封装到 Map 中,再存放到 List 里 |
public void testApache() throws SQLException {
// 获得连接
Connection connection = JdbcUtilsByDruid.getConnection();
// 获得 Apache 实现的查询对象
QueryRunner queryRunner = new QueryRunner();
String select = "SELECT * FROM cost WHERE id >= ? AND id <= ?";
List<Fishing> fishings = queryRunner.query(connection, select, new BeanListHandler<>(Fishing.class), 1, 10);
for (Fishing fishing : fishings) {
System.out.println(fishing);
}
JdbcUtilsByDruid.close(null, null, connection);
}
850. ApDBUtils源码分析
- 在创建 JavaBean 类时类的字段的数据类型强制使用八大基本数据类型对应的包装类,因为 MySQL 数据库表中的字段值可能为空,而 Java 只有引用数据类型才有 NULL 值
851. ApDBUtils查询2
852. ApDBUtilsDML
853. BasicDAO问题
- Apache-dbutils + Druid-connectionPoll 简化了 JDBC 开发,但仍有以下不足:
- SQL 语句是固定的,不能通过参数传入,通用性不好,需进行改进,以方便 CRUD 操作
- 对于查询 SELECT 操作,如果有返回值,返回类型不能确定,需要使用泛型解决
- 未来数据库中的表会有很多,业务需求复杂,不可能只靠一个 Java 类完成
- 解决方案:为每个表设计一个 JavaBean 类,同时为每一张数据库表设计一个专门操作它的类 Dao,将所有的具体 Dao 类中的共有部分抽象出父类 BasciDao,以更好地利用多态完成功能
- DAO(data access object):访问数据库数据的对象
- 创建 JavaBeam 类的时候一定要给一个无参构造器,以方便反射机制获取该类信息
854. BasicDAO分析
855. BasicDAO实现1
package dao.dao;
import dao.utils.JdbcUtilsByDruid;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
/**
* 封装对数据库表的操作
*
* @author Spring-_-Bear
* @version 2021-11-10 11:03
*/
public class BasicDao<T> {
QueryRunner queryRunner = new QueryRunner();
/**
* DML操作
*
* @param sql SQL 语句
* @param params SQL 中参数的值
* @return 受影响的行数
*/
public int update(String sql, Object... params) {
Connection connection = null;
try {
connection = JdbcUtilsByDruid.getConnection();
return queryRunner.update(connection, sql, params);
} catch (SQLException e) {
// 将编译异常转换为运行异常,方便调用者处理
throw new RuntimeException(e);
} finally {
JdbcUtilsByDruid.close(null, null, connection);
}
}
/**
* 查询多行
*
* @param sql SQL 语句
* @param clazz 具体类的 Class 对象
* @param params SQL 语句中的具体值
* @return 从数据库查询到的结果集,封装进 ArrayList
*/
public List<T> getMultiRows(String sql, Class<T> clazz, Object... params) {
Connection connection = null;
try {
connection = JdbcUtilsByDruid.getConnection();
return queryRunner.query(connection, sql, new BeanListHandler<T>(clazz), params);
} catch (SQLException e) {
// 将编译异常转换为运行异常,方便调用者处理
throw new RuntimeException(e);
} finally {
JdbcUtilsByDruid.close(null, null, connection);
}
}
/**
* 查询一行
*
* @param sql SQL 语句
* @param clazz 具体类的 Class 对象
* @param params SQL 语句中的具体值
* @return 从数据库查询到的一行数据
*/
public T getOneRow(String sql, Class<T> clazz, Object... params) {
Connection connection = null;
try {
connection = JdbcUtilsByDruid.getConnection();
return queryRunner.query(connection, sql, new BeanHandler<T>(clazz), params);
} catch (SQLException e) {
// 将编译异常转换为运行异常,方便调用者处理
throw new RuntimeException(e);
} finally {
JdbcUtilsByDruid.close(null, null, connection);
}
}
/**
* 查询单行单列
*
* @param sql SQL 语句
* @param params SQL 语句中的具体值
* @return 从数据库查询到的一个单元格数据
*/
public Object getOneObj(String sql, Object... params) {
Connection connection = null;
try {
connection = JdbcUtilsByDruid.getConnection();
return queryRunner.query(connection, sql, new ScalarHandler(), params);
} catch (SQLException e) {
// 将编译异常转换为运行异常,方便调用者处理
throw new RuntimeException(e);
} finally {
JdbcUtilsByDruid.close(null, null, connection);
}
}
}