1. 数据库连接池

JDBC部分的前两个总结主要总结了一下JDBC的基本操作,而且有个共同点,就是应用程序都是直接获取数据库连接的。这会有个弊端:用户每次请求都需要向数据库获得连接,而数据库创建连接通常需要消耗相对较大的资源,创建时间也较长。假设一个网站每天有10万次访问量,那么数据库服务器就需要创建10万次连接,这极大的浪费数据库的资源,并且极易造成数据库服务器内存溢出、脱机。 
  为了解决上述问题,数据库连接池的技术便得到了广泛的使用。为了优化性能,应用程序一启动,就向数据库要一批连接放到池(也就是一个集合而已)中,当用户箱操作数据库的时候,直接找连接池要,当完成对数据库的操作后,再把连接还给连接池供其他用户使用。 
  有了上面的思路,我们可以自己写一个数据库的连接池,主要使用动态代理技术。

2. 自己编写的数据库连接池

2.1 连接池JdbcPool类的实现

  编写连接池需要实现Java.sql.DateSource接口。DateSource接口中定义了两个重载的getConnection方法:Connection getConnection(); 方法和 Connection getConnection(String username, String password); 方法,实现连接池功能的步骤如下:

  • 在DateSource构造函数或者静态代码块中批量创建与数据库的连接,并把创建的连接加入LinkedList对象中;
  • 实现getConnection方法,让getConnection方法每次调用时,从LinkedList中取一个Connection返回给用户;
  • 当用户使用完Connection,调用Connection.close()方法时,Connection对象应保证将自己返回到LinkedList中(编程的难点,动态代理实现),而不要把conn还给数据库。
public class JdbcPool implements DataSource {

        private static LinkedList<Connection> list = new LinkedList<Connection>();
static{
try{
InputStream in = JdbcPool.class.getClassLoader().getResourceAsStream("db.properties");
Properties prop = new Properties();
prop.load(in);//加载配置文件 String driver = prop.getProperty("driver");
String url = prop.getProperty("url");
String username = prop.getProperty("username");
String password = prop.getProperty("password"); Class.forName(driver); //加载驱动
//连接池中创建10个Connection
for(int i = 0; i < 10; i++){
Connection conn = DriverManager.getConnection(url, username, password);
System.out.println("获取到了连接" + conn);
list.add(conn);
}
} catch(Exception e){
e.printStackTrace();
throw new ExceptionInInitializerError(e);
}
} /*
* 用动态代理,返回一个代理对象出去,拦截close方法的调用,对close进行增强
*/
@Override
public synchronized Connection getConnection() throws SQLException {
if(list.size() > 0){
final Connection conn = list.removeFirst();//删掉并返回给conn
// return conn;//这里不能直接return,因为用户使用完了后,调用conn.close()会操作数据库,并没有把这个conn返回给连接池中
System.out.println("池大小是" + list.size()); //下面用动态代理技术来写:
//动态代理技术:使用的是拦截技术
return (Connection) Proxy.newProxyInstance(JdbcPool.class.getClassLoader(), new Class[]{Connection.class}, new InvocationHandler() {
//这里的第二个参数原来为conn.getClass.getInterface(),不过会出错,最终改成了new Class[]{Connection.class}
//原因见帖子:http://blog.csdn.net/njchenyi/article/details/3091092
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable { if(!method.getName().equals("close")){//如果判断不是调用close方法,不管就是了
return method.invoke(conn, args);
}
else{//如果是调用close方法,将conn放到连接池里
list.add(conn);
System.out.println(conn + "被还到池中");
System.out.println("池大小为" + list.size());
return null;
}
}
}); }
else{
throw new RuntimeException("对不起,数据库忙");
}
}
//下面是其他需要实现的方法,默认生成即可,不用写代码
@Override
......
}

2.2 Jdbc工具类JdbcUtils的实现

  有了上面的连接池,我们在工具类中获取Collection就不用像原来那样使用DriverManager来获取Connection了,我们可以直接使用自己的连接池了,如下:

