Day16

Tip:事务

l事务的概念
•事务指逻辑上的一组操作,组成这组操作的各个单元,要不全部成功,要不全部不成功。
•例如:A——B转帐,对应于如下两条sql语句

  update from account set money=money+100 where name=‘b’;

  update from account set money=money-100 where name=‘a’;

假如不将转账的两个sql语句放到同一个事物中去执行,有可能执行第一句sql成功了,增加了100元钱,但是在执行第二句sql

语句时抛了异常,这个时候就没有执行减去100,那么这个转账其实是存在不安全因素的,所以我们必须将这样的sql语句放到同

一个事物中执行,要不全部成功,要不全不成功

•数据库默认事务是自动提交的(以前我们没有用事物时),也就是发一条sql它就执行一条。如果想多条sql放在一个事务中执行,则需要使用如下语句。
 
l数据库开启事务命令
•start transaction  开启事务
•Rollback  回滚事务
•Commit   提交事务
我们可以将多条sql语句放到Start transation 和 commit之间成为一个事务,当开始事务时start transaction就会去执行内部的sql语句,如果内部有sql抛异常就不会执行下面的代码,也就不会提交事务commit,不能提交事务,数据库就会自动将之前执行的sql语句的影响清除掉。也就是说只有数据库接收到提交事务
commit才会将代码块中的sql语句执行掉
 
下面我们来做练习
首先我们先建立一张数据表account
create database day16;
use day16;

create table account(
id int primary key auto_increment,
name varchar(40),
money float
)character set utf8 collate utf8_general_ci;

insert into account(name,money) values('aaa',1000);
insert into account(name,money) values('bbb',1000);
insert into account(name,money) values('ccc',1000);

 
查看一下
select * from account;
下面我来模拟银行转账,aaa向bbb转帐100块
首先开启事物
start transaction;
然后加入sql语句
update account set money=money+100 where name="bbb";
然后我们查询一下
select * from account;
发现bbb多了100,如果程序执行到这里抛了异常,就会中断后面的程序了(关掉当前的mysql)
接着我们重新开启一个mysql查看accout
use day16;
select * from account;
结果应该是这样的
发现并没有改变,因为你还没有提交事务Commit
但是实际我测试到和老方的不一样,我测试的还是改变了
 
再来测试一次
start transaction;
update account set money=money+100 where name="bbb";
update account set money=money-100 where name="aaa";
select * from account;
当我关掉当前的mysql重新打开一个mysql查看
还是改变的了(怎么回事哦,本来不因该改变的呀??好奇怪),(我查了一下资料发现这里有自动提交事务的可能,所以导致这种情况)
不管了,再来测试一下又提交事物的情况
start transaction;
update account set money=money+100 where name="bbb";
update account set money=money-100 where name="aaa";
select * from account;
commit;
查看一下发现改变了
 
这个时候即使你断开mysql连接在查询也是改变的
 
当然我们可以手动回滚事务rollback
start transaction;
update account set money=money+100 where name="bbb";
update account set money=money-100 where name="aaa";
rollback;
select * from account;
关闭当前的mysql,重新开一个mysql查看
use day16;
select * from account;
结果发现也没有改变(但是实际上我测试时变化了的,可能我这里开启了自动提交事务)
 
接下来我们要在程序中操作事务

Tip:使用事务

l当Jdbc程序向数据库获得一个Connection对象时,默认情况下这个Connection对象会自动向数据库提交在它上面发送的SQL语句。若想关闭这种默认提交方式,让多条SQL在一个事务中执行,可使用下列语句:
lJDBC控制事务语句
•Connection.setAutoCommit(false); //相当于start transaction
•Connection.rollback();  //相当于rollback
•Connection.commit();  //相当于commit
 
现在我们要用程序实现转账
首先我们将money都置位1000
update account set money=1000;
然后我建立工程day16,接着倒入mysql的驱动以及工具包cn.itcast.utils还有配置文件db.properties
/day16/src/cn/itcast/demo/Demo1.java
package cn.itcast.demo;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import cn.itcast.utils.JdbcUtils_Dbcp;

public class Demo1 {

    /**
     * 事务:a--->b转帐100元
     * @param args
     * @throws SQLException 
     */
    public static void main(String[] args) throws SQLException {
        
        Connection conn = null;
        PreparedStatement st = null;
        ResultSet rs = null;
        try{
            conn = JdbcUtils_Dbcp.getConnection();
            //这句话的意思是关闭自动提交,相当于start transaction开启事务
            conn.setAutoCommit(false);   //禁止自动开启事务,也就是设置手动开启事务
            
            String sql1 = "update account set money=money-100 where name='aaa'";
            st = conn.prepareStatement(sql1);    //将sql语句预编译一下
            st.executeUpdate();
            
            
            String sql2 = "update account set money=money+100 where name='bbb'";
            st = conn.prepareStatement(sql2);
            st.executeUpdate();
            //模拟抛异常了,虽然数据库收到了sql语句,但是抛了异常,如果没有catch住异常,数据库会自动回滚了
            //在开发中我们不希望数据库自动回滚,所以我们用catch抓住异常,记录遗产,手动回滚
            //int x = 1/0; 
            
            conn.commit();   //执行完slq语句之后,一定要记住commit提交事务
            
            System.out.println("转帐成功");
        }catch (Exception e) {
            e.printStackTrace();            //抓住异常
            conn.rollback();                //手动回滚,其实是我们向数据库发rollback命令,通知数据库主动回滚
        }finally{
            JdbcUtils_Dbcp.release(conn, st, rs);
        }
        

    }

}

 默认情况下只要没有提交事务commit就不会真正执行sql语句

Tip:演示银行转帐案例

lJDBC代码中使如下转帐操作在同一事务中执行。

  update from account set money=money-100 where name=‘a’;

  update from account set money=money+100 where name=‘b’;

l设置事务回滚点
•Savepoint sp = conn.setSavepoint();
•Conn.rollback(sp);
•Conn.commit();   //回滚后必须要提交
有的时候我们向事务中插入多条sql语句时,当发生异常了我们不想将所有的sql语句都回滚了,这个时候我们可以设置回滚点,可以通过回滚到回滚点处,然后当提交事务后,会将回滚点之前的sql语句保留,之后的sql语句回滚

/day16/src/cn/itcast/demo/Demo2.java

以下测试我还是失败了,大概我的Mysql是集成安装的,需要修改一些配置文件吧

package cn.itcast.demo;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Savepoint;

import cn.itcast.utils.JdbcUtils_Dbcp;

public class Demo2 {

    /**
     * 设置事务回滚点
     * @param args
     * @throws SQLException 
     */
    public static void main(String[] args) throws SQLException {
        
        Connection conn = null;
        PreparedStatement st = null;
        ResultSet rs = null;
        Savepoint sp = null;
        try{
            conn = JdbcUtils_Dbcp.getConnection();
            conn.setAutoCommit(false);   //start transaction
            
            String sql1 = "update account set money=money-100 where name='aaa'";
            st = conn.prepareStatement(sql1);
            st.executeUpdate();
            
            //设置一个事务回滚点
            sp = conn.setSavepoint();  
            
            String sql2 = "update account set money=money+100 where name='bbb'";
            st = conn.prepareStatement(sql2);
            st.executeUpdate();
            
            //当抛异常之后就被catch住了,在catch中记录异常,回滚到回滚到回滚点,最后一定要记住事务提交
            int x = 1/0;
            
            conn.commit();   //commit
        }catch (Exception e) {
            e.printStackTrace();
            conn.rollback(sp);    //回滚到回滚点
            conn.commit();   //事务回滚后,一定要记得提交
        }finally{
            JdbcUtils_Dbcp.release(conn, st, rs);
        }
    }
}

 

 

Tip:事务的特性(ACID)

l原子性(Atomicity)
原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。 事务最基本最核心的概率
l一致性(Consistency)
事务前后数据的完整性必须保持一致。(也就是说钱转来转去钱总额都不变)
l隔离性(Isolation)(多个用户并发开启事务操作数据库的时候,数据库要隔离多个并发事务以保证数据的准确性,关于隔离性等会有个专题去讲解它)
事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。
l持久性(Durability)(持久性表示一旦用户成功地向数据库提交事务commit,无论遇到什么情况(系统奔溃了,停电了,网络中断了等等)那数据库就必须把sql影响保存,否则这个数据库就不具备支持事务的特性)
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。

如果市面上号称支持事务的数据库则必有这四大特性,同样一个数据库支持则四大特性则说明它也支持事务

这个是面试中最容易考的,如果有人问你什么事事务的四大特性,直接回答ACID

 -----------------------------------------------------------------------------------------------------------------------------------------------

Tip:事务的隔离级别

l多个线程开启各自事务操作数据库中数据时,数据库系统要负责隔离操作,以保证各个线程在获取数据时的准确性。
l如果不考虑隔离性,可能会引发如下问题:
(1)脏读:
•脏读指一个事务读取了另外一个事务未提交的数据。

  这是非常危险的,假设A向B转帐100元,对应sql语句如下所示

  1.update account set money=money+100 while name=‘b’; 

  2.update account set money=money-100 while name=‘a’;

  当第1条sql执行完,第2条还没执行(A未提交时),如果此时B查询自己的帐户,就会发现自己多了100元钱,然后就给A发货了。如果A等B发货后在执行回滚rollback,B就会损失100元,而且还损失了货物

 

(2)不可重复读:
•在一个事务内读取表中的某一行数据,多次读取结果不同。

  例如银行想查询A帐户余额,第一次查询A帐户为200元,此时A向帐户存了100元并提交了,银行接着又进行了一次查询,此时A帐户为300元了。银行两次查询不一致,可能就会很困惑,不知道哪次查询是准的。

