您的位置:首页 > 数据库

数据库连接池活尿泥模拟及c3p0的使用

2017-12-10 14:49 435 查看
第一部分内容为转载,关于数据库连接池技术产生背景和介绍,原文请参考http://blog.csdn.net/shuaihj/article/details/14223015

第一部分、数据库连接池产生的原因以及分析

一.早期我们怎么进行数据库操作



1.原理:一般来说,java应用程序访问数据库的过程是:

   ①装载数据库驱动程序;

   ②通过jdbc建立数据库连接;

   ③访问数据库,执行sql语句;

   ④断开数据库连接。

2.分析

       程序开发过程中,存在很多问题:首先,每一次web请求都要建立一次数据库连接。建立连接是一个费时的活动,每次都得花费0.05s~1s的时间,而且系统还要分配内存资源。这个时间对于一次或几次数据库操作,或许感觉不出系统有多大的开销。可是对于现在的web应用,尤其是大型电子商务网站,同时有几百人甚至几千人在线是很正常的事。在这种情况下,频繁的进行数据库连接操作势必占用很多的系统资源,网站的响应速度必定下降,严重的甚至会造成服务器的崩溃。不是危言耸听,这就是制约某些电子商务网站发展的技术瓶颈问题。其次,对于每一次数据库连接,使用完后都得断开。否则,如果程序出现异常而未能关闭,将会导致数据库系统中的内存泄漏,最终将不得不重启数据库。还有,这种开发不能控制被创建的连接对象数,系统资源会被毫无顾及的分配出去,如连接过多,也可能导致内存泄漏,服务器崩溃。

       上述的用户查询案例,如果同时有1000人访问,就会不断的有数据库连接、断开操作:

 


       通过上面的分析,我们可以看出来,“数据库连接”是一种稀缺的资源,为了保障网站的正常使用,应该对其进行妥善管理。其实我们查询完数据库后,如果不关闭连接,而是暂时存放起来,当别人使用时,把这个连接给他们使用。就避免了一次建立数据库连接和断开的操作时间消耗。原理如下:



二. 技术演进出来的数据库连接池
       由上面的分析可以看出,问题的根源就在于对数据库连接资源的低效管理。我们知道,对于共享资源,有一个很著名的设计模式:资源池(resource pool)。该模式正是为了解决资源的频繁分配﹑释放所造成的问题。为解决上述问题,可以采用数据库连接池技术。数据库连接池的基本思想就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。我们可以通过设定连接池最大连接数来防止系统无尽的与数据库连接。更为重要的是我们可以通过连接池的管理机制监视数据库的连接的数量﹑使用情况,为系统开发﹑测试及性能调整提供依据。

 

       数据库连接池它大大提供了数据库连接的利用率,减小了内存吞吐的开销。我们在开发过程中,就不需要再关心数据库连接的问题,自然有数据库连接池帮助我们处理,这回放心了吧。但连接池需要考虑的问题不仅仅如此,下面我们就看看还有哪些问题需要考虑。

三.连接池还要考虑更多的问题
       1、并发问题

       为了使连接管理服务具有最大的通用性,必须考虑多线程环境,即并发问题。这个问题相对比较好解决,因为java语言自身提供了对并发管理的支持,使用synchronized关键字即可确保线程是同步的。使用方法为直接在类方法前面加上synchronized关键字,如:

      publicsynchronized connection getconnection()

       2、多数据库服务器和多用户

       对于大型的企业级应用,常常需要同时连接不同的数据库(如连接oracle和sybase)。如何连接不同的数据库呢?我们采用的策略是:设计一个符合单例模式的连接池管理类,在连接池管理类的唯一实例被创建时读取一个资源文件,其中资源文件中存放着多个数据库的url地址等信息。根据资源文件提供的信息,创建多个连接池类的实例,每一个实例都是一个特定数据库的连接池。连接池管理类实例为每个连接池实例取一个名字,通过不同的名字来管理不同的连接池。

       对于同一个数据库有多个用户使用不同的名称和密码访问的情况,也可以通过资源文件处理,即在资源文件中设置多个具有相同url地址,但具有不同用户名和密码的数据库连接信息。

       3、事务处理

       我们知道,事务具有原子性,此时要求对数据库的操作符合“all-all-nothing”原则即对于一组sql语句要么全做,要么全不做。

       在java语言中,connection类本身提供了对事务的支持,可以通过设置connection的autocommit属性为false然后显式的调用commit或rollback方法来实现。但要高效的进行connection复用,就必须提供相应的事务支持机制。可采用每一个事务独占一个连接来实现,这种方法可以大大降低事务管理的复杂性。

       4、连接池的分配与释放

       连接池的分配与释放,对系统的性能有很大的影响。合理的分配与释放,可以提高连接的复用度,从而降低建立新连接的开销,同时还可以加快用户的访问速度。

       对于连接的管理可使用空闲池。即把已经创建但尚未分配出去的连接按创建时间存放到一个空闲池中。每当用户请求一个连接时,系统首先检查空闲池内有没有空闲连接。如果有就把建立时间最长(通过容器的顺序存放实现)的那个连接分配给他(实际是先做连接是否有效的判断,如果可用就分配给用户,如不可用就把这个连接从空闲池删掉,重新检测空闲池是否还有连接);如果没有则检查当前所开连接池是否达到连接池所允许的最大连接数(maxconn)如果没有达到,就新建一个连接,如果已经达到,就等待一定的时间(timeout)。如果在等待的时间内有连接被释放出来就可以把这个连接分配给等待的用户,如果等待时间超过预定时间timeout则返回空值(null)。系统对已经分配出去正在使用的连接只做计数,当使用完后再返还给空闲池。对于空闲连接的状态,可开辟专门的线程定时检测,这样会花费一定的系统开销,但可以保证较快的响应速度。也可采取不开辟专门线程,只是在分配前检测的方法。

       5、连接池的配置与维护

       连接池中到底应该放置多少连接,才能使系统的性能最佳?系统可采取设置最小连接数(minconn)和最大连接数(maxconn)来控制连接池中的连接。最小连接数是系统启动时连接池所创建的连接数。如果创建过多,则系统启动就慢,但创建后系统的响应速度会很快;如果创建过少,则系统启动的很快,响应起来却慢。这样,可以在开发时,设置较小的最小连接数,开发起来会快,而在系统实际使用时设置较大的,因为这样对访问客户来说速度会快些。最大连接数是连接池中允许连接的最大数目,具体设置多少,要看系统的访问量,可通过反复测试,找到最佳点。

       如何确保连接池中的最小连接数呢?有动态和静态两种策略。动态即每隔一定时间就对连接池进行检测,如果发现连接数量小于最小连接数,则补充相应数量的新连接以保证连接池的正常运转。静态是发现空闲连接不够时再去检查。

