1.为什么需要连接池
当前的jdbc程序每次访问数据库都需要创建一个新的连接,访问完毕之后,还需要释放资源。
注意:connection的创建与销毁所消耗的资源远远大于数据库执行sql消耗的资源。基于这种实际情况,当前jdbc实现方式有如下弊端:
1.一次访问,就创建一次connection,降低程序的性能
2.connection的创建与销毁所消耗的资源远远大于数据库执行sql消耗的资源
3.频繁的连接数据会导致资源消耗过多,导致数据库宕机
思考上面的结构,大部分的时间浪费在了创建和销毁connection上。那么我们能不能实现将这些connection回收利用呢?这样我们就不需要不停的对connection进行创建和销毁了。只需要创建一次,放在指定的地方。而我们使用的时候,直接从里面拿就行了。用完放回原来的地方。不去销毁,当我再次使用的时候,去拿就行了。这样的解决方案就是我们需要的。
实现原理:一次性创建多个连接,将多个连接缓存在内存中 ,形成数据库连接池,如果应用程序需要操作数据库,只需要从连接池中获取一个连接,使用后,并不需要关闭连接,只需要将连接放回到连接池中。
连接池的好处:节省创建连接与释放连接的性能消耗,连接池中连接起到复用的作用 ,提高程序性能。
2.自定义数据库连接池案例
package 自己的包名;
import JdbcUtil连接工具类;
import com.mysql.jdbc.ConnectionFeatureNotAvailableException;
import javax.sql.DataSource;
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.LinkedList;
import java.util.logging.Logger;
public class MyDatasource implements DataSource{
//创建集合对象作为数据库连接池
LinkedList<Connection> list=new LinkedList<Connection>();
//定义一个构造方法,在创建这个类的对象时给连接池中存放连接
public MyDatasource() throws SQLException {
//循环决定连接池的连接数
for (int i = 0; i < 5; i++) {
//使用jdbc原始方法进行获取连接,将其封装为JDBCUtils工具类
Connection conn =JDBCUtils.getConnection();
//添加到集合中
list.addLast(conn);
}
System.out.println("初始化连接池成功,连接池的大小是:"+list.size());
}
//从连接池获取连接
@Override
public Connection getConnection() throws SQLException {
//从连接池即集合中获取连接,那么连接也不会存在于集合中了
System.out.println("从池子中获取连接");
final Connection conn = list.removeLast();
/*Connection conn = list.removeFirst();
System.out.println("从连接池中获取连接,连接池的大小是:"+list.size());*/
//采用动态代理,将connection的close方法进行拦截,将其功能改为放回连接池,这是数据源的功能实现的核心。(关于动态代理,会有一篇博客进行介绍及简单实现)
/*
* 第一个参数:被代理对象的类加载器。
* 第二个参数:被代理对象的实现的接口。
* 第三个参数:代理类需要做的工作。
* */
Connection proxyConn= (Connection) Proxy.newProxyInstance(conn.getClass().getClassLoader(),
new Class[]{com.mysql.jdbc.Connection.class}, new InvocationHandler() {
/*
* 第一个参数:代理对象本身
* 第二个参数:被代理类的方法
* 第三个参数:arg1方法执行所需要的参数
* */
@Override
public Object invoke(Object arg0, Method arg1, Object[] arg2) throws Throwable {
if ("close".equals(arg1.getName())){
System.out.println("改写close方法");
//只改写close方法
//将conn放到连接池就行了。
list.add(conn);
System.out.println("此时池子中还有"+list.size()+"个连接");
return null;//close 方法被代理放回容器后,不应该继续被执行
}else{
//保持原有方法的功能
return arg1.invoke(conn,arg2);
}
}
});
return proxyConn;
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return null;
}
//定义方法将连接还给连接池
public void backPool(Connection conn){
list.addLast(conn);
System.out.println("将连接还给连接池,连接池的大小是:"+list.size());
}
@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;
}
}
看实现可以知道,自定义数据连接池仅能实现很少一部分功能,所以我们可以使用当前市面上开源的连接池进行开发,功能更加强大,安全性能更加高效。
3. 什么是德鲁伊Druid连接池
3.1.DRUID简介
Druid是阿里巴巴开发的号称为监控而生的数据库连接池(可以监控访问数据库的性能),Druid是目前最好的数据库连接池。在功能、性能、扩展性方面,都超过其他数据库连接池。Druid已经在阿里巴巴部署了超过600个应用,经过一年多生产环境大规模部署的严苛考验。如:一年一度的双十一活动,每年春运的抢火车票。
Druid的下载地址:https://github.com/alibaba/druid
DRUID连接池使用的jar包:druid-1.0.9.jar
3.2.DRUID常用的配置参数
参数 说明
url 数据库连接字符串 jdbc:mysql://localhost:3306/数据库名
username 数据库的用户名
password 数据库的密码
driverClassName 驱动类名。根据url自动识别,这一项可配可不配,如果不配置druid会根据url自动识别数据库的类型,然后选择相应的数据库驱动名
initialSize 初始化时建立的物理连接的个数。初始化发生在显式调用init方法,或者第一次获取连接对象时
maxActive 连接池中最大连接数
maxWait 获取连接时最长等待时间,单位是毫秒。
3.3.DRUID连接池基本使用
3.3.1.API介绍
核心类:DruidDataSourceFactory
获取数据源的方法:使用DruidDataSourceFactory类中的静态方法:
public static DataSource createDataSource(Properties properties):读取属性配置文件,创建一个数据源对象。
配置信息在properties属性对象中。
3.3.2.使用步骤
1)导入核心包druid-1.0.9.jar
2)在项目下创建一个properties文件,文件名随意,设置对应参数
3)加载properties文件的内容到Properties对象中
4)创建DRUID连接池,使用配置文件中的参数
5)从DRUID连接池中取出连接
6)执行SQL语句
7)关闭资源
3.3.3 配置文件
属性文件:在项目下新建一个druid配置文件,命名为:druid.properties
数据库连接参数
url=jdbc:mysql://localhost:3306/day05
username=root
password=123
driverClassName=com.mysql.jdbc.Driver
连接池的参数
initialSize=3
maxActive=10
maxWait=2000
4.德鲁伊Druid手动实现案例
package com.c3p0;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import com.jdbc_01.JDBCUtils;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Properties;
public class Demo_druid {
public static void main(String[] args) throws Exception {
//加载properties 文件的内容到Properties 对象中
Properties info = new Properties();
//加载项目下的属性文件
FileInputStream fis = new FileInputStream("druiddb.properties");
//从输入流中加载属性
info.load(fis);
//System.out.println(info);
//创建DRUID连接池,使用配置文件中的参数
DataSource dataSource = DruidDataSourceFactory.createDataSource(info);
//从DRUID连接池中取出连接
//Connection conn=dataSource.getConnection();
//System.out.println("conn = " + conn);
//需求:根据用户名和密码 查询用户信息
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs=null;
try{
//获得连接
conn=dataSource.getConnection();
//获得发送sql的对象
String sql="select * from user where username=? and password=?";
pstmt=conn.prepareStatement(sql);
//如果有问号,需要设置参数,注意:下标从1开始
pstmt.setString(1,"zhangsan");
pstmt.setString(2,"abcdef");
//执行sql获取结果
rs=pstmt.executeQuery();
//处理结果
if (rs.next()){
int id=rs.getInt("id");
String username=rs.getString("username");
String pwd=rs.getString("password");
System.out.println(id+":::"+username+"==="+pwd);
}else {
System.out.println("没有查到对应的用户信息!");
}
}catch (Exception e){
throw new RuntimeException(e);
}finally {
JDBCUtils.release(conn,pstmt,rs);
}
}
}
依据自身情况,对应实现。
5.封装DruidUtil工具类
package com.c3p0;
import com.alibaba.druid.pool.DruidDataSource;
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;
public class DruidJdbcUtil {
static DruidDataSource dataSource;
static {
Properties properties = new Properties();
try{
properties.load(new FileInputStream("druiddb.properties"));
dataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(properties);
}catch (Exception e){
e.printStackTrace();
}
}
//获取数据源
public static DataSource getDataSource(){
return dataSource;
}
//获取连接
public static Connection getConnection(){
try {
return dataSource.getConnection();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
//释放资源
public static void release(ResultSet rs, Statement st, Connection conn)
{
if(rs!=null)
{
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
rs=null;
}
if(st!=null)
{
try {
st.close();
} catch (SQLException e) {
e.printStackTrace();
}
st=null;
}
if(conn!=null)
{
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
conn=null;
}
}
}
小结:对于C3P0与Druid连接池的比较分析:
1.从配置文件上讲:
C3P0配置文件:c3p0-config.xml 规定命名,而且指定了路径存放,如果路径有误则配置失效。优势:可以自动读取,无需配置
Druid配置文件:druid.properties ,除了需要.properties文件类型外,对命名没有要求,对于配置文件存放位置也没有要求。可以随意放置。 不足之处:需要开发人员去指定文件路径,并进行加载,将加载内容直接丢给德鲁伊框架,框架会自动解析。
2. 从执行效率上讲:
个人认为Druid数据库连接池执行效率会略微高一些,故本文以介绍druid连接池为主,C3P0连接池实现方式类似。