一、为什么使用数据库连接池
用户每次请求都需要向数据库获得链接,而数据库创建连接通常需要消耗相对较大的资源,创建时间也较长。假设网站一天10万访问量,数据库服务器就需要创建10万次连接,极大的浪费数据库的资源。所以我们需要使用连接池技术来进行改进。
二、数据库连接池的特点
数据库连接池与普通的数据库连接的区别就是:普通的数据库在连接数据库是及时的重新创建一个连接,而数据库连接池是当一个服务器启动时,他就在启动的时候,创建了一些连接 (最小连接数) 保持与数据库的连接,当有用户访问服务器时,不是及时的重新创建一个新的连接,而是首先看我们创建的数据库连接池中是否还有连接,如果有的话,那么就直接那到这个连接来使用,当使用完之后,不是把这个连接关掉,而是把使用的连接,再放回到数据库连接池中。如果发现数据库连接池中没有连接了,那么才会申请去创建一个新的连接。当然我们不可能无止尽的创建连接,我们设置一个最大的连接数来控制,当达到最大连接数的时候,我们就必须等待直到有连接被返回到数据库连接池中。
数据库最大连接数与最小连接数的设置要考虑的因素:
- 最小连接数:是连接池一直保持的数据库连接,所以如果应用程序对数据库连接的使用量不大,将会有大量的数据库连接资源被浪费.
- 最大连接数:是连接池能申请的最大连接数,如果数据库连接请求超过次数,后面的数据库连接请求将被加入到等待队列中,这会影响以后的数据库操作
- 如果最小连接数与最大连接数相差很大:那么最先连接请求将会获利,之后超过最小连接数量的连接请求等价于建立一个新的数据库连接.不过,这些大于最小连接数的数据库连接在使用完不会马上被释放,他将被放到连接池中等待重复使用或是空间超时后被释放.
三、编写数据库连接池:
1、编写MyDataSource
package datasource;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.LinkedList;
public class MyDataSource {
private static String url="jdbc:mysql://localhost:3306/students?useSSL=false";//jdbc:mysql://localhost:3306/students?useSSL=false
private static String user="root";
private static String password="158283";
private static int currentCounts=0;//当前的连接数
private static int minCounts=5;
private static int maxCounts=10;
//使用LinkedList的原因,由于要频繁的读写操作,所以这里使用LinkedList来操作,提高效率
private static LinkedList<Connection> connectionsPool=new LinkedList<Connection>();
public MyDataSource(){//构造函数中来向池中添加连接
try{
for(int i=0;i<minCounts;i++){
currentCounts++;//创建一个就要自增当前连接数
this.connectionsPool.addLast(this.creatConnection());
}
}catch(SQLException e){
e.printStackTrace();
}
}
public Connection getConnection() throws SQLException {
//加锁为了防止获取连接的时候发生异常 但是这也会减低效率
synchronized (this.getClass()) {
if(connectionsPool.size()>0)
return connectionsPool.removeFirst();
if(currentCounts<maxCounts) {
currentCounts++;
return this.creatConnection();
}
throw new SQLException("没有链接了");
}
}
public Connection creatConnection() throws SQLException {
return DriverManager.getConnection(url,user,password);
}
public void free(Connection conn){
connectionsPool.addLast(conn);//放回到连接池中
currentCounts--;//当前连接数减一
}
}
/*
使用这种模式,是用在网络上服务器开启的时候加载n个链接,在使用jdbc工具中获取链接的时候,使用这个类来创建数据库的链接,
这个类的设计思想主要是利用linkedlist<Connection>链表的结构来存取这些已经创建好的链接,并采用先进先出的方法来存取,
当预先创建的链接已经用完的时候,根据这个数据库同时连接的最大用户量来creatconnection()新的链接,当创建的链接数大于数据库
的最大连接数时,抛出异常,用这种模式,就不需要释放链接资源,而是把使用removerfrist()方法的获取链接资源通过add Last()方法
再重新放进来,记住在获取链接的时候要加上并发控制来控制异常的发生。其实就是吧创建资源的任务从jdbc中接过来。
*/
2、编写JdbcUtils
package JDBCUP;
import datasource.MyDataSource;
import java.sql.*;
public final class JDBCTools {
private JDBCTools(){}
private static String url="jdbc:mysql://localhost:3306/students?useSSL=false";//jdbc:mysql://localhost:3306/students?useSSL=false
private static String user="root";
private static String password="158283";
private static MyDataSource mds=null;
static {
try {
Class.forName("com.mysql.jdbc.Driver");
mds=new MyDataSource();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static Connection getConn(){
try {
return mds.getConnection();//这里就不是直接创建数据库连接,而是去数据库连接池中获取链接欸
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static void free(ResultSet rs, PreparedStatement ps,Connection conn){
try {
if(rs!=null)
rs.close();
}
catch (SQLException e) {
e.printStackTrace();
}
finally {
try{
if(ps!=null)
ps.close();
}
catch (SQLException e){
e.printStackTrace();
}
finally {
try {
if(conn!=null)
mds.free(conn);//注意这里就不能关闭数据库连接了,而是应该把连接返回到连接池中
}
catch (Exception s){
s.printStackTrace();
}
}
}
}
}
3、测试:
package JDBCUP;
import java.sql.Connection;
public class Base {
public static void main(String[] args) {
for(int i=0;i<10;i++){
Connection conn=JDBCTools.getConn();
System.out.println(conn);
JDBCTools.free(null,null,conn);
}
}
}
测试结果。
在这个数据库连接池中,有一个地方让我们容易犯错误,没错就是我们关闭数据库连接的时候,这里是调用MyDataSource中的free()来进行连接的放回,但是我们可能习惯性的使用close()方法,这样就关闭了数据库连接,所以我们需要使用动态代理的方法来防止这样的问题发生。
四、使用动态代理模式来实现昂数据库连接池.
在上面的代码中我们可以发现,在我们把数据库连接资源返回到池中,我们可能因为不知道规范,导致我们使用了close()的方法来关闭掉了数据库资源,而非放回到数据库连接池。所以这里我们使用动态代理的设计模式来完成。
1、编写我们的MyDataSourceProxy
package datasource;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.LinkedList;
public class MyDataSourceProxy {
private static String url="jdbc:mysql://localhost:3306/students?useSSL=false";//jdbc:mysql://localhost:3306/students?useSSL=false
private static String user="root";
private static String password="158283";
int currentCounts=0;
private static int maxCounts=10;
LinkedList<Connection> connectionsPool=new LinkedList<Connection>();//理解为什么这里的泛型是Connection。
public MyDataSourceProxy(){
try{
for(int i=0;i<5;i++){
currentCounts++;
this.connectionsPool.addLast(this.creatConnection());
}
}catch(SQLException e){
e.printStackTrace();
}
}
public Connection getConnection() throws SQLException {
synchronized (this.getClass()) {
if(connectionsPool.size()>0)
return connectionsPool.removeFirst();
if(currentCounts<maxCounts) {
currentCounts++;
return this.creatConnection();
}
throw new SQLException("没有链接了");
}
}
public Connection creatConnection() throws SQLException {//只有这里和上面的代码不同
Connection realConn=DriverManager.getConnection(url,user,password);
MyConnectionHandler myConnectionHandler=new MyConnectionHandler(this);
return myConnectionHandler.bind(realConn);
}
}
2、编写我们的动态代理的代码
package datasource;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
public class MyConnectionHandler implements InvocationHandler {
private Connection realConn;
private Connection wrapedConnection;
private MyDataSourceProxy dataSourceProxy;
public MyConnectionHandler(MyDataSourceProxy dataSourceProxy){
this.dataSourceProxy=dataSourceProxy;
}
Connection bind(Connection realConn){
this.realConn=realConn;
this.wrapedConnection=(Connection)Proxy.newProxyInstance(this.getClass().getClassLoader(),new Class[]{Connection.class},this);
return wrapedConnection;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(method.getName().equals("close")){
dataSourceProxy.connectionsPool.addLast(wrapedConnection);
dataSourceProxy.currentCounts--;
}
return method.invoke(realConn,args);
}
}
3、JDBCUtils的改变
package JDBCUP;
import datasource.MyDataSourceProxy;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public final class JDBCTools {
private JDBCTools(){}
private static String url="jdbc:mysql://localhost:3306/students?useSSL=false";
private static String user="root";
private static String password="158283";
private static MyDataSourceProxy mds=null;
static {
try {
Class.forName("com.mysql.jdbc.Driver");
mds=new MyDataSourceProxy();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static Connection getConn(){
try {
return mds.getConnection();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static void free(ResultSet rs, PreparedStatement ps,Connection conn){
try {
if(rs!=null)
rs.close();
}
catch (SQLException e) {
e.printStackTrace();
}
finally {
try{
if(ps!=null)
ps.close();
}
catch (SQLException e){
e.printStackTrace();
}
finally {
try {
if(conn!=null)
conn.close();//这里我们就可以直接使用close()来关闭了,因为我们在调用这个方法后,会先走MyConnectionHandler中的invoke()方法,我们对close的方法进行拦截,更改close的方法
catch (Exception s){
s.printStackTrace();
}
}
}
}
}
tips:这里主要使用了JDK动态代理的设计模式,简化了我们的代码。我们如果不适用动态代理设计模式,而是是哟个静态代理设计模式的话,那么它的问题就是我们必须自己俩编写调用connection的方法,动态代理我们可以理解为,java在帮我们写了代码,而不是我们写代码,因为有很多时候,我们只需要修改其中的某几个方法。这种设计模式在Spring中的AOP特别常见。