1.连接池
1.1 概述
池(Pool)技术在一定程度上可以明显优化服务器应用程序的性能,提高程序执行效率和降低系统资 源开销。
例如,数据库连接池,在系统初始化时创建一定数量数据库连接对象,需要时直接从池中取出一个空闲 对象,用完后并不直接释放掉对象,而是再放到对象池中,以便下一次对象请求可以直接复用。
这样可以消除对象创建和销毁所带来的延迟,从而提高系统的性能。
例如,连续不断的创建数据库连接对象
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import org.junit.jupiter.api.Test;
public class ConnectionTest {
private String driverClass = "oracle.jdbc.driver.OracleDriver";
private String url = "jdbc:oracle:thin:@127.0.0.1:1521:XE";
private String user = "briup";
private String password = "briup";
@Test
public void test() {
Connection conn = null;
try {
Class.forName(driverClass);
for (int i = 1; i < 30; i++) {
conn = DriverManager.getConnection(url,user,password);
System.out.println(i+" : "+conn);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
//运行结果:
1 : oracle.jdbc.driver.T4CConnection@28d6290
2 : oracle.jdbc.driver.T4CConnection@408b35bf
3 : oracle.jdbc.driver.T4CConnection@15bcf458
4 : oracle.jdbc.driver.T4CConnection@43c67247
5 : oracle.jdbc.driver.T4CConnection@726386ed
6 : oracle.jdbc.driver.T4CConnection@14bb2297
7 : oracle.jdbc.driver.T4CConnection@797501a
8 : oracle.jdbc.driver.T4CConnection@57f791c6
9 : oracle.jdbc.driver.T4CConnection@6c4f9535
10 : oracle.jdbc.driver.T4CConnection@30c31dd7
11 : oracle.jdbc.driver.T4CConnection@596df867
12 : oracle.jdbc.driver.T4CConnection@241a53ef
13 : oracle.jdbc.driver.T4CConnection@2db2cd5
14 : oracle.jdbc.driver.T4CConnection@615f972
15 : oracle.jdbc.driver.T4CConnection@31500940
16 : oracle.jdbc.driver.T4CConnection@48e64352
17 : oracle.jdbc.driver.T4CConnection@2ef8a8c3
18 : oracle.jdbc.driver.T4CConnection@63fd4873
java.sql.SQLException: Listener refused the connection with the following error:
ORA-12519, TNS:no appropriate service handler found
可以看出,在当前数据库环境中,创建出一定数据的数据库连接对象之后,就不无法在创建新对 象了。
而数据库连接池,就可以在种情况下,预先创建出一批数据库连接对象,然后对它们进行管理,并反复 使用,这样就不需要频繁的销毁和创建了,提供了资源的利用率。
我们使用的连接池是Druid, 是阿里巴巴开源的数据库连接池项目
1.2使用
javax.sql.DataSource 是Java中定义的一个数据源标准的接口,通过它获取到的数据库连接对象, 如果调用 close() 方法,不会再关闭连接,而是归还连接到连接池中。
例如,项目中 src下面,创建资源文件: druid.properties
注意:
maven 下的配置文件应该放到 resorces 下,也就是需要将druid.properties
放到 resources 目录下
文件内容是druid连接池的配置信息:
driverClassName=oracle.jdbc.OracleDriver
url=jdbc:oracle:thin:@127.0.0.1:1521:XE
username=briup
password=briup
# 初始化连接数量
initialSize=5
# 最大连接数
maxActive=10
# 最大等待时间
maxWait=3000
通过DruidDataSourceFactory获取到一个连接池对象,然后就可以通过这个连接池对象获得数据库的连接.
private static DataSource dataSource = null;
static {
Properties properties = new Properties();
try {
properties.load(DBUtil.class.getClassLoader().getResourceAsStream("druid.properties"));
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
System.out.println("创建连接池发生异常:"+e.getMessage());
System.exit(-1);
}
}
public static Connection getConnection(boolean auto) throws SQLException {
Connection connection = dataSource.getConnection();
connection.setAutoCommit(auto);
return connection;
}
也可以不使用配置文件,自己手动配置连接池:
// 创建数据库连接池
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driverClass);
dataSource.setUrl(url);
dataSource.setUsername(user);
dataSource.setPassword(password);
// 设置创建连接池时创建多少个连接
dataSource.setInitialSize(5);
// 设置最大连接数,当从连接池里面获取的连接超过了5个会继续创建,超过了10个就需要等待
dataSource.setMaxActive(10);
2.封装
通过前面的例子,可以看出来,很多代码都是一样,特别是在执行DDL和DML语句的时候,除了SQL语句 不同之外,其他代码都是一样,那么我们就可以把这些相同的代码进行封装:
package jdbctest;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.function.Function;
public class DBUtil implements Serializable {
private static DataSource dataSource = null;
static {
Properties properties = new Properties();
try {
properties.load(DBUtil.class.getClassLoader().getResourceAsStream("druid.properties"));
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
System.out.println("创建连接池发生异常:"+e.getMessage());
System.exit(-1);
}
}
public static Connection getConnection(boolean auto) throws SQLException {
Connection connection = dataSource.getConnection();
connection.setAutoCommit(auto);
return connection;
}
public static void executeUpdate(String sql) throws SQLException {
Connection connection = getConnection(false);
Statement statement = connection.createStatement();
statement.execute(sql);
connection.commit();
close(connection,statement,null);
}
//执行查询语句的时候,和DML、DDL语句不同,需要处理结果集 ResultSet ,就是通过把一个rs对
象,处理过后的得到一个指定类型的对象,那么这里可以利用函数式编程中的 Function 接口
public static <T> List<T> queryToList(String sql, Function<ResultSet,List<T>> function) throws SQLException {
Connection connection = getConnection(true);
Statement st = connection.createStatement();
ResultSet resultSet = st.executeQuery(sql);
List<T> list = function.apply(resultSet);
close(connection,st,resultSet);
return list;
}
public static <E> E queryOne(String sql,Function<ResultSet,E> function) throws SQLException {
Connection connection = getConnection(true);
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(sql);
E e = function.apply(resultSet);
close(connection,statement,resultSet);
return e;
}
//
public static <U> List<U> queryToList(String sql,Class<U> clazz) throws SQLException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
/*
* 得到结果集
* 将结果集转换成U类型的对象
* 将结果添加到List
* */
Connection connection = getConnection(true);
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(sql);
List<U> list = new ArrayList<>();
while (resultSet.next()){
U u = clazz.getConstructor().newInstance();
list.add(u);
}
return null;
}
public static void close(Connection conn, Statement statement, ResultSet rs){
try {
if (conn!=null) {
conn.close();
}
if (statement!=null)
statement.close();
if (rs!=null)
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
将来使用 queryToList的时候,传一个sql语句和函数即可,这里同时也使用泛型
思考,能否封装一个方法,我们传一个指定类型,将来查询结果就自动返回这个指定的类型?
//该方法表示将查询结果封装为指定类型的对象,并存放集合中,再返回
public static <T> List<T> queryForList(String sql,Class<T> clazz) {
List<T> result = new ArrayList<>();
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
conn = getConnection();
stmt = conn.createStatement();
rs = stmt.executeQuery(sql);
//解析这个传入的类型(clazz),获取到它的每一个 属性类型和属性名
List<TypeAndName> list = parse(clazz);
T obj = null;
while(rs.next()) {
//反射创建对象
obj = clazz.newInstance();
//循环拿到指定类型中的每一个 属性类型和属性名字
for(TypeAndName typeAndName : list) {
//根据属性类型,决定调用getXxx方法获取查询结果中指定字段上的值
//并将值封装到对象的属性中
if("long".equals(typeAndName.type)) {
long value = rs.getLong(typeAndName.name);
typeAndName.invokeSet(obj,long.class, value);
}
else if("String".equals(typeAndName.type)) {
String value = rs.getString(typeAndName.name);
typeAndName.invokeSet(obj,String.class, value);
}
else if("int".equals(typeAndName.type)) {
int value = rs.getInt(typeAndName.name);
typeAndName.invokeSet(obj,int.class, value);
}
//...如果需要可以继续编写其他类型和getXxx方法的对应关系
}
//将封装好的对象存入集合
result.add(obj);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
close(rs,stmt,conn);
}
return result;
}
//解析后,返回封装好的List集合
private static <T> List<TypeAndName> parse(Class<T> clazz) {
Field[] declaredFields = clazz.getDeclaredFields();
List<TypeAndName> list = new ArrayList<>();
for (int i = 0; i < declaredFields.length; i++) {
Field field = declaredFields[i];
String name = field.getName();
String type = field.getType().getSimpleName();
list.add(new TypeAndName(type,name));
}
return list;
}
//私有的静态内部类,辅助解析类型(clazz)
//封装指定类型中的 属性类型和属性名
private static class TypeAndName{
String type;
String name;
public TypeAndName(String type, String name) {
this.type = type;
this.name = name;
}
//反射调用指定对象中的setXxx方法,将值存放到属性中
public <T> void invokeSet(Object obj,Class<T> c,Object value) {
try {
Method m =
obj.getClass().getDeclaredMethod("set"+initCap(name),c);
m.invoke(obj, value);
} catch (Exception e) {
e.printStackTrace();
}
}
//字符串首字母大写
private String initCap(String name) {
return name.substring(0, 1).toUpperCase()+name.substring(1);
}
}