JDBC 从入门到精通

引入:   

先猜想一下  java程序如何和mysql建立连接:
        思路:可以用socket,需要遵循的是mysql协议。那么mysql又是如何规定发送的数据的格式和mysql返回的响应数据的格式呢?
        java程序通过mysql协议发送请求(内容以sql语句)给服务端,服务端按照mysql协议处理数据结果,
        java程序再解析协议,处理数据(这些过程mysql已经给我们提供了一些API)
         但是又出现了一个问题:不同的数据库的协议不同,那么API也不会不同,那我们的java程序就得重写去适配不同的数据库。
    
        此时sun公司指定了一套标准协议,所有支持java语言的数据库,必须支持java语言的一套协议:jdbc
        

JDBC:java database connectivity

        也就是数据库和java程序交互的一套标准API。
        但是这些API,大多都是接口,我们还必须实现?
        这些具体实现,都通过不同的数据库厂商自己来实现,也就是  数据库的驱动程序。
        所以虽然实现不同,但是都是相同的接口。
         驱动程序:  实现mysql协议的jar包
        
    所以jdbc的流程就清晰了:
        1.建立连接
        2.书写sql语句,mysql服务器发送sql语句
        3.mysql接收并按照sql语句来处理数据
        4.mysql服务器发送响应到客户端,客户端接收数据
        5.java程序按照业务逻辑,处理响应数据。
        6.释放资源。
        

        

测试框架:

    
    对public class 类名,的类名右键,Goto ——> Test ——>JUnit4(开源测试框架)
    里面的所有方法都可以单独运行。
    一旦添加过这个JUnit4框架,以后就直接在普通类中的 要运行的方法 添加注解:@Test


            

编写JDBC程序:

    
    1.向JVM注册驱动:注册的是具体的数据库开发厂商实现的Driver类对象
        DriverManager.registerDriver(new Driver());(这个Driver是mysql的)
        
    2.建立连接(数据库jdbc和java程序):
        String url="jdbc:mysql://localhost:3306/jdbc"
        url:要连接哪个数据库,格式: jdbc:mysql(主协议):[子协议(可有可无)] //localhost:3306(主机端口号)/test(数据库名称)(?参数名=参数值)
        String user="root"
        连接数据库的用户名
        String password="1234"
        密码
        Connection connection = DriverManager.getConnection(url,user,password)
        引用表示java和mysql服务器的连接。
        
    3.写sql语句,将请求发送给服务器。
        String sql ="insert into user(id,name,age) values(1,'zs',18)"
        
        Statement statement = connection.createStatement();
        //增/删/改(DML语句)的时候使用,返回值 int i 代表 本次操作所影响的行数。
        int i = statement.excuteUpdate(sql);
        
        //查(DQL语句)
        ResultSet resultSet = statement.executeQuery(sql);
        while(resultSet.next()){
            //查找数据表的属性值
            1.通过列名获取每个属性

            resultSet.getInt("id");
            resultSet.getString("name");
            2.通过列名的index索引来获取(index 从1 开始编号,表示列数)
            resultSet.getInt(1);
        }

        
    4.根据业务逻辑处理响应数据。
        if(i>0){
            sout("插入成功")
        }

    
    5.释放相关资源
        //如果只有增删改操作的话:
        statement.close();
        connection.close();

            

