应用程序直接获取数据库连接缺点
用户每次请求都会建立一次数据库连接,并且数据库创建连接会消耗相对大的资源和时间。
如果针对于个别的工具或者是大量的代码测试甚至系统运行,对数据库操作次数频繁,极大的占用数据库资源,有可能会发生宕机或者内存溢出的现象。
而在大多的项目中,常常用到阿里巴巴开源的数据库连接池框架,准确来说它不仅仅包括数据库连接池,
原因其实很简单,在Spring框架的配置文件中仅仅一个配置datasource就可以使用Druid了
我们对Druid不加探讨,实现自定义的数据库连接池,来对数据进行处理。
在pom文件中引人数据库所需要的jar包,这里不一一介绍。
建立JdbcConnectionsPool类实现数据源接口DataSource重写其getConnection方法
package com.herbert.test.db;
import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.LinkedList;
import java.util.Properties;
import java.util.logging.Logger;
/**
* 实现数据库连接池
*/
public class JdbcConnectionsPool implements DataSource {
//
/*
* 使用静态块代码,初始化连接池,创建连接池的中最小链接数量连接,
* 创建linkedlist集合,将这些连接放入集合中
*/
//创建linkedlist集合
private static LinkedList<Connection> linkedlist1 = new LinkedList<Connection>();
public static String GENERATOR_JDBCDRIVER = "";
public static String GENERATOR_JDBCURL = "";
public static String GENERATOR_JDBCUSERNAME = "";
public static String GENERATOR_JDBCPASSWORD = "";
private static int jdbcConnectionInitSize;//最小连接数量
private static int max = 1; //当前最大连接数量=max*jdbcConnectionInitSize
private static Properties prop = new Properties();
static {
//通过反射机制获取访问db.properties文件
InputStream in = Object.class.getResourceAsStream("/generator.properties");
try {
//加载db.properties文件
prop.load(in);
//获取db.properties文件中的数据库连接信息
GENERATOR_JDBCDRIVER = prop.getProperty("generator.jdbc.driver").trim();
GENERATOR_JDBCURL = prop.getProperty("generator.jdbc.url").trim();
GENERATOR_JDBCUSERNAME = prop.getProperty("generator.jdbc.username").trim();
GENERATOR_JDBCPASSWORD = prop.getProperty("generator.jdbc.password").trim();
jdbcConnectionInitSize = Integer.parseInt(prop.getProperty("jdbcConnectionInitSize"));
Class.forName(GENERATOR_JDBCDRIVER);
//创建最小连接数个数据库连接对象以备使用
for (int i = 0; i < jdbcConnectionInitSize; i++) {
Connection conn = DriverManager.getConnection(GENERATOR_JDBCURL, GENERATOR_JDBCUSERNAME, GENERATOR_JDBCPASSWORD);
System.out.println("获取到了链接" + conn);
//将创建好的数据库连接对象添加到Linkedlist集合中
linkedlist1.add(conn);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
public Connection getConnection() throws SQLException {
//如果集合中没有数据库连接对象了,且创建的数据库连接对象没有达到最大连接数量,可以再创建一组数据库连接对象以备使用
if (linkedlist1.size() == 0 && max <= jdbcConnectionInitSize) {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
for (int i = 0; i < jdbcConnectionInitSize; i++) {
Connection conn = DriverManager.getConnection(GENERATOR_JDBCURL, GENERATOR_JDBCUSERNAME, GENERATOR_JDBCPASSWORD);
System.out.println("获取到了链接" + conn);
//将创建好的数据库连接对象添加到Linkedlist集合中
linkedlist1.add(conn);
}
max++;
}
if (linkedlist1.size() > 0) {
//从linkedlist集合中取出一个数据库链接对象Connection使用
final Connection conn1 = linkedlist1.removeFirst();
System.out.println("linkedlist1数据库连接池大小是" + linkedlist1.size());
/*返回一个Connection对象,并且设置Connection对象方法调用的限制,
*当调用connection类对象的close()方法时会将Connection对象重新收集放入linkedlist集合中。
*/
return
(Connection) Proxy.newProxyInstance(JdbcConnectionsPool.class.getClassLoader(),//这里换成JdbcConnectionsPool.class.getClassLoader();也行
new Class[] { Connection.class }, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (!method.getName().equalsIgnoreCase("close")) {
return method.invoke(conn1, args);
} else {
linkedlist1.add(conn1);
System.out.println(conn1 + "对象被释放,重新放回linkedlist集合中!");
System.out.println("此时Linkedlist集合中有" + linkedlist1.size() + "个数据库连接对象!");
return null;
}
}
});
} else {
System.out.println("连接数据库失败!");
}
return null;
}
@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;
}
}
对方法进行进一步封装
package com.herbert.test.db;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
/**
* Created by Herbert on 2018/7/23.
*/
public class JdbcConnectionPoolUtil {
/**
* @Field: pool
* 数据库连接池
*/
private static JdbcConnectionsPool pool = new JdbcConnectionsPool();
/**
* @return Connection数据库连接对象
* @throws SQLException
* @Method: getConnection
* @Description: 从数据库连接池中获取数据库连接对象
*/
public static Connection getConnection() throws SQLException {
return pool.getConnection();
}
/**
* @param conn
* @param st
* @param rs
* @Method: release
* @Description: 释放资源,
* 释放的资源包括Connection数据库连接对象,负责执行SQL命令的Statement对象,存储查询结果的ResultSet对象
*/
public static void release(Connection conn, Statement st, ResultSet rs) {
if (rs != null) {
try {
//关闭存储查询结果的ResultSet对象
rs.close();
} catch (Exception e) {
e.printStackTrace();
}
rs = null;
}
if (st != null) {
try {
//关闭负责执行SQL命令的Statement对象
st.close();
} catch (Exception e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
//关闭Connection数据库连接对象
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
测试方法
public static void main(String[] args) throws SQLException {
JdbcConnectionPoolUtil jcpt = new JdbcConnectionPoolUtil();
List list = new ArrayList();
Connection c = null;
Statement stmt = null;
try {
c = jcpt.getConnection();
c.setAutoCommit(false);
stmt = c.createStatement();
String sql = "SELECT id FROM test";
PreparedStatement preState = c.prepareStatement(sql);
ResultSet rs = preState.executeQuery();
while (rs.next()) {
String id = rs.getString("id");
list.add(id);
}
jcpt.release(c, stmt, rs);
} catch (Exception e) {
System.err.println(e.getClass().getName() + ": " + e.getMessage());
System.exit(0);
}
System.out.println("测试:"+list.toString());
}
输出结果:
获取到了链接com.mysql.jdbc.JDBC4Connection@b1a58a3
获取到了链接com.mysql.jdbc.JDBC4Connection@46ee7fe8
获取到了链接com.mysql.jdbc.JDBC4Connection@722c41f4
获取到了链接com.mysql.jdbc.JDBC4Connection@2957fcb0
获取到了链接com.mysql.jdbc.JDBC4Connection@f2a0b8e
获取到了链接com.mysql.jdbc.JDBC4Connection@64a294a6
获取到了链接com.mysql.jdbc.JDBC4Connection@61e717c2
获取到了链接com.mysql.jdbc.JDBC4Connection@504bae78
获取到了链接com.mysql.jdbc.JDBC4Connection@5fcfe4b2
获取到了链接com.mysql.jdbc.JDBC4Connection@3f8f9dd6
linkedlist1数据库连接池大小是9
com.mysql.jdbc.JDBC4Connection@b1a58a3对象被释放,重新放回linkedlist集合中!
此时Linkedlist集合中有10个数据库连接对象!
测试:[1, 2]
异常处理
java.lang.ClassCastException: com.sun.proxy.$Proxy0 cannot be cast to java.sql.Connection异常问题解决
代码如下
Connection proxy = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(),
Connection.class.getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
if ("close".equals(method.getName())) {
returnConn(conn);
return null;
} else {
return method.invoke(conn, args);
}
}
});
在使用动态代理增强Connection连接对象的close方法时,我碰到了如题所示的异常。通过搜索我发现这个异常出现的原因在于我使用的mysql数据库驱动的问题,由于数据库驱动不同,Connection.class.getInterfaces()返回的结果也不同,它返回的是一个Class[]数组,然而此数组的第一个元素必须是Connection才能把创建的代理类转为Connection对象,否则就会报错。
所以这里我们可以采取一个替代方式替换Connection.class.getInterfaces(),即new Class[] { Connection.class },这样无论数据库驱动是什么版本的驱动,都能保证这个类型转换不出错。