•和脏读的区别是,脏读是读取前一事务未提交的脏数据,不可重复读是重新读取了前一事务已提交的数据。
•很多人认为这种情况就对了,无须困惑,当然是后面的为准。我们可以考虑这样一种情况,比如银行程序需要将查询结果分别输出到电脑屏幕和写到文件中,结果在一个事务中针对输出的目的地,进行的两次查询不一致,导致文件和屏幕中的结果不一致,银行工作人员就不知道以哪个为准了。

 

(3)虚读(幻读)(虚读指的是读取到别人插入的数据,虚读和不可重复读是有区别的,不可重复读是读取一行的数据不一致,虚读是读取一个表不一致)
•是指在一个事务内读取到了别的事务插入的数据,导致前后读取不一致。
•如丙存款100元未提交,这时银行做报表统计account表中所有用户的总额为500元,然后丙提交了,这时银行再统计发现帐户为600元了,造成虚读同样会使银行不知所措,到底以哪个为准。

 

 Tip:事务隔离性的设置语句

l数据库共定义了四种隔离级别:
•Serializable:可避免脏读、不可重复读、虚读情况的发生。(串行化)      //这种级别最高,可以避免脏读,不可重复度,虚读
•Repeatable read:可避免脏读、不可重复读情况的发生。(可重复读)      //这种级别其次,可避免脏读,不可重复读,但是解决不了虚读
•Read committed:可避免脏读情况发生(读已提交)。          //这种级别在其次,只可避免脏读,但是解决不了不可重复读和虚读
•Read uncommitted:最低级别,以上情况均无法保证。(读未提交)    //这种级别最低,什么都不能解决
数据库的隔离级别越高性能就越低,隔离级别越低性能越好,在开发时要更具不同的情况选择不同的隔离级别
 
lset   transaction isolation level 设置事务隔离级别
 
lselect @@tx_isolation  查询当前事务隔离级别
 
 
下面来做练习演示这三类问题,加深对这三类问题的认识
要演示多线程并发操作数据库,这里可以开两个mysql窗口来模拟多线程,每个mysql窗口就是一个客户端
1号窗口
2号窗口
然后我们设置第一号窗口的隔离级别为最低级别
set transaction isolation level read uncommitted;
 
然后在查看一下当前事务隔离级别
select @@tx_isolation;
 
在这种最低级别的隔离会发生所有问题
接着我们还是在第一窗口开启事务,并查看当前的状态
start transaction;
select * from account;
 
 
 
加入一号窗口是一个淘宝商的老板(账户时aaa),二号窗口是一个客户(账户是bbb)
现在二号窗口开启事务向一号转账100元
start transaction;
update account set money=money+100 where name='aaa';
 
此时客户打电话给淘宝商老板说钱已经给他打过去了,要他立刻发货
于是淘宝商老板打开自己账户(在一号窗口查询account),发现自己账户多了100元
select * from account;
于是淘宝商老板就给客户发货了,此时客户又给淘宝商打电话问他是否发货,淘宝商老板说发了
此时邪恶的客户回滚数据(在二号窗口回滚)
rollback;
然后淘宝商老板再次确认自己账户,发现却没有受到钱
在一号窗口查询account
select * from account;
所以在最低级别下三种问题都可以发生
所以以上事例表现了脏读,也就是一个事务读取了另一个事务未提交的数据,
这个事例同样表现了不可重复读,在一个事务内读取表中的某一行数据,多次读取结果不同(也就是淘宝商老板第一次读取自己的账户和第二次读取自己的账户得到的结果不一样)
同样这个事例也可以表现虚读(假如这个时候有三号窗口插入一个账户ddd,钱也是1000元,那么此时淘宝商老板以及客户再次查询都可以看见ddd账户)
三号窗口
 
淘宝商老板(一号窗口)以及客户(二号窗口)都可以查询到ddd的账户
select * from account;
 *****************************************************
 
现在我们将事务级别提升为避免脏读,但是避免不了不可重复读和虚读
在一号窗口改变事务隔离等级为read committed
 set transaction isolation level read committed;
然后查看一下当前事务的隔离级别
select @@tx_isolation;
 
然后一号窗口开启事务
start transaction;
二号窗口也开启事务然后向一号账户(aaa)转100钱,然后给淘宝商老板(一号窗口)打电话问他是否到账
start transaction;
update account set money=money+100 where name='aaa';
淘宝老板查账户查账(一号窗口),发现自己的账户并没有改变,他就不会给一号窗口客户发货了
select * from account;
这个时候二号窗口客户客户提交事务
commit;
然后一号窗口(淘宝商老板)查询则可以发现钱到账了这个时候才会发货给客户
 
但是此种隔离级别虽然可以避免脏读,但是却避免不了不可重复读和虚度
不可重复读表现在淘宝商老板前后查询自己的账户不同,
虚度表现在,如果现在有四号窗口向accout中插入一个账户ddd则其它号窗口也可以查询到多了一条数据
四号窗口
insert into account(name,money) value('eee',1000);
其它窗口查询
 
*********************************************
现在我们再来将事务隔离级别提升到可避免脏读和不可重复读,却避免不了虚读(repeatable read)
(注意repeatable是mysql默认的事务隔离级别,打开一个mysql窗口就是这个级别)
我们将一号窗口设置成repeatable read,然后在查询一下当前的事务级别
set transaction isolation level repeatable read;
select @@tx_isolation;
 
 接着一号窗口开启事务查看表
start transaction;
select * from account;
然后二号窗口(客户)也开启了事务向一号窗口(aaa账户,淘宝商老板)转账100,但是没有提交事务
start transaction;
update account set money=money+100 where name='aaa';
然后一号窗口再来查询,这个时候发现自己的账户并没有改变
select * from account;
然后二号窗口提交了事务(commit),然而一号窗口再去查询账户发现账户任然没有改变(因为此时一号窗口的事务级别可以脏读和不可重复读,也就是说,多次查询不会看到自己账户的改变)
二号窗口
commit;
一号窗口
select * from account;
但是这种事务级别避免不了虚读,也就是一号窗口可以查询到account表添加的账户
此时我们在二号窗口添加一个账户
insert into account(name,money) value('fff',1000);
然后在一号窗口查询account,可以看见fff这个账户(但是注意在这个事务隔离级别下不一定能看到虚读,有时候可以看到fff这个账户,有时候看不到)
(也就是说这个事务隔离级别避免不了虚读,但是虚读不一定会发生)
*****************************************************
接着我们将事务的级别提升为最高级别serializable
这种级别可以避免三种问提
我们在一号窗口开启最高事务级别,然后开启事务,然后查询account
set transaction isolation level serializable;
start transaction;
select * from account;
 
然后此时在二号窗口开启事务并向account插入账户ggg
start transaction;
insert into account(name,money) value('ggg',1000);
 
此时我们发现光标停在插入行闪烁并没有插入成功
然后我们再一号窗口提交事务
commit;
此时二号窗口中的插入数据提示插入成功
********************************************************
以上就是数据库的四种级别
但是注意一点:mysql完全支持这四种隔离级别,但是Oracle只支持Read commited和Serializable这两种隔离级别
所以在以后使用Oracle时注意这点
还有一点注意的是我们在座JDBC程序的时候如何选用级别的问题
假如我要做一个账户查询程序的则最低选用Read Committed级别,因为至少要避免脏读
假如我要做一个账户总额统计的程序则最低选用Repeatable read级别,因为至少要避免脏读和不可重复读
假如我要做一个表单账户统计个数的程序则选用最高级别的Serializable
 
 
开始时在mysql窗口选用事务级别
在程序中如何选用事务级别,我们可以通过JDK中Connection中的方法去实现,因为一个Connection就相当一个mysql窗口
 下面来做一个统计账户个数的程序,来说明如何写事务程序
这个程序主要通过在两次查询账户歌手之间延迟10s,然后在这个10s期间开启一个mysql窗口向数据库插入新的账户
通过改变事务隔离等级来查看账户个数
 
package cn.itcast.demo;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Savepoint;

import cn.itcast.utils.JdbcUtils_Dbcp;

public class Demo3 {

    /**
     * 设置程序的事务隔离级别  
     * 现在做一个账户统计的程序要用最高级别的事务隔离Connection.TRANSACTION_SERIALIZABLE
     * 通过两次查询账户个数,在两次查询中间间隔10s,然后在这10s期间用一个mysql窗口向数据库
     * 插入一个账户可以发现账户个数不便
     * @param args
     * @throws SQLException 
     */
    public static void main(String[] args) throws SQLException {
        
        Connection conn = null;
        PreparedStatement st = null;
        ResultSet rs = null;
        Savepoint sp = null;
        try{
            conn = JdbcUtils_Dbcp.getConnection();
            //将事务隔离级别设置为可以避免脏读,但是不可避免不可重复读和虚读
            conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
            conn.setAutoCommit(false);   
            
            String sql = "select count(*) from account";
            //第一次查询
            st = conn.prepareStatement(sql);
            rs = st.executeQuery();
            if(rs.next()){
                System.out.println(rs.getInt(1));
            }
            //中间等待10s
            Thread.sleep(1000*10);
            
            //第二次查询
            st = conn.prepareStatement(sql);
            rs = st.executeQuery();
            if(rs.next()){
                System.out.println(rs.getInt(1));
            }
            
            conn.commit();   //提交事务commit
        }catch (Exception e) {
            e.printStackTrace();
            conn.rollback(sp);
            conn.commit();   //事务回滚后,一定要记得提交
        }finally{
            JdbcUtils_Dbcp.release(conn, st, rs);
        }
    }
}

 

 —————————————————————————————————————————————————————————————————————
 

 Tip:使用数据库连接池优化程序性能

 