/*以上内容为转载,原文请参考本文开头的链接*/

二、模拟数据库连接池创建思路

关于数据库连接池,如果单从使用角度去学习是很快的,但是要想到该技术产生的原因。结合上文,可以模拟一下数据库连接池,但是数据库连接池的分配和回收存在着大量的问题,其中最难的就是并发,故在此模拟一下连接池基本的思路代码,做一个活尿泥的连接池,其目的在于更好的理解这一技术,代码如下

这里的代码仅仅是思路的模拟,对于真正的数据库连接池来说,这里的模拟是胎儿级别的,只作为理解连接池的思路参考

private static final int minCount;
private static final int maxCount;
private static final int increasement;

private static volatile List<connectionmodel> connectionPool;

private static final String url;
private static final String user;
private static final String psw;

static {
/*
* 通过properties文件来进行最大连接、最小连接的设置ConfigReader类
* 主要为properties文件的解析,其主要目的在于更改这些值时只需要更改
* properties文件
* */
minCount = Integer.valueOf(ConfigReader.getValue("min_count"));
maxCount = Integer.valueOf(ConfigReader.getValue("max_count"));
increasement = Integer.valueOf(ConfigReader.getValue("increasement"));

url = ConfigReader.getValue("database_url");
user = ConfigReader.getValue("database_user");
psw = ConfigReader.getValue("database_password");

listenerList = new ArrayList<>();

try {
Class.forName(ConfigReader.getValue("database_driver"));
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}

public ConnectionPool() {
}

protected List<connectionmodel> getConnectionPool() {
return connectionPool;
}

private static void makeConnections(int count) throws SQLException {
for(int i = 0; i < count; i++) {
Connection connection = null;
connection = DriverManager.getConnection(url, user, psw);
ConnectionModel connectionModel = new ConnectionModel();
connectionModel.setConnection(connection);
/*
* public enum EConnectionStatus {
*
* 空闲
*
* FREE,
*
* 已分配
*
* USED,
*
* 超时
*
* DEAD
}
*/
connectionModel.setStatus(EConnectionStatus.FREE);
connectionPool.add(connectionModel);
}
}

private synchronized static void initPool() throws SQLException {
if(connectionPool == null) {
connectionPool = new ArrayList<>();
makeConnections(minCount);
}
}

/**
* 获取“自由”连接;<br/>
* 若存在free连接,查到并return;<br/>
* 若不存在free,则,查询当前连接池连接数量是否到达最大?<br/>
* 若没有到达最大,则,增加一组连接,并取得其中free连接,返回;
* 否则,暂时返回null
* @return
*/
private ConnectionModel getFreeConnection() {
for(ConnectionModel connection : connectionPool) {
if(connection.getStatus() == EConnectionStatus.FREE) {
return connection.getConnection();
}
}
if(connectionPool.size() < maxCount) {
try {
makeConnections(increasement);
ConnectionModel connection = getFreeConnection();
return connection;
} catch (SQLException e) {
// TODO 增加失败,暂时返回null
return null;
}
}
// TODO 达到连接池最大量,暂时返回null
return null;
}

public void close() {
if(connectionPool == null) {
return;
}
for(ConnectionModel connection : connectionPool) {
try {
connection.close();
} catch (SQLException e) {}
}
}

/*
*	外部通过getConnection()来进行数据库连接池的使用
*  注意上面的诸多方法都是private,这个是public
*/
public ConnectionModel getConnection() {
if(connectionPool == null) {
try
a1e7
{
initPool();
} catch (SQLException e) {
return null;
}
}

return getFreeConnection();
}

/* 使用举例
ConnectionPool pool = new ConnectionPool();
ConnectionModel connection = pool.getConnection();
try {
PreparedStatement statement = connection.prepareStatement("");
statement.executeQuery();
} catch (SQLException e) {
e.printStackTrace();
} catch (ConnectionOutofDutyException e) {
e.printStackTrace();
}
*/
</connectionmodel></connectionmodel>


顺便附上model的内容

/**
* 数据库连接模板成员:
*
* 	数据库连接对象
* 	数据库连接状态;其取值有:
* 			空闲:FREE
* 			已分配:USED
*          超时:DEAD;本项暂时不处理
*
*  已分配数据库连接对象的状态:为了进一步处理包括:编译SQL语句等内容
*/
public class ConnectionModel {
/**
* 数据库连接对象
*/
private Connection connection;
private AtomicInteger time;
/**
* 数据库连接状态
*/
private EConnectionStatus status;

protected ConnectionModel() { }

protected void setConnection(Connection connection) {
this.time = new AtomicInteger(0);
this.connection = connection;
}

protected ConnectionModel getConnection() {
status = EConnectionStatus.USED;
time.set(0);

return this;
}

protected void check(int maxTime) {
synchronized (time) {
if(status == EConnectionStatus.FREE) {
return;
}
if(time.incrementAndGet() >= maxTime) {
status = EConnectionStatus.FREE;
}
}
}

protected int getRequestTime() {
return time.get();
}

public PreparedStatement prepareStatement(String sql)
throws SQLException, ConnectionOutofDutyException {
if(status == EConnectionStatus.USED) {
return connection.prepareStatement(sql);

}
throw new ConnectionOutofDutyException("连接已超时");
}

public void close() throws SQLException {
status = EConnectionStatus.FREE;
}

protected EConnectionStatus getStatus() {
return status;
}

protected void setStatus(EConnectionStatus status) {
this.status = status;
}
}

/*关于properties文件,即ConfigReader类的实现,网上有很多解析properties文件的教程,这里不作为重点*/

database_driver=com.mysql.jdbc.Driver
database_url=jdbc:mysql://192.168.1.4:3306/***
database_user=root
database_password=***

min_count=4
max_count=30
increasement=3
max_delay_time=30000
interval_time=2000


三、关于c3p0的使用

public class ConnectionManager {
private static ConnectionManager instance;
private static ComboPooledDataSource dataSource;

private ConnectionManager() throws SQLException, PropertyVetoException {
/*
* 通过newComboPooledDataSource类,来实现使用连接池的目的
* */
dataSource = new ComboPooledDataSource();

dataSource.setUser(ConfigReader.getValue("database_user"));     //用户名
dataSource.setPassword(ConfigReader.getValue("database_password")); //密码
dataSource.setJdbcUrl(ConfigReader.getValue("database_url"));//数据库地址
dataSource.setDriverClass(ConfigReader.getValue("database_driver"));
dataSource.setInitialPoolSize(Integer.valueOf(ConfigReader.getValue("init_count"))); //初始化连接数
dataSource.setMinPoolSize(Integer.valueOf(ConfigReader.getValue("min_count")));//最小连接数
dataSource.setMaxPoolSize(Integer.valueOf(ConfigReader.getValue("max_count")));//最大连接数
dataSource.setAcquireIncrement(Integer.valueOf(ConfigReader.getValue("increasement")));//增加数量
dataSource.setMaxStatements(Integer.valueOf(ConfigReader.getValue("max_delay_time")));//最长等待时间
dataSource.setMaxIdleTime(Integer.valueOf(ConfigReader.getValue("interval_time")));//最大空闲时间,单位毫秒
}

public static final ConnectionManager getInstance() {
if (instance == null) {
try {
/*通过静态块进行构造*/
instance = new ConnectionManager();
} catch (Exception e) {
e.printStackTrace();
}
}
return instance;
}

public synchronized final Connection getConnection() {
Connection conn = null;
try {
/*真正的获取一次连接*/
conn = dataSource.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
return conn;
}
}

public class Test {
public static void main(String[] args) {
/*分配20个连接*/
for (int i = 0; i < 20; i++) {
Connection connection = ConnectionManager.getInstance().getConnection();
try {
PreparedStatement prepare = connection
.prepareStatement("select * from sys_stu_info");
ResultSet rs = prepare.executeQuery();
while (rs.next()) {
String id = rs.getString("id");
String name = rs.getString("name");
System.out.println(id + ":" + name);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
connection.close();
System.out.println("第" + i + "次连接");
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}

database_driver=com.mysql.jdbc.Driver
database_url=jdbc:mysql://192.168.1.211:3306/sys_student_info
database_user=root
database_password=

min_count=1
init_count=5
max_count=50
increasement=5
max_delay_time=30000
interval_time=2000
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: