zoukankan      html  css  js  c++  java
  • 数据库 -- 由数据库连接池引出的三种设计模式

    笔记摘要:

         这里首先对数据库连接池的优化进行了说明,同时自己编写了一个数据库连接池,在实际开发中,为了获取标准的数据源,我们需要去实现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;
        }
    
    }
    复制代码
  • 相关阅读:
    CF960G-Bandit Blues【第一类斯特林数,分治,NTT】
    P6122-[NEERC2016]Mole Tunnels【模拟费用流】
    P5404-[CTS2019]重复【KMP,dp】
    P5405-[CTS2019]氪金手游【树形dp,容斥,数学期望】
    T183637-变异距离(2021 CoE III C)【单调栈】
    61-A
    2021-4-1考试
    JAVA日常练习—程序输入string转化为int并求和
    并发编程
    git clone 报filename too long 错误的解决方法
  • 原文地址:https://www.cnblogs.com/daichangya/p/12959266.html
Copyright © 2011-2022 走看看