一、问题
当应用在执行持久化数据到数据库的时候,会使用jdbc连接数据库,并插入数据
我们都知道jdbc在连接数据库分成如下步骤
- 建立数据库连接
- 增删改查数据
- 关闭数据库连接
如果有上万个请求同时进行持久化的操作,那就意味着开关数据库的次数达到上万次,这样会带来一些问题
1、如果持久化的时候出现异常,导致数据库没有正确的关闭连接,会造成大量的连接积压在数据库的会话列表中,出现数据库无法访问的情况。(专有名词:数据库连接泄漏)
2、假如每次插数开关操作整体耗费1秒的话,那么上万条请求同时执行,开关带来的系统负担是巨大的,而且是无意义的。
二、介绍连接池
什么是数据库连接池:听名字就知道,这是一个池子,里面装了很多连接,连接被池子进行管理。
数据库连接池的职责:添加连接,管理连接、释放连接
数据库连接池的连接数:分为最大连接数和最小连接数,旨在将数据库的连接维持在一定的范围内,应用程序可以使用已经打开的连接去访问数据库,相当于市面上投入最少五个最多十个共享单车,供一百个人使用,一个人用完后不会去解锁,而是转交给另外的人用,这就加快了使用效率。(第一个解锁的人是土豪= 。=)
三、创建连接池
编写连接池需实现 java.sql.DataSource 接口,并先初始化连接链表。
public class DataSourcetest implements DataSource{
//定义一个连接池 用linkedlist是为了增加删除插入的速度
private static List<Connection> connectionList = new LinkedList<Connection>();
static{
//获取资源文件中的数据库连接配置文件
// jdbc.driverClassName=com.mysql.jdbc.Driver
// jdbc.url=jdbc:mysql://127.0.0.1:3306/umbrelladatabase?useUnicode=true&characterEncoding=UTF-8
// jdbc.host=127.0.0.1
// jdbc.username=root
// jdbc.password=root
InputStream is = DataSourcetest.class.getClassLoader().getResourceAsStream("load.properties");
Properties p = new Properties();
try{
p.load(is);
//获取配置文件中的属性
String driverClassName = p.getProperty("jdbc.driverClassName");
String url = p.getProperty("jdbc.url");
String host = p.getProperty("jdbc.host");
String username = p.getProperty("jdbc.username");
String password = p.getProperty("jdbc.password");
String poolSize = p.getProperty("jdbc.poolSize");
int jdbcPoolSize = Integer.parseInt(poolSize);
//获取driver的名称
Class.forName(driverClassName);
//根据连接池大小,初始化连接并加入链表中
for(int i=0;i<jdbcPoolSize;i++){
Connection conn = DriverManager.getConnection(url, username, password);
connectionList.add(conn);
}
}catch(Exception e){
e.printStackTrace();
for(int i=0;i<connectionList.size();i++){
Connection conn = connectionList.get(i);
try {
conn.close();
} catch (SQLException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
}.........}
之后需要重写getConnection方法:
@Override
public Connection getConnection() throws SQLException {
if(connectionList.size()>0){
Connection conn = connectionList.get(0);connectionList.remove(0);
return (Connection) Proxy.newProxyInstance(DataSourcetest.class.getClassLoader(), conn.getClass().getInterfaces(), new InvocationHandler(){
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if(!method.getName().equals("close")){
return method.invoke(conn, args);
}else{
//如果调用的是Connection对象的close方法,就把conn还给数据库连接池
connectionList.add(conn);
System.out.println(conn + "被还给listConnections数据库连接池了!!");
System.out.println("connectionList数据库连接池大小为" + connectionList.size());
return null;
}
}
});
}
return null;
}
这里额外说下Proxy.newProxyInstance 代理类的创建:
Proxy的模式最主要的目的,原有的类对象由于某种原因不能访问,需要通过一个新的类来间接地去实现,这个新的类就称为代理类
Proxy类的newInstance()方法有三个参数:
- ClassLoader loader:它是类加载器类型,你不用去理睬它,你只需要知道怎么可以获得它就可以了:DataSourcetest.class.getClassLoader()就可以获取到ClassLoader对象
- Class[] interfaces:指定newProxyInstance()方法返回的对象要实现哪些接口,没错,可以指定多个接口,例如上面例子只我们只指定了一个接口:Class[] cs = {Connection .class};
- InvocationHandler h:它的名字叫调用处理器,其实无论你调用代理对象的什么方法,它都是在调用InvocationHandler的invoke()方法!
- Object proxy:代理对象,也就是Proxy.newProxyInstance()方法返回的对象,通常我们用不上它;
- Method method:表示当前被调用方法的反射对象,例如method.getName().equals("close"),那么method就是close()方法的反射对象;
- Object[] args:表示当前被调用方法的参数,当然close()这个方法调用是没有参数的,所以args是一个零长数组。
四、开源连接池
DBCP 数据库连接池
C3P0 数据库连接池
Druid连接池
HikariCP连接池
BoneCP连接池
简单介绍一下Druid:
Druid是一个JDBC组件,它包括三部分:
- DruidDriver 代理Driver,能够提供基于Filter-Chain模式的插件体系。
- DruidDataSource 高效可管理的数据库连接池。
- SQLParser
1) 可以监控数据库访问性能,Druid内置提供了一个功能强大的StatFilter插件,能够详细统计SQL的执行性能,这对于线上分析数据库访问性能有帮助。
2) 替换DBCP和C3P0。Druid提供了一个高效、功能强大、可扩展性好的数据库连接池。
3) 数据库密码加密。直接把数据库密码写在配置文件中,这是不好的行为,容易导致安全问题。DruidDruiver和DruidDataSource都支持PasswordCallback。
4) SQL执行日志,Druid提供了不同的LogFilter,能够支持Common-Logging、Log4j和JdkLog,你可以按需要选择相应的LogFilter,监控你应用的数据库访问情况。扩展JDBC,如果你要对JDBC层有编程的需求,可以通过Druid提供的Filter-Chain机制,很方便编写JDBC层的扩展插件。