自定义数据库连接池

举报
wangweijun 发表于 2022/03/30 23:32:37 2022/03/30
【摘要】 时间很快就到周末了,学习计划也已经进行了五天了,既然是周末的话,那当然要多学习一点知识,毕竟拥有这么充裕的时间。 今天的学习内容是数据库连接池。 那什么是数据库连接池,它有什么作用是我们首先会想到的问题...

时间很快就到周末了,学习计划也已经进行了五天了,既然是周末的话,那当然要多学习一点知识,毕竟拥有这么充裕的时间。

今天的学习内容是数据库连接池。
那什么是数据库连接池,它有什么作用是我们首先会想到的问题。
数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个。

应用程序直接获取连接的缺点:
用户每次请求都需要向数据库获得链接,而数据库创建连接通常需要消耗相对较大的资源,创建时间也较长。假设网站一天10万访问量,数据库服务器就需要创建10万次连接,极大的浪费数据库的资源,并且极易造成数据库服务器内存溢出、拓机。

缺点显而易见,应用程序在创建连接和销毁连接的时候是极其消耗资源的,而使用数据库连接池则能够优化程序性能。

连接池原理:
在服务器端一次性地创建多个连接,将多个连接保存在一个连接池对象中,当请求需要操作数据库时,不会为请求创建新的连接,而是直接从连接池中获得一个连接。当操作数据库结束,并不需要真正的去关闭连接,而是将连接放回到连接池中。

了解了数据库连接池的优点后,我们关心的是该如何去实现数据库连接池呢?
在Java中提供了javax.sql.Datasource接口用于实现数据库连接池。
老话说得好,光说不练假把式,只练不打无用功,现在我们就来写一个程序感受一下。

在MyEclipse中新建一个web项目,取名demo。
新建一个类,取名MyDataSource,然后实现DataSource接口,要实现的方法非常多,但是不用紧张,我们只关注两个方法。

public Connection getConnection() throws SQLException {
	return null;
}