public class JdbcUtils {

    private static JdbcPool pool = new JdbcPool();//定义一个连接池
public static Connection getConnection() throws SQLException {
return pool.getConnection();//直接从连接池中获取一个Connection
} public static void release(Connection conn, Statement st, ResultSet rs) {
if(rs != null){
try{
rs.close();
}catch(Exception e) {
e.printStackTrace();
}
rs = null;
}
if(st != null){
try{
st.close();
}catch(Exception e) {
e.printStackTrace();
}
st = null;
} if(conn != null){
try{
conn.close();
}catch(Exception e) {
e.printStackTrace();
}
conn = null;
}
}
}

2.3 写一个测试用例

  我们写一个模拟转账的程序来测试一下自己写的连接池能不能正常使用:

public class Demo1 {
//模拟转账
@Test
public void changeAccount () {
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
Savepoint sp = null; try{
conn = JdbcUtils.getConnection();
conn.setAutoCommit(false);//相当于开启事务start transaction
String sql1 = "update account set money=money-100 where name=?";
st = conn.prepareStatement(sql1);
st.setString(1, "aaa");
st.executeUpdate(); // sp = conn.setSavepoint();
// int x = 1 / 0; //故意让程序抛异常,用来抓取然后手动回滚,测回滚用的 String sql2 = "update account set money=money+100 where name=?";
st = conn.prepareStatement(sql2);
st.setString(1, "bbb");
st.executeUpdate(); conn.commit();
}catch(Exception e){
try {
conn.rollback(sp);//回滚到指定位置,该位置之前的sql都被有效处理
conn.commit();//回滚了要记得提交
} catch (SQLException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
e.printStackTrace();
}finally{
JdbcUtils.release(conn, st, rs);
}
}
}

3. 开源数据库连接池

 现在很多web服务器(Weblogic,WebSphere, Tomcat)都提供了DataSource的实现,即连接池的实现。通常我们把DataSource的实现,按其英文含义称之为数据源,数据源中都包含了数据库连接池的实现。也有一些开源组织提供了数据源的独立实现:如DBCP数据库连接池和C3P0数据库连接池(spring中用的就是这个)。使用这些连接池时不需要编写连接数据库的代码,直接从数据源获得数据库的连接,程序员编程时也尽量使用这些数据源的实现,以提升程序的数据库访问性能。 
  下面我们来学习一下这些开源数据库的连接池。

3.1 DPCP连接池

