最近开发一个项目会使用MySQL,SqlServer,Oracle,Hive,SparkSql等多类型数据源,通过JDBC形式连接数据源操作数据库。初始频繁的创建JDBC连接和关闭发现非常消耗资源,并且连接hive2和SparkSql时耗时比较长,基于此类需求发现是否可以自定JDBC连接池,实现一次连接反复使用。
方法一:
下面是基于JDBC连接写的一个多线程测试类,该测试方法主要是实现初始化一组数据,线程访问到数据后将数据输出并再返回数据列表中,基本可以实现JDBC连接池功能:
public class Demo {
public static void main(String[] args) {
for (int j = 0; j < 2; j++) {
Test test = new Demo().new Test();
ExecutorService executorService = Executors.newFixedThreadPool(20);
for(int i = 0; i < 20; i ++){
executorService.submit(getThread(test));
}
executorService.shutdown();
}
}
private static Thread getThread(Test test) {
Thread thr = new Thread(new Runnable() {
@Override
public void run() {
try {
String str = test.getStr("1");
System.out.println( Thread.currentThread().getName() + "\t" + str);
Thread.sleep(100);
LinkedList
linkedList.add(str);
test.map.put("1", linkedList);
} catch (Exception e) {
e.printStackTrace();
}
}
});
return thr;
}
class Test {
ConcurrentHashMap
public String getStr(String key) throws Exception {
String str = "";
synchronized (key) {
LinkedList
if (linkedList == null) {
linkedList = new LinkedList<>();
for (int i = 0; i < 10; i++) {
linkedList.add(i + "");
}
map.put(key, linkedList);
str = linkedList.remove(0);
}else /*if (linkedList.isEmpty())*/ {
while(true) {
// 等候10s再看是否有归还连接
System.out.println("----------------------");
Thread.sleep(10);
if (!map.get(key).isEmpty()) {
str = map.get(key).remove(0);
break;
}
}
}/*else {
str = linkedList.remove(0);
} */
}
return str;
}
}
}
代码中标注红色部分放开后会报错,报错原因是多线程访问时,链表有数据在其他线程还未消费,但是自己本线程消费时,链表数据已经清空,因此会报空指针异常。注释后代码不会报错,但是会影响代码性能,链表中有数据依旧需要等候睡眠时间间隔才能获得数据。
方法二:
使用非阻塞线程安全队列ConcurrentLinkedQueue来实现:
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.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.logging.Logger;
import javax.sql.DataSource;
public class MyDataSource implements DataSource {
// 声明连接池变量,由于操作连接池主要是增加,删除操作,
// LinkedList比ArrayList增删快,所以这里选用LinkedList
private ConcurrentLinkedQueue
public MyDataSource() throws SQLException {
for (int i = 0; i < 10; i++) {
Connection conn = PoolConnection.getConn();
pools.add(conn);
}
System.out.println("连接池初始化:" + pools.size());
}
@Override
public Connection getConnection() throws SQLException {
//如果集合中没有数据库连接对象了,且创建的数据库连接对象没有达到最大连接数量,可以再创建一组数据库连接对象以备使用
if (!pools.isEmpty()) {
//从pools集合中取出一个数据库链接对象Connection使用
final Connection conn1 = pools.poll();
return (Connection) Proxy.newProxyInstance(MyDataSource.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 {
pools.add(conn1);
return null;
}
}
});
} else {
// 如果jdbc数据源连接全部用完,等待有连接
while(true) {
// 等候10s再看是否有归还连接
try {
Thread.sleep(10000);
return getConnection();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
@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;
}
@Override
public
return null;
}
@Override
public boolean isWrapperFor(Class> iface) throws SQLException {
return false;
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return null;
}
}
jdbc连接工具类
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class PoolConnection {
/**
* 获取连接对象
* @return
*/
public static Connection getConn() {
Connection conn = null;
try {
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://ip/dw?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull", "username", "pwd");
} catch (Exception e) {
e.printStackTrace();
}
return conn;
}
/**
* 释放资源
* @param conn
* @param st
* @param rs
*
*/
public static void release(Connection conn, Statement st, ResultSet rs) {
closeRs(rs);
closeSt(st);
closeConn(conn);
}
public static void release(Connection conn, Statement st) {
closeSt(st);
closeConn(conn);
}
private static void closeRs(ResultSet rs) {
try {
if (rs != null) {
rs.close();
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
rs = null;
}
}
private static void closeSt(Statement st) {
try {
if (st != null) {
st.close();
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
st = null;
}
}
private static void closeConn(Connection conn) {
try {
if (conn != null) {
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
conn = null;
}
}
}
测试方法:
import java.sql.Connection;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo {
public static void main(String[] args) throws Exception {
MyDataSource ds = new MyDataSource();
ExecutorService executorService = Executors.newFixedThreadPool(15);
for(int i = 0; i < 15; i ++){
executorService.submit(getThread(ds));
}
executorService.shutdown();
}
private static Thread getThread(MyDataSource ds) {
Thread thr = new Thread(new Runnable() {
@Override
public void run() {
try {
Connection conn = ds.getConnection();
if (conn != null) {
Thread.sleep(10000);
conn.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
});
return thr;
}
}
比较以上两种方法,最终采用了方法二