2014-07-15 Java Web的学习(12)-----JDBC数据源的详解以&包装模式&动态代理

一、数据库连接池
1.何为数据库连接池
   连接池是创建和管理一个连接的缓冲池的技术,这些连接准备好被任何需要的它们的线程使用,这种把连接“汇集”起来的技术基于这样的一个事实:对于大多数应用程序,当它们正在处理通常需要数毫秒完成的事务时,仅需要能够访问JDBC连接的1个线程  。当不处理事务时,这个连接就会闲置。相反,连接池允许闲置的连接被其它需要的线程使用。
2.使用连接池的好处
   a.减少连接创建的时间.获取数据库Connection对象相对来说是花销系统资源.
   b.简化了编程模式,每一个单独的线程都能够'像'创建一个自己的JDBC Connection对象,这时用户就能直接使用JDBC编程技术.
   c.受控的资源使用,如果不使用连接池技术,而是直接创建获取新的连接,这时程序的资源就会产生非常大的浪费并且可能会导致高负载下的异常发生.
3.数据库连接池技术的原理
    其实看上去连接池的技术是挺简单的,传统上每一次访问数据库时,就会打开数据库,获取数据库连接,操作数据然后关闭数据库的连接.这是非常耗费资源滴.
    数据库连接池:在项目启动时,会创建一定数量的数据库连接对象存储在一个池中(内存中,Java语言就是集合里),需要使用到数据库连接时,就去池中取连接,而操作完数据库,就将连接放回池中。这样的话,通过复用这些数据库连接,减少对数据库连接直接创建.极大地节省系统资源和时间.
二、实现简单连接池
1.连接池的使用步骤可以简单化为这些步骤:
(1)建立连接池对象(服务器启动)。
(2)按照事先指定的参数创建初始数量的数据库连接(即:空闲连接数)。
(3)对于一个数据库访问请求,直接从连接池中得到一个连接。如果连接池对象中没有空闲的连接,且连接数没有达到最大(即:最大活跃连接数),创建一个新的数据库连接。
(4)存取数据库。
(5)关闭数据库,释放所有数据库连接(此时的关闭数据库连接,并非真正关闭,而是将其放入空闲队列中。如实际空闲连接数大于初始空闲连接数则释放连接)。
(6)释放连接池对象(服务器停止、维护期间,释放数据库连接池对象,并释放所有连接)
2.简单代码实现
 
三、数据源DataSource的概念
   JDBC技术是属于J2EE技术中的一种,数据库数据源在J2EE中也早有规范定义.我们可以查看JDK API文档,看一看DataSource接口是怎么定义的:
  
public interface DataSource extends , 

    该工厂用于提供到此 DataSource 对象所表示的物理数据源的连接。作为 DriverManager 工具的替代项,DataSource 对象是获取连接的首选方法。实现 DataSource   接口的对象通常在基于 JavaTM Naming and Directory Interface (JNDI) API 的命名服务中注册。

    DataSource 接口由驱动程序供应商实现。共有三种类型的实现:

基本实现 - 生成标准的 Connection 对象 连接池实现 - 生成自动参与连接池的 Connection 对象。此实现与中间层连接池管理器一起使用。 分布式事务实现 - 生成一个 Connection 对象,该对象可用于分布式事务,大多数情况下总是参与连接池。此实现与中间层事务管理器一起使用,大多数情况下总是与连接池管理器一起使用。

    DataSource 对象的属性在必要时可以修改。例如,如果将数据源移动到另一个服务器,则可更改与服务器相关的属性。其优点在于,由于可以更改数据源的属性,所以任何访问该数据源的代码都无需更改。

    通过 DataSource 对象访问的驱动程序本身不会向 DriverManager 注册。通过查找操作获取 DataSource 对象,然后使用该对象创建 Connection 对象。使用基本的实现,通过 DataSource 对象获取的连接与通过 DriverManager 设施获取的连接相同。

   
    从上面的文档我自己的理解是:
    1.DataSource可以获取对数据库的连接(Connection对象),和DriverManager获取连接的方式是相同的
    2.DataSource是SUN公司定义出来的接口,需要不同厂商实现该接口,而实现有三种方式(常用的是连接池的技术实现).这就是连接池和数据源的关系啦.
    3.面向接口的编程,使我们将具体的数据库抽离出来.让编程变得简单.
 
四、动手编写实现一个标准的数据源(连接池)
 
