后端学习(二)实现自己的数据库连接池


前言

现如今做软件开发离不开数据库,一般我们都会用第三方的数据库连接池技术C3P0、Druid等,而数据库连接池到底是怎么实现的呢?
本文将介绍一个简易的数据库连接池,来便于理解其原理。


一、数据库连接池是什么?


众所周知,数据库连接作为一种有限的、昂贵的资源,会大大影响整个应用程序的性能。
采用池化技术,将大大节省资源连接的浪费,具体逻辑如下:
1、每当用户做出请求时,应用程序进行业务处理,进而调用数据层资源建立数据库连接。
2、数据库连接统一交由连接池管理,若连接池内有空闲的连接,则直接使用该连接,并且标记该连接为繁忙。
3、数据库连接使用结束后,并不是传统的进行流的关闭,而是直接标记为空闲连接,等待其他请求。

二、实现连接池

1.配置jdbc.properties

Driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/atm
username=root
psd=root

2.创建MyConnection

因为数据库连接池的连接的close方法并不能直接关闭连接,而是应该将其标记为空闲状态,为了方便程序员根据初始代码习惯使用连接池,需要重写Connection的close方法。
此处采用实现Connection接口的方法。

public class MyConnection implements Connection {
	//isUsed标记是否空闲,初始为false
    public boolean isUsed=false;
    private Connection conn;
    //构造方法,创建MyConnection时传入Connection对象
    public  MyConnection(Connection conn){
        this.conn=conn;
    }
    //调用MyConnection方法时,实则使用Connection方法
    @Override
    public Statement createStatement() throws SQLException {
        return conn.createStatement();
    }

    @Override
    public PreparedStatement prepareStatement(String sql) throws SQLException {
        return conn.prepareStatement(sql);
    }
    //重写close方法
    @Override
    public  void  close() throws SQLException {
    	//将连接标记为空闲
        this.isUsed=false;
        System.out.println("我已经释放了");
    }
    //若需要真正关闭连接,则可使用realclose方法
    public  void realclose() throws SQLException{
        this.conn.close();
        MyConnectionPool.myConnections.remove(this);
        System.out.println("我已经关闭了");
    }
    /**
    **其他重写方法....
    **/
}

3.创建自己的连接池

public class MyConnectionPool{
    public static String Driver;
    public static String url;
    public static String username;
    public static String psd;
    //利用CopyOnWriteArrayList存放MyConnection,初始化池
    public  static CopyOnWriteArrayList<MyConnection> myConnections=new CopyOnWriteArrayList<MyConnection>();
    //初始化池内连接数量
    public static int count=0;
    
    //读取jdbc配置文件
    private static void readPro(){
        Properties pro=new Properties();
        InputStream in=MyConnectionPool.class.getClassLoader().getResourceAsStream("jdbc.properties");
        try{
            pro.load(in);
            Driver=pro.getProperty("Driver");
            url=pro.getProperty("url");
            username=pro.getProperty("username");
            psd=pro.getProperty("psd");
        }catch (IOException e){
            e.printStackTrace();
        }
    }
		//在池中获取连接
	public static synchronized Connection getConnection(){
        for(int i=0;i<count;i++){      //若池内有空闲的连接则连上
            if(!myConnections.get(i).isUsed){
                myConnections.get(i).isUsed=true;
                System.out.println("我空闲我连上");
                return myConnections.get(i);
            };
        }
		//若池内连接数超过10个连接(可自定义池内最大连接数),则提示系统繁忙
        if(count>=10){
            System.out.println("系统繁忙!");
            return null;
        }
        //若池内没有连接空闲,且连接数不超过池可以承载的最大数量,则创建一个新连接
        readPro();
        Connection conn=null;
        try{
            //--1,加载Driver驱动--
            Class.forName(Driver);
            //--2,创建数据库连接对象Connection--
            conn = DriverManager.getConnection(url, username, psd);
        }catch (ClassNotFoundException |SQLException e){
            e.printStackTrace();
        }
        MyConnection myconnection=new MyConnection(conn);
        //标记连接为繁忙
        myconnection.isUsed=true;
        //将其加入池中
        myConnections.add(myconnection);
        System.out.println("我新建一个");
        count++; //池内连接数量+1
        return myconnection;
    }
}

4.连接池应用

以登录功能为例

    public int doLogin(String code_input,int password_input){
         Connection conn = null;
         PreparedStatement pstmt=null;
         ResultSet rs = null;
        try{
        	//数据库连接从连接池内获得
            conn=MyConnectionPool.getConnection();
            //创建一个可向数据库发送SQL命令并返回结果的传送对象Statement--
            String sql="SELECT * FROM account WHERE code = ? and password = ?";
            pstmt=conn.prepareStatement(sql);
            pstmt.setString(1,code_input); //防止SQL注入
            pstmt.setInt(2,password_input);
            rs=pstmt.executeQuery();
            if(rs.next()){
                return rs.getInt("id");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            if(rs!=null){
                try{
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if(pstmt!=null){
                try{
                    pstmt.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if(conn!=null){
                try {
                	//此处的close方法实则是MyConnection的close方法,标记连接为空闲
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
       return -1;
    }

拓展

连接池的连接还需要更为复杂的管理,以下展示了两种情况。
1、池内空闲数太多,一段时间内请求数太少了,需要关闭流以免占用空间

public static  void release(){
        int count=0;
        for(MyConnection myConnection:MyConnectionPool.myConnections){
            if (!myConnection.isUsed){ //若连接池有6个空闲,则关闭其他空闲
                if(count>=6){
                    try {
                        myConnection.realclose();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                    continue;
                }
                count++;
            }
        }
    }

2、连接空闲太久需要不定时向数据库发送假的sql语句,方式数据源检测到连接空闲时长太长而自动断开连接,导致空闲连接失效。

    public static  void send(){
        Statement stmt = null;
        String sql="SELECT * FROM account";
        /**
         * 定时发送sql语句确保连接
         */
        for(MyConnection myConnection:MyConnectionPool.myConnections){
            if(myConnection.isUsed)
                continue;
            try {
                stmt = myConnection.createStatement();
                stmt.executeQuery(sql);
            } catch (SQLException e) {
                e.printStackTrace();
            }finally {
                if (stmt!=null){
                    try {
                        stmt.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
                if(myConnection!=null){
                    try {
                        myConnection.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

    }

可以在连接池类中采用静态代码块,类加载时即开启线程,实时监测数据库连接的情况,定时调用release方法以及send方法。

    static{
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                release();
            }
        },0,1000);
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                send();
            }
        },0,1000*10L);
    }

数据库连接池的内容,需要管理的东西不止如此,这里只简单介绍了实现自己的数据库连接池,便于原理的理解。有兴趣也可以自己手动实现拓展数据库连接池的功能。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值