程序详解:

    1.驱动的加载:
         DriverManager.registerDriver(new Driver());
            
        查看mysql的源码发现,Driver类自己已经有了静态方法:Static { DriverManager.registerDriver(new Driver());}
        所以我们就不需要重复书写这句代码,只要确保这句代码在我们自己的程序执行之前执行就可以了。
        但是如何做呢?注意到 此代码是Driver类的静态代码块中的,又因为静态代码块在类加载的时候执行。
        那么有几种方式类加载:
            1.生成对象:DriverManager.registerDriver(new Driver());//需要导mysql.Driver包。
            2.Class.forName("com.mysql.jdbc.Driver") 全类名(推荐)
            3.放在程序的静态代码块中,statice{Class.forName("com.mysql.jdbc.Driver")}
            4.坑,不会加载静态代码块的方法:driver.class()
            如果我想要跨数据库来加载(不同的驱动都可以运行) : 通过配置文件

            
    
 2.建立连接
      方式一:
             设置好url,user,password
            Connection connection DriverManager.getConnection(url,user,password);     
            
    方式二:
            原url:String url="jdbc:mysql://localhost:3306/jdbc"

             灵活使用url(user/password通过参数给) :  String url="jdbc:mysql://localhost:3306/jdbc?user=root&password=****"
            如果默认IP地址是localhost、端口号是3306,则简写:String url="jdbc:mysql:///jbdc?...."
        
    方式三:
          配置文件:在 src 目录下new 一个配置文件(configure)。
                url="jdbc:mysql://localhost:3306/jdbc"
                user=""
                password=""
         在类中:
                把配置文件当Map<String,String>用
                Properties properties = new Properties();

                
                new FileInputStream("相对路径")
                那么相对路径是什么?sout(System.getProperty("user.dir"))
                相对路径会改动,如果启动服务器的话,所以行不通
                
                获取配置文件:
                法一:URL resouce = 类名.class.getClassLoader().getResource("配置文件名")

                     这个路径 是 相对于 src的路径(虽然其的路径是到了classes目录下,但是是把配置文件复制到classes目录下的)
                
                 法二:
                (通过反射拿到配置文件,放入输入流中)

                InputStream RS = 类名.class.getClassLoader().getResourceAsStream("配置文件名");
                    //加载配置文件

                    properties.load(RS);
                    //获取Driver的全类名:在配置文件中 : URL=com.mysql.jdbc.Driver
                    String url = properties.getProperty("URL");
                    //建立连接
                    Connection connection = DriverManager.getConnection(url,properties);


                    
    3.写sql语句,发送给服务器。
        1.获取statement对象
            Statement statement = connection.createStatement();
        
        2.ResultSet — 结果集(也是一种需要释放的资源)
            ResultSet resultSet = statement.executeQuery(sql);
            
            ResultSet 结果集 中会维护一个游标,用游标来指示表的具体行。
            遍历 ResultSet 结果集:
                先将游标向后移动一个位置,如果游标的当前位置是一个有效的位置(表内)就返回true
                (无效位置就是表的第一行数据之前和最后一行数据之后。)
            
            //开始的时候游标处在第一行数据之前的位置
            while(resultSet.next()){ //.next()结果返回true或false
                //遍历每一行,获取其每一个属性值
                //两种方式获取当前行的各个属性值
            1.通过列名获取(游标所指的行)
                int id = resultSet.getInt("id");
                String name = resultSet.getString("name");
                int age = resultSet.getInt("age");

                
            2.通过列的 index索引值 来获取(不太推荐,可读性差)
                index 从第一列开始编号.
                int id = resultSet.getInt(1);
                String name = resultSet.getString(2);
                int age = resultSet.getInt(3);

            }    
            游标的位置还可以通过API进行修改:
                resultSet.afterLast() ——将游标放在表中最后一行之后
                resultSet.BeforeFrist() ——将游标放在表中第一行之前
                absolute(int row) ——将游标放在指定行(从1开始),要注意遍历的时候,游标会先移动一位
            游标的前后数据判断:(返回的都是boolean类型)
                resultSet.next()/resultSet.previous()

一个注意点(注意区分):

                  
    //查询操作,返回的是一个结果集

    ResultSet resultSet = statement.executeQuery(sql);
    // 增 删 改   操作,返回的是一个修改了多少行的数值

    int i = statement.executeUpdate(sql);


3.根据业务逻辑处理响应
        1.引入 TestCase ,让测试框架根据我们的业务逻辑来判断测试成功与否
            TestCase.assertTrue(i > 0);(这里i是上面的修改数据表的行数)
            执行条件表达式如果为false,会直接抛出异常。
            
        2.测试框架中异常的处理:
            如果测试框架中,自己捕获了异常,那么测试框架就会忽略这个异常,继续执行。


4.释放资源
因为Statement/Connection/ResultSet  都继承了  AutoCloseable,所以他们的释放可以写一个方法来释放,而不用在主代码中写:if(st != null){st.close()}if(ct != null){ct.close()}....
            public static void release(Statement st,Result rs,Connection con){
                closeQuietly.st;
                closeQuietly.rs;
                closeQuietly.con;
            }
            private static void closeQuietly(AutoCloseable closeable){
                //因为Statement/Connection/ResultSet  都继承了  AutoCloseable

                if(closeable != null){
                    closeable.close();
                }
            }

      

发现上面的代码有很多重复的代码,自己创建一个工具类DBUtil:
public class DBUtil{
      1.注册驱动
        static {
             2.建立链接  
            //将全类名放入配置文件 class="com.mysql.jdbc.Driver"
         Properties properties = new Properties();
         InputStream configureStream = 类名.class.getClassLoader().getResourceAsStream("配置文件名.properties");
                
            //加载配置文件
                properties.load(configureStream);
            //从配置文件中拿东西
                Class.forName("com.mysql.jdbc.Driver")}
                
        2.public static Connection getConnection(){ //这里的Connection对象最好导java.sql.Connection
            return DriverManager.getConnection(url,properties)
            }
            