1.作为一个标准的数据源就必须要实现java.sql.DataSource接口
2.连接池实现的话,内部必须要在程序启动时创建一定数量的数据库连接,然后存放在集合中(内存).
  private static List<Connection> dataSource = new ArrayList<Connection>();
  static {
          for ( int i = 0; i < 10; i++) {
              dataSource .add(JdbcUtil.getConnection());
          }
  }
3.Override接口中的getConnection方法获取连接(从连接池中获取).
   @Override
  public synchronized Connection getConnection () throws SQLException {
          if ( dataSource .size() > 0) {
              Connection conn = dataSource .remove(0);
              return conn;
          } else {
              throw new RuntimeException( "服务器正在忙." );
          }
   }
4.结束完成....(????真的好了吗?)
 
问题:让我们来测试实现DataSource的类吧.问题出现在关闭Connection对象时,看下面代码:
     @Test
     public void testDataSource(){
              Connection conn = null ;
              try {
                   conn = dataSource .getConnection();
                   /*com.mysql.jdbc.Connection,打印实现Connection接口的对象(明显是MySQL驱动实现)*/
                   System. out .println(conn.getClass());
              } catch (Exception e) {
                   e.printStackTrace();
              } finally {
                   if (conn!= null ){
                        try {
                             conn.close();   //这并不是将连接放回连接池中,而是直接关闭了连接."那这个连接池的效果就不起作用啦"?
                        } catch (SQLException e) {
                             e.printStackTrace();
                        }
                   }
              }
 
怎么解决呢??有三种解决方案(看一下它们的利弊吧)
 
* 由于是对连接资源的直接关闭,而不是将连接放回连接池.
* 这时就需要在Connection实现类添加/修改行为.---将连接放回连接池
* 而此时,Connection的实现为:com.mysql.jdbc.Connection存在 Mysql厂商提供的驱动包中.
* 所以,就不能直接修改驱动包中的源代码.----可以直接查看该实现类开源码---没有将连接放回连接池的方法.
* 这时解决方法:
* 1.继承:由于Mysql的中实现类不是final,需要继承该实现类.添加方法.
*    class  MyConnection extends com.mysql.jdbc.Connection
*    劣势:如果更改数据库时,就要将继承的实现类更改啦.就要依赖具体实现的不同厂商类.所以不推荐使用
* 2.包装设计模式:
*   典型例子:读取文件,并且将行数读出(BufferedReader例子).
*   一般步骤:a.编写一个类,实现被包装类相同的接口.这次被包装的类是com.mysql.jdbc.Connection.
*            所以:class MyConnection1 implements javax.sql.Connection.
*            SUN公司规范接口,重写父类的所有的未实现的方法 .
*          b.在新编写的类中,定义一个成员变量.为原有(被包装)类的引用.
*            private Connection conn;
*          c.定义构造方法,把原有类的实例注入进来
*            public MyDataSource1(Connection conn){
*                 this.conn = conn;
*            }
*          d.对要修改的方法,编写自己的代码
*            @Override
*            public void close() throws SQLException {
*                 dataSource.add(conn);  //不是关闭连接,而是放回连接池
*            }
*          e.对于不需要改变的方法,调用原有类的对应方法
*            @Override
*            public <T> T unwrap(Class<T> iface) throws SQLException {
*                 return conn.unwrap(iface);
*            }
* 3.包装模式加强版(适配器):
*         3.1 适配器类的编写:
*          a、编写一个类,实现与被包装类相同的接口(包装类和原有类有着相同的行为)
*             public class MyConnectionWrapper implements javax.sql.Connection
*          b、定义一个变量,引用原有类的实例
*             private Connection conn;
*          c、定义构造方法,把原有类的实例注入进来
*             public MyConnectionWrapper(Connection conn){
*                 this.conn = conn;
*             }
*          d、全部调用原有类的对应方法
*             conn.unwrap(iface);.....等等
*         3.2 适配器类的使用:
*         a、编写一个类,继承已经实现了接口的包装类
*         public class MyConnection2 extends MyConnectionWrapper
*         b、定义一个变量,引用原有类的实例
*         private Connection conn;
*         c、定义构造方法,把原有类的实例注入进来
*         public MyConnection2(Connection conn,List<Connection> dataSource){
                 super( conn);
                 this.conn = conn;
                 this.dataSource = dataSource;
          }
*         d、对于要改变的方法,编写自己的代码
*       
            @Override
            public void close() throws SQLException {
                 dataSource.add( conn);
            }
*       
* 4.动态代理模式:从实际看来,包装模式和静态代理十分的相似.
*   JDK动态代理:代理类和被代理类都要实现相同的接口
*   CGLIB实现动态代理:基于子类的动态代理.
                       
   public synchronized Connection getConnection() throws SQLException {
          if ( dataSource .size() > 0) {
              final Connection conn = dataSource .remove(0);
//包装         Connection myconn = new MyConnection1(conn, dataSource); 
//适配器       Connection myconn = new MyConnection2(conn, dataSource);
              Connection proxyConn = (Connection) Proxy.newProxyInstance(
                        conn.getClass().getClassLoader(), conn.getClass()
                                  .getInterfaces(), new InvocationHandler() {
                             @Override
                             public Object invoke(Object proxy, Method method,
                                      Object[] args) throws Throwable {
                                  if ( "close" .equals(method.getName())) {
                                      return dataSource .add(conn);
                                  }
                                  return method.invoke(conn, args);
                             }
                        });
              return proxyConn;
          } else {
              throw new RuntimeException( "服务器正在忙." );
          }
     }
 
五、动态代理的简单介绍
   代理的概念:就是一个拦截器的作用.为什么常用动态代理(说是可以优雅的编程,面向切面的处理业务).我一直的疑问:动态代理是如何实现( JDK内部)

 
  基于接口的动态代理: Proxy,被代理对象一定要实现了某个接口
public class DynamicProxy implements InvocationHandler {
 
    private Object target;
 
    public DynamicProxy(Object target) {
        this .target = target;
    }
 
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object result = method.invoke(target, args);
        after();
        return result;
    }
  @SuppressWarnings ( "unchecked" )
    public <T> T getProxy() {
        return (T) Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            this
        );
    }
}

