MySQL---数据库从入门走向大神系列(十)-Connection对象池、装饰模式与动态代理模式

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS PostgreSQL,集群系列 2核4GB
简介: 问题概述:之前本系列博客写的,全部都是一个connection对象,不知道大家发现没有,我们既然做了一个Connection工具类,那么大家肯定都是从那里面拿Connection对象的,之前的如果是多线程运行,很容易出问题的,你想想事务处理就知道了,同时用事务处理操作同一个Connection,肯定会出问题的。

问题概述:

之前本系列博客写的,全部都是一个connection对象,不知道大家发现没有,我们既然做了一个Connection工具类,那么大家肯定都是从那里面拿Connection对象的,之前的如果是多线程运行,很容易出问题的,你想想事务处理就知道了,同时用事务处理操作同一个Connection,肯定会出问题的。

例如:
一方的事务在提交的时候,你正好运行了一个事务中的一个操作,那么你这个操作也会被提交,而且你后面的提交或回滚失效的,如果对方把Connection关闭了,你的程序还会挂。
等等问题…

那么,我们就需要一个Connection对象池,谁来了,就去池中拿对象,不会再是同一个了,而且可以很好的控制池中Connection对象的数目,不可能一直new Connection对象的,如果池中的对象用完了,我们就让那个客户端等,等别人用完Connection对象还回到池中。

配置文件:jdbc.propertise

##MySQL
driver=com.mysql.jdbc.Driver
url=jdbc:mysql:///hncu?useUnicode=true&characterEncoding=utf-8
username=root
password=1234

##Oracle
#driver=oracle.jdbc.driver.OracleDriver
#url=jdbc:oracle:thin:@localhost:1521:orcl
#username=scott
#password=tiger

ConnsUtil:Connection池

package cn.hncu.pool;

import java.sql.Connection;
import java.sql.DriverManager;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

public class ConnsUtil {