        3.将释放资源的代码放入(很重要的一种释放流的方式!!!)
            public static void release(Statement st,Result rs,Connection con){
                closeQuietly.st;
                closeQuietly.rs;
                closeQuietly.con;
            }
            private static void closeQuietly(AutoCloseable closeable){
                //因为Statement/Connection/ResultSet  都继承了  AutoCloseable

                if(closeable != null){
                    closeable.close();
                }
            }

}

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

Day20   SQL注入(安全问题):

    当用户名输入成:zs' or 1=1 -- 还有一个空格
    则这条数据会变成:NAME='zs' OR 1=1 --  'and password=1234'
    后面的密码变成了注释,前面的语句变成了true。
    主要原因是用户输入的东西里面包含了sql语句,会被解析和执行。
    解决方案一:
       
分开发送  指令 和 参数值(PrepareStatement)
        在发送的时候,只发送占位符sql = select ...name=? and password=?;
        connection.prepareStatement(sql);
        
        第二次发送他们的账号和密码:(这时,mysql会将所有符号,转义,就仅代表符号了)
            
            statement.executeUpDate()

    prepareStatement和statement的区别:
        1.statement发送sql语句 和 mysql服务器通信一次
        2.perpareStatement 和mysql服务器通信两次,第一次发送sql指令,第二次发送sql参数值。

        

实例:(通过prepareStatement拿到数据库数据)

     connection = MyDBUtil.getConnection();//建立连接的方法已经写MyDBUtil..这是什么颜色
    
    //引入两个占位符,占位符都有编号,从左往右依次编号(从1开始)
    String sql = "select * from user where name=? and password=?;";
    
    //发送sql语句,mysql服务器会进行预编译,使用 prepareStatement 方法。
    pre = nonnection.prepareStatement(sql);
    
    //设置参数值,第一个参数:parameterIndex:占位符编号,第二个参数:实际传递的参数值
    pre.setString(1,"zs");
    pre.setString(2,"1234");

   
//查找
    //发送参数(连接已经建立好了,所以只需要拼接executeQuery)
    ResultSet rs = pre.executeQuery();
            
        while(rs.next()){
            int id = es.getInt("id");
            ...
        }    

        
//修改     
//插入也是差不多
        String sql = "insert into user values(?,?,?,?);"; //使用占位符,后面的批处理中会发现这个的好处。
        pre.setInt(1);
        pre.setString("zs");
        pre.setInt(18);
        pre.setString("1234");

        //增删改的时候使用executeUpdate
        int i = pre.executeUpdate(sql);
        TestCase.assertTrue(i > 0);//如果i<=0会直接抛出异常
        
单条sql语句的执行:statement的效率 > prepareStatement (prepare继承自Statement)
        但是 prepareStatement 可以阻止sql注入问题的发送,但是statement不行
        

引入JDBC的批处理机制:

    主要用于数据表的 增  删  改  操作,查询不需要批处理
    一次执行多条sql语句。Batch
    API:addBatch() /executeBatch() /clearBatch()
        
        //sql语句添加到批处理中
        statement.addBatch(sql语句);
        
        //发送批处理,数组每一个元素代表每个sql语句(代码顺序从上到下)所影响的行数
        //表明 批处理 只是针对 增删改,查询不需要批处理
        int[] result = statement.executeBatch()
        
        很多条sql语句的时候:
        方法一:
            使用 statement 对象。
            for(int i = 0; i < 50 ;i++)
                String sql = "insert into user values('"+i+"');";
                //每十条执行一次发送
                statement.executeBatch();
                //清空上一次的批处理缓存
                statement.clearBatch();
            }

            如果sql语句都是一样的,只是其中的参数值不同,那么上面的方法就不是很好,多次编译同一句sql语句
            可以使用另一种实现形式:
        
        方法二:
            利用  prepareStatement对象  执行批处理,不过具有局限性:只能针对sql指令一样,参数值不同的情况。
            因为指令会传送到服务器端预编译后放入缓存,然后一次性发送所有参数
            String insertSql="insert into user values(?,?,?,?);";
            PrepareStatement pre = connection.prepareStatement(insertSql)
            
            for(int i = 1;i<50;i++){
                pre.setInt(1,i);
                pre.setString(2,i+"");
                pre.setInt(3,i);
                //添加到批处理中
                pre.addBatch();
            }
            pre.executeBatch();

            
        方法一、二的比较:
            statement 对象执行批处理,相同的sql每次都会执行,效率低
            prepareStatement 对象执行批处理,sql语句会被编译一次,但是,会和mysql服务器
                    通信两次(第一次发送预编译的sql指令,第二次发送sql参数)
                    
        所以在批处理中,prepaStatement执行效率比较高,不过有限定条件(指令相同,参数不同)

        

        

 JDBC事务(Transaction)


    1.概述

        事务(transaction)是作为单个逻辑工作单元执行的一系列操作。
        这些操作作为一个整体一起向系统提交,要么都执行、要么都不执行。

    

        事务的特点(ACID特性):

            1.原子性(Atomicity)
                事务是一个不可分割的工作单位。
            2.一致性(Consistency)
                事务必须使数据库从一个一致性状态转变到另一个 一致性状态。

                (例如银行转账,张三要给李四转100元。则第一步张三的账户需要减去100元,第二步李四的账户需要加上100元。
                这是两个操作,但是应该在一个事务里面。如果没有在一个事务里面,张三减去100,李四并没有增加100,
                那这样数据就出现了不一致性,张三的钱跑哪去了呢?)
            3.永久性(Durability)
                修改会被数据库永久保存,
            4.隔离性(Isolation)
                事务是数据库中并发控制的基本单位。
                多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,
                多个并发事务之间要相互隔离。(事物之间不能相互干扰)

                比如业务A:张三减100,李四加100;同时业务B也是张三减100,李四加100进行操作。
                业务A和B是同时的,这时候就出现了并发,这个时候是怎么变化的呢?
                当业务员A进行操作的时候,业务员B就要等待……就是同一时间对数据库的操作要保持一个事务的锁定。
                也就是说我在做的时候,别人是不能做的。我做完了之后别人才能做,彼此之间是隔离的

                

