Spring frame :基于 jdk 动态代理实现连接池复用

news2024/11/19 10:38:26

前言

在数据库开发中,连接池是一种重要的技术手段,它可以提高数据库连接的复用性和性能。连接池的原理是在应用启动时创建一定数量的数据库连接,并将这些连接保存在一个池中,应用程序需要数据库连接时,从连接池中获取一个连接进行操作,不再频繁地创建和关闭连接。

为了更好地利用连接池并实现连接的复用,我们可以借助JDK动态代理机制来实现连接对象的自动重用。通过在动态代理中拦截连接的获取和归还操作,我们可以对连接对象进行有效地管理,确保每次使用完毕后将连接归还到连接池中,从而实现连接的复用,提高性能和效率。

接下来,我们将结合JDK动态代理机制和连接池技术,实现一个基于动态代理的连接池复用功能。

如果不了解 jdk 动态代理的,可以去回顾一下我的这篇文章: http://t.csdnimg.cn/BJnzh

一、开始学习

1、新建项目,结构如下

2、导入依赖
 
    <!-- spring 的核心依赖 -->
    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.23</version>
        </dependency>
 
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.4.5</version>
        </dependency>
 
         <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.33</version>
        </dependency>
 
 
    </dependencies>
 3、在 pool 包下新建三个类,ConnectionInvocationHandler、ConnectionPool、ConnUtils

ConnUtils 类


@Slf4j
public class ConnUtils {


    public static Connection getConnection(String url, String name, String password) {
        try {
            return DriverManager.getConnection(url, name, password);
        } catch (SQLException e) {
            throw new RuntimeException("connection erro", e);
        }
    }


}

主要是用来获取数据库连接的工具类

ConnectionPool (连接池)


@Setter
public class ConnectionPool {

    /**
     * 连接池(存放连接的集合)
     */
    private LinkedList<Connection> pool = new LinkedList<>();

    /**
     * 连接属性
     */
    private String url;
    private String name;
    private String password;
    /**
     * 池大小
     */
    private Integer poolSize;


    /**
     * 在构造方法中初始化连接池大小
     *
     */
    public void init() throws SQLException {
        for (int i = 0; i < poolSize; i++) {
            // 1、从数据库获取连接对象
            Connection connection = ConnUtils.getConnection(url,name,password);

            // 2、对连接对象创建代理
            connection = createProxy(connection);

            // 3、将连接对象返回连接池中
            connection.close();

        }
    }

    /**
     * 为连接对象创建代理
     *
     * @param connection
     * @return
     */
    private Connection createProxy(Connection connection) {
        // 创建回调处理器
        ConnectionInvocationHandler connectionInvocationHandler = new ConnectionInvocationHandler(connection,pool);
        // 获取连接对象的所有接口
        Class<?>[] interfaces = new Class[]{Connection.class};
        // 获取类加载器
        ClassLoader loader = ConnectionPool.class.getClassLoader();
        // 创建代理
        connection = (Connection) Proxy.newProxyInstance(loader,interfaces,connectionInvocationHandler);
        return connection;
    }

    /**
     * 从池里获取代理连接
     *
     * @return
     */
    public Connection getConnection() {
        return pool.removeFirst();
    }

    /**
     * 查看连接池的大小
     * @return
     */
    public int size(){
        return pool.size();
    }

}

这个类是一个连接池的实现,用于管理数据库连接对象。连接池的作用是在应用程序初始化时创建一定数量的数据库连接,并将这些连接保存在连接池中,当应用程序需要使用数据库连接时,可以从连接池中获取连接,使用完毕后将连接放回连接池,以便其他线程或请求继续使用。

连接池可以提高数据库的性能和效率,主要有以下几个方面的作用:

  1. 连接的复用:连接池中已经创建好的连接可以被反复利用,避免频繁地创建和关闭连接,从而减少了连接的创建和销毁开销。

  2. 连接的管理:连接池可以对连接进行有效的管理,包括连接的创建、销毁、状态的监控等,确保连接的可用性和稳定性。

  3. 连接的限制:连接池可以设置最大连接数,防止因为连接过多导致数据库负载过高或内存资源耗尽,从而提高系统的稳定性和安全性。

通过使用连接池,可以减少数据库连接的创建和销毁次数,提高数据库访问的效率,同时能够更好地管理和控制数据库连接的使用,确保系统的性能和可靠性。

 ConnectionInvocationHandler类


public class ConnectionInvocationHandler implements InvocationHandler {

    /**
     * 目标对象(被代理对象)
     */
    private Connection connection;

    /**
     * 连接池
     */
    private LinkedList<Connection> pool;