缺点:从上图我们可以看出用户每次请求都需要向数据库获得链接,而数据库创建连接通常需要消耗相对较大的资源,创建时间也较长。假设网站一天10万访问量,数据库服务器就需要创建10万次连接,然后销毁10万次连接,极大的浪费数据库的资源,并且极易造成数据库服务器内存溢出、拓机
 
为了解决这个问题,从而有了连接池,在连接池中定义若干个connection,当一个用户要访问数据时,dao层就会从连接池中取一个connection,然后通过这个
connection和数据库通信,当这个用户取完了数据后,dao层又将这个connection返还给连接池,以便以后的用户用
 

Tip:编写数据库连接池

下面我们来写一个连接池

l编写连接池需实现java.sql.DataSource接口。DataSource接口中定义了两个重载的getConnection方法:
•Connection getConnection()
•Connection getConnection(String username, String password) 
 
l实现DataSource接口,并实现连接池功能的步骤:
•在DataSource构造函数中批量创建与数据库的连接,并把创建的连接加入LinkedList对象中。
•实现getConnection方法,让getConnection方法每次调用时,从LinkedList中取一个Connection返回给用户。
•当用户使用完Connection,调用Connection.close()方法时,Collection对象应保证将自己返回到LinkedList中,而不要把conn还给数据库。
•Collection保证将自己返回到LinkedList中是此处编程的难点。
需要实现的方法
但是实际上实现DateSource这个接口不止要实现上面两个方法,还得实现它父类借口
由于我们连接数据库只需要Connection,所以重点放在getConnection()这个方法上
 这个连接池写的很悲剧,真的
package cn.itcast.utils;

import java.io.InputStream;
import java.io.PrintWriter;
import java.sql.Array;
import java.sql.Blob;
import java.sql.CallableStatement;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.NClob;
import java.sql.PreparedStatement;
import java.sql.SQLClientInfoException;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.SQLXML;
import java.sql.Savepoint;
import java.sql.Statement;
import java.sql.Struct;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.sql.DataSource;

public class JdbcPool implements DataSource {

    private static String url;
    private static String username;
    private static String password;
    private static String driver;
    //当整个应用一启起来,这个连接池就会找数据库要一批连接
    //然后用户要访问数据的时候dao层就会调用连接池的getConnection()这个方法
    //所以整个应用一启起来,这个连接池就必须要有一个静态的集合保持获取到的一批连接
    //这里还要注意一点,由于这个保存连接的集合不存在索引,所以选定list集合,而不闲着map集合
    //又由于在操作这个list集合时涉及到大量的增删,所以选定LinkedList,而不选额ArrayList
    //因为LinkedList是链表结构,ArrayList是数组结构
    private static LinkedList<Connection> list = new LinkedList<Connection>();
    //由于当应用一启动起来就会找数据库要一批连接,这个只会操作一次,所以放在静态代码块中
    static{
        try {
            //通过配置文件读取数据库和驱动
            InputStream in = JdbcUtils_Dbcp.class.getClassLoader().getResourceAsStream("db.properties");
            Properties prop = new Properties();
            prop.load(in);
            
            url = prop.getProperty("url");
            username = prop.getProperty("username");
            password = prop.getProperty("password");
            driver = prop.getProperty("driver");
            
            //装载数据库驱动
            Class.forName(driver);
            //向数据库要10个链接
            for(int i=0;i<10;i++){
                //通过驱动管理器向数据库要连接
                Connection conn = DriverManager.getConnection(url, username, password);
                //要到有一个链接之后就将这个连接加入到list中
                list.add(conn);
            }
        } catch (Exception e) {
            throw new ExceptionInInitializerError(e);
        }
    }
    
    //dao connection conn.close();
    /*
     1.写一个子类,重写close方法  ×
     2.用包装设计模式
     3.用动态代理进行增强
     */
    public Connection getConnection() throws SQLException {
        //当dao层调用getConnectin()这个方法时,需要判断list集合是否为空
        if(list.size()>0){
            //这里要用removeFirst(),理由是多个连接不能同时使用同一个connection,
            //所以这里一处第一个connection,同时返回移除的connection
            Connection conn = list.removeFirst();  
            /*
             *这里 不能直接返回Connection,因为在dao层用完这个连接之后就必须关闭
             *一关闭就将这个连接返回给数据库了,而并没有返给连接池,如果是这样写则这个
             *连接池就没有意义了,所以这里就必须重写Connection的close方法从而保证连接
             *的close()方法不会将连接返还给数据库,而是返回给连接池
             *这里就引出了java里面一个重要的概念:当一个类的方法不足以满足你的要求时
             *你想对这个方法加强,这个加强的方式有三种
             *1.写一个这个类的子类并重写这个方法
             *2.用包装设计模式去增强
             *3.用动态代理去增强
             *
             *我们这里假如用第一种方式写一个子类去继承Connection并从写了close()方法
             *但是这种方式却是行不通的,理由这些连接Connection是DriveManager通过驱动获取到的
             *此时得到的Connection包含了很多数据库的信息,而你继承Connection的类却不具备
             *这些数据,即使这个子类重写了close方法,也不可能连接到数据库,所以想通过子类加强
             *重写加强close的方法是行不通的,从这个问题上我们又引出了java中的一个概念,当要通过
             *子类去加强父类的方法时,必须要求父类不具有封装的数据,如果父类有封装的数据则不能使用
             *子类加强父类的方法
             *
             *既然第一种继承加强方式不行,那我们选用包装设计模式
             *包装设计模式的步骤:
             *1.写一个类实现与被增强对象相同接口(Connection)
             *2.定义一个变量,记住被增强对象
             *3.定义一个构造函数,接受被增强的对象
             *4.重写需要增强的方法
             *5.对于不需要增强的方法直接调用被增强对象的方法
             *
             *虽然包装类可以解决这个问题,但是却是很麻烦,理由是实现了接口Connection之后
             *虽然只加强了close方法,但是对于不需要加强的方法则直接调用被增强对象的方法
             *但是不需要增强的方法实在是太多了,所以最终极的方法非动态代理不可,但是动态
             *代理需要在后面讲解
             */
            MyConnection myconn = new MyConnection(conn,list);
            return myconn;
        }else{
            //当没有链接时,就给调用者抛一个异常,提示系统忙
            throw new RuntimeException("系统忙,等一会再来!!");
        }
    }

    public Connection getConnection(String username, String password)
            throws SQLException {
        // TODO Auto-generated method stub
        return null;
    }

    public PrintWriter getLogWriter() throws SQLException {
        // TODO Auto-generated method stub
        return null;
    }

    public int getLoginTimeout() throws SQLException {
        // TODO Auto-generated method stub
        return 0;
    }

    public void setLogWriter(PrintWriter arg0) throws SQLException {
        // TODO Auto-generated method stub

    }

    public void setLoginTimeout(int arg0) throws SQLException {
        // TODO Auto-generated method stub

    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        // TODO Auto-generated method stub
        return null;
    }
}

/*
             *既然第一种继承加强方式不行,那我们选用包装设计模式
             *包装设计模式的步骤:
             *1.写一个类实现与被增强对象相同接口(Connection)
             *2.定义一个变量,记住被增强对象
             *3.定义一个构造函数,接受被增强的对象
             *4.重写需要增强的方法
             *5.对于不需要增强的方法直接调用被增强的方法
 */
//1.写一个类实现与被增强对象相同接口(Connection)
class MyConnection implements Connection{
    //2.定义一个变量,记住被增强对象
    private Connection connection;
    private LinkedList list;
    //3.定义一个构造函数,接受被增强的对象
    //这里由于要将Connection存入到Linkedlist中,所以这里也将Linkedlist传递过来了
    public MyConnection(Connection connection,LinkedList list){
        this.connection = connection;
        this.list = list;
    }
    //4.重写需要增强的方法
    public void close() throws SQLException {
        //当调用close方法时就将这个connection存入到集合中
        this.list.add(this.connection);
    }
    //下面其它的方法都是调用要增强对象的方法
    public void clearWarnings() throws SQLException {
        this.connection.clearWarnings();
        
    }
    