事务的 隔离性 和 隔离级别:

    
    如果事务不考虑隔离性,可能会产生以下三种情况:
            (因为虽然事务是原子操作,但是事务还有 隔离级别 之分)
                1.脏读:当一个事务修改了某一数据行的值而未提交时,另一事务读取了此行值。
                        倘若前一事务发生了回退,则后一事务将得到一个无效的值(“脏”数据)。
                
                2.不可重复读:当一个事务在读取某一数据行时,另一事务同时在修改此数据行。
                              则前一事务在重复读取此行时将得到一个不一致的值。
                    
                3.幻读:在一次事务中读取到了别的事务插入的数据,导致前后读取不一致。
            
    如何处理这些情况呢?隔离级别
        数据库一共设置了四种隔离级别。
            Serializable :可避免 脏读、不可重复读、虚读情况的发生。(串行化)
            Repeatable read:可避免 脏读、不可重复读情况的发生。(无法避免虚读)
            Read committed:可避免 脏读情况发生。(无法避免不可重复读)
            Read uncommitted:最低级别,以上情况均无法保证。(读未提交)
                
                
        设置方式:set session transaction isolation level read uncommitted;
            set  session transaction isolation level    设置事务隔离级别
            select @@tx_isolation      查询当前事务隔离级别。

        数据库 对 共享数据 天然存在加锁的操作。
                
        在MySQL中的默认隔离级别是比较高的,但是实现不同:(REPEATABLE-READ)
        隔离级别名称(可重复读):Repeatable-read (可避免脏读,不可重复读,幻读等的情况)

        

执行事务        

    1.命令行(DOM)中执行一次事务:
            start transaction;//开始事务
            执行原子操作;
            commit;//托付(交给sql服务器)
        
    2.利用java代码使用事务。
        当java程序向数据库获得一个connnection对象时,默认会一句一句执行sql语句。
        作为事务,执行单位应该是一组原子操作, 事务操作默认是自动提交。
        API:
            setAutoCommit(false) : 禁止自动提交
            (相当于命令行的 start transaction;)
            
            connection.rollback();事务回滚,
            (相当于命令行的commit)
            比如说我们插入的数据、更新的数据都会变成原来没有更新、没有插入时的样子。
            (一般来说这个是mysql服务器自己做的,)

        
        

代码流程:

        1.获取连接
            Connection connection = MyDBUtil.getConnection();
            
        2.创建sql语句
            String sql1 ="update account set money=money-100 where name='zs';";
            String sql2 ="update account set money=money+100" where name='lisi';";
            
            //开启事务
            connection.setAutoCommit(false);
                
            Statement statement = connection.createStetament();
            
            statement.executeUpdate(sql1);
            int i = 1/0; //会报异常 并 执行回滚操作,开启事务之后的所有修改都会被抹除。
            statement.executeUpdate(sql2);
            //提交,完成本次事务
            connection.commit();