在DynamicProxy类中,我定义了一个 Object 类型的 target 变量,它就是被代理的目标对象,通过构造函数来初始化(现在流行叫“注入”了

DynamicProxy 实现了 InvocationHandler 接口,那么必须实现该接口的 invoke 方法,参数不做解释,望文生义吧.在该方法中,直接通过反射去 invoke method,在调用前后分别处理 before 与 after
最后将 result 返回.

Proxy.newProxyInstance() 方法的参数:

参数1:ClassLoader
参数2:该实现类的所有接口
参数3:动态代理对象


 
4b99944de990a6b1d148a6d62ee8f5d1 f74ce2c0a920bdc6c7845ddc8e6ad611 c6e35bcadaf400ef3bdc01e7df6645b8

   基于子类的动态代理: CGLIB( cgLibProxy )
0dbb7d4847c62975d4bf5f5c9c63c9cf 265dcfda459ba6beabfcc8e04136488f
 
 
六、常用数据源(一般是连接池实现)
 
1.DBCP数据源(Apache组织Tomcat专用)
  具体的配置请看:http://commons.apache.org/proper/commons-dbcp/index.html
  封装DBCPUtil工具类:导入相关的开发架即可
  a6e6493b51d1d058433ab373867eb03432319660c570f2ec4263bebb3fd0fa49
2.C3P0数据源(牛人个人开发)
  具体的配置请看:架包中帮助文档
  封装C3P0Util工具类:导入相关的开发架即可
  54ae5915ff92e0be81b5625cea22990f9ce9506ad62300eeffef180c6df1749b
3.Druid数据源(阿里巴巴开源)
  GitHub:https://github.com/alibaba/druid
  使用教程:http://blog.csdn.net/yunnysunny/article/details/8657095
 数据源,就是内存集合存在一定的数量Connection对象.以后开发使用DataSource获取连接.

七、利用Tomcat服务器管理数据源

JNDIJavaTM Naming and Directory Interface

他是一个集合:Map<String,Object>.Stringpath+nameObject:任意对象。类似window系统的注册表。

一般服务器都是通过JNDI存放数据源的。数据源的配置

 

1.配置Tomcat管理的数据源

     a:把数据库驱动拷贝Tomcat\lib目录中
     b: 在应用的 META-INF 目录下,建立一个名称为 context.xml 的配置文件,内容如下:
图片1
     c: 部署应用,启动 Tomcat ,服务器就会按照配置在 JNDI 容器中注册数据源的实例.
       d:获取数据源, 部署应用,启动 Tomcat ,服务器就会按照配置在 JNDI 容器中注册数据源的实例
       图片2

转载于:https://my.oschina.net/codeWatching/blog/398483

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值