    //声明一个池
    private static List<Connection> pool = new ArrayList<Connection>();
    //声明池中的Connection对象个数
    private static final int NUM = 3;
    static{
        try {
            //读取配置文件
            Properties p = new Properties();
            p.load(ConnsUtil.class.getClassLoader().getResourceAsStream("jdbc.properties"));
            String driver = p.getProperty("driver");
            String url = p.getProperty("url");
            String user = p.getProperty("username");
            String password = p.getProperty("password");

            Class.forName(driver);
            for(int i=0;i<NUM;i++){
                Connection con = DriverManager.getConnection(url, user, password);
                //加入池中
                pool.add(con);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static synchronized Connection getConnection() throws InterruptedException{
        if(pool.size()<=0){
            Thread.sleep(100);
            return getConnection();
        }
        return pool.remove(0);
    }

    public static void back(Connection con){
        System.out.println("还回来一个Connection连接...");
        pool.add(con);
    }


}

测试类:

package cn.hncu.pool;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;

public class TestPool {

    public static void main(String[] args) {
        Connection con = null;
        try{
            con = ConnsUtil.getConnection();
            con.setAutoCommit(false);

            Statement st = con.createStatement();
            String sql ="insert into stud values('P201','关羽',30) ";
            st.execute(sql);
            sql ="insert into stud values('P202','张飞',25) ";
            st.execute(sql);
            new OneThread(1).start();
            new OneThread(2).start();
            new OneThread(3).start();
            new OneThread(4).start();
            new OneThread(5).start();

            System.out.println("主线程准备提交...");
            con.commit();
            System.out.println("主线程提交完毕...");
        }catch (Exception e) {
            try {
                con.rollback();
                System.out.println("主线程回滚了...");
            } catch (SQLException e1) {
                throw new RuntimeException("主线程事务回滚失败!", e1);
            }
        }finally{
            try {
                if(con!=null){
                    con.setAutoCommit(true);
                    ConnsUtil.back(con);
                    //con.close();//如果要把close内部的功能换成还连接,就需要我们以后的技术来实现
                }
            } catch (SQLException e) {
                throw new RuntimeException("主线程连接关闭失败!", e);
            }
        }

    }
}

class OneThread extends Thread{
    private int n;
    public OneThread(int n) {
        this.n = n;
    }

    @Override
    public void run() {
        Connection con = null;
        try{
            con = ConnsUtil.getConnection();
            con.setAutoCommit(false);

            Statement st = con.createStatement();
            String sql ="insert into stud values('P30"+n+"','刘备',30) ";
            st.execute(sql);
            sql ="insert into stud values('P31"+n+"','曹操',25) ";
            st.execute(sql);
            System.out.println("第"+n+"个线程准备提交...");
            con.commit();
            System.out.println("第"+n+"个线程提交完毕...");
        }catch (Exception e) {
            try {
                con.rollback();
                System.out.println("第"+n+"个线程回滚了...");
            } catch (SQLException e1) {
                throw new RuntimeException("第"+n+"事务回滚失败!", e1);
            }
        }finally{
            try {
                if(con!=null){
                    con.setAutoCommit(true);
                    //con.close();//这里就是我们需要改进的地方
                    ConnsUtil.back(con);
                }
            } catch (SQLException e) {
                throw new RuntimeException("第"+n+"连接关闭失败!", e);
            }
        }
    }

}

在调用返回Connection对象回到池中的时候,用普通的调用,不可避免的要去调用自己写的一个返回方法,如何在不改变程序员变成习惯(一般都习惯用close()去关闭那个对象)的情况下去实现Connection对象的回收呢,那就需要用到装饰模式,或者代理模式了。

装饰模式:

装饰模式又名包装(Wrapper)模式。装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案。

装饰模式以对客户透明的方式动态地给一个对象附加上更多的责任。换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同。装饰模式可以在不使用创造更多子类的情况下,将对象的功能加以扩展。

其实我们要做的就是写一个MyConnection类实现Connection接口,实现所有方法,再进行扩展!

public class ConnsUtil {

    private static List<Connection> pool = new ArrayList<Connection>();
    private static final int NUM=3;
    static{
        try {
            Properties p = new Properties();
            p.load(ConnsUtil.class.getClassLoader().getResourceAsStream("jdbc.properties"));
            String drive = p.getProperty("driver");
            String url = p.getProperty("url");
            String user = p.getProperty("username");
            String password = p.getProperty("password");

            Class.forName(drive);

            for(int i=0;i<NUM;i++){
                Connection conn = DriverManager.getConnection(url, user, password);


            }


        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public static synchronized Connection getConnection() throws InterruptedException{
        if(pool.size()<=0){
            Thread.sleep(50);
            return getConnection();
        }
        return pool.remove(0);
    }

    static class MyConnection implements Connection{
        //封装一个Connection
        private Connection con;
        public MyConnection(Connection con){
            this.con=con;
        }
        @Override
        public <T> T unwrap(Class<T> iface) throws SQLException {
            return con.unwrap(iface);
        }
        @Override
        public void rollback() throws SQLException {
            con.rollback();
        }
        @Override
        public Statement createStatement() throws SQLException {
            return con.createStatement();
        }
        @Override
        public void setAutoCommit(boolean autoCommit) throws SQLException {
            con.setAutoCommit(autoCommit);
        }
        @Override
        public void commit() throws SQLException {
            con.commit();
        }
        ......
        //...需要把接口的所有方法都实现了,按照上面的样式去写就行,如果不用增加功能或修改的,直接返回con.方法(参数);就可以了。如果需要修改,就自己写。比如:这里我们需要修改close();方法.
        @Override
        public void close() throws SQLException {
            pool.add(this);
            //直接把自己这个对象加进去
        }
    }
}

测试类:

package cn.hncu.pool2;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;

public class Testpool {

    public static void main(String[] args) {
        Connection con = null;
        try{
            con = ConnsUtil.getConnection();
            con.setAutoCommit(false);

            Statement st = con.createStatement();
            String sql ="insert into stud values('P203','关羽',30) ";
            st.execute(sql);
            sql ="insert into stud values('P204','张飞',25) ";
            st.execute(sql);
            new OneThread(1).start();
            new OneThread(2).start();
            new OneThread(3).start();
            new OneThread(4).start();
            new OneThread(5).start();

            System.out.println("主线程准备提交...");
            con.commit();
            System.out.println("主线程提交完毕...");
        }catch (Exception e) {
            try {
                con.rollback();
                System.out.println("主线程回滚了...");
            } catch (SQLException e1) {
                throw new RuntimeException("主线程事务回滚失败!", e1);
            }
        }finally{
            try {
                if(con!=null){
                    con.setAutoCommit(true);
                    con.close();//这样,我们直接调用close方法就可以了!!!
                }
            } catch (SQLException e) {
                throw new RuntimeException("主线程连接关闭失败!", e);
            }
        }

    }
}

class OneThread extends Thread{
    private int n;
    public OneThread(int n) {
        this.n = n;
    }

    @Override
    public void run() {
        Connection con = null;
        try{
            con = ConnsUtil.getConnection();
            con.setAutoCommit(false);

            Statement st = con.createStatement();
            String sql ="insert into stud values('P40"+n+"','刘备"+n+"',30) ";
            st.execute(sql);
            sql ="insert into stud values('P41"+n+"','曹操"+n+"',25) ";
            st.execute(sql);
            System.out.println("第"+n+"个线程准备提交...");
            con.commit();
            System.out.println("第"+n+"个线程提交完毕...");
        }catch (Exception e) {
            try {
                con.rollback();
                System.out.println("第"+n+"个线程回滚了...");
            } catch (SQLException e1) {
                throw new RuntimeException("第"+n+"事务回滚失败!", e1);
            }
        }finally{
            try {
                if(con!=null){
                    con.setAutoCommit(true);
                    con.close();//这样,我们直接调用close方法就可以了!!!
                }
            } catch (SQLException e) {
                throw new RuntimeException("第"+n+"连接关闭失败!", e);
            }
        }
    }

}

测试结果:

代理模式:

代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问。

好处就是:
在某些情况下,如果不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

可以为原来的方法增加或者修改动作!

代理模式一般需要3个角色:

抽象角色:声明真实对象和代理对象的共同接口(必须是接口;

代理角色:代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装与包装。

真实角色(被代理的角色):代理角色所代表的真实对象,是我们最终要引用的对象。

代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。 代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后 续处理。

接口:

package cn.hncu.proxy.demo1;

public interface IRenter {
    public void rent(int i);
    public String say();
}

实际对象:

package cn.hncu.proxy.demo1;

public class Renter implements IRenter{
    @Override
    public void rent(int i) {
        System.out.println("给你"+i+"个房间,需要交500元!");
    }

    public String say() {
        System.out.println("Renter:你好,我是房东,房子实际只要300元");
        return "房东的返回结果...";
    }
}

代理模式的核心部分:

package cn.hncu.proxy.demo1;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;


public class Client {

    public static void main(String[] args) {
        //被代理的对象
        final IRenter r = new Renter();

        //这个obj是代理后的对象
        Object obj = Proxy.newProxyInstance(Client.class.getClassLoader(),
                new Class[]{IRenter.class},
                new InvocationHandler() {
                    @Override
                    //proxy是代理后的对象(等价于返回的obj),method就是类反射中的方法对象, args是执行method方法所需的参数
                    public Object invoke(Object proxy, Method method, Object[] args)
                            throws Throwable {
                        System.out.println("我是中介...");
                        //在这里,我们把房东说的话和返回值给拦截了。
                        if(method.getName().equals("say")){
                            System.out.println("我是中介,你还没交中介费,无法联系房东...");
                            return "";
                        }
                        Object obj = method.invoke(r, args);
                        System.out.println("欢迎再来找我中介...");
                        //r就是原来的对象
                        return obj;
                        //这个返回结果是调用r中的方法method.invoke(r, args);返回的结果
                    }
                });
        IRenter ir = (IRenter) obj;
        ir.rent(3);
        System.out.println(ir.say());
    }
}

可以看出来InvocationHandler这个接口中的invoke方法是最核心的部分!!!
在里面我们利用类加载器和类反射,直接可以来调用原来的类,而且可以加上自己需要的功能,或者实现拦截!!!

上面的例子只能实现对Renter 类的代理。

通用的代理模板类:

public interface IAnimal {
   public void run();
}

public class Dog implements IAnimal{
    private String name;
    public Dog(String name) {
        this.name = name;
    }
    @Override
    public void run() {
        System.out.println("Doge:"+name+"is 

running...");
    }
}
public interface IPerson {
   public void sayHi();
}

public class Person implements IPerson {
    private String name;
    public Person(String name){
        this.name = name;
    }
    @Override
    public void sayHi() {
        System.out.println(name+"说:祝你快乐...");
    }

}

ProxyUtil.java

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ProxyUtil implements InvocationHandler{
   private Object srcObj=null;

    public ProxyUtil(Object srcObj) {
        this.srcObj=srcObj;
    }

    public static Object getProxy(Object srcObj){
        Object obj = Proxy.newProxyInstance(
                ProxyUtil.class.getClassLoader(),
                srcObj.getClass().getInterfaces(),
                new ProxyUtil(srcObj)); 

        //obj是代理后的返回结果其实就是method.invoke(srcObj, args)的返回结果
        return obj;
    }

    //这个就是代理的方法了
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        System.out.println("进入了代理区域~~");

        //这个srcObj就是原来的对象
        return method.invoke(srcObj, args);
    }
}

client

package cn.hncu.proxy.demo2;

import cn.hncu.proxy.demo2.domain.Dog;
import cn.hncu.proxy.demo2.domain.IAnimal;
import cn.hncu.proxy.demo2.domain.IPerson;
import cn.hncu.proxy.demo2.domain.Person;

public class Client {
    public static void main(String[] args) {
        Dog dag = new Dog("旺财");
        IAnimal iDag = (IAnimal) ProxyUtil.getProxy(dag);
        iDag.run();

        Person p = new Person("小明");
        IPerson ip =(IPerson)  ProxyUtil.getProxy(p);
        ip.sayHi(); 
    }
}

输出结果:

我们现在再来把之前用装饰模式写的ConnsUtil类改写成代理模式:

ConnsUtil类的代理模式:

package cn.hncu.pool3;

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.util.ArrayList;
import java.util.List;
import java.util.Properties;

public class ConnsUtil {
    private static List<Connection> pool = new ArrayList<Connection>();
    private static final int NUM=3;
    static{
        try {
            //读取配置文件
            Properties p = new Properties();
            p.load(ConnsUtil.class.getClassLoader().getResourceAsStream("jdbc.properties"));
            String driver = p.getProperty("driver");
            String url = p.getProperty("url");
            String user = p.getProperty("username");
            String password = p.getProperty("password");
            Class.forName(driver);

            for(int i=0;i<NUM;i++){
                final Connection conn = DriverManager.getConnection(url, user, password);

                //只需要改这里就行了!
                //使用动态代理代理conn对象,实现对close方法的拦截
                Object obj = Proxy.newProxyInstance(
                        ConnsUtil.class.getClassLoader(),
                        conn.getClass().getInterfaces(),
                        new InvocationHandler() {

                            @Override
                            public Object invoke(Object proxy, Method method, Object[] args)
                                    throws Throwable {
                                if(method.getName().equalsIgnoreCase("close") && (args==null || args.length==0)){
                                    pool.add((Connection)proxy);
                                    return null;
                                }else{
                                    return method.invoke(conn, args);
                                }
                            }
                        });
                pool.add((Connection)obj);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static synchronized Connection getConn() throws Exception{
        if(pool.size()<=0){
            Thread.sleep(100);
            return getConn();
        }
        return pool.remove(0);
    }

}

大家可以自己动手写一写,加深印象, Proxy.newProxyInstance(类加载器,class数组(被代理对象的接口的class数组),new InvocationHandler());
记住这个就可以了!
被代理对象的接口的class数组可以用conn.getClass().getInterfaces(),来获得。
conn为被代理的对象!

动态代理比装饰模式方便很多!

相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
6天前
|
关系型数据库 MySQL 数据库连接
docker拉取MySQL后数据库连接失败解决方案
通过以上方法,可以解决Docker中拉取MySQL镜像后数据库连接失败的常见问题。关键步骤包括确保容器正确启动、配置正确的环境变量、合理设置网络和权限,以及检查主机防火墙设置等。通过逐步排查,可以快速定位并解决连接问题,确保MySQL服务的正常使用。
114 82
|
1天前
|
关系型数据库 MySQL Java
【YashanDB知识库】原生mysql驱动配置连接崖山数据库
【YashanDB知识库】原生mysql驱动配置连接崖山数据库
【YashanDB知识库】原生mysql驱动配置连接崖山数据库
|
9天前
|
消息中间件 缓存 NoSQL
缓存与数据库的一致性方案,Redis与Mysql一致性方案,大厂P8的终极方案(图解+秒懂+史上最全)
缓存与数据库的一致性方案,Redis与Mysql一致性方案,大厂P8的终极方案(图解+秒懂+史上最全)
|
4天前
|
存储 SQL 关系型数据库
从 MySQL 到时序数据库 TDengine:Zendure 如何实现高效储能数据管理?
TDengine 助力广州疆海科技有限公司高效完成储能业务的数据分析任务,轻松应对海量功率、电能及输入输出数据的实时统计与分析,并以接近 1 : 20 的数据文件压缩率大幅降低存储成本。此外,taosX 强大的 transform 功能帮助用户完成原始数据的清洗和结构优化,而其零代码迁移能力更实现了历史数据从 TDengine OSS 与 MySQL 到 TDengine 企业版的平滑迁移,全面提升了企业的数据管理效率。本文将详细解读这一实践案例。
19 0
|
2月前
|
关系型数据库 MySQL 数据库连接
数据库连接工具连接mysql提示:“Host ‘172.23.0.1‘ is not allowed to connect to this MySQL server“
docker-compose部署mysql8服务后,连接时提示不允许连接问题解决
|
1月前
|
关系型数据库 MySQL 数据库
Docker Compose V2 安装常用数据库MySQL+Mongo
以上内容涵盖了使用 Docker Compose 安装和管理 MySQL 和 MongoDB 的详细步骤,希望对您有所帮助。
202 42
|
14天前
|
SQL 关系型数据库 MySQL
MySQL生产环境迁移至YashanDB数据库深度体验
这篇文章是作者将 MySQL 生产环境迁移至 YashanDB 数据库的深度体验。介绍了 YashanDB 迁移平台 YMP 的产品相关信息、安装步骤、迁移中遇到的各种兼容问题及解决方案,最后总结了迁移体验,包括工具部署和操作特点,也指出功能有优化空间及暂不支持的部分,期待其不断优化。
|
1月前
|
关系型数据库 MySQL 网络安全
如何排查和解决PHP连接数据库MYSQL失败写锁的问题
通过本文的介绍,您可以系统地了解如何排查和解决PHP连接MySQL数据库失败及写锁问题。通过检查配置、确保服务启动、调整防火墙设置和用户权限,以及识别和解决长时间运行的事务和死锁问题,可以有效地保障应用的稳定运行。
156 25
|
26天前
|
监控 关系型数据库 MySQL
云数据库:从零到一,构建高可用MySQL集群
在互联网时代,数据成为企业核心资产,传统单机数据库难以满足高并发、高可用需求。云数据库通过弹性扩展、分布式架构等优势解决了这些问题,但也面临数据安全和性能优化挑战。本文介绍了如何从零开始构建高可用MySQL集群,涵盖选择云服务提供商、创建实例、配置高可用架构、数据备份恢复及性能优化等内容,并通过电商平台案例展示了具体应用。

热门文章

最新文章