how2j.cn


工具版本兼容问题
线程池类似的,数据库也有一个数据库连接池。 不过他们的实现思路是不一样的。
本章节讲解了自定义数据库连接池类:ConnectionPool,虽然不是很完善和健壮,但是足以帮助大家理解ConnectionPool的基本原理。


本视频是解读性视频,所以希望您已经看过了本知识点的内容,并且编写了相应的代码之后,带着疑问来观看,这样收获才多。 不建议一开始就观看视频



10分40秒
本视频采用html5方式播放,如无法正常播放,请将浏览器升级至最新版本,推荐火狐,chrome,360浏览器。 如果装有迅雷,播放视频呈现直接下载状态,请调整 迅雷系统设置-基本设置-启动-监视全部浏览器 (去掉这个选项)。 chrome 的 视频下载插件会影响播放,如 IDM 等,请关闭或者切换其他浏览器



步骤 1 : 数据库连接池原理-传统方式   
步骤 2 : 数据库连接池原理-使用池   
步骤 3 : ConnectionPool构造方法和初始化   
步骤 4 : 测试类   
步骤 5 : 练习-数据库连接池   
步骤 6 : 答案-数据库连接池   

步骤 1 :

数据库连接池原理-传统方式

edit
当有多个线程,每个线程都需要连接数据库执行SQL语句的话,那么每个线程都会创建一个连接,并且在使用完毕后,关闭连接。

创建连接和关闭连接的过程也是比较消耗时间的,当多线程并发的时候,系统就会变得很卡顿。

同时,一个数据库同时支持的连接总数也是有限的,如果多线程并发量很大,那么数据库连接的总数就会被消耗光,后续线程发起的数据库连接就会失败。
数据库连接池原理-传统方式
步骤 2 :

数据库连接池原理-使用池

edit
与传统方式不同,连接池在使用之前,就会创建好一定数量的连接。
如果有任何线程需要使用连接,那么就从连接池里面借用而不是自己重新创建.
使用完毕后,又把这个连接归还给连接池供下一次或者其他线程使用。
倘若发生多线程并发情况,连接池里的连接被借用光了,那么其他线程就会临时等待,直到有连接被归还回来,再继续使用。
整个过程,这些连接都不会被关闭,而是不断的被循环使用,从而节约了启动和关闭连接的时间。
数据库连接池原理-使用池
步骤 3 :

ConnectionPool构造方法和初始化

edit
1. ConnectionPool() 构造方法约定了这个连接池一共有多少连接

2. 在init() 初始化方法中,创建了size条连接。 注意,这里不能使用try-with-resource这种自动关闭连接的方式,因为连接恰恰需要保持不关闭状态,供后续循环使用

3. getConnection, 判断是否为空,如果是空的就wait等待,否则就借用一条连接出去

4. returnConnection, 在使用完毕后,归还这个连接到连接池,并且在归还完毕后,调用notifyAll,通知那些等待的线程,有新的连接可以借用了。