设置回滚点:(三处注意点)

            如果不想抹除开启事务之后的所有修改,可以设置回滚点。
            Savepoint save = connection.setSavapoint();
            //且必须自己调用回滚方法,告诉程序要回滚到哪个点
            try{}catch(exception e){
                connection.rollback(save);

                //如果想要让回滚点之前的代码生效,还必须提交才行。

                connection.commit();
            }
            设置回滚点之后,回滚的位置就不是整个事务,而是从发生异常处 —— 到回滚点。回滚点之前,事务开启之后 的修改不会被抹除。
            

总结一下jdbc的事务:

1.什么是事务?
事务:单独的一系列逻辑工作的执行单元。
这些操作作为一个整体,一起向系统提交,也就是事务中的操作,要么都执行,要么都不执行。

2.事务有哪些特性?
    1.原子性:
        原子操作,事务是一个不可分割的工作单位。
    2.一致性:
        完成一次事务的前后,数据库的状态必须保持一致性。
        就比如银行转账:张三给李四转了100元,分为两步:张三—100,李四+100,。两个操作应该在同一个事务中。
    3.永久性:
        一次成功的事务中的操作对数据库的修改应该是永久保存下来了的。
    4.隔离性:
       事务是数据库中并发控制的基本单位。
       多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。(事物之间不能相互干扰)
       

3.mysql数据库针对事务的隔离性,有哪些隔离级别?
    4个隔离级别,从低到高依次为:不坚定<坚定<可重读<可串行化
    Read-uncommitted < Read-committed < repeatable-read < serializable

    set  session transaction isolation level 设置事务隔离级别
    select @@tx_isolation 查询当前事务隔离级别

4.每个隔离级别的作用分别是什么?
    read-uncommitted : 最低级别,可能产生脏读,幻读,不可重复读
    read-committed : 可避免脏读。
    repeatable-read:可避免脏读,不可重复读,无法避免幻读。
    serializable:脏读,不可重复读,幻读都可以避免



5.如果数据库的隔离级别比较低,会发生什么问题?
    如果隔离级别比较低,
    脏读:当一个事务修改了某一数据行的值而未提交时,另一事务读取了此行值。倘若前一事务发生了回退,则后一事务将得到一个无效的值(“脏”数据)。

    不可重复读:当一个事务在读取某一数据行时,另一事务同时在修改此数据行。则前一事务在重复读取此行时将得到一个不一致的值。

    幻读:在一个事务中读到了别的事务刚刚插入的数据,导致前后读取不一致。

6.数据库的隔离级别是设置的越高越好吗?为什么?
    首先事务的独立性增加,可以更有效地防止事务操作之间的冲突,但同时也增加了锁的开销,降低了用户之间访问的并发性,程序的运行效率也随之降低。
    比如如果  有很多个 事务 都只是要读取一个静态的数据库,那么就没有必要将两个隔离级别都设置成最高,这样的话,查询的效率就会降低。
       

数据库连接池(DBCP / C3P0)

连接池产生的背景

  数据库连接是一种重要资源。大部分很重要的数据都存在数据库里,那么在产生连接池之前,我们连接数据库的方式:直连。(获取连接->使用->关闭连接)程序小的话可以采用这种方式,但是如果程序很大,比如大型网站,它可能每分钟或者每秒变化量在100万次,就是说同时访问数据库有100万个用户,这时候如果我们不用连接池的话,我们就需要创建100万个连接这样的话就会对数据库造成很大的压力,如果数据库承受不了的话就崩溃了,服务器也崩溃了,网站就瘫痪了。

即:

①数据库连接是一种重要资源;

②频繁的连接数据库会增加数据库的压力;

③为解决以上问题出现连接池技术

(池子里保持一定数量的连接,当使用时就从池子中拿一个连接出来,当使用完连接后就把它释放到池子里。当你同时访问数据库人很多的时候,这个时候连接不够用,就需要等待,减少数据库的压力)

常用的开源数据库连接池:

  1. dbcp
  2. c3p0

引述取自http://www.cnblogs.com/Qian123/p/5349884.html

spring开发组推荐使用dbcp(c3p0连接池有weblogic连接池同样的问题,就是强行关闭连接或数据库重启后,无法reconnect,告诉连接被重置,这个设置可以解决);




先丢个重点,帮助理解:

这两个连接池的代码无论是使用DBCP还是使用C3P0连接池,在写成工具类后都要对外提供一个      数据源DateSource          和一个     连接Connection。作用是,如果我们在进行数据库操作的时候要是使用DBUtils类的时候,就要使用QueryRunner核心类,而这个类在进行与数据库连接的时候只要一句代码:

     QueryRunner qr=newQueryRunner(数据源);这里的数据源,我们可以通过两个连接池获得:C3P0Utils.getDataSource() ,或者:DBCPUtils.getDataSource()获得。如果不使用DBUtils工具类,我们就可以直接通过C3P0Utils.getConnection()或者  DBCPUtils.getConnection()来直接获得连接。