public Connection getConnection(String username, String password)
		throws SQLException {
	return null;
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

方便起见,我们只看无参的getConnection()方法。自定义的连接池需要有如下功能

  • 一次性地创建多个连接
  • 实现getConnection方法,从连接池获得一个连接
  • 当用户使用连接后,提供方法将连接放回到连接池中
    代码如下:
/**
 * 自定义连接池
 * 
 *  一次性地创建多个连接
 *  
 *  实现getConnection方法,从连接池获得一个连接
 *  
 *  当用户使用连接后,提供方法将连接放回到连接池中
 *  
 * @author Administrator
 * 
 */
public class MyDataSource implements DataSource {
	
	private LinkedList<Connection> dataSources = new LinkedList<Connection>();
	
	public MyDataSource(){
		//一次性创建10个连接
		for(int i = 0;i < 10;i++){
			try {
				Connection connection = JDBCUtils.getConnection();
				//将连接加入到连接池中
				dataSources.add(connection);
			} catch (Exception e) {
				e.printStackTrace();
			}
			
		}
	}

	public PrintWriter getLogWriter() throws SQLException {
		return null;
	}

	public void setLogWriter(PrintWriter out) throws SQLException {

	}

	public void setLoginTimeout(int seconds) throws SQLException {

	}

	public int getLoginTimeout() throws SQLException {
		return 0;
	}

	public Logger getParentLogger() throws SQLFeatureNotSupportedException {
		return null;
	}

	public <T> T unwrap(Class<T> iface) throws SQLException {
		return null;
	}

	public boolean isWrapperFor(Class<?> iface) throws SQLException {
		return false;
	}

	public Connection getConnection() throws SQLException {
		//取出连接池中的一个连接
		Connection connection = dataSources.removeFirst();//删除第一个连接并返回
		return connection;
	}

	public Connection getConnection(String username, String password)
			throws SQLException {
		return null;
	}
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69

代码中的JDBCUtils是我编写的一个工具类,在之前的博客中也都有提及,为了方便大家,我就再贴一次。

/**
 * JDBC 工具类,抽取公共方法
 * 
 * @author seawind
 * 
 */
public class JDBCUtils {
	private static final String DRIVERCLASS;
	private static final String URL;
	private static final String USER;
	private static final String PWD;

	static {
		ResourceBundle bundle = ResourceBundle.getBundle("dbconfig");
		DRIVERCLASS = bundle.getString("DRIVERCLASS");
		URL = bundle.getString("URL");
		USER = bundle.getString("USER");
		PWD = bundle.getString("PWD");
	}

	// 建立连接
	public static Connection getConnection() throws Exception {
		loadDriver();
		return DriverManager.getConnection(URL, USER, PWD);
	}

	// 装载驱动
	private static void loadDriver() throws ClassNotFoundException {
		Class.forName(DRIVERCLASS);
	}

	// 释放资源
	public static void release(ResultSet rs, Statement stmt, Connection conn) {
		if (rs != null) {
			try {
				rs.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
			rs = null;
		}

		release(stmt, conn);
	}

	public static void release(Statement stmt, Connection conn) {
		if (stmt != null) {
			try {
				stmt.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
			stmt = null;
		}
		if (conn != null) {
			try {
				conn.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
			conn = null;
		}
	}
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64

现在新建一个测试类,取名MyDataSourceTest

public class MyDataSourceTest {
	public static void main(String[] args) throws SQLException {
		//创建连接池
		MyDataSource dataSource = new MyDataSource();
		//从连接池中获得一个连接
		Connection connection = dataSource.getConnection();
		//操作数据库
		String sql = "select * from account";
		PreparedStatement stmt = connection.prepareStatement(sql);
		ResultSet rs = stmt.executeQuery();
		while (rs.next()) {
			System.out.println(rs.getString("name"));
		}
		JDBCUtils.release(stmt, connection);
	}
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

运行测试类
在这里插入图片描述
控制台成功输出用户姓名。
但是这段程序是有问题的,因为JDBCUtils工具类中的release()方法会将连接关闭,而我们的想法是将连接归还到连接池而不是关闭它。实现方法有很多种,你可以在自定义连接池MyDataSource中添加一个方法用于归还连接。
重新修改MyDataSource类,代码如下

/**
 * 自定义连接池
 * 
 *  一次性地创建多个连接
 *  
 *  实现getConnection方法,从连接池获得一个连接
 *  
 *  当用户使用连接后,提供方法将连接放回到连接池中
 *  
 * @author Administrator
 * 
 */
public class MyDataSource implements DataSource {
	
	private LinkedList<Connection> dataSources = new LinkedList<Connection>();
	
	public MyDataSource(){
		//一次性创建10个连接
		for(int i = 0;i < 10;i++){
			try {
				Connection connection = JDBCUtils.getConnection();
				//将连接加入到连接池中
				dataSources.add(connection);
			} catch (Exception e) {
				e.printStackTrace();
			}
			
		}
	}

	public PrintWriter getLogWriter() throws SQLException {
		return null;
	}

	public void setLogWriter(PrintWriter out) throws SQLException {

	}

	public void setLoginTimeout(int seconds) throws SQLException {

	}

	public int getLoginTimeout() throws SQLException {
		return 0;
	}

	public Logger getParentLogger() throws SQLFeatureNotSupportedException {
		return null;
	}

	public <T> T unwrap(Class<T> iface) throws SQLException {
		return null;
	}

	public boolean isWrapperFor(Class<?> iface) throws SQLException {
		return false;
	}
	
	/**
	 * 添加一个方法,用于归还连接
	 * @param conn
	 */
	public void releaseConnection(Connection conn){
		dataSources.add(conn);
		System.out.println("将连放回到连接池中,数量" + dataSources.size());
	}

	public Connection getConnection() throws SQLException {
		//取出连接池中的一个连接
		Connection connection = dataSources.removeFirst();//删除第一个连接并返回
		System.out.println("取出一个连接,剩余" + dataSources.size() + "个连接");
		return connection;
	}

	public Connection getConnection(String username, String password)
			throws SQLException {
		return null;
	}
}


  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80

测试类中最后就应该调用你刚刚添加的方法,运行测试类
在这里插入图片描述
结果很明显,目的达到了。
这样就实现了一个简易的数据库连接池,但该程序其实有一个很不好的地方,因为如果想要将连接放回连接池而不是关闭它,就得调用在MyDataSource类中自己添加的方法,而很多人或许已经习惯了JDBC的编程,会很习惯地去调用close()方法,而一旦调用了close()方法,连接就被关闭了,不会放回连接池了。所以你就需要通知用户去主动使用你添加的API,这种方法其实是不可取的。那有什么办法能够让调用者随着自己的编程习惯去调用close()方法的同时,还能够将连接放回连接池而不是关闭它呢?
我们可以去修改close()方法原来的逻辑,怎么修改呢?
在Java中有三种方法可以增强原有的方法

  • 类继承 、方法覆盖
    必须控制对象创建,才能使用该方式

  • 装饰者模式方法加强
    必须和目标对象实现相同接口或继续相同父类,特殊构造器(传入被包装对象)

  • 动态代理

有关方法增强的问题,可以参考我的这篇博客。
理解Java方法增强
熟悉方法增强的小伙伴们可以忽略哈。(手动滑稽)

我们使用动态代理来对Connection接口的run()方法进行增强。
修改MyDataSource类中的代码

/**
 * 自定义连接池
 * 
 * 一次性地创建多个连接
 * 
 * 实现getConnection方法,从连接池获得一个连接
 * 
 * 当用户使用连接后,提供方法将连接放回到连接池中
 * 
 * @author Administrator
 * 
 */
public class MyDataSource implements DataSource {

	private LinkedList<Connection> dataSources = new LinkedList<Connection>();

	public MyDataSource() {
		// 一次性创建10个连接
		for (int i = 0; i < 10; i++) {
			try {
				Connection connection = JDBCUtils.getConnection();
				// 将连接加入到连接池中
				dataSources.add(connection);
			} catch (Exception e) {
				e.printStackTrace();
			}

		}
	}

	public PrintWriter getLogWriter() throws SQLException {
		return null;
	}

	public void setLogWriter(PrintWriter out) throws SQLException {

	}

	public void setLoginTimeout(int seconds) throws SQLException {

	}

	public int getLoginTimeout() throws SQLException {
		return 0;
	}

	public Logger getParentLogger() throws SQLFeatureNotSupportedException {
		return null;
	}

	public <T> T unwrap(Class<T> iface) throws SQLException {
		return null;
	}

	public boolean isWrapperFor(Class<?> iface) throws SQLException {
		return false;
	}

	/**
	 * 添加一个方法,用于归还连接
	 * 
	 * @param conn
	 */
	public void releaseConnection(Connection conn) {
		dataSources.add(conn);
		System.out.println("将连放回到连接池中,数量" + dataSources.size());
	}

	public Connection getConnection() throws SQLException {
		// 取出连接池中的一个连接
		final Connection connection = dataSources.removeFirst();// 删除第一个连接并返回
		System.out.println("取出一个连接,剩余" + dataSources.size() + "个连接");
		// 将目标connection对象进行增强
		Connection connProxy = (Connection) Proxy.newProxyInstance(connection
				.getClass().getClassLoader(), connection.getClass()
				.getInterfaces(), new InvocationHandler() {

			//执行代理对象的任何方法都将执行invoke方法
			public Object invoke(Object proxy, Method method, Object[] args)
					throws Throwable {
				//只需要增强close方法
				if(method.getName().equals("close")){
					//需要增强的方法
					//不将连接真正关闭,将连接放回连接池
					releaseConnection(connection);
					return null;
				}else{
					//不需要增强的方法,保持方法原有的功能即可
					return method.invoke(connection, args);
				}
			}
		});
		return connProxy;
	}

	public Connection getConnection(String username, String password)
			throws SQLException {
		return null;
	}
}


  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101

回到测试类,此时调用Connection接口中的close()方法,然后执行
在这里插入图片描述
方法成功增强了。调用者在调用close()方法后并不会把连接关闭了,而是放回连接池中。由此我们的想法便实现了。

文章来源: blizzawang.blog.csdn.net,作者:·wangweijun,版权归原作者所有,如需转载,请联系作者。

原文链接:blizzawang.blog.csdn.net/article/details/89416208

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。