连接池和JDBC优化
一、数据库连接池
1. 什么是连接池
传统的开发模式下,Servlet处理用户的请求,找Dao查询数据,dao会创建与数据库之间的链接,完成数据查询后会关闭数据库的链接。
这样的方式会导致用户每次请求都要向数据库建立链接而数据库创建连接通常需要消耗相对较大的资源,创建时间也较长。假设网站一天10万访问量,数据库服务器就需要创建10万次连接,极大的浪费数据库的资源,并且极易造成数据库服务器内存溢出、宕机。
解决方案就是数据库连接池
连接池就是数据库连接对象的一个缓冲池
我们可以先创建10个数据库连接缓存在连接池中,当用户有请求过来的时候,dao不必创建数据库连接,而是从数据库连接池中获取一个,用完了也不必关闭连接,而是将连接换回池子当中,继续缓存
使用数据库连接池可以极大地提高系统的性能
2. 实现数据库连接池
jdbc针对数据库连接池也定义的接口java.sql.DataSource,所有的数据库连接池实现都要实现该接口
该接口中定义了两个重载的方法
Connection getConnection()
Connection getConnection(String username, String password)
数据库连接池实现思路
1) 定义一个类实现java.sql.DataSource接口
2) 定义一个集合用于保存Connection对象,由于频繁地增删操作,用LinkedList比较好
3) 实现getConnection方法,在方法中取出LinkedList集合中的一个连接对象返回
注意:
返回的Connection对象不是从集合中获得,而是删除
用户用完Connection,会调用close方法释放资源,此时要保证连接换回连接池,而不是关闭连接
重写close方法是难点,解决方案: 装饰设计模式、动态代理
二、 数据源
通常我们把DataSource的实现,按其英文含义称之为数据源,数据源中都包含了数据库连接池的实现。
一些开源组织提供了数据源的独立实现,常用的有:
DBCP 数据库连接池
C3P0 数据库连接池
1. DBCP 数据源
介绍
DBCP 是 Apache 软件基金组织下的开源连接池实现
tomcat服务器就是使用DBCP作为数据库连接池
使用DBCP数据源,需要导入两个jar包
Commons-dbcp.jar:连接池的实现
Commons-pool.jar:连接池实现的依赖库
DBCP核心 API
BasicDataSource
数据源实现
BasicDataSourceFactory
用于创建数据源的工厂类
dbcp 创建连接池
方法1: 直接创建对象,设置参数
BasicDataSource bds = new BasicDataSource();
// 设置连接数据库需要的配置信息
bds.setDriverClassName("com.mysql.jdbc.Driver");
bds.setUrl("jdbc:mysql://localhost:3306/jdbc3");
bds.setUsername("root");
bds.setPassword("root");
// 设置连接池的参数
bds.setInitialSize(5);
bds.setMaxActive(10);
ds = bds
方法2: 通过工厂类创建对象,读取配置文件
try {
Properties prop = new Properties();
// 读配置文件
InputStream in =
JdbcUtils.class.getClassLoader().getResourceAsStream("dbcpconfig.properties");
prop.load(in);
ds = BasicDataSourceFactory.createDataSource(prop);
}catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
配置文件为dbcpconfig.properties
#连接设置
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbc3
username=root
password=root
#<!-- 初始化连接 -->
initialSize=5
#最大连接数量
maxActive=10
#<!-- 最大空闲连接 -->
maxIdle=10
#<!-- 超时等待时间以毫秒为单位 6000毫秒/1000等于60秒 -->
maxWait=60000
#JDBC驱动建立连接时附带的连接属性属性的格式必须为这样:[属性名=property;]
#注意:"user" 与 "password" 两个属性会被明确地传递,因此这里不需要包含他们。
connectionProperties=useUnicode=true;characterEncoding=gbk
#指定由连接池所创建的连接的自动提交(auto-commit)状态。
defaultAutoCommit=true
#driver default 指定由连接池所创建的连接的只读(read-only)状态。
#如果没有设置该值,则“setReadOnly”方法将不被调用。(某些驱动并不支持只读模式,如:Informix)
defaultReadOnly=
#driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation)。
#可用值为下列之一:(详情可见javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
defaultTransactionIsolation=READ_UNCOMMITTED
2. C3P0 数据源
介绍
c3p0是一个开源的jdbc连接池,我们熟悉的 Hibernate和 Sprint 框架使用的都是该数据源
创建连接池对象
方法1:直接创建对象,设置参数
ComboPooledDataSource cpds = new ComboPooledDataSource();
cpds.setDriverClass("com.mysql.jdbc.Driver");
cpds.setJdbcUrl("jdbc:mysql://localhost:3306/jdbc3");
cpds.setUser("root");
cpds.setPassword("root");
cpds.setInitialPoolSize(5);
cpds.setMaxPoolSize(15);
方法2:读取配置文件
ComboPooledDataSource cpds = new ComboPooledDataSource("itcast");
配置文件为c3p0-config.xml 该文件需要放在类路径下
<c3p0-config>
<default-config>
<!—- 默认配置 –->
<property name="initialPoolSize">5</property>
<property name="maxPoolSize">15</property>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbc3</property>
<property name="user">root</property>
<property name="password">root</property>
</default-config>
<named-config name="xwh">
<property name="initialPoolSize">5</property>
<property name="maxPoolSize">15</property>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbc3</property>
<property name="user">root</property>
<property name="password">root</property>
</named-config>
</c3p0-config>
三、ResultSetMetaData对象
元数据,可以理解为描述数据的数据
jdbc中的元数据是指数据库、表、列的定义信息
ResultSetMetaData对象表示结果集 ResultSet对象的元数据
获得该对象:
ResultSetMetaData metaData = rs.getMetaData();
常用方法:
getColumnCount() 返回resultset对象的列数
getColumnName(int column) 获得指定列的名称
getColumnTypeName(int column) 获得指定列的类型
四、jdbc优化
使用jdbc对数据库进行crud操作时,会有很多重复的代码,仔细分析不难发现其实变化的只是其中几行代码
对于 cud(增删改) 操作,代码几乎完全一样, 唯一的区别就是sql语句不同,我们完全可以把相同的代码抽取出来定义在一个工具方法中,然后定义一个参数来接收sql语句
对于 r(查询) 操作,除SQL语句不同之外,根据操作的实体不同,对ResultSet结果集的处理也有所不相同,因此可义一个query方法,除以参数形式接收变化的SQL语句外,可以使用策略模式由qurey方法的调用者决定如何把ResultSet中的数据映射到实体对象中
优化后的工具类 JdbcUtils
// 通用的增删改方法
public static int update(String sql, Object[] params) throws SQLException {
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
// 获得连接
conn = getConnection();
// 预编译sql
pstmt = conn.prepareStatement(sql);
// 将参数设置进去
for(int i=0; params!=null&&i<params.length; i++) {
pstmt.setObject(i+1, params[i]);
}
// 发送sql
int num = pstmt.executeUpdate();
return num;
} finally {
// 释放资源
release(conn, pstmt, rs);
}
}
// 优化查询
public static Object query(String sql, Object[] params, ResultSetHandler rsh) throws SQLException {
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
// 获得连接
conn = getConnection();
// 预编译sql
pstmt = conn.prepareStatement(sql);
// 将参数设置进去
for(int i=0; params!=null&&i<params.length; i++) {
pstmt.setObject(i+1, params[i]);
}
// 发送sql
rs = pstmt.executeQuery();
// 不知道别人想如何处理结果集
// 干脆想别人所要一个结果集的处理器
// 为了让当前代码继续,定义一个结果集处理器接口
// 策略模式, 规定算法,具体的算法留给将来的调用者实现
Object obj = rsh.handle(rs);
return obj;
} finally {
// 释放资源
release(conn, pstmt, rs);
}
}
结果集处理器接口
public interface ResultSetHandler {
// 处理结果集的方法
public Object handle(ResultSet rs);
}
实现类:
BeanListHandler
public class BeanListHandler implements ResultSetHandler {
private Class clazz;
public BeanListHandler(Class clazz) {
this.clazz = clazz;
}
public Object handle(ResultSet rs) {
try {
// 取出结果集所有的记录,封装到bean,存入list返回
List list = new ArrayList();
while (rs.next()) {
Object bean = clazz.newInstance();
// 获得元数据
ResultSetMetaData metaData = rs.getMetaData();
// 获得列的数量
int count = metaData.getColumnCount();
// 遍历列
for(int i=1; i<=count; i++) {
// 取列名
String columnName = metaData.getColumnName(i);
// 取这列的值
Object value = rs.getObject(columnName);
// 反射出属性
Field field = clazz.getDeclaredField(columnName);
// 设置属性
field.setAccessible(true);
field.set(bean, value);
}
// 加入list
list.add(bean);
}
return list;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
BeanHandler
public class BeanHandler implements ResultSetHandler {
private Class clazz;
public BeanHandler(Class clazz) {
this.clazz = clazz;
}
public Object handle(ResultSet rs) {
// 不知道有几列数据,不知道列名,不知道封装到什么样的bean
// 表的列明和javabean的字段名一致
try {
if(rs.next()) {
// 创建bean
Object bean = clazz.newInstance();
// 封装数据
// 获得结果集的元数据
ResultSetMetaData metaData = rs.getMetaData();
int count = metaData.getColumnCount();
// 迭代取每一列的数据
for(int i=1; i<=count; i++) {
// 获得列名 username
String columnName = metaData.getColumnName(i);
// 获得数据 ddd
Object value = rs.getObject(columnName);
// 根据列名反射出映射的属性 username
Field field = clazz.getDeclaredField(columnName);
// 为属性赋值
field.setAccessible(true);
field.set(bean, value);
}
return bean;
}
return null;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
ArrayHandler
// 取出第一行的所有记录存入一个Object数组
public class ArrayHandler implements ResultSetHandler {
public Object handle(ResultSet rs) {
try {
if (rs.next()) {
// 指向了第一行的记录
// 获得元数据
ResultSetMetaData metaData = rs.getMetaData();
// 获得列数
int count = metaData.getColumnCount();
// 创建数组
Object[] arr = new Object[count];
// 迭代所有列的值,存入数组
for(int i=1; i<=count; i++) {
Object value = rs.getObject(i); // 获得指定列的值
arr[i-1] = value;
}
return arr;
}
return null;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}