这些可以看我的轻量级框架DBUtils,里面有详细的代码。


引入 设计模式 :

想要使用连接池就必须要获取连接池:

public class MyConnectionPool{
    
    private static LinkedList<Connection> pool;    
    private static final int INIT_COUNT_NUM = 5;
    //初始化
    static{
        pool = new LinkedList<Connection>();
        
        //初始化连接数
        for(){
            Connection connection = MyDBUtil.getConnection();
            pool.add(0,connection);            
        }    
    }
    //从池子里拿
    public static Connection getConnFromPool(){
        return pool.removeFirst();//你拿了别人不能拿
        //removeFirst 会删除
    }
    //还给池子
    public static void addToConnPool (Connection conn){
        pool.addLast();//从表尾放入
    }
}

包装设计模式 和 适配器模式 引入:

上述代码的不足之处:
    功能上:
    1.无法扩容(了解Arraylist的扩容机制)
    2.空闲的连接回收。
    3.自定义初始化size。

    设计上:
    1.易用性还可以
    2.通用性不行(接口的通用性才可以)

JAVAEE为了解决连接池的通用性的问题,提供了一个连接池的标准接口(规范):DataSource 接口
        编写连接池需要 实现  javax.sql.DataSource  接口:
javax.sql.DataSource 定义了两个方法需要实现:
    getConnection()getConnection(String username,String password);
还有父类一堆方法。。。
    放入上述代码
    
    //定义了取连接的API
    //但是没有定义 还连接 到池子里的API,这是不是他们没考虑到的问题呢?
    //两种情况,怎么还连接?如果用户connection.close()释放了这个连接怎么办?
    其实就是直接让用户close(),但是重写了Connection 的 close()方法。

    那么该如何实现?
    如果写一个类,去继承自Connection接口,然后发现,有几十个API要实现,我不知道怎么做,但是Connection对象知道怎么做。
    方法1:(包装设计模式)
        写一个构造和函数,然后将参数(connection,pool)传进来
        我只需要获取Connection接口的close()方法就行了,那么就对每一个API,使用return 他们自己的connection.他们自己的实现

        然后重写里面的close()方法:自己包装成pool.addlist(this);

 
 

每一个方法我们不知道怎么实现,但是Connection接口它知道怎么实现,所以都让他自己去实现,我们就包装了一个接口,然后只实现了我们自己需要的方法,其他的方法让这个类本身自己去实现。这就是包装设计模式

    方法2:适配器的设计模式(Adapter)
        优化包装设计模式的实现:        将上面一个类变成   抽象类 :abstrct class adapter,然后删除close()方法,        然后写一个类,继承这个抽象类,就只需要实现没有实现的close()方法。可以让代码变得简洁。
//但是只需要重写 getconnection close方法
public class MyConnection2 extends  MyConnectionAdapter {

    public MyConnection2(Connection connection, LinkedList<Connection> pool) {
        super(connection, pool);
    }

    @Override
    public void close() throws SQLException {
        pool.add(this);
    }
}


考虑到如果每次为了得到这个connection连接,都要重新做这些步骤,太麻烦了,所以引入DBCP和C3P0


不过如果你能看懂下面这些代码,就可以不用看连接池了:

那么连接池的工具类怎么写的?如下,:

1.声明私有 静态的  (BasicDateSource(dbcp的) 或 ComboPooledDataSource(C3P0的)) 

2.静态代码块:初始化配置文件(.properties)或者(XML文件)

3.设置3个方法便于代码复用:getConnection()、closeConnection(Connection con)、getDateSource()

代码如下:

C3P0的Utils:

public class C3P0Utils {

    private static ComboPooledDataSource cpds;

    static{

        //方法1  硬编码
       /* cpds = new ComboPooledDataSource();
        try {
            //设置
            cpds.setUser("root");
            cpds.setPassword("123456");
            cpds.setDriverClass("com.mysql.jdbc.Driver");
            cpds.setJdbcUrl("jdbc:mysql://localhost:3306/logindemo");
            cpds.setMinPoolSize(5);
            //增加的size
            cpds.setAcquireIncrement(5);
            cpds.setMaxPoolSize(20);
        } catch (PropertyVetoException e) {
            e.printStackTrace();
        }*/

        //方法2  通过配置文件
        //如果什么都没写,使用的是c3p0-config.xml配置文件里的默认项

        cpds = new ComboPooledDataSource("mysql");
    }