    public void commit() throws SQLException {
        this.connection.commit();
        
    }
    public Statement createStatement() throws SQLException {
        return this.connection.createStatement();
    }
    public Statement createStatement(int resultSetType,
            int resultSetConcurrency, int resultSetHoldability)
            throws SQLException {
        return this.connection.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability);
    }
    
    public Statement createStatement(int resultSetType, int resultSetConcurrency)
            throws SQLException {
        // TODO Auto-generated method stub
        return null;
    }
    public boolean getAutoCommit() throws SQLException {
        // TODO Auto-generated method stub
        return false;
    }
    public String getCatalog() throws SQLException {
        // TODO Auto-generated method stub
        return null;
    }
    public int getHoldability() throws SQLException {
        // TODO Auto-generated method stub
        return 0;
    }
    public DatabaseMetaData getMetaData() throws SQLException {
        // TODO Auto-generated method stub
        return null;
    }
    public int getTransactionIsolation() throws SQLException {
        // TODO Auto-generated method stub
        return 0;
    }
    public Map<String, Class<?>> getTypeMap() throws SQLException {
        // TODO Auto-generated method stub
        return null;
    }
    public SQLWarning getWarnings() throws SQLException {
        // TODO Auto-generated method stub
        return null;
    }
    public boolean isClosed() throws SQLException {
        // TODO Auto-generated method stub
        return false;
    }
    public boolean isReadOnly() throws SQLException {
        // TODO Auto-generated method stub
        return false;
    }
    public String nativeSQL(String sql) throws SQLException {
        // TODO Auto-generated method stub
        return null;
    }
    public CallableStatement prepareCall(String sql, int resultSetType,
            int resultSetConcurrency, int resultSetHoldability)
            throws SQLException {
        // TODO Auto-generated method stub
        return null;
    }
    public CallableStatement prepareCall(String sql, int resultSetType,
            int resultSetConcurrency) throws SQLException {
        // TODO Auto-generated method stub
        return null;
    }
    public CallableStatement prepareCall(String sql) throws SQLException {
        // TODO Auto-generated method stub
        return null;
    }
    public PreparedStatement prepareStatement(String sql, int resultSetType,
            int resultSetConcurrency, int resultSetHoldability)
            throws SQLException {
        // TODO Auto-generated method stub
        return null;
    }
    public PreparedStatement prepareStatement(String sql, int resultSetType,
            int resultSetConcurrency) throws SQLException {
        // TODO Auto-generated method stub
        return null;
    }
    public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys)
            throws SQLException {
        // TODO Auto-generated method stub
        return null;
    }
    public PreparedStatement prepareStatement(String sql, int[] columnIndexes)
            throws SQLException {
        // TODO Auto-generated method stub
        return null;
    }
    public PreparedStatement prepareStatement(String sql, String[] columnNames)
            throws SQLException {
        // TODO Auto-generated method stub
        return null;
    }
    public PreparedStatement prepareStatement(String sql) throws SQLException {
        // TODO Auto-generated method stub
        return null;
    }
    public void releaseSavepoint(Savepoint savepoint) throws SQLException {
        // TODO Auto-generated method stub
        
    }
    public void rollback() throws SQLException {
        // TODO Auto-generated method stub
        
    }
    public void rollback(Savepoint savepoint) throws SQLException {
        // TODO Auto-generated method stub
        
    }
    public void setAutoCommit(boolean autoCommit) throws SQLException {
        // TODO Auto-generated method stub
        
    }
    public void setCatalog(String catalog) throws SQLException {
        // TODO Auto-generated method stub
        
    }
    public void setHoldability(int holdability) throws SQLException {
        // TODO Auto-generated method stub
        
    }
    public void setReadOnly(boolean readOnly) throws SQLException {
        // TODO Auto-generated method stub
        
    }
    public Savepoint setSavepoint() throws SQLException {
        // TODO Auto-generated method stub
        return null;
    }
    public Savepoint setSavepoint(String name) throws SQLException {
        // TODO Auto-generated method stub
        return null;
    }
    public void setTransactionIsolation(int level) throws SQLException {
        // TODO Auto-generated method stub
        
    }
    public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
        // TODO Auto-generated method stub
        
    }

    @Override
    public Array createArrayOf(String typeName, Object[] elements)
            throws SQLException {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public Blob createBlob() throws SQLException {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public Clob createClob() throws SQLException {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public NClob createNClob() throws SQLException {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public SQLXML createSQLXML() throws SQLException {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public Struct createStruct(String typeName, Object[] attributes)
            throws SQLException {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public Properties getClientInfo() throws SQLException {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public String getClientInfo(String name) throws SQLException {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public boolean isValid(int timeout) throws SQLException {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public void setClientInfo(Properties properties)
            throws SQLClientInfoException {
        // TODO Auto-generated method stub
        
    }

    @Override
    public void setClientInfo(String name, String value)
            throws SQLClientInfoException {
        // TODO Auto-generated method stub
        
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        // TODO Auto-generated method stub
        return null;
    }
}

 然后一个dao层来调用连接池

/day16/src/cn/itcast/demo/Dao.java

 
package cn.itcast.demo;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Savepoint;

import org.junit.Test;

import cn.itcast.utils.JdbcPool;
import cn.itcast.utils.JdbcUtils_Dbcp;

public class Dao{

    /**使用dbcp  
     * @param args
     * @throws SQLException 
     */
    
    @Test
    public void select1() throws SQLException {
        
        Connection conn = null;
        PreparedStatement st = null;
        ResultSet rs = null;
        Savepoint sp = null;
        try{
    //通过连接池获取链接
JdbcPool pool = new JdbcPool(); conn=pool.getConnection(); String sql = "select count(*) from account"; st = conn.prepareStatement(sql); rs = st.executeQuery(); if(rs.next()){ System.out.println(rs.getInt(1)); } }catch (Exception e) { e.printStackTrace(); }finally{
        //这里关闭链接其实会将链接返回给连接池 JdbcUtils_Dbcp.release(conn, st, rs); } }
}

 /day16/src/cn/itcast/utils/JdbcUtils.java

package cn.itcast.utils;

import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

public class JdbcUtils {

    private static String url;
    private static String username;
    private static String password;
    private static String driver;
    
    static{
        try {
            InputStream in = JdbcUtils.class.getClassLoader().getResourceAsStream("db.properties");
            Properties prop = new Properties();
            prop.load(in);
            
            url = prop.getProperty("url");
            username = prop.getProperty("username");
            password = prop.getProperty("password");
            driver = prop.getProperty("driver");
            
            Class.forName(driver);
        } catch (Exception e) {
            throw new ExceptionInInitializerError(e);
        }
    }
    
    //加载驱动,获取链接
    public static Connection getConnection() throws SQLException{
        Connection conn = DriverManager.getConnection(url,username,password);
        return conn;
    }
    
    //释放资源
    public static void release(Connection conn,Statement st,ResultSet rs){
        try{
            if(rs!=null) 
                rs.close();
        }catch (Exception e) {
            e.printStackTrace();
            rs=null;
        }
        try{
            if(st!=null) st.close();
        }catch (Exception e) {
            e.printStackTrace();
            st=null;
        }
        try{
            //这里关闭资源会掉用MyConnection的close方法,
            //这个close方法会将Connection添加到连接池中的LinkedList
            if(conn!=null) conn.close();
        }catch (Exception e) {
            e.printStackTrace();
            conn=null;
        }
        
    }
    
}

 

 Tip:数据库连接池核心代码

//注意:这个用动态代理构建连接池后面学完javaweb后会详细讲解,这里只是一个引子

l使用动态代理技术构建连接池中的connection

 

 

Tip:开源数据库连接池

 由于连接池实在是太重要了,于是一个开源组织提供了一些开源的连接池

l现在很多WEB服务器(Weblogic, WebSphere, Tomcat)都提供了DataSoruce的实现,即连接池的实现。通常我们把DataSource的实现,按其英文含义称之为数据源,数据源中都包含了数据库连接池的实现。
l也有一些开源组织提供了数据源的独立实现:
•DBCP 数据库连接池(这个是阿帕奇开发的)(这个也是Tomcat内置的连接池,毕竟Tomcat也是阿帕奇的)
•C3P0 数据库连接池(sprint里面就是用的这种)
l实际应用时不需要编写连接数据库代码,直接从数据源获得数据库的连接。程序员编程时也应尽量使用这些数据源的实现,以提升程序的数据库访问性能。

 Tomcat内置的数据库连接池其实是DBCP

在C:\Tomcat6.0\lib

 Tip:DBCP数据源

lDBCP 是 Apache 软件基金组织下的开源连接池实现,使用DBCP数据源,应用程序应在系统中增加如下两个 jar 文件:
•Commons-dbcp.jar:连接池的实现
•Commons-pool.jar:连接池实现的依赖库
lTomcat 的连接池正是采用该连接池来实现的。该数据库连接池既可以与应用服务器整合使用,也可由应用程序独立使用。

 

在使用的DBCP,也要通过读取配置文件来连接数据库

下面就是配置文件

/day16/src/mysqldbcpconfig.properties

#连接设置
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/day16
username=root
password=root

#<!-- 初始化连接找数据库要多少个连接 -->
initialSize=10

#最大连接数量为50个
maxActive=50

#<!-- 最大空闲连接为20个 -->
maxIdle=20

#<!-- 最小空闲连接为5个 -->
minIdle=5

#<!-- 超时等待时间以毫秒为单位 6000毫秒/1000等于60秒,当连接池中没有连接时等待60秒,等待空闲连接回来 -->
maxWait=60000


#JDBC驱动建立连接时附带的连接属性属性的格式必须为这样:[属性名=property;] 
#注意:"user" 与 "password" 两个属性会被明确地传递,因此这里不需要包含他们。
#这里设置连接的属性,设置了是否用Uicode编码,设置字符集为UTF8 connectionProperties
=useUnicode=true;characterEncoding=UTF8 #指定由连接池所创建的连接的自动提交(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包和配置文件后我们开始在代码中使用DBCP连接池

不过在这之前我们需要更改JDBC工具类。

/day16/src/cn/itcast/utils/JdbcUtils.java

package cn.itcast.utils;

import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

import javax.sql.DataSource;

import org.apache.commons.dbcp.BasicDataSourceFactory;

public class JdbcUtils {
    //这里的DataSource要设置成静态的
    private static DataSource ds;
    static{
        try {
            InputStream in = JdbcUtils.class.getClassLoader().getResourceAsStream("mysqldbcpconfig.properties");
            Properties prop = new Properties();
            prop.load(in);
            //这里通过BadicDataSourceFactory工厂类创建DBCP
            //然后将Properties作为参数传入,表示要连接那个数据库
            ds = BasicDataSourceFactory.createDataSource(prop);
        } catch (Exception e) {
            throw new ExceptionInInitializerError(e);
        }
    }
    
    //加载驱动,获取链接
    public static Connection getConnection() throws SQLException{
        //以前是向数据库要连接,现在是向DataSource要连接
        Connection conn = ds.getConnection();  
        return conn;
    }
    
    //释放资源
    public static void release(Connection conn,Statement st,ResultSet rs){
        try{
            if(rs!=null) 
                rs.close();
        }catch (Exception e) {
            e.printStackTrace();
            rs=null;
        }
        try{
            if(st!=null) st.close();
        }catch (Exception e) {
            e.printStackTrace();
            st=null;
        }
        try{
            //这里关闭资源会掉用MyConnection的close方法,
            //这个close方法会将Connection添加到连接池中的LinkedList
            if(conn!=null) conn.close();
        }catch (Exception e) {
            e.printStackTrace();
            conn=null;
        }
        
    }
    
}

 

 dao层调用通过DBCP连接池

package cn.itcast.demo;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Savepoint;

import org.junit.Test;

import cn.itcast.utils.JdbcPool;
import cn.itcast.utils.JdbcUtils_Dbcp;

public class Dao{

    /**使用dbcp  
     * @param args
     * @throws SQLException 
     */
    
    @Test
    public void select() throws SQLException {
        
        Connection conn = null;
        PreparedStatement st = null;
        ResultSet rs = null;
        Savepoint sp = null;
        try{
            conn = JdbcUtils.getConnection();  //Myconection
            String sql = "select count(*) from account";
            st = conn.prepareStatement(sql);
            rs = st.executeQuery();
            if(rs.next()){
                System.out.println(rs.getInt(1));
            }
        
        }catch (Exception e) {
            e.printStackTrace();
    
        }finally{
            JdbcUtils_Dbcp.release(conn, st, rs);
        }
    }
    
    @Test
    public void select2() throws SQLException {
        
        Connection conn = null;
        PreparedStatement st = null;
        ResultSet rs = null;
        Savepoint sp = null;
        try{
            conn = JdbcUtils_c3p0.getConnection();  //Myconection
            String sql = "select count(*) from account";
            st = conn.prepareStatement(sql);
            rs = st.executeQuery();
            if(rs.next()){
                System.out.println(rs.getInt(1));
            }
        }catch (Exception e) {
            e.printStackTrace();
    
        }finally{
            JdbcUtils_Dbcp.release(conn, st, rs);
        }
    }
}

 

 Tip: C3P0 数据源

 要想使用C3P0就必须先导入它的包

当数据库是mysql时则只需导入如下两个jar包就可以了

 

 当数据库是Oracle时则需要导入上面三个

 

 

 JdbcUtils_c3p0.java

package cn.itcast.utils;

import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

import javax.sql.DataSource;

import org.apache.commons.dbcp.BasicDataSourceFactory;

import com.mchange.v2.c3p0.ComboPooledDataSource;

public class JdbcUtils_c3p0 {

    private static ComboPooledDataSource ds;
    static{
        try {
            ds = new ComboPooledDataSource();
            ds.setDriverClass("com.mysq1.jdbc.Driver");
            ds.setJdbcUrl("jdbc:mysql://localhost:3306/day16");
            ds.setUser("root");
            ds.setPassword("root");
            
            ds.setMaxPoolSize(20);
            ds.setMinPoolSize(10);
            ds.setInitialPoolSize(10);
        } catch (Exception e) {
            throw new ExceptionInInitializerError(e);
        }
    }
    
    //加载驱动,获取链接
    public static Connection getConnection() throws SQLException{
        Connection conn = ds.getConnection();  //myconnection
        return conn;
    }
    
    //释放资源
    public static void release(Connection conn,Statement st,ResultSet rs){
        try{
            if(rs!=null) 
                rs.close();
        }catch (Exception e) {
            e.printStackTrace();
            rs=null;
        }
        try{
            if(st!=null) st.close();
        }catch (Exception e) {
            e.printStackTrace();
            st=null;
        }
        try{
            //MyConnection
            if(conn!=null) conn.close();
        }catch (Exception e) {
            e.printStackTrace();
            conn=null;
        }
        
    }
    
}

 

/day16/src/cn/itcast/demo/Dao.java

package cn.itcast.demo;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Savepoint;

import org.junit.Test;

import cn.itcast.utils.JdbcPool;
import cn.itcast.utils.JdbcUtils_Dbcp;

public class Dao{

    /**使用dbcp  
     * @param args
     * @throws SQLException 
     */
    
    @Test
    public void select2() throws SQLException {
        
        Connection conn = null;
        PreparedStatement st = null;
        ResultSet rs = null;
        Savepoint sp = null;
        try{
            conn = JdbcUtils_c3p0.getConnection();  //Myconection
            String sql = "select count(*) from account";
            st = conn.prepareStatement(sql);
            rs = st.executeQuery();
            if(rs.next()){
                System.out.println(rs.getInt(1));
            }
        }catch (Exception e) {
            e.printStackTrace();
    
        }finally{
            JdbcUtils_Dbcp.release(conn, st, rs);
        }
    }
}

 

 

 由于/day16/src/cn/itcast/utils/JdbcUtils_c3p0.java中直接输入的配置文件,这样写死了,并不好,所以这里我们想要c3p0通过读取配置文件去连接数据库

此时我们就需要去查看c3p0文档

在c3p0的src帮助文档中Configutation有如下一段话

说的是可以通过简单的javaproperties文件,也可以通过XML配置文件,还可以通过系统配置文件来设置c3p0的配置文件,并且配置文件一般以 (c3p0.properties or c3p0-config.xml)

命名

于是我们在工程src上建立一个c3p0-config.xml

然后我们再向下查看c3p0的配置文件,我们可以找到一个配置文件的例子,我们将这个例子粘贴到x3p0-config.xml

 然后在MyEclipse中按快捷键Ctrl+Shift+f,自动格式化

在c3p0的配置文件中可以为多个数据库做配置,也有默认配置,实在是很方便

 /day16/src/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/day16</property>
        <property name="user">root</property>
        <property name="password">root</property>

        <property name="acquireIncrement">10</property>
        <property name="initialPoolSize">10</property>
        <property name="minPoolSize">5</property>
        <property name="maxPoolSize">20</property>
    </default-config><!-- This app is massive! -->


    <named-config name="mysql">
        <!-- 驱动 -->
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <!-- 数据库地址-->
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/day16</property>
        <!-- 用户名-->
        <property name="user">root</property>
        <!-- 密码-->
        <property name="password">root</property>
    <!-- 这个表示增长量,当连接池中没有连接时,会想数据库要10个连接 -->
        <property name="acquireIncrement">10</property>    
    <!-- 默认初始化连接个数为10个 -->
        <property name="initialPoolSize">10</property>
    <!-- 最小连接数为5个 -->
        <property name="minPoolSize">5</property>
    <!-- 最大连接数位20个 -->
        <property name="maxPoolSize">20</property>
        <!--
            intergalactoApp adopts a different approach to configuring statement
            caching
        -->
    </named-config>

    <named-config name="oracle">
        <property name="acquireIncrement">50</property>
        <property name="initialPoolSize">100</property>
        <property name="minPoolSize">50</property>
        <property name="maxPoolSize">1000</property>
        <!--
            intergalactoApp adopts a different approach to configuring statement
            caching
        -->
        <!-- 这个maxStatements表示缓存多少条sql语句 -->
        <property name="maxStatements">0</property>
        <property name="maxStatementsPerConnection">5</property>
        <!-- he's important, but there's only one of him -->
        <user-overrides user="master-of-the-universe">
            <property name="acquireIncrement">1</property>
            <property name="initialPoolSize">1</property>
            <property name="minPoolSize">1</property>
            <property name="maxPoolSize">5</property>
            <property name="maxStatementsPerConnection">50</property>
        </user-overrides>
    </named-config>
</c3p0-config>

 

 此时有了xml文件,我们就不能采用开始那种直接连接数据的方式了,那我们该怎么做呢?还是看C3P0的帮助文档赛
这里说了可以直接用这个引用ComboPooledDataSource这个去引用c3p0-config.xml中的配置
/day16/src/cn/itcast/demo/JdbcUtils_c3p0.java
package cn.itcast.demo;

import java.io.InputStream;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

import javax.sql.DataSource;

import org.apache.commons.dbcp.BasicDataSourceFactory;

import com.mchange.v2.c3p0.ComboPooledDataSource;

public class JdbcUtils_c3p0 {

    private static ComboPooledDataSource ds;
    static{
        try{
            //由于 c3p0采用了缺省xml的名称,所以这里可以不指定,这个ds会自动搜索c3p0-config.xml这个文件
            ds = new ComboPooledDataSource();                //这个表示选用c3p0-config.xml中默认的配置
            //ds = new ComboPooledDataSource("mysql");        //这个表示选用c3p0-config.xml中名称为mysql的配置
            //ds = new ComboPooledDataSource("oracle");        //这个表示选用c3p0-config.xml中名为oracle的配置
        }catch (Exception e) {
            throw new ExceptionInInitializerError(e);
        }
    }
    
    //加载驱动,获取链接
    public static Connection getConnection() throws SQLException{
        Connection conn = ds.getConnection();  //myconnection
        return conn;
    }
    
    //释放资源
    public static void release(Connection conn,Statement st,ResultSet rs){
        try{
            if(rs!=null) 
                rs.close();
        }catch (Exception e) {
            e.printStackTrace();
            rs=null;
        }
        try{
            if(st!=null) st.close();
        }catch (Exception e) {
            e.printStackTrace();
            st=null;
        }
        try{
            //MyConnection
            if(conn!=null) conn.close();
        }catch (Exception e) {
            e.printStackTrace();
            conn=null;
        }
    }
    
    //增删改  sql
    public static void update(String sql,Object params[]) throws SQLException{
        Connection conn = null;
        PreparedStatement st = null;
        ResultSet rs = null;
        try{
            conn = JdbcUtils_c3p0.getConnection();
            st = conn.prepareStatement(sql);
            for(int i=0;i<params.length;i++){
                st.setObject(i+1, params[i]);
            }
            st.executeUpdate();
        }finally{
            JdbcUtils_c3p0.release(conn, st, rs);
        }
    }
    
    public static Object query(String sql,Object params[],ResultSetHandler rsh) throws SQLException{
        
        Connection conn = null;
        PreparedStatement st = null;
        ResultSet rs = null;
        try{
            conn = JdbcUtils_c3p0.getConnection();
            st = conn.prepareStatement(sql);
            for(int i=0;i<params.length;i++){
                st.setObject(i+1, params[i]);
            }
            rs = st.executeQuery();
            
            //调用用户的方法去处理
            return rsh.handler(rs);
        }finally{
            JdbcUtils_c3p0.release(conn, st, rs);
        }
    }
}

interface ResultSetHandler{
    public Object handler(ResultSet rs);
}

class BeanHandler implements ResultSetHandler{

    private Class clazz;
    public BeanHandler(Class clazz){
        this.clazz = clazz;
    }
    
    public Object handler(ResultSet rs) {
        try{
            //1.准备好于保存数据的bean
            Object bean = clazz.newInstance();
            
            if(!rs.next()){
                return null;
            }
            
            //2.通过元技术获取rs中封装的数据的信息
            ResultSetMetaData rsm = rs.getMetaData();
            
            int columnCount = rsm.getColumnCount();
            for(int i=0;i<columnCount;i++){
                String columnName = rsm.getColumnName(i+1);  //获取处理的列的列名  name
                Object data = rs.getObject(columnName);  //获取到列的数据  aa
                
                Field f = bean.getClass().getDeclaredField(columnName); //得到bean上面列名对应的属性
                f.setAccessible(true);
                f.set(bean, data);
            }
            return bean;
        
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
}

 

 /day16/src/cn/itcast/demo/Dao.java
 
package cn.itcast.demo;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Savepoint;

import org.junit.Test;

import cn.itcast.utils.JdbcPool;
import cn.itcast.utils.JdbcUtils_Dbcp;

public class Dao {

    /**
     * 使用dbcp
     * 
     * @param args
     * @throws SQLException
     */

    @Test
    public void select2() throws SQLException {

        Connection conn = null;
        PreparedStatement st = null;
        ResultSet rs = null;
        Savepoint sp = null;
        try {
            conn = JdbcUtils_c3p0.getConnection(); // Myconection
            String sql = "select count(*) from account";
            st = conn.prepareStatement(sql);
            rs = st.executeQuery();
            if (rs.next()) {
                System.out.println(rs.getInt(1));
            }
        } catch (Exception e) {
            e.printStackTrace();

        } finally {
            JdbcUtils_Dbcp.release(conn, st, rs);
        }
    }
}

 

 Tip:配置Tomcat数据源

下面我们开看Tomcat内置的连接池(注意有些人也将连接池叫做数据源(DataSource))

l查看Tomcat文档,示例代码:
 
l特别提醒:此种配置下,驱动jar文件需放置在tomcat的lib下
这里要注意一下,Tomcat会将这个连接池保存在一个JNDI的容器中
Resource name="jdbc/datasource" auth="Container其中这句中的name就是保存到JNDI中的
名称,这样在Tomcat中就可以通过这个名称直接检索到这个连接池。而这里的auth="Container"是
表示这个池是有容器来创建的
 
还要注意一点,如果要使用Tomcat里面的连接池就必须是web工程,不能是普通的java工程
所以我们这里建立一个javaweb工程day16_datasource,然后我们来配置这个工程
关于Context.xml可以在很多地方配置,这里选择在META—INF中来创建这个xml文件
/day16_datasource/WebRoot/META-INF/context.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"
            maxActive="8"
            maxIdle="4"/>
</Context>

由于开始我们说过Tomcat的连接池是存入到JNDI中的,所以我们在Tomcat的帮助文档中这个地方JNDI Resources获取到如何写Context.xml

我没进入到JNDI Resources中,向下拉

这里就是教你如何配置Context.xml

 
当Tomcat启动以后就会启动一个名叫JNDI这么一个容器,然后将你创建的连接池存在这个JNDI中,而且是以Context.xml中命的名称存入到JNDI中的 
当某个servlet想去连接数据库,只需要这个连接从JDNI中查找出这个名称的连接池就可以,然后通过连接池获取连接
这里有引申出java中一个重要的概念,对象(Tomcat)与对象(想要连接数据库的servlet)之间如何传对象(连接池)的问题,在以前没有学JNDI时,
要通过调用对象的方法才能拿到想要的东西,而现在对象直接可以将东西存入到容器中(JNDI容器),其它对象想要则到容器中检索出来
 
写好了Context.xml后,现在有一个servlet想要从JNDI容器中检索出连接池,又该如何做呢?其实在Tomcat的帮助文档中还是有说明的
 
 /day16_datasource/src/cn/itcast/web/servlet/ServletDemo1.java
 
 
package cn.itcast.web.servlet;

import java.io.IOException;
import java.sql.Connection;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;

public class ServletDemo1 extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        //只需要从tomcat中的jndi容器检索出连接池
        
        try{
            Context initCtx = new InitialContext();  //初始化jndi,JNDI最核心的类就是Context
            Context envCtx = (Context) initCtx.lookup("java:comp/env");  //得到tomcat用于保存连接池的jndi容器
            DataSource ds = (DataSource)envCtx.lookup("jdbc/EmployeeDB");  //从容器中检索连接池
            Connection conn = ds.getConnection();
            System.out.println(conn);
        }catch (Exception e) {
            e.printStackTrace();
        }
    
    }

    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }

}

 接着我们开启Tomcat服务器,运行SrevletDemo1,发现抛异常了

 
 但是明明我们将驱动倒入了应用中,可以为什么还要抛异常说没有找到驱动异常
 
这是因为Tomcat在启动时,不会在应用中去找这个驱动包,会直接在它自己的lib包中去找驱动jar包
所以我们要将mysql的驱动包倒入到Tomcat的lib文件下
 
 
 以上讲解的三种开源连接池在开发中都很常用,所以我们都要好好学习
 

Tip:JNDI技术简介

JNDI是javaee十三种技术中的一种
lJNDI(Java Naming and Directory Interface)Java命名和目录接口,它对应于J2SE中的javax.naming包,
l这套API的主要作用在于:它可以把Java对象放在一个容器中(JNDI容器),并为容器中的java对象取一个名称,以后程序想获得Java对象,只需通过名称检索即可。
l其核心APIContext,它代表JNDI容器,其lookup方法为检索容器中对应名称的对象。
 
 在开发中程序员都是在服务下编程,所写的程序都是给程序调用的,服务器启动时经常要给你的程序传一些对象
服务器要给你传对象无非有两种方法第一种服务器调用你的程序的方法,然后传递参数对象给你,第二种就是服务器将
参数对象保存到容器里面,你的程序什么时候想去就到容器中去检索就可以取到
 
 ——————————————————————————————————————————————————————————————————

 编写自己的JDBC框架

这节课我们学习jdbc优化

Tip:元数据- DataBaseMetaData

l元数据:数据库、表、列的定义信息。(比如:数据库表到底有几行几列,这个是表的第一信息,我们就把这个称谓元数据)
lConnection.getDatabaseMetaData(),(我们可以通过这个方法拿到一个DataBaseMetaDate对象,拿到这个对象也就相当于拿到数据库的第一信息)
lDataBaseMetaData对象  (然后我们可以通过这个对象拿到数据库的元数据)
•getURL():返回一个String类对象,代表数据库的URL。
•getUserName():返回连接当前数据库管理系统的用户名。
•getDatabaseProductName():返回数据库的产品名称。
•getDatabaseProductVersion():返回数据库的版本号。
•getDriverName():返回驱动驱动程序的名称。
•getDriverVersion():返回驱动程序的版本号。
•isReadOnly():返回一个boolean值,指示数据库是否只允许读操作。
 
 我们什么情况下需要获取数据库的元数据,我们在做框架的时候就需要元信息,所以在做框架的时候就需要获取元数据
比如后面要学的habernet框架就需要获取数据库的元信息
 /day16/src/cn/itcast/demo/Demo4.java
package cn.itcast.demo;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import cn.itcast.utils.JdbcUtils_Dbcp;

public class Demo4 {

    /**获取数据库的元信息
     * @param args
     * @throws SQLException 
     */
    public static void main(String[] args) throws SQLException {

        Connection conn = null;
        PreparedStatement st = null;
        ResultSet rs = null;
        try{
            conn = JdbcUtils_Dbcp.getConnection();
            DatabaseMetaData  metadata = conn.getMetaData();
            System.out.println(metadata.getDatabaseProductName());
            System.out.println(metadata.getURL());
            System.out.println(metadata.getDriverName());
            
            
        }finally{
            JdbcUtils_Dbcp.release(conn, st, rs);
        }
        
    }

}

 这个技术学好之后有助于将来学习harbenet等技术

 

Tip:元数据- ParameterMetaData

在开发中有时候我们也要获取参数的原信息,由于PrepareStatement中包含了sql语句,这些sql语句有有?号代表参数,于是我们可以通过getParameterMetaDate()

获取到这些参数的原信息,这个技术还是用在框架上,如果你用这个技术不用再框架上则一点用都没有

lPreparedStatement . getParameterMetaData()
•获得代表PreparedStatement元数据的ParameterMetaData对象。
•Select * from user where name=? And password=?
lParameterMetaData对象
•getParameterCount()
•获得指定参数的个数
•getParameterType(int param)
•获得指定参数的sql类型

 /day16/src/cn/itcast/demo/Demo5.java

package cn.itcast.demo;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ParameterMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import cn.itcast.utils.JdbcUtils_Dbcp;

public class Demo5 {

    /**获取sql语句参数的元信息
     * @param args
     * @throws SQLException 
     */
    public static void main(String[] args) throws SQLException {

        Connection conn = null;
        PreparedStatement st = null;
        ResultSet rs = null;
        try{
            conn = JdbcUtils_Dbcp.getConnection();
            String sql = "insert into account(name,money) values(?,?)";
            st = conn.prepareStatement(sql);
            //这条信息就包含了sql语句中?参数的元信息
            ParameterMetaData pmd = st.getParameterMetaData();
            //获取到sql语句中参数的个数
            System.out.println(pmd.getParameterCount());
            //这个是获取sql语句中参数的类型,但是
            //这个要报异常java.sql.SQLException
            //理由是ParameterMetaDate是接口,这个接口由mysql去实现
            //这里其实是调用Mysql实现类的getParameterType()方法,
            //由于mysql驱动没有支持这个方法,所以要报异常
            System.out.println(pmd.getParameterType(1));  //throw 
        }finally{
            JdbcUtils_Dbcp.release(conn, st, rs);
        }
    }
}

 

 报的异常

 

 Tip:元数据- ResultSetMetaData

获取结果集的元数据,这个技术还是用于做框架
lResultSet. getMetaData()  //获得代表ResultSet对象元数据的ResultSetMetaData对象。
lResultSetMetaData对象的方法
•getColumnCount()  //返回resultset对象的列数
•getColumnName(int column)  //获得指定列的名称
• getColumnTypeName(int column)    //获得指定列的类型 

 /day16/src/cn/itcast/demo/Demo6.java

package cn.itcast.demo;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;

import cn.itcast.utils.JdbcUtils_Dbcp;

public class Demo6 {

    /**获取结果集的元信息
     * @param args
     * @throws SQLException 
     */
    public static void main(String[] args) throws SQLException {

        Connection conn = null;
        PreparedStatement st = null;
        ResultSet rs = null;
        try{
            conn = JdbcUtils_Dbcp.getConnection();
            String sql = "select * from account";
            rs = conn.prepareStatement(sql).executeQuery();
            //获取结果集元信息
            ResultSetMetaData  rsmd = rs.getMetaData();
            //获取account这个表有多少列
            System.out.println(rsmd.getColumnCount());
            //获取每列的名称
            System.out.println(rsmd.getColumnName(1));
            System.out.println(rsmd.getColumnName(2));
            System.out.println(rsmd.getColumnName(3));
       rs.getObject("id");  //最后通过结果集的getObject(获取到的列名)就可以获取值了 }
finally{ JdbcUtils_Dbcp.release(conn, st, rs); } } }

 

 

 有了以上获取元数据的基础,我们就可以开始做jdbc框架,简化jdbc开发

 Tip:使用元数据简化JDBC代码

l业务背景:系统中所有实体对象都涉及到基本的CRUD操作:
•所有实体的CUD操作代码基本相同,仅仅发送给数据库的SQL语句不同而已,因此可以把CUD操作的所有相同代码抽取到工具类的一个update方法中,并定义参数接收变化的SQL语句。
•实体的R操作,除SQL语句不同之外,根据操作的实体不同,对ResultSet的映射也各不相同,因此可义一个query方法,除以参数形式接收变化的SQL语句外,可以使用策略模式由qurey方法的调用者决定如何把ResultSet中的数据映射到实体对象中。

 

由于JDBC操作数据时,增删改都有重复的代码(唯独查的代码不同),所以我们可以在JdbcUtil工具类中将这些代码重复代码统一起来,以达到优化的目的

增删改的优化,主要是通过将增删改的sql语句和参数传递过来,实现了统一代码,然后只是在增删改的的方法中调用这个工具方法就能简化代码

这里对查找的优化就有些麻烦了,主要是通过元件技术获取到结果集中的数据,然后再通过反射技术存入到用户指定的bean对象中,这里对查的优化是在是

非常精辟的代码

/day16/src/cn/itcast/demo/JdbcUtils_c3p0.java

 

package cn.itcast.demo;

import java.io.InputStream;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

import javax.sql.DataSource;

import org.apache.commons.dbcp.BasicDataSourceFactory;

import com.mchange.v2.c3p0.ComboPooledDataSource;

public class JdbcUtils_c3p0 {

    private static ComboPooledDataSource ds;
    static{
        try{
            ds = new ComboPooledDataSource();
        }catch (Exception e) {
            throw new ExceptionInInitializerError(e);
        }
    }
    
    //加载驱动,获取链接
    public static Connection getConnection() throws SQLException{
        Connection conn = ds.getConnection();  //myconnection
        return conn;
    }
    
    //释放资源
    public static void release(Connection conn,Statement st,ResultSet rs){
        try{
            if(rs!=null) 
                rs.close();
        }catch (Exception e) {
            e.printStackTrace();
            rs=null;
        }
        try{
            if(st!=null) st.close();
        }catch (Exception e) {
            e.printStackTrace();
            st=null;
        }
        try{
            //MyConnection
            if(conn!=null) conn.close();
        }catch (Exception e) {
            e.printStackTrace();
            conn=null;
        }
    }
    
    //这里是对增删改的优化,主要是通过将增删改的sql语句和参数传递过来,实现了统一代码,
    //然后只是在增删改的的方法中调用这个工具方法就能简化代码
    public static void update(String sql,Object params[]) throws SQLException{
        Connection conn = null;
        PreparedStatement st = null;
        ResultSet rs = null;
        try{
            conn = JdbcUtils_c3p0.getConnection();
            st = conn.prepareStatement(sql);
            for(int i=0;i<params.length;i++){
                //这里将传递过来的参数付给了sql语句中的?
                st.setObject(i+1, params[i]);
            }
            st.executeUpdate();
        }finally{
            JdbcUtils_c3p0.release(conn, st, rs);
        }
    }
    //此处是对查的优化
    public static Object query(String sql,Object params[],ResultSetHandler rsh) throws SQLException{
        
        Connection conn = null;
        PreparedStatement st = null;
        ResultSet rs = null;
        try{
            conn = JdbcUtils_c3p0.getConnection();
            st = conn.prepareStatement(sql);
            for(int i=0;i<params.length;i++){
                st.setObject(i+1, params[i]);
            }
            rs = st.executeQuery();
            //当查询得到了结果集后到这里不知道如何对结果集处理,也不知道该向那个bean插入结果集中的数据
            //但是用户知道该向那个bean对象插入结果集中的数据,所以这里就调用用户的方法去处理结果集
            //为了调用用户的方法去处理,这里就必须定义用户的行为,所以这里就对外暴露一个接口,由用户去
            //实现接口处理结果集,本来做到这里就完了,但是这样的框架对于用户来说除了要出入sql语句和参数外
            //还要自己实现一个处理结果集的方法,在下面我们做了常见的结果集处理方式
            return rsh.handler(rs);
        }finally{
            JdbcUtils_c3p0.release(conn, st, rs);
        }
    }
}

interface ResultSetHandler{
    public Object handler(ResultSet rs);
}
//此处是常见的结果集处理方式
//再写结果集处理器时由于作者不知道用户会将这个结果集中的数据存入到那个bean中
//但是这里可以通过用户在new结果集处理器时将bean类名传递过来,然后
//通过元技术取出结果集中的数据,然后在通过反射技术存入到用户指定的bean中
class BeanHandler implements ResultSetHandler{

    private Class clazz;
    public BeanHandler(Class clazz){
        this.clazz = clazz;
    }
    
    public Object handler(ResultSet rs) {
        try{
            //1.准备好于保存数据的bean
            Object bean = clazz.newInstance();
            //加入这个结果集没有数据则返回null
            if(!rs.next()){
                return null;
            }
            
            //2.通过元技术获取rs中封装的数据的信息
            ResultSetMetaData rsm = rs.getMetaData();
            //查看数据库表有几列
            int columnCount = rsm.getColumnCount();
            for(int i=0;i<columnCount;i++){
                String columnName = rsm.getColumnName(i+1);  //获取处理的列的列名  name
                Object data = rs.getObject(columnName);  //获取到列的数据  aa
                
                Field f = bean.getClass().getDeclaredField(columnName); //得到bean上面列名对应的属性
                f.setAccessible(true);
                f.set(bean, data);
            }
            return bean;
        
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
}

 

 /day16/src/cn/itcast/demo/Demo7.java

package cn.itcast.demo;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.junit.Test;

import cn.itcast.domain.User;

public class Demo7 {

    public static void main(String[] args) throws SQLException {
        Demo7 d = new Demo7();
        d.find();
    }
    
    @Test
    public void insert() throws SQLException{
        String sql = "insert into users(id,name,password,email,birthday) values(?,?,?,?,?)";
        Object params[] = {1,"hhh","123","hh@sina.com",new Date()};
        JdbcUtils_c3p0.update(sql, params);
    }
    
    @Test
    public void update() throws SQLException{
        String sql = "update users set name=?,email=? where id=?";
        Object params[] = {"xxx","xx@sina.com",1};
        JdbcUtils_c3p0.update(sql, params);
    }
    
    @Test
    public void delete() throws SQLException{
        String sql = "delete from users where id=?";
        Object params[] = {1};
        JdbcUtils_c3p0.update(sql, params);
    }
    
    @Test
    public User find() throws SQLException{
        String sql = "select * from users where id=?";
        Object params[] = {1};
        //这里只传递了sql语句,sql语句中的参数,以及结果集处理对象
        User user = (User) JdbcUtils_c3p0.query(sql, params, new BeanHandler(User.class));    
        return user;
            
    }
    
    public List getAll(){
        Connection conn = null;
        Statement st = null;
        ResultSet rs = null;
        try{
            conn = JdbcUtils_c3p0.getConnection();
            st = conn.createStatement();
            String sql = "select * from users";
            rs = st.executeQuery(sql);
            List list = new ArrayList();
            while(rs.next()){  //false
                User user = new User();
                user.setBirthday(rs.getDate("birthday"));
                user.setEmail(rs.getString("email"));
                user.setId(rs.getInt("id"));
                user.setName(rs.getString("name"));
                user.setPassword(rs.getString("password"));
                list.add(user);
            }
            return list;
        }catch (Exception e) {
            e.printStackTrace();
        }finally{
            JdbcUtils_c3p0.release(conn, st, rs);
        }
        return null;
    }
    
}

 

 /day16/src/cn/itcast/demo/JdbcUtils_c3p0.java中的接口ResultSetHandler和结果集处理器BeanHandler都应该独立出来,方便外部调用

public interface ResultSetHandler{
    public Object handler(ResultSet rs);
}
public class BeanHandler implements ResultSetHandler{

    private Class clazz;
    public BeanHandler(Class clazz){
        this.clazz = clazz;
    }
    
    public Object handler(ResultSet rs) {
        try{
            //1.准备好于保存数据的bean
            Object bean = clazz.newInstance();
            //加入这个结果集没有数据则返回null
            if(!rs.next()){
                return null;
            }
            
            //2.通过元技术获取rs中封装的数据的信息
            ResultSetMetaData rsm = rs.getMetaData();
            //查看数据库表有几列
            int columnCount = rsm.getColumnCount();
            for(int i=0;i<columnCount;i++){
                String columnName = rsm.getColumnName(i+1);  //获取处理的列的列名  name
                Object data = rs.getObject(columnName);  //获取到列的数据  aa
                
                Field f = bean.getClass().getDeclaredField(columnName); //得到bean上面列名对应的属性
                f.setAccessible(true);
                f.set(bean, data);
            }
            return bean;
        
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

 

下面将优化day14_customer项目中的JDBC,这个JDBC优化中涉及到了三种结果集处理器

一是将结果集中的数据存入到用户指定的bean中,

二是将结果集中的数据先存入到用户指定的bean中,然后再存入到list中

三十将结果集中的数据直接返回

/day16_customer/src/cn/itcast/dao/impl/CustomerDaoImpl.java

package cn.itcast.dao.impl;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;

import cn.itcast.dao.CustomerDao;
import cn.itcast.domain.Customer;
import cn.itcast.domain.QueryResult;
import cn.itcast.exception.DaoException;
import cn.itcast.utils.BeanHandler;
import cn.itcast.utils.BeanListHandler;
import cn.itcast.utils.JdbcUtils;
import cn.itcast.utils.JdbcUtils_c3p0;
import cn.itcast.utils.NumberHandler;

public class CustomerDaoImpl implements CustomerDao {

    public void add(Customer c){
        try{
        String sql = "insert into customer(id,name,gender,birthday,cellphone,email,preference,type,description) values(?,?,?,?,?,?,?,?,?)";
        Object params[] = {c.getId(),c.getName(),c.getGender(),c.getBirthday(),c.getCellphone(),c.getEmail(),c.getPreference(),c.getType(),c.getDescription()};
        JdbcUtils_c3p0.update(sql, params);
        }catch (Exception e) {
            throw new DaoException(e);
        }
    }
    
    public void update(Customer c){
        try{
        String sql = "update customer set name=?,gender=?,birthday=?,cellphone=?,email=?,preference=?,type=?,description=? where id=?";
        Object params[] = {c.getName(),c.getGender(),c.getBirthday(),c.getCellphone(),c.getEmail(),c.getPreference(),c.getType(),c.getDescription(),c.getId()};
        JdbcUtils_c3p0.update(sql, params);
        }catch (Exception e) {
            throw new DaoException(e);
        }
    }
    
    public void delete(String id){
        try{
            String sql = "delete from customer where id=?";
            Object params[] = {id};
            JdbcUtils_c3p0.update(sql, params);
        }catch (Exception e) {
            throw new DaoException(e);
        }
    }
    
    public Customer find(String id){
        try{
            String sql = "select * from customer where id=?";
            Object params[] = {id};
            return (Customer) JdbcUtils_c3p0.query(sql, params, new BeanHandler(Customer.class));
        }catch (Exception e) {
            throw new DaoException(e);
        }
    }
    
    public List<Customer> getAll(){
        try{
            String sql = "select * from customer";
            //参数为空
            Object params[] = {};
            //由于返回的Object,所以要强转成list
            return (List<Customer>) JdbcUtils_c3p0.query(sql, params, new BeanListHandler(Customer.class));
        }catch (Exception e) {
            throw new DaoException(e);
        }    
    }
    
    public QueryResult pageQuery(int startindex,int pagesize){
        try{
            QueryResult qr = new QueryResult();
            String sql = "select * from customer limit ?,?";
            Object params[] = {startindex,pagesize};
            //由于这里
            List list = (List) JdbcUtils_c3p0.query(sql, params, new BeanListHandler(Customer.class));
            qr.setList(list);
            
            sql = "select count(*) from customer";  // 88
            params = new Object[0];
            //注意select count(*) from customer这句sql语句返回的是一个bigLong类型的数据,
            //所以这里必须用long类型,不能用int类型
            long totalrecord = (Long)JdbcUtils_c3p0.query(sql, params, new NumberHandler());
            //这里在讲long类型强转成int类型
            qr.setTotalrecord((int)totalrecord);
            
            return qr;
        }catch (Exception e) {
            throw new DaoException(e);
        }    
        
        
    }
}

 

 /day16_customer/src/cn/itcast/utils/ResultSetHandler.java接口

package cn.itcast.utils;

import java.sql.ResultSet;

public interface ResultSetHandler {
    public Object handler(ResultSet rs);
}

 

 /day16_customer/src/cn/itcast/utils/BeanHandler.java(这个是将结果集中的数据存入到用户指定的bean中)

 

package cn.itcast.utils;

import java.lang.reflect.Field;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;

public class BeanHandler implements ResultSetHandler{

    private Class clazz;
    public BeanHandler(Class clazz){
        this.clazz = clazz;
    }
    
    public Object handler(ResultSet rs) {
        try{
            //1.准备好于保存数据的bean
            Object bean = clazz.newInstance();
            
            if(!rs.next()){
                return null;
            }
            
            //2.通过元技术获取rs中封装的数据的信息
            ResultSetMetaData rsm = rs.getMetaData();
            
            int columnCount = rsm.getColumnCount();
            for(int i=0;i<columnCount;i++){
                String columnName = rsm.getColumnName(i+1);  //获取处理的列的列名  name
                Object data = rs.getObject(columnName);  //获取到列的数据  aa
                
                Field f = bean.getClass().getDeclaredField(columnName); //得到bean上面列名对应的属性
                f.setAccessible(true);
                f.set(bean, data);
            }
            return bean;
        
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
}

 

 /day16_customer/src/cn/itcast/utils/BeanListHandler.java(这个是将结果集中的数据先存入到用户指定的bean中然后在存入到list集合中)

 

package cn.itcast.utils;

import java.util.List;
import java.lang.reflect.Field;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;

public class BeanListHandler implements ResultSetHandler {

    private Class clazz;
    //通过构造方法获取到用户传递过来的bean类
    public BeanListHandler(Class clazz){
        this.clazz = clazz;
    }
    
    public Object handler(ResultSet rs) {
        
        List list = new ArrayList();
        try{
            //有while(rs.next())循环就可以保证结果集是一条一条的保存到bean中
            while(rs.next()){
                //当结果集中有数据时,整出一个bean对象
                Object bean = clazz.newInstance();
                //通过元技术获取到结果集中的数据
                ResultSetMetaData rsmd = rs.getMetaData();
                //获取到数据库表有几列
                int columnCount = rsmd.getColumnCount();
                for(int i=0;i<columnCount;i++){
                    //获取到列明
                    String columnName = rsmd.getColumnName(i+1);
                    //根据列名获取到值
                    Object value = rs.getObject(columnName);
                    //通过反射技术将将值存入到bean中
                    Field f = bean.getClass().getDeclaredField(columnName);
                    f.setAccessible(true);
                    f.set(bean, value);
                }
                //将bean存入到list集合中去
                list.add(bean);
            }
            //最后返回list集合
            return list;
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

}

 

/day16_customer/src/cn/itcast/utils/NumberHandler.java(这个是直接返回结果集中的数据)

package cn.itcast.utils;

import java.sql.ResultSet;

public class NumberHandler implements ResultSetHandler {

    public Object handler(ResultSet rs) {
        try{
            //如果结果集有数据则返回,如果没有就返回0
            if(rs.next()){
                return rs.getObject(1);
            }
            return 0;
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

}

 

对于这个JDBC的优化的使用条件必须是bean中的名称和数据库表中的名称对应,不然用不了这个优化框架,这个优化框架也就是明天要学的DButils框架

 

Tip:O-R Mapping简介

l什么是O-R  Mapping
  就是对象(Object)关系(Ralation)映射,就是把对象的数据映射到关系型数据库中的工具
由于通常我们要将对象中的数据存入到关系型数据库中,这个将对象中的数据存入到关系型数据库中是一个难题,所以市面上出现了一些O-R Mapping映射工具
l常用O-R Mapping映射工具
•Hibernate
•Ibatis
•Commons DbUtils(只是对JDBC简单封装)

 

 

 

posted @ 2013-11-04 21:12  ysfox  阅读(176)  评论(0编辑  收藏  举报