数据库 -- 由数据库连接池引出的三种设计模式
笔记摘要:
这里首先对数据库连接池的优化进行了说明,同时自己编写了一个数据库连接池,在实际开发中,为了获取标准的数据源,我们需要去实现javax.sal.DataSource接口,
在实现过程中对于链接对象的close方法进行了不同的实现,以便在关闭close的时候,将连接对象放回连接池,而不是关闭掉,针对这一问题,提供了3种不同的解决
方案,涉及了3种设计模式:装饰,适配器和代理。
一、直接获取连接与使用连接池的比较
应用程序直接获取连接示意图
缺点:
用户每次请求都需要向数据库获得链接,而数据库创建连接通常需要消耗相对较大的资源,创建时间也较长。假设网站一天10万访问量,数据库服务器就需要创建10万次连接,极大的浪费数据库的资源,并且极易造成数据库服务器内存溢出、拓机。
数据库连接池示意图
优势:
连接池中会有指定个数的连接对象,每次连接的时候,只要将请求发给连接池,连接池就会提供连接对象,使用完之后,再将连接对象放回连接池,
这样不用每次都去连接,大大提高了性能。
二、编写一个基本的连接池实现连接复用
原理:
通过一个LinkedList来模拟连接池,每次取Connection的时候,就remove,当释放的时候就再add进去,这里通过打印remove的前后,来说明每次使用之后会放回“连接池”
dbinfo.properties
className=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mydb
user=root
password=root
自定义连接池
import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.util.Collections; import java.util.LinkedList; import java.util.ResourceBundle; import javax.management.RuntimeErrorException; public class SimpleConnectionPool { private static String className; private static String url; private static String user; private static String password; //创建一个集合用于模拟连接池 private static LinkedList<Connection> pool = new LinkedList<Connection>(); // private static LinkedList<Connection> pool = (LinkedList<Connection>) Collections.synchronizedCollection(new LinkedList<Connection>()); //加载配置文件并注册驱动 static{ try { ResourceBundle bundle = ResourceBundle.getBundle("cn.itmonkey.util.dbinfo"); className = bundle.getString("className"); url = bundle.getString("url"); user = bundle.getString("user"); password = bundle.getString("password"); Class.forName(className); //创建10个连接对象 for(int i=0;i<10;i++){ Connection conn = DriverManager.getConnection(url,user,password); pool.add(conn); } System.out.println("初始化连接"); //打印所创建的连接对象 int i=0; for(Connection conn: pool){ System.out.println(conn+"..."+i++); } } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } } //获取连接对象,由于可能同时会有多个对象来取,所以使用同步 public synchronized static Connection getConnection(){ System.out.println("取之前的连接如下"); int i=0; for(Connection conn: pool){ System.out.println(conn+"..."+i++); } //从队列中移出一个连接对象,返回给调用者 if(pool.size()>0){ //防止为0的时候,导致异常,所以要进行判断 Connection conn = pool.remove(); return conn; } else{ throw new RuntimeException("对不起,服务器忙!"); } } //将连接对象释放到队列中 public static void release(Connection conn){ pool.addLast(conn); } }
二、编写标准的数据库连接池
标准的数据源:
需要实现javax.sal.DataSource接口的类,标准的数据源里默认了维护了一个连接池
问题:
在直接获取标准数据源的Connection后,在调用它的close方法时,不是还回连接池中,而是直接关闭,我们希望在调用close方法的时候,是将Connection还回连接池中,而不是关闭
解决方案一:装饰设计模式
通过装饰,对需要的已有功能进行增强
装饰设计模式编写步骤
1、编写一个类,实现与被增强对象相同的接口
2、定义一个引用变量,记住被增强对象
3、定义构造方法,参数为接口后者父类,比便实现多态,传入被增强对象, 并给第2部的变量赋值
4、对于要增强的方法,自己改写
5、对于不需要增强的方法,调用原有对象的对应方法。
public class MyConnection implements Connection{ private Connection conn; private LinkedList<Connection> pool;//模拟连接池 public MyConnection(Connection conn,LinkedList<Connection> pool){ this.conn = conn; this.pool = pool; } //释放连接对象到连接池 @Override public void close() throws SQLException { pool.add(conn); } @Override public void clearWarnings() throws SQLException { conn.clearWarnings(); } @Override public void commit() throws SQLException { conn.commit(); } //不需要增强的方法,调用原有对象的对应方法即可, @Override public Array createArrayOf(String typeName, Object[] elements) throws SQLException { return conn.createArrayOf(typeName, elements); } //后面有很多方法,因为不需要增强的方法,调用原有对象的对应方法即可,这里略去 ………… }
解决方案二:适配器模式
通过一个类去继承一个接口或父类,对需要增强的方法进行改写即可,适配器模式在监听机制中出现比较多,由于父类中有很多的抽象方法,如果一一实现,比较麻烦,所以通常在API中会提供一个已经默认实现的类,我们只需去继承这个类,然后对希望增强的方法进行复写即可
适配器模式编写步骤
1.编写一个类,继承默认适配器
2.定义一个引用变量,记住被增强对象
3.定义构造方法,传入被增强的对象,并给第2部的变量赋值
4.对于要增强的方法,自己改写
为Connection准备的适配器
适配器本身也是一个包装类,实现或者继承一个类,但是什么都不做,所有的方法都调用原有对象的对应方法
public class ConnectionWrapper implements Connection{ protected Connection conn; public ConnectionWrapper(Connection conn){ this.conn = conn; } @Override public void clearWarnings() throws SQLException { conn.clearWarnings(); } @Override public void close() throws SQLException { conn.close(); } @Override public void commit() throws SQLException { conn.commit(); } @Override public Array createArrayOf(String typeName, Object[] elements) throws SQLException { return null; } ………… }
继承适配器,对需要增强的方法复写即可
public class MyConnection2 extends ConnectionWrapper { private LinkedList<Connection> pool; public MyConnection2(Connection conn, LinkedList<Connection> pool) { super(conn); this.pool = pool; } //将连接对象还回池中 public void close() throws SQLException { pool.add(conn); } }
解决方案三:动态代理
使用动态代理,在获取Connection的方法中使用代理,所以在获取Connection的时候,就是一个代理的Connection,该代理的Connection对象,会对调用方法进行判断,如果是close方法,就对返回值进行改写(这里是将Connection对象放回连接池中),否则就按照原来方法去执行
关于更多动态代理的知识,笔者在Java基础里面有详细地提到,其机制,实现等,想深刻了解的亲们可以查看:http://www.cnblogs.com/xushuai123/archive/2012/12/02/2978070.html
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.DriverManager; import java.sql.SQLException; import java.util.LinkedList; import java.util.ResourceBundle; import javax.sql.DataSource; //标准的数据源 public class ProxyDataSource implements DataSource{ private static String className;// 驱动类名 private static String url;// 连接串 private static String user; private static String password; private static LinkedList<Connection> pool = new LinkedList<Connection>(); static { try { ResourceBundle rb = ResourceBundle.getBundle("cn.itxushuai.util.dbinfo"); className = rb.getString("className"); url = rb.getString("url"); user = rb.getString("user"); password = rb.getString("password"); Class.forName(className); // 初始化10个连接 for (int i = 0; i < 10; i++) { Connection conn = DriverManager.getConnection(url, user, password); pool.add(conn); } } catch (Exception e) { throw new ExceptionInInitializerError("驱动加载失败"); } } @Override public synchronized Connection getConnection() throws SQLException { if(pool.size()>0){ System.out.println("池中的连接如下"); int i=1; for(Connection conn : pool){ System.out.println(conn+"----"+i++); } final Connection conn = pool.remove(); Connection proxyConn = (Connection)Proxy.newProxyInstance(conn.getClass().getClassLoader(), conn.getClass().getInterfaces(), new InvocationHandler(){ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if("close".equals(method.getName())){ return pool.add(conn); }else{ return method.invoke(conn, args); } }} ); return proxyConn; }else{ throw new RuntimeException("服务器忙"); } } @Override public Connection getConnection(String username, String password) throws SQLException { // TODO Auto-generated method stub return null; } @Override public PrintWriter getLogWriter() throws SQLException { // TODO Auto-generated method stub return null; } @Override public int getLoginTimeout() throws SQLException { // TODO Auto-generated method stub return 0; } @Override public void setLogWriter(PrintWriter out) throws SQLException { // TODO Auto-generated method stub } @Override public void setLoginTimeout(int seconds) throws SQLException { // TODO Auto-generated method stub } @Override public boolean isWrapperFor(Class<?> iface) throws SQLException { // TODO Auto-generated method stub return false; } @Override public <T> T unwrap(Class<T> iface) throws SQLException { // TODO Auto-generated method stub return null; } }