    public static Connection getConnection() throws SQLException {
        return  cpds.getConnection();
    }

    public static void releaseConnection(Connection conn) throws SQLException {
        if (conn!=null){
            conn.close();
        }
    }
    public static  ComboPooledDataSource  getDateSource(){
        return cpds;
    }
}
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/logindemo</property>//这个看你数据库的名称
        <property name="user">root</property>       //数据库账号
        <property name="password">123456</property>    //数据库密码
        <property name="acquireIncrement">5</property> //每次扩容池子的大小
        <property name="initialPoolSize">10</property> //初始池子大小
        <property name="minPoolSize">5</property>      //最小空闲池子保持的连接数
        <property name="maxPoolSize">20</property>     //连接池最大容量
    </default-config>
    <named-config name="mysql">
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/logindemo</property>
        <property name="user">root</property>
        <property name="password">123456</property>
        <property name="acquireIncrement">5</property>
        <property name="initialPoolSize">15</property>
        <property name="minPoolSize">5</property>
        <property name="maxPoolSize">20</property>
     </named-config>
    <named-config name="oracle"> //如果有多个数据库的话
        <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">5</property>
        <property name="initialPoolSize">10</property>
        <property name="minPoolSize">5</property>
        <property name="maxPoolSize">20</property>
    </named-config>
</c3p0-config>


DBCP的Utils:

public class MyDBCPUtil {

    private static BasicDataSource pool;