  DBCP是Apache软件基金组织下的开源连接池实现,使用DBCP数据源需要在程序中加入下面两个jar包(dbcp的jar包下载地址:http://download.csdn.net/detail/eson_15/9525736):

  • commons-dbcp.jar:连接池的实现;
  • commons-pool.jar:连接池实现的依赖库

Tomcat的连接池正是采用DBCP连接池来实现的,该数据库连接池既可以与应用服务器整合使用,也可以独立使用,下面我们使用DBCP来改写上面的JdbcUtils工具类:

public class JdbcUtils_DBCP {

    private static DataSource ds = null;//定义数据源
static{
try{
InputStream in = JdbcUtils_DBCP.class.getClassLoader().getResourceAsStream("dbcp.properties");
Properties prop = new Properties();
prop.load(in); BasicDataSourceFactory factory = new BasicDataSourceFactory();//数据源工厂
ds = factory.createDataSource(prop);//工厂产生数据源
System.out.println(ds);//打印出来瞧瞧是何方神圣~~ }catch(Exception e){
throw new ExceptionInInitializerError(e);
} } public static synchronized Connection getConnection() throws SQLException {
Connection conn = ds.getConnection();//从数据源中拿一个Connection来用~
return conn;
} public static void release(Connection conn, Statement st, ResultSet rs) {
if(rs != null){
try{
rs.close();
}catch(Exception e) {
e.printStackTrace();
}
rs = null;
}
if(st != null){
try{
st.close();
}catch(Exception e) {
e.printStackTrace();
}
st = null;
} if(conn != null){
try{
conn.close();
}catch(Exception e) {
e.printStackTrace();
}
conn = null;
}
}
}

从上面代码中可以看出,获得了数据源dataSource后,就可以直接通过这个数据源拿到Connection,说明拿之前连接池中已经放好了一些Connection了,这些都已经被DBCP封装好了,我们不用去管,我们需要做的就是在配置文件中做一些配置,DBCP会根据配置文件中的配置去初始化数据库连接池的。我们看一下都需要配置啥:

#连接设置
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/databasename
username=root
password=root #初始化连接:10个
initialSize=10 #最大连接数量
maxActive=50 #最大空闲连接
maxIdle=20 #最小空闲连接
minIdle=5 #超时等待时间以毫秒为单位 6000毫秒/1000等于60秒
maxWait=60000 #JDBC驱动建立连接时附带的连接属性属性的格式必须为这样:[属性名=property;]
#注意:"user" 与 "password" 两个属性会被明确地传递,因此这里不需要包含他们。
connectionProperties=useUnicode=true;characterEncoding=gbk #指定由连接池所创建的连接的自动提交(auto-commit)状态。
defaultAutoCommit=true #driver default 指定由连接池所创建的连接的只读(read-only)状态。
#如果没有设置该值,则“setReadOnly”方法将不被调用。(某些驱动并不支持只读模式,如:Informix)
defaultReadOnly= #driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation)。
#可用值为下列之一:(详情可见javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
defaultTransactionIsolation=READ_UNCOMMITTED

 上面就是DBCP连接池的基本配置,我们只要配置好了,它就会自己根据配置文件中的配置进行初始化。

3.2 C3P0连接池

  用C3P0数据库连接池,需要导入下面两个jar包(c3p0的jar包下载地址:http://download.csdn.net/detail/eson_15/9525734):

  • c3p0-0.9.5.1.jar
  • mchange-commons-java-0.2.10.jar

这样就可以使用C3P0来改写JdbcUtils工具类了:

public class JdbcUtils_C3P0 {

    private static ComboPooledDataSource ds = null;

    static {

        try {
//配置文件可以用properties文件,也可以用xml,这里使用xml配置,下面给出配置好的xml(要放在类路径下)
ds = new ComboPooledDataSource(); //使用默认配置
//ds = new ComboPooledDataSource("mysql"); //指定配置 } catch (Exception e) {
throw new ExceptionInInitializerError(e);
} } public static synchronized Connection getConnection() throws SQLException {
Connection conn = ds.getConnection();
return conn;
} public static void release(Connection conn, Statement st, ResultSet rs) {....}
}

下面看一下C3P0的配置文件c3p0-config.xml:

<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config> <default-config> <!--默认配置-->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/databasename</property>
<property name="user">root</property>
<property name="password">root</property> <property name="acquireIncrement">5</property>
<property name="initialPoolSize">10</property>
<property name="minPoolSize">5</property>
<property name="maxPoolSize">20</property> <!-- intergalactoApp adopts a different approach to configuring statement
caching -->
<property name="maxStatements">0</property>
<property name="maxStatementsPerConnection">5</property> <!-- he's important, but there's only one of him -->
</default-config> <named-config name="mysql"> <!--mysql的配置,在new ComboPooledDataSource()时候括号中指定,所以还可以再对oracle进行配置-->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/databasename</property>
<property name="user">root</property>
<property name="password">root</property> <property name="acquireIncrement">5</property>
<property name="initialPoolSize">10</property>
<property name="minPoolSize">5</property>
<property name="maxPoolSize">20</property> <!-- intergalactoApp adopts a different approach to configuring statement
caching -->
<property name="maxStatements">0</property>
<property name="maxStatementsPerConnection">5</property> <!-- he's important, but there's only one of him -->
</named-config>
</c3p0-config>

 我们可以看出,C3P0的配置文件中可以指定哪个数据库,也可以指定默认的数据库,这就很方便了,我们在获得数据源的时候就可以直接用自己配置的参数指定即可。

4.Tomcat配置数据源

  这种方式在开发中也用的比较多。Tomcat服务器在启动时可以帮我们创建一个池,这样我们可以直接利用这个连接池,但是需要进行配置。在META-INF目录下新建一个context.xml文档(也可以在WEB-INF目录下的web.xml中进行配置),然后在里面进行如下配置

<Context>
<Resource name="jdbc/EmployeeDB"
auth="Container"
type="javax.sql.DataSource" username="root"
password="root"
driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/day16"
maxTotal="8"
maxIdle="4"/>
</Context>

其他参数可以参照dbcp的配置文件进行设置,因为tomcat连接池内部就是dbcp。然后新建一个servlet,在servlet中编写如下代码(Tomcat连接池的模板代码):

try {
Context initCtx = new InitialContext();// 初始化jndi
Context envCtx = (Context) initCtx.lookup("java:comp/env");// 得到jndi容器
DataSource ds = (DataSource) envCtx.lookup("jdbc/EmployeeDB");// 从容器中检索连接池
Connection conn = ds.getConnection();
System.out.println(conn);
} catch (Exception e) {
e.printStackTrace();
}

启动服务器,运行该servlet即可在控制台打印出连接信息。 
注:如果程序抛出异常,可能是eclipse版本问题,低版本需要将工程中MySQL-connector-java-5.1.26-bin.jar包拷贝到Tomcat服务器的lib文件夹下。 
  好了,关于JDBC的内容就介绍这么多吧~如有错误之处,欢迎留言指正~

原本以为jdbc比较简单但是 test出的问题也多,后续可能在JavaWeb基础总结里面 再次总结。

 

JDBC技术总结(三)的更多相关文章

  1. Javaweb学习笔记7—JDBC技术

      今天来讲javaweb的第7阶段学习. JDBC技术,关于JDBC本篇博客只介绍了它的一部分,后面博客会更加深入探讨. 老规矩,首先先用一张思维导图来展现今天的博客内容.   ps:我的思维是用的 ...

  2. 注册Jdbc驱动程序的三种方式

    注册Jdbc驱动程序的三种方式 1. Class.forName("com.mysql.jdbc.Driver"); 2. DriverManager.registerDriver ...

  3. 使用JDBC技术连接数据库(附源码)--JAVA的简单应用

    一.创建数据库(以mysql数据库为例) mysql数据库的下载安装与配置 -可参考博主之前的随笔:Windows平台下搭建MySQL数据库 创建wxb数据库-create database wxb; ...

  4. JDBC完成的三个基本工作

    JDBC完成的三个基本工作 1.与数据库建立连接 2.执行SQL语句 3.获得SQL语句的执行结果

  5. JDBC连接池(三)DBCP连接池

    JDBC连接池(三)DBCP连接池 在前面的随笔中提到 了  1.JDBC自定义连接池  2. C3P0连接池 今天将介绍DBCP连接池 第一步要导入jar包   (注意:mysql和mysql 驱动 ...

  6. Android NFC技术(三)——初次开发Android NFC你须知道NdefMessage和NdefRecord

    Android NFC技术(三)--初次开发Android NFC你须知道NdefMessage和NdefRecord 这最近也是有好多天没写博客了,除了到处张罗着搬家之外,依旧还是许许多多的琐事阻碍 ...

  7. DriverManager 驱动管理器类简介 JDBC简介(三)

    驱动程序管理器是负责管理驱动程序的,驱动注册以后,会保存在DriverManager中的已注册列表中 后续的处理就可以对这个列表进行操作 简言之,驱动管理器,就是字面含义,主要负责就是管理 驱动 概述 ...

  8. JAVA数据库编程(JDBC技术)-入门笔记

    本菜鸟才介入Java,我现在不急着去看那些基本的语法或者一些Java里面的版本的特征或者是一些晋级的知识,因为有一点.Net的OOP编程思想,所以对于Java的这些语法以及什么的在用到的时候在去发现学 ...

  9. JDBC技术总结(一)

    1. JDBC简介 SUN公司为了简化.统一对数据库的操作,定义了一套Java操作数据库的规范,称之为JDBC,JDBC不能直接操作数据库,JDBC通过接口加载数据库的驱动,然后操作数据库.JDBC: ...

  10. 大型网站技术架构(四)--核心架构要素 开启mac上印象笔记的代码块 大型网站技术架构(三)--架构模式 JDK8 stream toMap() java.lang.IllegalStateException: Duplicate key异常解决(key重复)

    大型网站技术架构(四)--核心架构要素   作者:13GitHub:https://github.com/ZHENFENG13版权声明:本文为原创文章,未经允许不得转载.此篇已收录至<大型网站技 ...

随机推荐

  1. python接口自动化7-参数关联【转载】

    本篇转自博客:上海-悠悠 原文地址:http://www.cnblogs.com/yoyoketang/tag/python%E6%8E%A5%E5%8F%A3%E8%87%AA%E5%8A%A8%E ...

  2. Python基础-列表、元祖

    1. 列表.元组操作 列表是我们最以后最常用的数据类型之一,通过列表可以对数据实现最方便的存储.修改等操作 定义列表 names = ['Alex',"Tenglan",'Eric ...

  3. 第一章:1-11、在上题的分组交换网中,设报文长度和分组长度分别为x和(p+h)(bit),其中p为分组的数据部分的长度,而h为每个分组所带的控制信息固定长度,与p的大小无关。通信的两端共经过k段链路。链路的数据率为b(bit/s),但传播时延和结点的排队时间均可忽略不计。若打算使总的时延为最小,问分组的数据部分长度p应取为多大?

    <计算机网络>谢希仁著第四版课后习题答案答: 分组个x/p, 传输的总比特数:(p+h)x/p 源发送时延:(p+h)x/pb 最后一个分组经过k-1个分组交换机的转发,中间发送时延:(k ...

  4. XXXX公司微课大赛技术储备

    XXXX公司微课大赛技术储备 发短信验证 http://www.yunpian.com/ 发邮件 http://sendcloud.sohu.com/ flash头像上传组件 http://www.h ...

  5. Flask插件系列之flask_celery

    现在继续学习在集成的框架中如何使用celery. 在Flask中使用celery 在Flask中集成celery需要做到两点: 创建celery的实例对象的名字必须是flask应用程序app的名字,否 ...

  6. [thinkphp]查看thinkphp系统定义的常量值

    echo 'MEMORY_LIMIT_ON: ' . MEMORY_LIMIT_ON . '<br>'; echo 'THINK_PATH: ' . THINK_PATH . '<b ...

  7. 2018 CCPC 女生专场

    可能是史上最弱的验题人—— Problem A (小)模拟. #include <bits/stdc++.h> using namespace std; int T; int main() ...

  8. 分金币 Uva 11300

    题意 给定N个人成环状坐,每个人初始分配Ai的金币,金币总数可以被N整除,每个人可以给左右相邻的人一定数量的金币使得最终每个人的金币数量相同,求转移数量最小的方案所转移的总金币数量. N<=10 ...

  9. InputSplit—>RecordReder—>map(key,value,context)的过程解析

    上图首先描述了在TaskTracker端Task(MapTask.ReduceTask)的执行过程,MapTask(org.apache.hadoop.mapred)首先被TaskRunner调用,然 ...

  10. Mac下Gmail不能访问的简单解决办法

    思路:Hosts Terminal下输入: curl -s http://freedom.txthinking.com/fuckGFW.py | sudo python 按提示输入密码即可 比较方便, ...