    public ConnectionInvocationHandler(Connection connection, LinkedList<Connection> pool) {
        this.connection = connection;
        this.pool = pool;
    }

    public ConnectionInvocationHandler(Connection connection) {
        this.connection = connection;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 如果当前调用的是 Connection 的 close 方法则将它放回到池中
        if ("close".equals(method.getName())) {
            // 从池的尾部放回去
            // 注意:这里放回池中的必须是代理对象,而不是目标 Connection
            pool.addLast((Connection) proxy);
            return null;
        } else {
            // 除 close 以外的其他方法则正常调用目标对象的行为
            return method.invoke(connection, args);
        }
    }
}

 这是一个代理类,实现了InvocationHandler接口,用于代理连接对象。主要作用是拦截连接对象的close方法,并将其放回连接池中,以便其他线程或请求继续使用。

在代理对象调用close方法时,ConnectionInvocationHandler会将代理对象(即当前正在被使用的连接)放回到连接池中,而不是直接关闭代理对象。这样可以确保连接池中的连接资源得到充分利用,提高应用程序的性能。

在代理对象调用除close以外的方法时,ConnectionInvocationHandler会将方法调用转发给目标对象(即被代理的连接对象),并返回方法的结果。这样可以保证应用程序的正常逻辑,对于使用者来说,无需关心连接对象被代理的具体实现,只需要按照正常的方式使用连接对象即可。

总之,ConnectionInvocationHandler是整个连接池的核心之一,它负责管理连接对象的生命周期,确保连接对象能够得到充分利用和正确释放,保障应用程序的性能和可靠性。

4、在 resources 包下新建一个 db.properties 文件
url = jdbc:mysql://localhost:3306/psm
name = root
password = 123456
poolSize = 5
5、在 config 包下新建一个 AppConfig 类

@Configuration
@PropertySource("classpath:db.properties")
@Slf4j
public class AppConfig {

    @Value("${url}")
    private String url;
    @Value("${name}")
    private String userName;
    @Value("${password}")
    private String password;
    @Value("${poolSize}")
    private Integer poolSize;

    /**
     * 装配连接池
     *
     * @return
     */
    @Bean
    public ConnectionPool connectionPool() throws SQLException {
        log.info("url:" + url);
        log.info("name:" + userName);
        log.info("password:" + password);
        log.info("size:" + poolSize);

        // 创建连接池并设置相关的属性
        ConnectionPool pool = new ConnectionPool();
        pool.setUrl(url);
        pool.setName(userName);
        pool.setPassword(password);
        pool.setPoolSize(poolSize);

        // 初始化连接池
        pool.init();

        return pool;
    }

}

这是一个用于配置连接池的类。通过@Configuration注解将它标记为一个配置类,并使用@PropertySource注解指定了属性文件的位置。

在该类中,使用了@Value注解来注入属性值。根据属性文件中的配置,注入了urlnamepasswordpoolSize等属性值,分别表示数据库的URL、用户名、密码和连接池的大小。

connectionPool()方法中,创建了一个ConnectionPool对象,并通过setter方法设置了相关属性的值。然后调用init()方法初始化连接池,使其预先创建一定数量的数据库连接。

最后将创建好的连接池对象返回,以供其他组件或类进行使用。

整个配置类的作用是将属性文件中的配置值注入到连接池对象中,并初始化连接池,以方便应用程序使用数据库连接。

6、测试

@Slf4j
public class Main {

    public static void main(String[] args) throws SQLException {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

        // 从容器中获取连接池
        ConnectionPool connectionPool = context.getBean(ConnectionPool.class);
        log.info("连接数:" + connectionPool.size());
        Connection conn1 = connectionPool.getConnection();
        Connection conn2 = connectionPool.getConnection();
        log.info("连接数:" + connectionPool.size());
        conn1.close();
        log.info("连接数:" + connectionPool.size());
    }
}

运行结果

二、 这个案例是干嘛的

这个案例主要用于配置和初始化连接池,以便在应用程序中高效地管理数据库连接。它具有以下几个作用:

  1. 抽象化数据库连接:通过使用连接池,应用程序可以从简单的直接获取和释放数据库连接的方式转变为通过连接池来获取和释放连接。连接池负责维护一定数量的数据库连接,并在需要时提供连接给应用程序使用。这样可以避免频繁地创建和关闭连接,提高数据库操作的效率。

  2. 提高性能和资源利用率:连接池可以预先创建一定数量的数据库连接,并将其保存在池中。当应用程序需要数据库连接时,可以从连接池中获取连接,而不是每次都创建一个新的连接。这样可以避免了频繁地创建和销毁连接的开销,提高了数据库操作的性能和资源的利用率。

  3. 简化配置和管理:通过将连接池的配置信息(如URL、用户名、密码、连接池大小等)放入属性文件中,并通过配置类进行加载和注入,可以方便地进行配置和管理。这使得连接池的配置可以与应用程序的其他部分分离,易于维护和修改。

