JDBC代码实现之第四版:利用连接池重构DBTool工具类
前言
由JDBC代码实现之第三版:创建语句对象与执行SQL语句 我们通过DBTool工具封装了注册驱动,建立、关闭连接的代码,并用连接创建了语句对象Statement,调用它的对应方法执行了相应的sql语句。但通过DriverManager直接创建连接还存在着一些问题,下面通过连接池帮助我们解决这些问题。
1.连接池的缘来
-
什么是连接池:
连接池(DataSource):类似于常量池和线程池,池实际上就是个对象,我们把一些特定的对象放到池对象里,利用池帮我们管理对象。它主要的优点,就是能够让池中的对象加以复用。连接池,有的人喜欢把它叫数据源,因为Sun规定那个连接池的接口叫DataSource,直译过来叫数据源,但其实它更应该叫连接池比较合适。连接池是帮我们管理数据库连接对象的对象,其主要功能是让连接池中的连接可以复用。 -
话题一,不用连接池时的问题:
- 正常地,如果不用连接池,我们在DBTool工具类中来创建连接,最终从根上讲,就是直接利用DriverManager帮我们创建连接Connection,那么直接使用DriverManager创建连接时,它会这样,这个DriverManager,它并没有办法让连接复用,我们每次调用DriverManager类中的getConnection()方法创建连接时,它都会给我们创建一个全新的连接,给我们返回来,而没有复用连接,但是创建一个新连接是很耗资源与开销的,如果可以复用的话,效率就会高一点,因为毕竟你每次都要创建一个新对象。而且,我们创建一个连接是需要连上数据库的,实际上,在真正的项目中,是由服务器去创建连接,并访问数据库的,而服务器和数据库,很有可能不在一个地方,比如说,可能是这样的,我在北京打开浏览器,去访问淘宝的服务器,淘宝的服务器比如说在杭州,然后淘宝的服务器访问淘宝的数据库,这个数据库可能在上海,这是有距离的,你每次创建连接,连上一个比较远的数据库,这个是有开销的,所以如果频繁的创建新连接的话,这个代码的执行效率就会很低。如果能够让连接复用的话就会更好,所以,最好是能复用。当我们使用DriverManager时,一调用它,就创建新连接,没有复用,这是第一个问题。
- 另外,还有第二个问题,我们每次调用DriverManager,它都会给我们创建新连接,它挺靠谱儿,那问题是,如果说我们同时有很多人调用它,同时访问它,它给每个人创建个连接,就是并发的人数太多的时候,这个可能是导致,连接超过数据库的极限,会导致数据库的崩溃,那通常,一个数据库的连接极限是五六百,这个没有一个固定的数字,说一定是五百,一定是600,不是的,就五百和六百差不多,那你只要超过500,随时有可能就会崩溃 (那你想啊,咱们双11的时候,同时访问淘宝的人会多少,上亿对吧,上亿人次,它不得创建上亿个连接,把数据库找崩溃了),所以,他还有一个缺点,就是DriverManager它没有这个管理连接的上限,那么当并发数大的时候,就可能导致数据库崩溃。总而言之,我们直接使用DriverManager,如果是开发一个小的项目还可以,就是没几个人用,这没问题,如果是开发一个比较大的项目,这显然是不行的,所以,我们需要用连接池来管理连接,能让连接复用,提高效率,我们也希望用连接池来管理连接上限,避免数据库崩溃。
-
话题二:连接池的作用,就是解决第一个话题问题
连接池的作用就是解决利用DriverManager创建连接时,它所存在这些问题。即连接池的作用,就是解决直接利用DriverManager创建连接时所存在的一些问题,准确来说,就是
1.它能复用连接,提高效率。
2.它能管理连接上限,避免数据库崩溃。
2.连接池的工作原理与工作场景
- 工作场景分析:
实际上,连接池管理连接这个工作场景是源于生活的,生活中能找到它的影子,但是它源于生活,高于生活。在生活中和它相似的一个场景,比如早晨卖油条的小摊老板,他给客人拿油条,他得用一个盘子装上来,他管理那个盘子和连接池管理连接的原理差不多,油条老板早上一出摊,首先要把桌子摆好,然后他先得拿出,比如说十个盘子,准备给大家用,如果说来的这个客户,吃早餐的客户不超过十个,然后谁要油条,他就十个盘子就够了,反复的复用,那你来了,我给你装个油条给你,你吃完了收回来,下个人来了接着用,都这么干的,对吧,是吧,你们吃过油条么,我瞅你们的反应啊,没吃过是吧,是这样吧,就是说,卖油条的老板啊,他都会这样啊,先掏出几个盘子来,反复的复用,也是为了他效率高,省事儿。 那问题是有些时候,这个突然来了一拨人,这个盘子不够用了,不够用怎么办呢,油条老板会从他的这个箱子里,再掏出另外十个盘子,让20个盘子,再这个复用,如果要来的人多,又不够用,怎么办呢,再掏出10个,30个复用,他为什么这么干,他怎么不一下掏出50个盘子,就复用呗,那样的话呢,比如就同时来几个人,或十几个人吃饭的话,这50个盘子,都被弄脏了,那他刷的话,不刷的多么,是吧,费劲,那如果说呢,这个他每次控制这个盘子数量,刚好够这些人用的话,他最终刷的盘子,比较少一点,是这个意思,那么连接池,它的工作原理,和这个有点相似,这个连接它的这个工作场景是一个动态的过程,但是呢,我用语言,或者用文字,很难直接表达这个动态的过程,我就把某几个场景,说一下,你把它在脑子里串连起来。就是一个动态的过程,自己脑补一下,
想象一下。 - 连接池的工作原理
那么连接池工作时,有这样几个场景:
- 当创建连接池之后,那么它会自动创建一批连接,放于池内,这连接池就是个对象儿,那我们创建连接池对象以后,它会自动地创建一批连接,放到它之内,你得把它理解为是个集合之类的东西,这里面装了很多连接,那创建一批连接,多少个呢,这是可以配的。
- 再有,当用户调用连接池时,调用它时,它会给用户一个连接,并将连接标记为占用,因为有用户已经占上了这个连接,别人就不能用了,所以标记为占用,不让别人用了 (就好像我去吃油条,这油条给我了,这盘子就是我用了,别人用不了了)。然后,当用户使用完连接后,他要将连接归还给连接池,那连接池会将这个连接标记为空闲。即我们给连接池默认创建的连接,他都是空闲的,然后谁用了,就占用了,谁归还了,又空闲了。
- 如果连接池发现连接快不够用了 (比如说还只有一个空闲连接了,马上就不够用了,这个时候),那么它会再创建一批连接,那这一批连接是多少个呢,可配,所以连接池,它是自动的,一个管理连接的过程,连接池连接快不够用了,它就再创建一批连接,自动的。
- 若占用的连接达到数据库上限的时候,那么连接池会让新的用户等待,因为此时没有新的连接可以给你分配了,你等着,这样就避免了这个连接池的连接超上限,如果不加管理的话,连接会超上限,就会导致数据库崩溃,所以,若占用的连接如果达到连接数据库的上限时,就会让用户等待,那当然,数据库的上限是多少呢,可配。
- 最后一条儿就是,若在高峰期过后,刚才那是高峰期,很多人要获取连接,要用,现在都用完了,基本上都不用了,那剩这些个连接,连接池会关闭一批连接,就不需要这么多了,关闭一批,关闭多少呢,可配。(比如卖油条啊,现在来了50多个人吃油条,高峰期,吃完以后,很多人都走了,还剩个三两个人,高峰期过了以后,因为刚才来的人太多了,是不是有很多盘子,现在他们都走了,一堆空盘子对吧,那这空盘子怎么处理,再装回箱子里吧。)
- 总结:在连接池的工作过程中会包含这么几个场景。
- 当我们创建连接池对象以后,它会自动创建一批(配)连接,放于池内。
- 当用户调用它时,它会给用户一个连接,并将连接标为占用。
- 当用户使用完连接后,将连接归还给连接池,它会将连接标为空闲,可以继续复用。
- 若连接池发现连接快不够用时,它会再创建一批(配)新连接。
- 如果占用的连接达到数据库上限(配)时,连接池会让新用户等待。
- 在高峰期过后,连接池会关闭一批(配)连接。
3.连接池的使用
- Sun,推出了JDBC技术,Sun只规定了接口,由数据库厂商提供具体的实现类,然后,DriverManager帮我们去实例化,同样的,Sun,只规定了连接池的接口,这个接口叫做DataSource,由各个连接池的厂商提供具体的实现类(比如DBCP,C3P0,Druid等),以DBCP为例,DBCP实现了连接池的接口DataSource,这个实现类叫BasicDataSource。
- DataSource与DriverManager比较:DataSource创建的Connection既有基本实现(DataSource中封装了DriverManager的使用),也有连接池实现(可以复用,DataSource帮我们实现了复用机制),而DriverManager创建的Connection则不能复用(当然自己写连接池,自己来实现复用机制也是可以的,比如使用动态代理实现自定义连接池)。使用DataSource的一个好处是可以在外边配置(如C3P0直接修改xml文件,不用自己写配置文件db.properties)。
- 通过对DataSource和DriverManager比较可知,实现了DataSource接口的连接池,是可以直接替代DriverManager功能的实现。使用连接池管理连接的具体使用步骤实际上和利用DriverManager直接管理连接的方式是大同小异的。只不过把DriverManager换成连接池DataSource而已。我们通过对JDBC代码实现之第三版中由DriverManager管理连接方式封装的DBTool工具类的基础上进行改进,从而重新写一个由连接池管理连接的新工具类DBUtil,getConnection方法依然是提供给调用者获取连接的主要方法,但是在这个方法里,不是用DriverManager的连接,而用的是DataSource连接池,使用连接池帮我们创建连接的前提依然是,需要我们把创建连接所需的条件,数据库的驱动类名,路径,账号,密码,那四个参数给他,所以,还要读这些参数,而这些参数依然在配置文件db.properties中配置,另外,这个文件里不单单要有数据库连接参数,还得加上一些连接池的参数,比如连接池默认创建几个连接,数据库的连接上限,每次关闭多少连接,每次创建多少连接等都是可配的。
- 同数据库的连接参数类似,为了便于修改,减少不必要的I/O操作,连接池的相关参数依然只读一次即可,为保证只读一次配置文件,连接池的参数也在static静态块中进行读取,读到参数以后,这些参数最终是要给连接池用,而getConnection()要调连接池,一个池中可以同时存储几百个连接,一个池就够了,所以连接池也在static块中创建。所以static中需要做三件事,一是读取参数,二是创建连接池,只创建一次,然后连接池创建好以后,连接池需要用到读取到的这些参数,所以,第三件事是给连接池设置参数。而static中创建好的连接池是要给getConnection()方法用,在static里声明的内容是不能直接被getConnection()方法用的,所以还需要把这个连接池声明到static外面去,把它做成一个全局的变量(因为是使用的是DBCP连接池,所以这个变量的类型为BasicDataSource),只是在static里初始化一次而已。那么getConnection()方法就能够直接取到这个创建好的连接池了。最终把由DriverManager方式管理连接的DBTool,改进为基于连接池对连接进行管理的DBUtil工具类。
步骤如下:
- 先读取properties文件,在类加载时,读取一次properties文件。
- 在static中读取参数,创建连接池,并将读到的参为连接池所用,设置参数,然后把这个连接池声明到static之外,做成一个全局的变量。
- 我们在static里创建连接池,赋值给它由BasicDataSource类型声明的全局变量,由getConnection调用这个变量,最后给调用者返回连接。
4.常用的连接池:DBCP导包
-
在市场中有很多的连接池,比较常用有DBCP,C3P0,Druid等,这里会对DBCP做出一些演示,其它两个具体的用法和区别参考相关文献,比如C3P0和Hibernate框架结合使用等,只作为了解内容。要使用DBCP连接池,因为它是第三方的,不是由Sun提供的,是别人提供的,所以要先导包。访问Maven的服务器,搜索commons-dbcp,能搜到很多的结果,选择如下版本:
Group: commons-dbcp Artifact: commons-dbcp Version: Show All Versions Most Popular Version: ? Download: pom,jar
它的组名叫commons-dbcp,项目名也叫commons-dbcp,它下面会有很多的版本,1.0的,1.1的,1.3的,还有一些如20030825.184428,这种带有年月日的版本,它通常不是正式版,是测试版,测试版可能有bug,尽量不要使用,这里用1.3的正式版,选择commons-dbcp-1.3.jar
,代码如下:Group: commons-dbcp
Artifact: commons-dbcp
Version: 1.3
Extension: jar
XML:
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.3<version>
</dependency>
-
复制这段代码,粘贴到Eclipse中的pom.xml中,在dependencies下与ojdbc的xml代码平级,代码如下:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.tedu</groupId>
<artifactId>jdbc</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc14</artifactId>
<version>10.2.0.4.0</version>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.3</version>
</dependency>
</dependencies>
</project>
-
DBCP这个包,它其实不是一个包,虽然在Maven服务器搜到的是一个包,但它是有依赖的包的,那么它有几个包,依赖于谁,可以从Eclipse工具中的pom.xml看到,打开以后有5个选项,第3个Dependency Hierarchy,从这里可以很直观的看到,我们导入的commons-dbcp这个包,它依赖于commons-pool:
ojdbc14 : 10.2.0.4.0 [compile]
commons-dbcp : 1.3 [compile]
commons-pool : 1.5.4 [compile]如上可知,commons-dbcp : 1.3和commons-pool : 1.5.4,这两个包将同时被下载下来,同时,在工具左侧的Maven Dedenpencies之下,展开会发现三个包,然后,每一个包存到哪儿去了,也能在这里看到:
Maven Dependencies
ojdbc14-10.2.0.4.0.jar - C:\Users\fhy.m2\repository\com\oracle\ojdbc14\10.2.0.4.0
commons-dbcp-1.3.jar - C:\Users\fhy.m2\repository\commons-dbcp\commons-dbcp\1.3
commons-pool-1.5.4.jar - C:\Users\fhy.m2\repository\commons-pool\commons-pool\1.5.4
5.配置文件db.properties:配置参数
这里的配置文件即db.properties,这里包含两个部分:一个是数据库的连接参数,一个是连接池的参数,数据库的连接参数已经配置,这里再加连接池的参数,配置连接池常用参数如下:
- 初始连接数,
- 最大连接数,
- 最小连接数,
- 每次增加的连接数,
- 超时时间
- 最大空闲数
- 最小空闲数
连接池的参数其实还有很多很多,在工作当中,一般由项目经理或者设计架构,他们会事先根据客户的数据库情况,客户的服务器情况 和 应用需求,配置好合适的值,具体的参数配置规则,这个经验是需要一个时间的积累和沉淀的。下面配置两个简单的连接池参数,一个是初始化连接数,取名叫initsize,设置为1,一个是最大连接数,取名叫maxsize,设置为2,最小一,最大二,一个人够玩了。一个数据库的连接上限就500,如果很多人共用一个数据库(比如同时开启多个线程),初始连接数太大,或最大连接数太大,超过了连接上限,数据库就很容易崩溃。
配置如下:# database connection parameters
driver=oracle.jdbc.driver.OracleDriver
url=jdbc:oracle:thin:@localhost:1521:orcl
user=SYSTEM
pwd=Oracle123
# datasource parameters
initsize=1
maxsize=2
6.代码实现
- 已经做好DBCP导包和db.properties配置参数这两个准备工作,这里便开始着手写DBUtil这个工具类,读取参数,创建连接池对象,写getConnection()方法。连接池比DriverManager更好,它是一个动态管理连接的方式,能让连接复用,提高效率;能够管理连接上限,避免数据库的崩溃。DBUtil与DBTool代码逻辑基本相似,连接池的其他参数也可以自己配置,官网可查,基本上都大同小异。DBUtil也可分为三个部分,变量,静态块和方法。主要的方法是获取连接,为了方便,把关闭连接的方法也写上。getConnection方法和关闭连接的方法,是给调用者调用的,所以方法必须是公有的,为了方便最好是静态的。getConnection方法给调用者返回连接,所以返回值是Connection。在util包下创建DBUtil类(名字自取)。代码结构:
package util;
import java.sql.Connection;
import org.apache.commons.dbcp.BasicDataSource;
public class DBUtil {
//连接池对象-由DBCP提供
private static BasicDataSource ds;
static {
}
public static Connection getConnection() {
return null;
}
public static void close(Connection conn) { }
}
变量
在DBUtil中需要声明一个全局变量,即创建一个连接池,并把它声明成一个全局的变量,用来记录这个连接池对象,因为这个变量是在static静态块中初始化,创建的,而且在静态的getConnection()方法里去使用它们,所以它也需要是静态的。因为我们在这个类中局部使用就可以,外界不需要用,所以把这个变量的访问权限指定为私有属性private。这个变量声明的类型是BasicDataSource,它是DBCP连接池给我们提供的一个实现类。代码示例:
package util;
import org.apache.commons.dbcp.BasicDataSource;
public class DBUtil { private static BasicDataSource ds; }
静态块
除了变量以外,第二部分静态块中的内容是读取参数,创建连接池,并给它设置参数
- 读取参数:
- 因为是要从classes下面去读取配置文件,所以实例化Properties类,调用它load方法加载配置文件。我们调用某一个类,获取它的ClassLoader,然后利用ClassLoader读取那个文件db.properties,那么任何类都有ClassLoader,通常,我们会写当前的类,因为不希望依赖于别人,依赖于自己比较靠谱:
p.load(DBUtil.class.getClassLoader().getResourceAsStream("db.properties"));
好了,那这句话写完了,因为他抛了异常,那我们得try-catch处理,catch到异常的时候,就证明这个文件或者是这文件中的内容,肯定有问题,没读好,所以,我们要给予提示,要向上抛,因为我们处理不了,只能向上抛:
throw new RuntimeException("加载db.properties失败", e);
代码示例:
package util;
import java.sql.Connection;
import java.util.Properties;
import org.apache.commons.dbcp.BasicDataSource;
public class DBUtil {
//连接池对象-由DBCP提供
private static BasicDataSource ds;
static {
Properties p = new Properties();
try {
p.load(DBUtil.class.getClassLoader().getResourceAsStream(“db.properties”));
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(“加载db.properties失败”, e);
}
}
public static Connection getConnection() {
return null;
}
public static void close(Connection conn) { }
} - 配置文件加载完以后,我们需要从这个文件中读参数,目前,一共是六个参数,四个是数据库用的,两个是连接池用的,那我们都得读取到,给写上注释,这是要做的第一个事儿,就是读取这六个参数,读取方式都一样,就是参数名不同,注意区分一下,就可以了。代码示例:
package util;
import java.sql.Connection;
import java.util.Properties;
import org.apache.commons.dbcp.BasicDataSource;
public class DBUtil {
//连接池对象-由DBCP提供
private static BasicDataSource ds;
static {
Properties p = new Properties();
try {
p.load(DBUtil.class.getClassLoader().getResourceAsStream(“db.properties”));
//读取参数
String driver = p.getProperty(“driver”);
String url = p.getProperty(“url”);
String user = p.getProperty(“user”);
String pwd = p.getProperty(“pwd”);
String initsize = p.getProperty(“initsize”);
String maxsize = p.getProperty(“maxsize”);
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(“加载db.properties失败”, e);
}
}
public static Connection getConnection() {
return null;
}
public static void close(Connection conn) { }
}
- 创建连接池:
一共读取到六个参数,其中最后两个,一个是初始连接数,一个是最大连接数,这两个数字应该是整数,但是从文件中都是以字符串的形式读取的,所以想当整数来用的话,后面应用时需要做相应的转型处理,读到参数以后,就需要创建连接池,创建连接池就是new,实例化,实际就一句话:
ds = new BasicDataSource();
- 设置参数:
那么创建连接池,实例化BasicDataSource以后,并将之前读取到参数设置连接池,DataSource中有很多的set方法,比如setDriverClassName设置驱动。ds.setDriverClassName(driver);,它会利用这个driver注册驱动,就是,有了连接池以后,我们就不用自己注册驱动了,连接池会帮我们注册,自动的;然后,驱动后面,还有三个参数是,路径,账号,密码,那么连接池会用这三个参数创建连接;后面,还有两个参数,当然这是连接池参数,连接池还可以有更多的参数。那我们读到是字符串,它需要的是整数,我们需要把字符串转为整数,将字符串转为整数,有多种方式,可以new Integer,可以调Interger的parseInt方法,或者是valueOf方法,都能实现,都可以,那总而言之,连接池会使用这些参数来管理连接,另外,因为连接池的这些参数,它都有默认值,不设置有默认值,可以用,我们设置的话,是改变了这个默认值,覆盖了而已。其实你连这两个参数不设置都没问题。代码如下:
package util;
import java.io.IOException;
import java.sql.Connection;
import java.util.Properties;
import org.apache.commons.dbcp.BasicDataSource;
public class DBUtil {
//连接池对象-由DBCP提供
private static BasicDataSource ds;
static {
Properties p = new Properties();
try {
p.load(DBUtil.class.getClassLoader().getResourceAsStream(“db.properties”));
//读取参数
String driver = p.getProperty(“driver”);
String url = p.getProperty(“url”);
String user = p.getProperty(“user”);
String pwd = p.getProperty(“pwd”);
String initsize = p.getProperty(“initsize”);
String maxsize = p.getProperty(“maxsize”);
//创建连接池
ds = new BasicDataSource();
//设置参数
//使用这个参数注册驱动
ds.setDriverClassName(driver);
//使用这3个参数创建连接
ds.setUrl(url);
ds.setUsername(user);
ds.setPassword(pwd);
//使用其他参数管理连接
ds.setInitialSize(new Integer(initsize));
ds.setMaxActive(new Integer(maxsize));
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(“加载db.properties失败”, e);
}
}
public static Connection getConnection() throws SQLException {
return null;
}
public static void close(Connection conn) { }
}
方法
- 完成读取参数和静态块部分,我们的连接池已经创建完成,那么我们在getConnection方法当中就可以使用了,然后,在这个方法里,我们使用连接池来创建连接,一句话,ds.getConnection()就可以了,这个方法声明抛了异常,我们就把异常抛出去,强制调用者catch,以免他忘了写finally,直接向上抛:
public static Connection getConnection() throws SQLException {
return ds.getConnection();
} - 还有个close方法,在利用DriverManager封装DBTool工具类时,这里是要关闭连接的,但在这里,这个方法名依然叫close,但实际上并不是关闭连接,而是归还连接。即连接池有很多连接,我去调用它,它给我一个连接,我不用时再归还给池,可以复用。为了严谨,归还连接时,首先判断一下,如果Connection非空,我们才close,
conn.close();
这句话也声明抛了异常,进行try-catch处理下,当然,即便catch到异常,我们也处理不了,给予提示,向上再抛:
public static void close(Connection conn) {
if(conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(“归还连接失败”, e);
}
}
} - 问:此处的close方法与基于DriverManager的DBTool中的逻辑和代码是一样的,但为什么同样是调用Connection的close方法,一个是关闭连接,一个却是归还连接呢?
**答**:由连接池创建的连接,它对这个连接做了手脚,做了处理。那么和传统DriverManager管理连接的close方法相比,它被重写了,本来是关闭连接,现在变成了归还连接的逻辑,也就是,即连接池,会将连接的状态设置为空闲,并清空里面(连接中)所包含的任何数据。
完整代码:
package util;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
import org.apache.commons.dbcp.BasicDataSource;
public class DBUtil {
//连接池对象-由DBCP提供
private static BasicDataSource ds;
static {
Properties p = new Properties();
try {
p.load(DBUtil.class.getClassLoader().getResourceAsStream("db.properties"));
//读取参数
String driver = p.getProperty("driver");
String url = p.getProperty("url");
String user = p.getProperty("user");
String pwd = p.getProperty("pwd");
String initsize = p.getProperty("initsize");
String maxsize = p.getProperty("maxsize");
//创建连接池
ds = new BasicDataSource();
//设置参数
//使用这个参数注册驱动
ds.setDriverClassName(driver);
//使用这3个参数创建连接
ds.setUrl(url);
ds.setUsername(user);
ds.setPassword(pwd);
//使用其他参数管理连接
ds.setInitialSize(new Integer(initsize));
ds.setMaxActive(new Integer(maxsize));
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("加载db.properties失败", e);
}
}
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
/**
* 由连接池创建的连接,连接的close方法被连接池重写了,变为了归还连接的逻辑,
* 即:连接池会将连接的状态设置为空闲,并清空连接中所包含的任何数据。
*/
public static void close(Connection conn) {
if(conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException("归还连接失败", e);
}
}
}
}
7.测试
创建连接
DBUtil工具类以上基本完成,下面利用JUnit工具对DBUtil创建连接进行测试。先声明这个Connection,然后先让它默认为null,再给它赋值,那么因为这个方法它抛异常啊,还得try-catch处理,然后因为这是测试代码,catch到异常以后,就先不管了,然后还得写finally,一定要在finally当中呢关闭连接,最终我们创建好连接以后,把这连接输出一下,看一看有没有结果,如果没报错,有结果,就是对的。代码如下:
/**
* 测试DBUtil的方法
*/
@Test
public void test() {
Connection conn = null;
try {
conn = DBUtil.getConnection();
System.out.println(conn);
} catch (SQLException e) {
e.printStackTrace();
} finally {
DBUtil.close(conn);
}
}
执行SQL
通过连接池创建的连接执行一个DQL语句,首先看一下表中数据:
select * from emps order by empno;
查询如下数据,其中条件是员工的id,是一个具体的数字:
select * from emps where empno>id;
因为在正式的项目中,不可能写死一个sql,那sql中的所需要的条件,应该是由用户输入的,是来源于浏览器的,为了更符合正式项目的基本规则,这里假设一个条件,并把它动态拼到sql上,作为从浏览器获取的条件(比如我去淘宝,要搜索一个双肩包,输入的条件就是双肩包,就把它作为sql的条件)。假设的条件可以用Scanner去模拟输入,但没什么意义,就在测试代码中直接写死一个数,赋值给条件变量即可。代码如下:
/**
* 测试DBUtil的方法
*/
@Test
public void test() {
//假设浏览器传入的查询条件是
int empno = 2525;
Connection conn = null;
try {
conn = DBUtil.getConnection();
System.out.println(conn);
Statement smt = conn.createStatement();
String sql = "select * from emps_lhh where empno>"+empno;
ResultSet rs = smt.executeQuery(sql);
while(rs.next()) {
System.out.println(rs.getInt("empno"));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
DBUtil.close(conn);
}
}
那么测试查询到的结果,肯定是多行,所以遍历结果集,每次遍历有多列,但只输出其中一列ID参考,其他的列与此类似。
8.总结
本篇从什么是连接池,通过DriverManager与DataSource两种方式管理连接的方式对比,不使用连接池存在的问题,与使用连接池的改进,阐明了连接池的作用。然后又介绍了市场上常用的有哪些连接池,并以DBCP为例,对连接池的工作过程和工作原理加以介绍,最后演示了如何使用连接池,总体可归结为五个方面,层层递进的对连接池形成了一个系统的认识。
- 为什么要使用连接池:
- 数据库连接的建立及关闭资源消耗巨大。
- 传统数据库访问方式:一次数据库访问对应一个物理连接,每次操作数据库都要打开、关闭该物理连接,系统性能严重受损。
- 解决方案:数据库连接池(Connection Pool):系统初始运行时,主动建立足够的连接,组成一个池。每次应用程序请求数据库连接时,无需重新打开连接,而是从池中取出已有的连接,使用完后,不再关闭,而是归还。
- 连接池中连接的释放与使用原则
- 应用启动时,创建初始化数据的连接
- 当申请时无连接可用或者达到指定的最小连接数,按增量参数值创建新的连接
- 为确保连接池中最小的连接数的策略:
- 动态检查:定时检查连接池,一旦发现数量小于最小连接数据,则补充相应的新连接,保证连接池正常运转
- 静态检查:空闲连接不足时,系统才检测是否达到最小连接数
- 按需分配,用过归还,空闲超时释放,获取超时报错。连接池也只是接口,具体实现由厂商来完成
参考文献(References)
文中如有侵权行为,请联系me。。。。。。。。。。。。。
文中的错误,理解不到位的地方在所难免,也请指教!在成长过程中,也将继续不断完善,不作为专业文章。不喜勿喷。