注:连接池设计用到了多线程的wait和notifyAll,这些内容可以在多线程交互章节查阅学习。
package jdbc; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; public class ConnectionPool { List<Connection> cs = new ArrayList<Connection>(); int size; public ConnectionPool(int size) { this.size = size; init(); } public void init() { //这里恰恰不能使用try-with-resource的方式,因为这些连接都需要是"活"的,不要被自动关闭了 try { Class.forName("com.mysql.jdbc.Driver"); for (int i = 0; i < size; i++) { Connection c = DriverManager .getConnection("jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8", "root", "admin"); cs.add(c); } } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public synchronized Connection getConnection() { while (cs.isEmpty()) { try { this.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } Connection c = cs.remove(0); return c; } public synchronized void returnConnection(Connection c) { cs.add(c); this.notifyAll(); } }
首先初始化一个有3条连接的数据库连接池
然后创建100个线程,每个线程都会从连接池中借用连接,并且在借用之后,归还连接。 拿到连接之后,执行一个耗时1秒的SQL语句。

运行程序,就可以观察到如图所示的效果
测试类
package jdbc; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import jdbc.ConnectionPool; public class TestConnectionPool { public static void main(String[] args) { ConnectionPool cp = new ConnectionPool(3); for (int i = 0; i < 100; i++) { new WorkingThread("working thread" + i, cp).start(); } } } class WorkingThread extends Thread { private ConnectionPool cp; public WorkingThread(String name, ConnectionPool cp) { super(name); this.cp = cp; } public void run() { Connection c = cp.getConnection(); System.out.println(this.getName()+ ":\t 获取了一根连接,并开始工作" ); try (Statement st = c.createStatement()){ //模拟时耗1秒的数据库SQL语句 Thread.sleep(1000); st.execute("select * from hero"); } catch (SQLException | InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } cp.returnConnection(c); } }
步骤 5 :

练习-数据库连接池

edit  姿势不对,事倍功半! 点击查看做练习的正确姿势
向数据库中插入100条数据,比较传统方式和数据库连接池方式的性能差异

1. 使用传统方式创建100个线程,每个线程都会创建新的连接,通过这个连接向数据库插入1条数据,然后关闭这个连接。

2. 使用数据库连接池的方式,创建一个有10条连接的连接池,然后创建100个线程,每个线程都会向连接池借用连接,借用到后,向数据库插入1条数据,然后归还这个连接。

通过时间统计,比较这两种方式的性能表现差异。
步骤 6 :

答案-数据库连接池

edit
在查看答案前,尽量先自己完成,碰到问题再来查看答案,收获会更多
在查看答案前,尽量先自己完成,碰到问题再来查看答案,收获会更多
在查看答案前,尽量先自己完成,碰到问题再来查看答案,收获会更多
查看本答案会花费4个积分,您目前总共有点积分。查看相同答案不会花费额外积分。 积分增加办法 或者一次性购买JAVA 中级总计0个答案 (总共需要0积分)
查看本答案会花费4个积分,您目前总共有点积分。查看相同答案不会花费额外积分。 积分增加办法 或者一次性购买JAVA 中级总计0个答案 (总共需要0积分)
账号未激活 账号未激活,功能受限。 请点击激活
本视频是解读性视频,所以希望您已经看过了本答案的内容,带着疑问来观看,这样收获才多。 不建议一开始就观看视频

3分37秒 本视频采用html5方式播放,如无法正常播放,请将浏览器升级至最新版本,推荐火狐,chrome,360浏览器。 如果装有迅雷,播放视频呈现直接下载状态,请调整 迅雷系统设置-基本设置-启动-监视全部浏览器 (去掉这个选项)。 chrome 的 视频下载插件会影响播放,如 IDM 等,请关闭或者切换其他浏览器


TraditionalWorkingThread 使用传统方式,建立连接的工作线程

ConnectionpoolWorkingThread 使用连接池方式,建立连接的工作线程

TestConnectionPool 测试类,在这个类中分别创建了100个TraditionalWorkingThread 和100ConnectionpoolWorkingThread ,并分别统计他们运行需要的时间
答案-数据库连接池
package jdbc; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; class TraditionalWorkingThread extends Thread { public void run() { try { Class.forName("com.mysql.jdbc.Driver"); } catch (ClassNotFoundException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } try (Connection c = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8", "root", "admin"); Statement st = c.createStatement()) { for (int i = 0; i < TestConnectionPool.insertTime; i++) { String sql = "insert into hero values(null," + "'提莫'" + "," + 313.0f + "," + 50 + ")"; st.execute(sql); } } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
package jdbc; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; class ConnectionpoolWorkingThread extends Thread { private ConnectionPool cp; public ConnectionpoolWorkingThread(ConnectionPool cp) { this.cp = cp; } public void run() { Connection c = cp.getConnection(); try (Statement st = c.createStatement()) { for (int i = 0; i < TestConnectionPool.insertTime; i++) { String sql = "insert into hero values(null," + "'提莫'" + "," + 313.0f + "," + 50 + ")"; st.execute(sql); } } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } cp.returnConnection(c); } }
package jdbc; import java.util.ArrayList; import java.util.List; public class TestConnectionPool { private static int threadNumber = 100; public static int insertTime = 1; public static void main(String[] args) { traditionalWay(); connectionPoolWay(); } private static void connectionPoolWay() { ConnectionPool cp = new ConnectionPool(10); System.out.println("开始连接池方式插入数据测试:"); long start = System.currentTimeMillis(); List<Thread> ts = new ArrayList<>(); for (int i = 0; i < threadNumber; i++) { Thread t =new ConnectionpoolWorkingThread(cp); t.start(); ts.add(t); } //等待所有线程结束 for (Thread t : ts) { try { t.join(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } long end = System.currentTimeMillis(); System.out.printf("使用连接池方式,启动%d条线程,每个线程插入%d条数据,一共耗时%d 毫秒%n",threadNumber,insertTime,end-start); } private static void traditionalWay() { System.out.println("开始传统方式插入数据测试:"); long start = System.currentTimeMillis(); List<Thread> ts = new ArrayList<>(); for (int i = 0; i < threadNumber; i++) { Thread t =new TraditionalWorkingThread(); t.start(); ts.add(t); } //等待所有线程结束 for (Thread t : ts) { try { t.join(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } long end = System.currentTimeMillis(); System.out.printf("使用传统方式,启动%d条线程,每个线程插入%d条数据,一共耗时%d 毫秒%n",threadNumber,insertTime,end-start); } }


HOW2J公众号,关注后实时获知最新的教程和优惠活动,谢谢。


问答区域    
2024-07-16 比较连接池和普通方式访问数据库的性能
虚心求学




执行 100 次插入操作: 全部执行完毕,普通方式耗时:573 ms 全部执行完毕,连接池方式耗时:56 ms 执行 50 次插入操作: 全部执行完毕,普通方式耗时:508 ms 全部执行完毕,连接池方式耗时:34 ms 执行 10 次插入操作 全部执行完毕,普通方式耗时:421 ms 全部执行完毕,连接池方式耗时:11 ms
public static void main(String[] args) {
		testConnectionNormalWay();
		testConnectionPoolWay();
	}

	public static void testConnectionPoolWay() {
		final ConnectionPool pool = new ConnectionPool(10);
		int count = 100;
		List<Thread> ts = new ArrayList<Thread>();
		long begin = System.currentTimeMillis();
		final String sql = "insert into item values(null,?,?)";
		for (int i = 0; i < count; i++) {
			Thread t = new Thread(() -> {
				Connection c = pool.getConnection();
				try {
					PreparedStatement ps = c.prepareStatement(sql);
					ps.setString(1, "item");
					ps.setFloat(2, 3f);
					ps.execute();
				} catch (Exception e) {
					e.printStackTrace();
				}
				pool.backConnection(c);
			});
			t.start();
			ts.add(t);
		}
		for (Thread t : ts) {
			try {
				t.join();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		long end = System.currentTimeMillis();
		System.out.printf("全部执行完毕,连接池方式耗时:%d ms%n", end - begin);
	}

	public static void testConnectionNormalWay() {
		int count = 100;
		final String sql = "insert into item values(null,?,?)";
		List<Thread> ts = new ArrayList<Thread>();
		long begin = System.currentTimeMillis();
		for (int i = 0; i < count; i++) {
			Thread t = new Thread(() -> {
				try(Connection c = getConnetion();) {
					PreparedStatement ps = c.prepareStatement(sql);
					ps.setString(1, "item");
					ps.setFloat(2, 3f);
					ps.execute();
				} catch (Exception e) {
					e.printStackTrace();
				}
			});
			t.start();
			ts.add(t);
		}
		for (Thread t : ts) {
			try {
				t.join();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		long end = System.currentTimeMillis();
		System.out.printf("全部执行完毕,普通方式耗时:%d ms%n", end - begin);
	}

package testJDBC;

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

public class ConnectionPool {
	private List<Connection> cs;
	private int size;

	public ConnectionPool(int size) {
		this.size = size;
		cs = new ArrayList<Connection>();
		init();
	}

	public void init() {
		try {
			Class.forName("com.mysql.cj.jdbc.Driver");
			String url = "jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding = UTF-8";
			for (int i = 0; i < size; i++) {
				Connection c = DriverManager.getConnection(url, "root", "admin");
				cs.add(c);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	public synchronized Connection getConnection()
	{
		while(cs.isEmpty())
		{
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		return cs.remove(0);
	}
	public synchronized void backConnection(Connection c)
	{
		cs.add(c);
		this.notifyAll();
	}
}

							





回答已经提交成功,正在审核。 请于 我的回答 处查看回答记录,谢谢
答案 或者 代码至少填写一项, 如果是自己有问题,请重新提问,否则站长有可能看不到





2022-12-07 答案:数据库连接池
logiczqr




如题
加载中
package com.hello.demo;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

class ConnectPool {
    static String url = "jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8" ;
    static String user = "root" ;
    static String pass = "admin" ;
    int size ;
    List<Connection> list ;
    public ConnectPool(int size) {
        this.size = size ;
        init(size);
    }
    public void init(int size) {
        list = new ArrayList() ;
        for (int i = 0 ; i < size ; i++) {
            try {
                Class.forName("com.mysql.jdbc.Driver") ;
                Connection c = DriverManager.getConnection(url, user, pass) ;
                list.add(c) ;
            } catch (SQLException e) {
                throw new RuntimeException(e);
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public synchronized Connection get() {
        try {
            while (list.size() == 0) {
                this.wait();
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return list.remove(0) ;
    }

    public synchronized void retu(Connection c) {
        list.add(c) ;
        this.notifyAll();
    }
}

class DatabaseThread extends Thread {
    static String url = "jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8" ;
    static String user = "root" ;
    static String pass = "admin" ;
    @Override
    public void run() {
        Connection c = null ;
        PreparedStatement ps = null ;
        try {
            Class.forName("com.mysql.jdbc.Driver") ;
            c = DriverManager.getConnection(url, user, pass) ;
            String sql = "insert into hero values(null, ?, ?, ?)" ;
            ps = c.prepareStatement(sql) ;
            ps.setString(1, "盖伦");
            ps.setFloat(2, 222.1f);
            ps.setInt(3, 100);
            ps.execute() ;
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }finally {
            try {
                if (ps != null) ps.close();
                if (c != null) c.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

class PoolThread extends Thread {
    ConnectPool pool ;
    public PoolThread(ConnectPool pool) {
        this.pool = pool ;
    }
    @Override
    public void run() {
        Connection c = pool.get() ;
        String sql = "insert into hero values(null, ?, ?, ?)" ;
        try {
            PreparedStatement ps = c.prepareStatement(sql);
            ps.setString(1, "盖伦");
            ps.setFloat(2, 222.1f);
            ps.setInt(3, 100);
            ps.execute() ;
            pool.retu(c);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

public class ConnectPoolDemo {

    public static void main(String[] args) {
        long s1 = System.currentTimeMillis() ;
        System.out.println("传统jdbc方式插入数据开始");
        List<Thread> ts = new ArrayList<>() ;
        for(int i = 0 ; i < 100 ; i++) {
            Thread t = new DatabaseThread() ;
            t.start();
            ts.add(t) ;
        }
        try {
            for (Thread t : ts) {
                t.join();//等待所有线程结束
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        long e1 = System.currentTimeMillis() ;
        System.out.println("创建100个线程使用传统jdbc方式插入数据共耗时:" + (e1 - s1) + " 毫秒");
        ConnectPool cp = new ConnectPool(10) ;
        long s2 = System.currentTimeMillis() ;
        List<Thread> ts2 = new ArrayList<>() ;
        System.out.println("使用数据库连接池插入数据开始");
        for(int i = 0 ; i < 100 ; i++) {
          Thread t2 = new PoolThread(cp) ;
          t2.start();
          ts2.add(t2) ;
        }
        try {
            for (Thread t : ts2) {
                t.join();//等待所有线程结束
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        long e2 = System.currentTimeMillis() ;
        System.out.println("创建100个线程使用数据库连接池插入数据共耗时:" + (e2 - s2) + " 毫秒");

    }
}

							





回答已经提交成功,正在审核。 请于 我的回答 处查看回答记录,谢谢
答案 或者 代码至少填写一项, 如果是自己有问题,请重新提问,否则站长有可能看不到





2022-05-02 答案
2022-03-08 自己的代码,可以运行。那些连接池比传统时间长的,可能是这个原因,没有加这个代码
2021-09-06 练习 - 数据库连接池


提问太多,页面渲染太慢,为了加快渲染速度,本页最多只显示几条提问。还有 35 条以前的提问,请 点击查看

提问之前请登陆
提问已经提交成功,正在审核。 请于 我的提问 处查看提问记录,谢谢
关于 JAVA 中级-JDBC-数据库连接池 的提问

尽量提供截图代码异常信息,有助于分析和解决问题。 也可进本站QQ群交流: 578362961
提问尽量提供完整的代码,环境描述,越是有利于问题的重现,您的问题越能更快得到解答。
对教程中代码有疑问,请提供是哪个步骤,哪一行有疑问,这样便于快速定位问题,提高问题得到解答的速度
在已经存在的几千个提问里,有相当大的比例,是因为使用了和站长不同版本的开发环境导致的,比如 jdk, eclpise, idea, mysql,tomcat 等等软件的版本不一致。
请使用和站长一样的版本,可以节约自己大量的学习时间。 站长把教学中用的软件版本整理了,都统一放在了这里, 方便大家下载: https://how2j.cn/k/helloworld/helloworld-version/1718.html

上传截图