    static{

        try {
            //获取配置文件对象
            Properties properties = new Properties();
            //获取流
            InputStream in = MyDBCPUtil.class.getClassLoader().getResourceAsStream("conf.properties");
            //加载配置文件
            properties.load(in);

            //使用工具类
            BasicDataSourceFactory factory = new BasicDataSourceFactory();
            pool = factory.createDataSource(properties);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //获取池子
    public static BasicDataSource getDataSource(){
        return pool;
    }

    //获得连接池
    public static Connection getConnection() throws SQLException {
        return pool.getConnection();
    }

    //放回池子
    public static void closeConnection(Connection connection) throws SQLException {
        connection.close();
    }
}
dbcp的配置文件 ( xxx.properties)配置如下:还有一点很重要, 配置文件的位置:src目录下
url=jdbc:mysql://localhost:3306/dbcp
username=root
password=1234
driverClassName=com.mysql.jdbc.Driver
#初始化连接数
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=false

#driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation)。
#可用值为下列之一:(详情可见javadoc。)NONE,
# READ_UNCOMMITTED,
# READ_COMMITTED,
# REPEATABLE_READ,
# SERIALIZABLE
# defaultTransactionIsolation=REPEATABLE_READ


使用DBCP的流程:

1.导包:

2.在项目根目录下创建一个配置文件:



3.创建一个DBUtils类,可以通过这个类得到一个连接池

//使用DBCP连接池作为数据库连接的工具
public class DBCPUtils {
    
    private static BasicDataSource pool ;
    
   static{
       /* pool = new BasicDataSource();*/
       //连接池的初始化 4个信息 username password url driver

       //方法1 直接硬编码
      /* pool.setUsername("root");
       pool.setPassword("123456");
       pool.setUrl("jdbc:mysql://localhost:3306/logindemo");
       pool.setDriverClassName("com.mysql.jdbc.Driver");
       pool.setInitialSize(5);*/

       //方法2 通过配置文件来配置
       try {
      
           InputStream in = DBCPUtils.class.getClassLoader().
                   getResourceAsStream("configure.properties");
           Properties prop = new Properties();

           prop.load(in);

           BasicDataSourceFactory factory = new BasicDataSourceFactory();
           pool = factory.createDataSource(prop);

       } catch (IOException e) {
           e.printStackTrace();
       } catch (Exception e) {
           e.printStackTrace();
       }
   }
    //获取连接(从DBCP连接池获取)
    public static  Connection getConnection() throws SQLException {

        Connection connection = pool.getConnection();

        return  connection;
    }

    //释放连接(放回到池子里),因为这个Connection已经被包装过了,所以可以直接close();
    public static void closeConnection(Connection connection){
       if (connection!=null){
           try {
               connection.close();
           } catch (SQLException e) {
               e.printStackTrace();
           }
       }
    }
  
  public static BasicDataSource getDataSource(){
        return pool;
  }
 }


也可以创建一个C3P0的类,来获取连接池

public class C3P0Utils {


    private static ComboPooledDataSource cpds;

    static{

        //方法1  硬编码

       /* cpds = new ComboPooledDataSource();

        try {
            //设置
            cpds.setUser("root");
            cpds.setPassword("123456");
            cpds.setDriverClass("com.mysql.jdbc.Driver");
            cpds.setJdbcUrl("jdbc:mysql://localhost:3306/logindemo");

            cpds.setMinPoolSize(5);
            //增加的size
            cpds.setAcquireIncrement(5);
            cpds.setMaxPoolSize(20);

        } catch (PropertyVetoException e) {
            e.printStackTrace();
        }*/

        //方法2  通过配置文件
        //如果什么都没写,使用的是c3p0-config.xml配置文件里的默认项
        cpds = new ComboPooledDataSource("mysql");

 }

    public static Connection getConnection() throws SQLException {
        return  cpds.getConnection();
    }
   
   public static void releaseConnection(Connection conn) throws SQLException {
        if (conn!=null){
            conn.close();
        }
    }

   public static BasicDataSource getDataSource(){
        return cpds;
   }
 
}

4.开始测试我们写的DBUtils类

public class MyTest {
    @Test
    public void testGetConntion(){
        try {
       //得到一个连接池
       Connection connection = DBCPUtils.getConnection();
            
            //connection.setAutoCommit(false);
            System.out.println("conn="+connection);
            Statement statement = connection.createStatement();
            statement.execute("DELETE from tt_user WHERE ID= 3")
                    
            //connection.commit();
            DBCPUtils.closeConnection(connection);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}


C3P0的方法和DBCP不同的地方主要是:

导包不同:
C3P0的包:
        Mchange Commons Java-0.2.11
        c3p0-0.9.5.2

DBCP的包:
        dbcp.jar
        pool.jar
        logging.jar;


实现类不同:
    C3P0的类实现类:
        private static ComboPooledDataSource cpds;
        opds = new ComboPooledDataSource();

    DBCP的实现类:
            private   static   BasicDataSource   pool;
             BasicDataSourceFactory   factory = new  BasicDataSourceFactory();
            pool = factory.createDataSource(properties);

实现方式不同:(按每种方法最简单的方式来比较)
    DBCP加载  配置文件.properties 中的东西注册连接池的驱动。
    C3P0读取 .XML 的文件内容来注册驱动

C3P0通过 .XML 文件来注册驱动:


通过配置文件注册的代码如下:
    import java.sql.Connection;  
    import java.sql.SQLException;  
    import javax.sql.DataSource;  
    import org.apache.commons.dbutils.QueryRunner;  
    import com.mchange.v2.c3p0.ComboPooledDataSource;  
      
    public class C3P0Utils {  
        //1、提供私有化的数据源    使用默认设置  
        private static ComboPooledDataSource datasource=new ComboPooledDataSource();  
        //使用命名配置  
        //private static ComboPooledDataSource datasource =new ComboPooledDataSource("itheima");  
        //2.提供对外的数据源  
         public static DataSource getDataSource(){  
            return datasource;    
        }  
        //3.提供对外的链接    
        public static Connection getConnection() throws SQLException{  
            return datasource.getConnection();  
        }  
    }  


C3P0也可以 不通过 配置文件.xml 的方法注册驱动。

 不使用配置文件的代码:三个步骤:
    
1、导入核心类:ComboPooledDataSource
                  2
、基本的四项设置,也就是设置驱动,URL,用户名和密码等四项。
                  3、其他四项。

    public class c3p0_test01 {  
        public static void main(String[] args) throws Exception {  
            //导入一个核心类  
            ComboPooledDataSource  dataSource = new ComboPooledDataSource();  
            //基本四项设置  
            dataSource.setDriverClass("com.mysql.jdbc.Driver");  
            dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/eeday09");  
            dataSource.setUser("root");  
            dataSource.setPassword("123");  
            //其他四项设置  
            dataSource.setInitialPoolSize(10);  //初始化连接个数  
            dataSource.setMaxPoolSize(40);   //最大链接数  
            dataSource.setMinPoolSize(5);  //设置最小链接数  
            dataSource.setAcquireIncrement(2);    //设置每次增加的连接数  
            Connection conn=dataSource.getConnection();      //进行数据库连接  
            System.out.println(conn);  
        }  
    }  

BasicDataSourceDataFactory 类

主要根据一个实现了DataSource接口的对象,获取该对象的相关数据源配置参数(通过Reference对象,采用类似指针的方法),然后将new一个BaiscDataSource对象,结合获取的参数,形成一个BasicDataSource对象,并将之返回。


已经DBCP只是最基础的实现方式,现在已经有一些不错的框架来代替DBCP的工作了,比如DBUtils、Mybaits、




猜你喜欢

转载自blog.csdn.net/qq_38962004/article/details/80315308