总之,这个案例提供了一种在应用程序中配置和初始化连接池的方式,帮助管理数据库连接,提高数据库操作的性能和资源利用率,同时简化了配置和管理的过程。

三、总结

这只是一个利用 jdk 动态代理实现的一个简单的连接池的案例,仅仅知识实现了连接池的复用,连接池中还有很多的 API 方法。

 

四、gitee 案例

地址:ch17/src/main/java/edu/nf/ch17/pool · qiuqiu/spring-framework - 码云 - 开源中国 (gitee.com)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1112499.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

静态路由与双线BFD热备份

✍ 路由具体是什么概念&#xff1f; ✍ 路由表和路由协议有什么关系&#xff1f; ✍ 电信联通双线如何做路由热备份&#xff1f; ---- 什么叫路由&#xff1f; ---- 路由器 - 网络设备 - 转发数据 - 要有一张地图 - 路由表 ---- 路由表 - 指明要到达某个目…

.NET开源且免费的Windows远程桌面管理软件

前言 今天要给大家推荐一款由.NET开源且免费的远程桌面管理软件&#xff1a;1Remote。 1Remote官方项目介绍 1Remote是一款现代的远程会话管理和启动器&#xff0c;它让你能够在任何时候快速开启一个远程会话。目前1Remote已支持 微软远程桌面(RDP)、VNC、SSH、Telnet、SFTP、…

Unity3D 游戏框架搭建的过程是什么详解

Unity3D 是一款广泛使用的游戏开发引擎&#xff0c;它提供了丰富的功能和工具&#xff0c;使得开发者可以轻松地创建高质量的游戏。在开始开发一个新的游戏项目之前&#xff0c;我们需要搭建一个游戏框架&#xff0c;这个框架将提供一些基本的功能和结构&#xff0c;为后续的开…

CRM自动化意味着什么?企业如何从中受益?

客户关系管理&#xff08;CRM&#xff09;软件不再仅仅适用于大公司或销售周期长的行业&#xff0c;它越来越成为各种规模企业的重要工具。 在日常工作中&#xff0c;当你陷入流程的所有细节时&#xff0c;可能会产生不必要的工作。因此&#xff0c;如果你想要CRM提供的组织和…

Python特征选择

1 特征选择的目的 机器学习中特征选择是一个重要步骤&#xff0c;以筛选出显著特征、摒弃非显著特征。这样做的作用是: 减少特征&#xff08;避免维度灾难&#xff09;&#xff0c;提高训练速度&#xff0c;降低运算开销&#xff1b; 减少干扰噪声&#xff0c;降低过拟合风险…

Leetcode 第 365 场周赛题解

Leetcode 第 365 场周赛题解 Leetcode 第 365 场周赛题解题目1&#xff1a;2873. 有序三元组中的最大值 I思路代码复杂度分析 题目2&#xff1a;2874. 有序三元组中的最大值 II思路代码复杂度分析思路2 题目3&#xff1a;2875. 无限数组的最短子数组思路代码复杂度分析 题目4&a…

Oracle的立场:官网更换首页与以色列站在一起

Oracle公司的官网&#xff0c;更换了首页内容&#xff0c;明确表明立场&#xff1a;Oracle与以色列站在一起。 声明指出&#xff1a; Oracle谴责针对以色列及其公民的恐怖袭击。Oracle将为其员工、以色列政府和国防机构提供一切必要的支持。 Magen David Adom是一家为以色列公民…

Java代码审计-因酷网校在线教育系统-越权漏洞分析

登录个人账号后&#xff0c;点击基本资料。有更新资料的功能。 查看这个页面的html源码&#xff0c;进行代码审计。&#xff08;这点怎么通过源码怎么找到的就不提了&#xff0c;写上实在啰嗦了。&#xff09; 代码jsp页面源码如下&#xff0c;查看这个表单信息 注意&#xf…

【Mysql】Mysql中的B+树索引

概述 从上一章节我们了解到InnoDB 的数据页都是由7个部分组成&#xff0c;然后各个数据页之间可以组成一个双向链表 &#xff0c;而每个数据页中的记录会按照主键值从小到大的顺序组成一个单向链表 &#xff0c;每个数据页都会为存储在它里边儿的记录生成一个页目录 &#xff…

【AIGC核心技术剖析】大型语言和视觉助手——LLaVA(论文+源码)

🔥 [新!LLaVA-1.5 在 11 个基准测试上实现了 SoTA,只需对原始 LLaVA 进行简单的修改,利用所有公共数据,在单个 1-A8 节点上在 ~100 天内完成训练,并超越使用数十亿级数据的方法。 LLaVA代表了一种新颖的端到端训练大型多模态模型,结合了视觉编码器和骆马 对于通用的视…

微信小程序开发的OA会议之会议个人中心的页面搭建及模板,自定义组件的学习

目录 一.自定义组件及会议效果编写 效果显示 二.个人中心布局 编写结果 ​编辑 一.自定义组件及会议效果编写 在页面中创建一个以components命名的项目来存放组件 再在components文件夹中创建一个组件&#xff0c;名为 :tabs &#xff0c;创建操作如图所示 刚刚创建好会报…

Android中级——MVVM

MVVM MVVM是什么&#xff1f;MVVM实现前提ModelViewModelView MVVM是什么&#xff1f; Model-View-ViewMode架构&#xff0c;可看作MVP改进版&#xff0c;将此前Presenter的逻辑操作交给ViewMode中的Binder去处理 Mode&#xff1a;封装数据存储及相关操作逻辑&#xff0c;与MV…

LeetCode 799. 香槟塔【数组,模拟,简单线性DP】1855

本文属于「征服LeetCode」系列文章之一&#xff0c;这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁&#xff0c;本系列将至少持续到刷完所有无锁题之日为止&#xff1b;由于LeetCode还在不断地创建新题&#xff0c;本系列的终止日期可能是永远。在这一系列刷题文章…

Py之tiktoken:tiktoken的简介、安装、使用方法之详细攻略

Py之tiktoken&#xff1a;tiktoken的简介、安装、使用方法之详细攻略 目录 tiktoken的简介 1、性能&#xff1a;tiktoken比一个类似的开源分词器快3到6倍 tiktoken的安装 tiktoken的使用方法 1、基础用法 (1)、用于OpenAI模型的快速BPE标记器 (2)、帮助可视化BPE过程的代…

【SA8295P 源码分析 (三)】97 - QNX AIS Camera 框架介绍 及 Camera 工作流程分析

【SA8295P 源码分析】97 - QNX AIS Camera 框架介绍 及 Camera 工作流程分析 一、QNX AIS Server 框架分析二、QNX Hypervisor / Android GVM 方案介绍三、Camera APP 调用流程分析四、QCarCam 状态转换过程介绍五、Camera 加串-解串 硬件链路分析六、摄像头初始化检测过程介绍…

观察者模式-对象间的联动

有个商城小程序&#xff0c;用户希望当有新品上市的时候能通知他们。这样用户就可以不要时刻盯着小程序了。在这个场景中&#xff0c;用户向小程序订阅了一个服务——发送新品短信。小程序在有新品上线时负责向订阅客户发出这个消息。 这就是发布-订阅模式&#xff0c;也称观察…

YOLOv5-调用官方权重进行检验(目标检测)

&#x1f368; 本文为[&#x1f517;365天深度学习训练营学习记录博客 &#x1f366; 参考文章&#xff1a;365天深度学习训练营-第7周&#xff1a;咖啡豆识别&#xff08;训练营内部成员可读&#xff09; &#x1f356; 原作者&#xff1a;[K同学啊 | 接辅导、项目定制](https…

运放的单电源供电设计

文章目录 运放单电源供电同向比例单电源放大电路设计反向比例单电源放大电路设计 运放单电源供电 同向比例单电源放大电路设计 不放大直流&#xff0c;对直流来说是一个电压跟随器&#xff0c;对交流来说是同向比例 反向比例单电源放大电路设计 注&#xff1a;仪表放大器不能直…

Redis内存回收机制-内存淘汰策略和过期策略

Redis是基于内存操作的非关系型数据库&#xff0c;在内存空间不足的时候&#xff0c;为了保证程序的运行和命中率&#xff0c;就会淘汰一部分数据。如何淘汰数据&#xff1f;这就是Redis的内存回收策略。 Redis中的内存回收策略主要有两个方面&#xff1a; Redis过期策略&#…

nodejs+vue云旅青城系统-旅游网站

用户可以实现首页、个人中心、订票信息管理、路线制定管理等。不仅使服务管理难度变低了&#xff0c;还提升了管理的灵活性。目 录 摘 要 I ABSTRACT II 目 录 II 第1章 绪论 1 1.1背景及意义 1 1.2 国内外研究概况 1 1.3 研究的内容 1 第2章 相关技术 3 2.1 nodejs简介 4 2.2 …