博客
关于我
2021-04-01
阅读量:198 次
发布时间:2019-02-28

本文共 35926 字,大约阅读时间需要 119 分钟。

Jdbc课件分享

文章目录

  • JDBC相关概念(了解)
  • 使用JDBC技术和数据库进行连接(掌握)
  • JDBC批处理和MySQL事务(掌握)
  • 数据库连接池(掌握)
  • Apache的DBUtils框架
  • BaseDAO(掌握)

第一章 JDBC相关概念

什么是JDBC? Java DataBase Connectivity

​ JDBC是Java连接数据库的技术,它提供一套操作所有关系型数据库的规则(接口)可以让所有的关系型数据库使用一套代码和Java程序进行交互。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u9e8JEw9-1617286236921)(day4_jdbc.assets/image-20210328092716831.png)]

第二章 JDBC核心接口(重点)

2.1 Connection

​ Connection接口是JDBC提供的连接对象,相当于Java程序和MySQL数据库之间的一座桥梁,Connection对象成功创建表示他们之间的桥梁架设好了,可以交互数据了。

​ Java程序和MySQL建立连接必须要有连要三要素:url、userName、password

​ url: 因为Java程序和数据库不在同一个进程,Java程序需要使用一个协议跟数据库交互

​ 协议的规格:

​ 主协议:子协议://主机地址:端口/数据库名称?参数名1=参数值1&参数名2=参数值2

jdbc:mysql://localhost:3306/j1012?useUnicode=true&characterEncoding=utf8&useSSL=false

​ user: 用户名 root

​ password: 密码 root

​ 注意:Java程序连接数据库必须知道数据库的用户名和密码才能连接成功

场景:使用JDBC创建Connection对象连接MySQL数据库

​ 步骤:

​ 1 导入单元测试包和MySQL提供的第三方驱动包 mysql-connector-java-5.1.40.jar

​ 2 注册MySQL驱动Jar包,告诉Java程序我此时使用的是MySQL驱动

​ 3 定义连接三要素 url、userName、password

​ 4 使用驱动管理器DriverManager 来创建Connection对象。

​ 5 打印连接对象Connecton。

​ 6 关闭连接对象

package com.atguigu.jdbc;import org.junit.Test;import java.sql.Connection;import java.sql.DriverManager;/** * JDBC程序测试类 */public class JDBCTest1 {       /**     *	场景:使用JDBC创建Connection对象连接MySQL数据库     * 	步骤:     * 	   1 导入单元测试包和MySQL提供的第三方驱动包 mysql-connector-java-5.1.40.jar     * 	   2  注册MySQL驱动Jar包,告诉Java程序我此时使用的是MySQL驱动     	   3  定义连接三要素 url、userName、password     * 	   4  使用驱动管理器DriverManager 来创建Connection对象,该对象有三个参数,连接三要素。     * 	   5  打印连接对象Connecton。     	   6  关闭连接     */    @Test    public void connectionTest() throws Exception{           // 告诉Java程序,此时我使用的是MySQL数据库        Class.forName("com.mysql.jdbc.Driver");        /*        * Java程序和MySQL数据库之间连接的URL        * jdbc:主协议        * mysql:子协议        * localhost:MySQL服务器主机名,localhost表示自己连自己        * 3306:MySQL数据库服务器的端口号        * mydb:连接MySQL数据库服务器里面的mydb数据库        * useUnicode=true:表示Java程序和MySQL数据库交互使用Unicode编码        * charsetEncoding=utf8:表示Java程序和MySQL数据库交互使用的编码方式为utf8        * useSSL:表示Java程序和MySQL数据库交互不使用安全成协议        * */        String url = "jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=utf8&useSSL=false";        String userName ="root";        String password = "root";        // 创建连接对象        Connection conn = null;        try{               conn = DriverManager.getConnection(url, userName, password);            // JDBC4Connection是MySQL公司编写的,它实现了Connection接口            System.out.println(conn);        }catch(Exception e){               e.printStackTrace();        }finally{               if(null != conn){                   conn.close();            }        }    }}

优化上一个示例,使用try(){}catch自动关闭资源

/**     * 优化上一个示例,使用try(){}catch自动关闭资源     * @throws Exception     */    @Test    public void connectionTest2() throws Exception{           // 告诉Java程序,此时我使用的是MySQL数据库        Class.forName("com.mysql.jdbc.Driver");        /*         * Java程序和MySQL数据库之间连接的URL         * jdbc:主协议         * mysql:子协议         * localhost:MySQL服务器主机名,localhost表示自己连自己         * 3306:MySQL数据库服务器的端口号         * mydb:连接MySQL数据库服务器里面的mydb数据库         * useUnicode=true:表示Java程序和MySQL数据库交互使用Unicode编码         * charsetEncoding=utf8:表示Java程序和MySQL数据库交互使用的编码方式为utf8         * useSSL:表示Java程序和MySQL数据库交互不使用安全成协议         * */        String url = "jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=utf8&useSSL=false";        String userName ="root";        String password = "root";        // 创建连接对象,在try(定义要关闭的资源)        try(Connection conn = DriverManager.getConnection(url, userName, password)){               // JDBC4Connection是MySQL公司编写的,它实现了Connection接口            System.out.println("Conn="+conn);        }catch(Exception e){               e.printStackTrace();        }    }

小结:Connection接口唯一的职责:负责和数据库之间的连接

​ Connection 接口是JDBC最核心的接口,也是内存资源消耗最大的一个接口。

2.2 PreparedStatement

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-03vzHz9B-1617286236928)(day4_jdbc.assets/image-20210328102642420.png)]

​ PreparedStatement相当于一个卡车:路(Connection)修好了开着车(PreparedStatement)拿着清单(SQL语句)去仓库拖货,最后把货成功运送到卖场(返回受影响的行数)。

​ PreparedStatement唯一职责用来执行动态SQL语句,该接口由Connection来创建。

场景:我有一张tb_account表,将id为1的账户余额更新为2000块钱

​ 步骤:

​ 1 加载(注册)MySQL驱动

​ 2 定义连接三要素 url、userName、password

​ 3 定义SQL语句

​ 4 通过DriverManager驱动管理器创建Connection对象

​ 5 由Connection对象创建PreparedStatement对象

​ 6 执行SQL语句(DML),返回受影响的行数

​ 7 打印执行的结果

/**     * 	场景:我有一张tb_account表,将id为1的账户余额更新为2000块钱     * 	步骤:     *     	1 加载(注册)MySQL驱动     * 		2 通过DriverManager 建立Connection对象     *      3 定义连接三要素 url、userName、password     *      4 定义SQL语句     * 		5 由Connection对象创建PreparedStatement对象     * 		6 执行SQL语句(DML),返回受影响的行数     * 		7 打印执行的结果     */    @Test    public void preparedStatementTest() throws Exception {           Class.forName("com.mysql.jdbc.Driver");        String url = "jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=utf8&useSSL=false";        String userName = "root";        String password ="root";        String sql = "update tb_account set account_balance = 2000 where id = 1";        try(                Connection conn = DriverManager.getConnection(url,userName,password);                PreparedStatement ps = conn.prepareStatement(sql);        ){               // executeUpdate()方法用于执行DML语句            // rows存储DML语句返回的受影响行数            int rows = ps.executeUpdate();            System.out.println("rows = "+ rows);        }catch(Exception e){               e.printStackTrace();        }    }

2.3JDBC工具类和Properties

上面几个示例有如下缺陷:

  1. 如果连接的数据库发生改动,那么必须修改Java源文件
  2. 创建Connection对象有大量重复的代码

我们如何解决上面的问题呢?

  1. 将所有数据库连接的字符串放到一个.properties配置文件中,程序启动加载配置文件数据到Properties集合。需要使用的时候获取集合里面的数据
  2. 定义一个工具类,把Connection对象的代码封装成一个静态方法getConnection(),创建连接的时候直接调用。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1ejmNZ56-1617286236933)(day4_jdbc.assets/image-20210328121100947.png)]

场景:使用JDBC工具类创建JDBC连接对象,从而达到数据库连接代码的复用

步骤如下:

​ 1 定义配置文件jdbc.properties,将所有数据库连接的字符串放到一个.properties配置文件中

​ 2 定义工具类JDBCUtils,加载配置文件数据到Properties集合

​ 2.1 创建Properties对象

​ 2.2 使用类加载器将磁盘配置文件数据加载到InputStream输入流

​ 2.3 将InputStream输入流的数据加载到Properties集合

​ 3 定义工具方法getConnection(),获取连接对象

​ 3.1 从Properties集合中获取配置文件信息:驱动信息、url、userName、passowrd

​ 3.2 注册JDBC驱动,告诉Java程序此时我使用了MySQL驱动

​ 3.3 创建数据库连接对象Connection并返回

​ 4 编写JDBC测试类,调用JDBC工具类创建连接对象

​ 4.1 调用JDBCUtils工具类的getConnection()方法获取连接对象

​ 4.2 打印连接对象

JDBC配置文件: jdbc.properties

jdbc.driver=com.mysql.jdbc.Driverjdbc.url=jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=utf8&useSSL=falsejdbc.username=rootjdbc.password=root

JDBC工具类:

package com.atguigu.utils;import java.io.InputStream;import java.sql.Connection;import java.sql.DriverManager;import java.util.Properties;/** * JDBC工具类 */public class JDBCUtils {       /**     * 存储JDBC配置信息     */    private static Properties props = new Properties();    /**     * 将磁盘配置文件加载到内存中     * 1 使用类加载器加载磁盘配置文件jdbc.properties,把加载的数据放入到InputStream流中     * 2 将InputStream流中的数据加载到Properties集合     */    static {           try(               // InputStream in = JDBCUtils.class.getClassLoader()               //         .getResourceAsStream("jdbc.properties");               // 还可以使用当前线程的类加载器加载配置文件               InputStream in = Thread.currentThread().getContextClassLoader()                       .getResourceAsStream("jdbc.properties")                ){               if (null == in) {                  throw new RuntimeException("配置文件加载失败");            }            props.load(in);        }catch(Exception e){               e.printStackTrace();            throw new RuntimeException("配置文件初始化失败");        }    }    /**     * 读取配置文件数据,创建数据库连接对象Connection     * 步骤:     *  1 从Properties集合中获取配置文件信息:驱动信息、url、userName、password     *  2 注册JDBC驱动     *  3 创建Connection对象     * @return Connection对象     * @throws Exception     */    public static Connection getConnection() throws Exception{           String driver = props.getProperty("jdbc.driver");        String url = props.getProperty("jdbc.url");        String userName = props.getProperty("jdbc.username");        String password = props.getProperty("jdbc.password");        Class.forName(driver);        return DriverManager.getConnection(url,userName,password);    }}

测试类:

package com.atguigu.jdbc;import com.atguigu.utils.JDBCUtils;import org.junit.Test;import java.sql.Connection;/** * 测试JDBC工具类 */public class JDBCTest2 {       /**     * 使用JDBC工具类创建连接对象     * 步骤:     *  1.调用JDBCUtils的getConnection()方法获取连接对象     *  2.打印连接对象     */    @Test    public void jdbcUtilsTest() throws Exception{           Connection conn = JDBCUtils.getConnection();        System.out.println("Conn==="+conn);    }}

使用工具类好处:

  1. 即使连接的数据库发生变化,只需要修改配置文件,不用修改Java源文件。
  2. 每次获取连接只需要调用静态方法getConnection()不需要重复创建连接,从而达到代码的复用。

2.4 PreparedStatement加强

​ 之前我们编写的JDBC程序修改张三的金额为2000,程序在编译期就能够确定SQL语句,这样的代码在软件开发过程中叫做“硬编码”,缺乏灵活性。我们如何在程序运行期确定SQL语句呢?就需要PreparedStatement设置占位符来完成。

场景:删除账户信息,当客户输入1删除张三的账户信息,输入2删除李四的账户信息

步骤:

1 定义JDBC测试类

2 创建Scanner对象,扫描用户的输入

3 接收用户输入的数据

4 编写SQL语句,delete from …

5 调用JDBCUtils工具类的getConnection()方法获取连接对象

6 创建PreparedStatement对象

7 设置占位符

8 执行SQL语句,返回受影响行数

9 打印受影响行数

package com.atguigu.jdbc;import com.atguigu.utils.JDBCUtils;import com.mysql.jdbc.JDBC4UpdatableResultSet;import org.junit.Test;import java.sql.Connection;import java.sql.DriverManager;import java.sql.PreparedStatement;import java.util.Scanner;/**    JDBC测试类 */public class JDBCTest3 {       /**     * Scanner对象用于扫描用户的输入     */    private static Scanner input = new Scanner(System.in);    /**     * 场景:删除账户信息,当客户输入1删除张三的账户信息,输入2删除李四的账户信息     * 步骤:     * 1 定义JDBC测试类     * 2 创建Scanner对象,扫描用户的输入     * 3 接收用户输入的数据     * 4 编写SQL语句,delete from ......     * 5 调用JDBCUtils工具类的getConnection()方法获取连接对象     * 6 创建PreparedStatement对象     * 7 设置占位符     * 8 执行SQL语句,返回受影响行数     * 9 打印受影响行数     */    public static void main(String[] args) throws Exception{           System.out.println("输入1删除张三的账户信息,输入2删除李四的账户信息");        int id = input.nextInt();        // 到底要删除那个用户编译期无法确定,此时使用?表示编译期不确定的值        String sql = "delete from tb_account where id = ?";        try(                Connection conn = JDBCUtils.getConnection();                PreparedStatement ps = conn.prepareStatement(sql);                ){               /**             * 设置占位符,将客户输入的id注入到?中             * 参数1:1此时表示为SQL语句的第一个?设置值             * 参数2:占位符的值,将客户输入的id设置到第一个占位符?里面             */            ps.setInt(1,id);            // executeUpdate()方法可以执行所有的DML语句            int rows = ps.executeUpdate();            String result = rows > 0 ?"删除成功":"删除失败";            System.out.println(result+"受影响行数:"+rows);        }catch(Exception e) {               e.printStackTrace();        }    }}

注意:Scanner不支持单元测试,必须使用main方法

小结:使用占位符的好处,能够在程序运行期确定SQL语句,让程序更加灵活

​ 占位符从1开始,不是从0开始。

场景:根据客户输入的账户名称和金额向tb_account表插入数据

步骤:

1 定义JDBC测试类

2 创建Scanner对象,扫描用户的输入

3 接收用户输入的账户名称和金额

4 编写SQL语句,inser into tb_account…

5 调用JDBCUtils工具类的getConnection()方法获取数据库连接对象

6 创建PreparedStatement对象

7 设置占位符

8 执行SQL语句,返回受影响行数

9 打印受影响行数

package com.atguigu.jdbc;import com.atguigu.utils.JDBCUtils;import java.sql.Connection;import java.sql.PreparedStatement;import java.util.Scanner;/** * JDBC测试类 */public class JDBCTest4 {       private static Scanner input = new Scanner(System.in);    /**     * **场景:删除账户信息,当客户输入1删除张三的账户信息,输入2删除李四的账户信息**     *     * 步骤:     * 1 定义JDBC测试类     * 2 创建Scanner对象,扫描用户的输入     * 3 接收用户输入的数据     * 4 编写SQL语句,insert into ......     * 5 调用JDBCUtils工具类的getConnection()方法获取连接对象     * 6 创建PreparedStatement对象     * 7 设置占位符     * 8 执行SQL语句,返回受影响行数     * 9 打印受影响行数     * @throws Exception     */    public static void main(String[] args) throws Exception {           System.out.println("请输入账户名称");        String accountName = input.next();        System.out.println("请输入金额");        int money = input.nextInt();        String sql = "insert into tb_account(account_name,account_balance)values(?,?)";        try(                Connection conn = JDBCUtils.getConnection();                PreparedStatement ps = conn.prepareStatement(sql);                ){               // 第一个占位符是String类型,所以调用setString方法            ps.setString(1,accountName);            ps.setInt(2,money);            int rows = ps.executeUpdate();            String result = rows > 0 ?"插入成功":"插入失败";            System.out.println(result+"::受影响行数 = "+rows);        }catch(Exception e){               e.printStackTrace();        }    }}

2.5 ResultSet

ResultSet接口用来存储从数据库中查询的结果,它的本质是一个集合

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pe104fK7-1617286236941)(day4_jdbc.assets/image-20210328133820911.png)]

场景:需要编写一个DQL语句从MySQL数据库中查询所有tb_account表的数据,然后将其打印到Java控制台。

步骤:

1 定义SQL语句 select …from tb_account

2 调用JDBCUtils工具类的getConnection()方法获取数据库连接对象

3 创建PreparedStatement对象

4 执行SQL语句,并返回ResultSet结果集对象

5 使用循环逐行读取结果集数据

6 打印读取的数据

package com.atguigu.jdbc;import com.atguigu.utils.JDBCUtils;import java.sql.Connection;import java.sql.PreparedStatement;import java.sql.ResultSet;/** * JDBC测试类:读取MySQL数据库的数据到Java程序 */public class JDBCTest5 {       /**     * 场景:需要编写一个DQL语句从MySQL数据库中查询所有tb_account表的数据,然后将其     * 打印到Java控制台。     * 步骤:     * 1 定义SQL语句 select ....from tb_account     * 2 调用JDBCUtils工具类的getConnection()方法获取数据库连接对象     * 3 创建PreparedStatement对象     * 4 执行SQL语句,并返回ResultSet结果集对象     * 5 使用循环逐行读取结果集数据     * 6 打印读取的数据     * @param args     */    public static void main(String[] args) {           String sql = "select id,account_name,account_balance from tb_account";        try(                Connection conn = JDBCUtils.getConnection();                PreparedStatement ps = conn.prepareStatement(sql);        ){               // executeQuery()方法专门执行DQL从数据库查询数据,查询结果返回结果集对象ResultSet            ResultSet rs = ps.executeQuery();            // 条件成立:表示结果集里面有下一条数据            while(rs.next()){                   // 获取每一列的数据                // 1表示获取结果集第一列数据,第一列是整数类型所以调用getInt方法                int id = rs.getInt(1);                // 2表示获取结果集第二列数据,第二列是字符型所以调用getString方法                String accountName = rs.getString(2);                // 3表示获取结果集第三列数据,第三列是整数类型所以调用getInt方法                int money = rs.getInt(3);                System.out.println(id+"---"+accountName+"---"+money);            }        }catch (Exception e){           }    }}

小结:

​ Connection 唯一职责:获取数据库连接(架桥)

​ PreparedStatement唯一职责:执行SQL,如果有占位符,设置占位符

​ ResultSet唯一职责:存储MySQL服务器返回的查询结果

第三章 数据库连接池

3.1什么是数据库连池

​ 池:是一个容器,里面容纳了数据库连接对象。通常项目启动会创建数据库连接池对象,当我们需要使用Connection连接对象的时候,从数据库连接池中获取连接对象Connection,不用自己创建。使用完毕之后不是销毁对象,而是将连接对象归还到数据库连接池中。

​ 任何生产数据库连接池的厂商必须实现javax.sql.DataSource接口。它是数据库连接池的核心,也是数据库连接池的标准。

3.2为什么要使用数据库连接池

​ 频繁创建Connection对象很消耗内存资源,我们需要将消耗资源的对象重用。工作中不允许直接创建Connection连接对象必须从数据库连接池中获取连接对象Connection。

3.3市面上有很多现成的数据库连接池技术

  • JDBC 的数据库连接池使用 javax.sql.DataSource 来表示,DataSource 只是一个接口(通常被称为数据源),该接口通常由服务器(Weblogic, WebSphere, Tomcat)提供实现,也有一些开源组织提供实现:
    • DBCP 是Apache提供的数据库连接池,速度相对c3p0较快,但因自身存在BUG,Hibernate3已不再提供支持
    • C3P0 是一个开源组织提供的一个数据库连接池,速度相对较慢,稳定性还可以
    • Proxool 是sourceforge下的一个开源项目数据库连接池,有监控连接池状态的功能,稳定性较c3p0差一点
    • BoneCP 是一个开源组织提供的数据库连接池,速度快
    • Druid 是阿里巴巴提供的数据库连接池,据说是集DBCP 、C3P0 、Proxool 优点于一身的数据库连接池

3.4连接池的机制

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-exHTGhtb-1617286236944)(day4_jdbc.assets/1606704967544-1616724501098.png)]

3.4Druid连接池连接使用步骤

1导入第三方连接池的jar包。druid-1.0.9.jar

2 定义druid.properties配置文件,编写连接池相关的参数

3 编写数据库连接池工具类,创建连接池对象

​ 3.1 定义连接池对象DataSource

​ 3.2 使用类加载器将druid.properties配置文件加载到InputStream输入流

​ 3.3 创建Properties对象

​ 3.4 将InputStream流里面的连接池参数加载到Properties集合

​ 3.5 使用DruidDataSourceFactory创建Druid连接池对象,将Properties里面的连接池参数注入到Druid连接池中

4 提供一个公有的静态的方法给外界,来获取连接池对象

5 提供一个公有的静态的方法给外界,让外界获取连接池的Connection连接对象

数据库连接池配置文件druid.properties

# 驱动名称driverClassName=com.mysql.jdbc.Driver# 告诉Druid我要连接的是MySQLurl=jdbc:mysql://localhost:3306/db1?useUnicode=true&characterEncoding=UTF8&useSSL=false# MySQL数据库的用户名username=root# MySQL数据库的密码password=root# 创建连接池设置的初始链接数量initialSize=5# 初始连接数量使用完毕,连接池的最大活动链接数量maxActive=10# 最大等待时间 连接池所有连接都在使用,还有新的请求,等待3000毫秒,如果还没有可用的连接对象就会抛出异常maxWait=3000

数据库连接池工具类:

package com.atguigu.utils;import com.alibaba.druid.pool.DruidDataSourceFactory;import javax.sql.DataSource;import java.io.InputStream;import java.sql.Connection;import java.util.Properties;/** * 数据库连接池工具类 * 在工具类中创建数据库连接池对象,提供工具方法让外界获取连接池里面的Connection对象。从而达到连接复用的目的 */public class DruidUtils {       /**     * 创建数据库连接池对象步骤:		1 定义连接池对象DataSource        2 使用类加载器将druid.properties配置文件加载到InputStream输入流        3 创建Properties对象        4 将InputStream流里面的连接池参数加载到Properties集合        5 使用DruidDataSourceFactory创建Druid连接池对象,将Properties里面的连接池参数注入到Druid连接池中     * 注意:连接池对象在创建的过程中会占用较大的内存,所以一个项目连接池对象只创建一次     */    private static DataSource ds ;    static {           try(                InputStream in = Thread.currentThread().getContextClassLoader()                        .getResourceAsStream("druid.properties");                ){               // 条件成立:表示配置文件加载失败            if(null == in){                   throw new RuntimeException("连接池配置文件加载失败");            }            Properties props = new Properties();            props.load(in);            // 使用DruidDataSourceFactory创建Druid连接池对象            ds = DruidDataSourceFactory.createDataSource(props);        }catch(Exception e){               e.printStackTrace();            throw new RuntimeException("连接池创建失败");        }    }    /**     * 提供一个公有的静态的方法给外界,来获取连接池对象     * @return 连接池对象     */    public static DataSource getDataSource() throws Exception{           return ds;    }    /**     * 提供一个公有的静态的方法,让外界获取连接池的Connection连接对象     */    public static Connection getConnection() throws Exception {           return ds.getConnection();    }}

数据库连接池测试类:

package com.atguigu.jdbc;import com.atguigu.utils.DruidUtils;import org.junit.Test;import javax.sql.DataSource;import java.sql.Connection;/** * 数据库连接池测试类 */public class JDBCDruidTest1 {       /**     * 获取连接池对象DataSource     * 步骤:     *  调用DruidUtils工具类的getDataSource()方法     */    @Test    public void getDataSourceTest() throws Exception {           DataSource ds = DruidUtils.getDataSource();    }    /**     * 测试数据库连接池,获取连接池里面的Connection对象     * @throws Exception     */    @Test    public void getConnectionTest() throws Exception {           Connection conn = DruidUtils.getConnection();        System.out.println(conn);    }     }

第四章 JDBC批处理和MySQL事务

​ 之前编写的程序创建一个Connection对象,只能执行一次DML语句,效率不高。我想使用一个Connection对象执行多个SQL语句就需要用到JDBC的批处理操作。批处理只能支持DML语句。

​ 批处理特征:所有的DML要么全部执行成功,要么全部执行失败。不允许部分成功,部分失败。如何做到全部执行成功or全部执行失败?开启事务去管理批处理。创建Connection成功之后开启事务,如果执行DML期间发生了异常,回滚事务回滚到事务开启之前的状态。如果DML全部执行成功,提交事务。

注意:回滚操作必须在catch块中进行

批处理好处:把要执行的DML操作收集起来一次性的执行

场景:一次性的删除tb_account表若干条数据,删除操作要么全部执行成功or全部执行失败

批量删除:使用批处理一次Connection连接对象执行多个DML操作,操作结果两种:全部成功or全部失败

步骤:

1定义数组,存储要删除的id

2定义要执行的delete from tb_account…语句

3调用DruidUtils工具类的getConnection()方法,获取Druid连接池的Connection对象

4创建PreparedStatement对象

5使用循环,设置每个SQL语句的占位符

6将设置好的SQL语句占位符,添加到批处理程序中

7执行批处理

8提交事务

9如果批处理执行发生异常,在catch块中回滚

10关闭连接对象(先开启的后关闭,后开启的先关闭) 。先关闭PreparedStatement后关闭Connection

package com.atguigu.jdbc;import com.atguigu.utils.DruidUtils;import org.junit.Test;import java.sql.Connection;import java.sql.PreparedStatement;/** * 测试JDBC批处理 * */public class JDBCBatchTest {       /**     * 场景:一次性的删除tb_account表若干条数据,删除操作要么全部执行成功or全部执行失败     * 步骤:     * 1定义数组,存储要删除的id     * 2定义要执行的insert into tb_account......语句     * 3使用Druid连接池获取Connection对象     * 4创建PreparedStatement对象     * 5使用循环,设置每个SQL语句的占位符     * 6将设置好的SQL语句占位符,添加到批处理程序中     * 7执行批处理     * 8提交事务     * 9如果批处理执行发生异常,在catch块中回滚     * 10关闭连接对象(先开启的后关闭,后开启的先关闭) 。先关闭PreparedStatement后关闭Connection     * */    @Test    public void batchTest() throws Exception{           int[] ids = new int[]{   6,7,8};        Connection conn = null;        PreparedStatement ps = null;        String sql = "delete from tb_account where id = ?";        try {               conn = DruidUtils.getConnection();            ps = conn.prepareStatement(sql);            // 开启事务,让自动提交失效            conn.setAutoCommit(false);            for (int i = 0; i < ids.length; i++) {                   // 设置SQL语句的第一个占位符                ps.setInt(1,ids[i]);                // 占位符添加到批处理                ps.addBatch();            }            // 执行批处理语句            int[] rows = ps.executeBatch();            System.out.println(1/0);            // 提交批处理            conn.commit();            String result = rows.length>0?"批处理执行成功":"批处理执行失败";            System.out.println(result+"受影响行数:"+rows.length);        } catch(Exception e){               // 批处理执行失败,回滚事务,回滚到事务开启之前的状态            if(null != conn){                   conn.rollback();            }            System.err.println("批处理失败,DML回滚");            e.printStackTrace();        } finally {               // 释放连接对象占用的内存,先开的资源后关闭,后开的资源先关闭            if(null != ps){                   ps.close();            }            if(null != conn){                   conn.close();            }        }    }}

小结:批处理在一次创建Connection连接对象的基础上,多次执行DML语句

​ 批处理只能支持DML语句。杜绝部分成功部分失败。

第五章 Apache的DBUtils

commons-dbutils 是 Apache 组织提供的一个开源的JDBC工具类库,它是对JDBC的简单封装,学习成本极低,并且使用dbutils能极大简化JDBC编码的工作量,同时也不会影响程序的性能。

DBUtils的核心是QueryRunner类,封装了SQL的执行。

(1)可以实现增、删、改、查、批处理

(2)该类最主要的就是简单化了SQL查询,它与ResultSetHandler组合在一起使用可以完成大部分的数据库操作,能够大大减少编码量。

(1)使用QueryRunner类实现插入、更新或删除

public int update( String sql, Object… params) throws SQLException:用来执行一个更新(插入、更新或删除)操作。

(2)使用QueryRunner类实现查询

public Object query(String sql, ResultSetHandler rsh,Object… params) throws SQLException:执行一个查询操作,在这个查询中,对象数组中的每个元素值被用来作为查询语句参数。该方法会自行处理 PreparedStatement 和 ResultSet 的创建和关闭。

ResultSetHandler接口用于处理 java.sql.ResultSet,将数据按要求转换为另一种形式。

该接口有如下实现类可以使用:

  • BeanHandler:将结果集中的第一行数据封装到一个对应的JavaBean实例中。

  • BeanListHandler:将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里。

  • ScalarHandler(int columnIndex):用来保存只有一行一列的结果集

    场景:使用DBUtils修改tb_account表中的数据

    步骤:

    1 获取DruidUtils工具类的DataSource对象

    2 定义要执行的SQL语句update tb_account…

    3 定义Object数组,存储要修改的内容

    4 创建QueryRunner对象,将DataSource作为参数传入到QueryRunner构造方法

    5 调用QueryRunner对象的update方法将2,3作为参数传入到该方法,执行update语句。返回受影响的行数

    6 打印结果

    /**     * 使用DBUtils对tb_account表的修改操作     * 步骤:     * 1 获取DruidUtils工具类的DataSource对象     * 2 定义要执行的SQL语句update tb_account.....     * 3 定义Object数组,存储要修改的内容     * 4 创建QueryRunner对象,将DataSource作为参数传入到QueryRunner构造方法     * 5 调用QueryRunner对象的update方法,执行update语句,返回受影响的行数     * 6 打印结果     */    @Test    public void testUpdate() throws Exception {             DataSource ds = DruidUtils.getDataSource();        String sql = "update tb_account set account_balance = ? where id = ?";        Object[] data = {     1010101,9};        QueryRunner runner = new QueryRunner(ds);        int rows = runner.update(sql, data);        String result = rows > 0 ? "update语句执行成功" : "update语句执行失败";        System.out.println(result+"受影响函数:"+rows);    }

    场景:使用DBUtils向tb_account表插入一行数据

    步骤:

    ​ 1 获取DruidUtils工具类的DataSource对象

    ​ 2 定义要插入的SQL语句,insert into tb_account…

    ​ 3 定义Object数组,存储要插入的数据

    ​ 4 创建QueryRunner对象执行JDBC操作,DataSource对象注入到QueryRunner的构造方法中

    ​ 5 调用QueryRunner的update方法执行SQL语句,返回受影响的行数

    ​ 6 打印结果

    /**     * 场景:使用DBUtils向tb_account表插入一行数据     * 1 获取DruidUtils工具类的DataSource对象     * 2 定义要插入的SQL语句,insert into tb_account....     * 3 定义Object数组存储,要插入的数据     * 4 创建QueryRunner对象执行JDBC操作,DataSource对象注入到QueryRunner的构造方法中     * 5 调用QueryRunner的update方法执行SQL语句,返回受影响的行数     * 6 打印结果     * @throws Exception     */    @Test    public void testInsert() throws Exception {             DataSource ds = DruidUtils.getDataSource();        String sql = "insert into tb_account(account_name,account_balance)values(?,?)";        Object[] data = {     "马化腾",1031329};        QueryRunner runner = new QueryRunner(ds);        int rows = runner.update(sql,data);        String result = rows > 0 ? "insert语句执行成功" : "update语句执行失败";        System.out.println(result+"受影响函数:"+rows);    }

    场景:使用DBUtils根据id删除tb_account表的数据

    步骤:

    1 获取DruidUtils工具类的DataSource对象

    2 定义要删除的SQL语句,delete from tb_account…

    3 定义对象数组,存储要删除的id

    4 创建QueryRunner对象执行JDBC操作,DataSource对象注入到QueryRunner的构造方法中

    5 调用QueryRunner的update方法执行SQL语句,返回受影响的行数

    6 打印结果

    /**     * 场景:使用DBUtils根据id删除tb_account表的数据     * 步骤:     * 1 获取DruidUtils工具类的DataSource对象     * 2 定义要删除的SQL语句,delete from tb_account....     * 3 定义对象数组,存储要删除的id     * 4 创建QueryRunner对象执行JDBC操作,DataSource对象注入到QueryRunner的构造方法中     * 5 调用QueryRunner的update方法执行SQL语句,返回受影响的行数     * 6 打印结果     * @throws Exception     */    @Test    public void testDelete() throws Exception {             DataSource ds = DruidUtils.getDataSource();        String sql = "delete from tb_account where id = ?";        int id = 13;        QueryRunner runner = new QueryRunner(ds);        int rows = runner.update(sql, id);        String result = rows > 0 ? "update语句执行成功" : "update语句执行失败";        System.out.println(result+"受影响函数:"+rows);    }

    场景:使用DBUtils查询tb_account表所有的数据,将获取的数据存储到List集合中

    步骤:

    1 定义实体类Account

    2 获取DruidUtils工具类的DataSource对象

    3 定义要执行查询的SQL语句,select … from tb_account

    4 创建BeanListHandler对象,将结果集的每一行数据都封装到一个对应的JavaBean实例中,存放到List里。

    5 创建QueryRunner对象执行JDBC操作,DataSource对象注入到QueryRunner的构造方法中

    6 调用QueryRunner对象的query方法,执行SQL语句,返回一个List集合

    7 打印List集合数据

    /**     * 场景:使用DBUtils查询tb_account表所有的数据     * 步骤:     * 1 定义实体类Account     * 2 获取DruidUtils工具类的DataSource对象     * 3 定义要执行查询的SQL语句,select .... from tb_account     * 4 创建BeanListHandler对象,将结果集的每一行数据都封装到一个对应的JavaBean实例中,存放到List里。     * 5 创建QueryRunner对象执行JDBC操作,DataSource对象注入到QueryRunner的构造方法中     * 6 调用QueryRunner对象的query方法,执行SQL语句,返回一个List集合     * 7 打印List集合数据     * @throws Exception     */    @Test    public void testSelectAll() throws Exception{             DataSource ds = DruidUtils.getDataSource();        String sql = "select id,account_name,account_balance from tb_account";        BeanListHandler
    listHandler = new BeanListHandler<>(Account.class); QueryRunner runner = new QueryRunner(ds); List
    list = runner.query(sql, listHandler); list.forEach(System.out::println); }

    场景:使用DBUtils根据ID查询tb_account表的某一条数据

    步骤:

    1 定义实体类Account,如果已经定义了请忽略该步骤

    2 获取DruidUtils工具类的DataSource对象

    3 定义要执行查询的SQL语句,select … from tb_account where id = ?

    4 定义要查询的id

    5 创建BeanHandler对象,将结果集的第一行数据都封装到一个对应的JavaBean实例中

    6 创建QueryRunner对象执行JDBC操作,DataSource对象注入到QueryRunner的构造方法中

    7 调用QueryRunner对象的query方法,执行SQL语句,返回一个Account对象

    8 打印Account对象

    /**     * 场景:使用DBUtils根据ID查询tb_account表的某一条数据     * 步骤:     * 1 定义实体类Account,如果已经定义了请忽略该步骤     * 2 获取DruidUtils工具类的DataSource对象     * 3 定义要执行查询的SQL语句,select .... from tb_account where id = ?     * 4 定义要查询的id     * 5 创建BeanHandler对象,将结果集的第一行数据都封装到一个对应的JavaBean实例中     * 6 创建QueryRunner对象执行JDBC操作,DataSource对象注入到QueryRunner的构造方法中     * 7 调用QueryRunner对象的query方法,执行SQL语句,返回一个Account对象     * 8 打印Account对象     * @throws Exception     */    @Test    public void testSelectOne() throws Exception {             DataSource ds = DruidUtils.getDataSource();        String sql = "select id,account_name,account_balance from tb_account where id = ?";        int id = 10;        BeanHandler
    handler = new BeanHandler<>(Account.class); QueryRunner runner = new QueryRunner(ds); Account account = runner.query(sql, handler, id); System.out.println(account); }

    场景:使用DBUtils获取tb_account表总行数

    步骤:

    1 获取DruidUtils工具类的DataSource对象

    2 定义要执行查询的SQL语句,select count(*) from tb_account;

    3 创建ScalarHandler对象,保存只有一行一列的结果集

    4 创建QueryRunner对象执行JDBC操作,DataSource对象注入到QueryRunner的构造方法中

    5 调用QueryRunner对象的query方法,执行SQL语句,返回tb_account表的总行数

    6 打印总行数

    /**     * 场景:使用DBUtils获取tb_account表总行数     * 步骤:     * 1 获取DruidUtils工具类的DataSource对象     * 2 定义要执行查询的SQL语句,select count(*) from tb_account;     * 3 创建ScalarHandler对象,保存只有一行一列的结果集     * 4 创建QueryRunner对象执行JDBC操作,DataSource对象注入到QueryRunner的构造方法中     * 5 调用QueryRunner对象的query方法,执行SQL语句,返回tb_account表的总行数     * 6 打印总行数     */    @Test    public void getEntityCount() throws Exception{             DataSource ds = DruidUtils.getDataSource();        String sql  = "select count(*) from tb_account";        ScalarHandler
    handler = new ScalarHandler<>(1); QueryRunner runner = new QueryRunner(ds); long count = runner.query(sql, handler); System.out.println("count === "+count); }

    小结:我们使用DBUtils完成了一套CRUD(增删改查)操作,对JDBC操作做了大量简化。但是还是存在大量重复的代码,无法完成复用。

    ​ BeanListHandler 封装了ResultSet 的多行多列结果集 。例如:查询所有数据

    ​ BeanHander封装了ResultSet单行多列结果集, 例如:根据id查询某一条数据

    ​ ScalarHandler 封装了ResultSet单行单列结果集,例如:统计表的总行数

第六章BaseDAO

​ 上一章我们使用DBUtils完成了一套CRUD操作,大家可以发现新增、删除、修改操作有很多重复的代码,只是它们SQL语句不同,要修改的值不一样。我们可以把这些重复的代码封装到BaseDAO中达到代码的复用。

步骤如下:

​ 1 创建BaseDAO,封装通用的CRUD方法

​ 2 创建AccountDAO接口,定义常用的增删改查(CRUD)方法

​ 3 创建AccountDAO接口的实现类,实现接口所有方法

​ 4 创建测试类,测试AccountDAO

​ BaseDAO

package com.atguigu.utils;import org.apache.commons.dbutils.QueryRunner;import org.apache.commons.dbutils.handlers.BeanHandler;import org.apache.commons.dbutils.handlers.BeanListHandler;import org.apache.commons.dbutils.handlers.ScalarHandler;import javax.sql.DataSource;import java.util.List;/** * BaseDAO是所有DAO的父类,子类通过继承BaseDAO完成CRUD操作 */public class BaseDAO
{ private Class clazz; private static DataSource ds = DruidUtils.getDataSource(); public BaseDAO(Class clazz){ this.clazz = clazz; } /** 通用的增、删、改的方法 步骤:1 创建QueryRunner对象,将DataSource注入到QueryRunner的构造方法 2 调用QueryRunner对象的update方法完成对DML语句的操作,并且返回受影响的行数 @param sql 要进行DML操作的SQL语句 @param params 增加、删除、修改的数据 */ public int update(String sql,Object[] params) throws Exception { QueryRunner runner = new QueryRunner(ds); return runner.update(sql,params); } /** * 根据SQL语句获取表的总行数 * 步骤: * 1 创建QueryRunner对象,将DataSource注入到QueryRunner的构造方法 * 2 创建ScalarHandler对象,保持只有一行一列的结果集 * 3 调用QueryRunner对象的query方法,将sql语句和ScalarHandler传入到方法中,执行SQL语句 * 并且返回表的总行数 * @param sql SQL语句 * @return 表的总行数 * @throws Exception */ public long getCount(String sql) throws Exception{ QueryRunner runner = new QueryRunner(ds); ScalarHandler
handler = new ScalarHandler<>(1); return runner.query(sql,handler); } /** * 获取表中所有数据 * 步骤: * 1 创建QueryRunner对象,将DataSource注入到QueryRunner的构造方法 * 2 创建BeanListHandler对象,将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里。 * 3 调用QueryRunner对象的query方法查询结果,将sql、handler对象、params传入到方法中 * @param sql SQL语句 * @return 查询的数据返回List集合 * @throws Exception */ public List
getAll(String sql,Object... params) throws Exception { QueryRunner runner = new QueryRunner(ds); BeanListHandler
handler = new BeanListHandler<>(clazz); return runner.query(sql, handler,params); } /** * 根据传入的SQL语句获取表中的一行数据 * 1 创建QueryRunner对象,将DataSource注入到QueryRunner的构造方法 * 2 创建BeanHandler对象,将结果集中的第一行数据都封装到一个对应的JavaBean实例中, * 3 调用QueryRunner对象的query方法查询结果,将sql、handler对象、params传入到方法中 * @param sql * @param params * @return * @throws Exception */ public T getOne(String sql,Object... params) throws Exception { QueryRunner runner = new QueryRunner(ds); BeanHandler
handler = new BeanHandler<>(clazz); return runner.query(sql,handler,params); }}

AccountDAO接口

package com.atguigu.dao;import com.atguigu.entity.Account;import java.util.List;public interface AccountDAO
{ /** * 向tb_account表新添加一条数据 * @param sql SQL语句 * @param account 要添加的数据 * @return 受影响行数 * @throws Exception */ int saveAccount(String sql, Account account) throws Exception; /** * 向tb_account表新修改一条数据 * @param sql SQL语句 * @param account 要添加的数据 * @return 受影响行数 * @throws Exception */ int updateAccount(String sql,Account account) throws Exception; /** * 根据id从tb_account表删除一行数据 * @param sql SQL语句 * @param id 要删除的id * @return 受影响行数 * @throws Exception */ int deleteAccount(String sql,Integer id) throws Exception; /** * 获取tb_account表中的所有数据 * @param sql SQL语句 * @param params 外界传入的分页参数 * @return List结果集 * @throws Exception */ List
listAccount(String sql,Object...params) throws Exception; /** * 根据id获取tb_account表中的一行数据 * @param sql SQL * @param params 外界传入的ID * @return 实体对象 * @throws Exception */ T getAccountById(String sql,Object...params) throws Exception; /** * 获取tb_account表的总行数 * @param sql SQL语句 * @return 总行数 * @throws Exception */ long getAccountCount(String sql) throws Exception;}

AccountDAOImpl实现类

package com.atguigu.dao;import com.atguigu.entity.Account;import com.atguigu.utils.BaseDAO;import java.util.List;public class AccountDAOImpl
extends BaseDAO
implements AccountDAO { public AccountDAOImpl(Class clazz) { super(clazz); } /** * 向tb_account表新添加一条数据 * @param sql SQL语句 * @param account 要添加的数据 * @return 受影响行数 * @throws Exception */ @Override public int saveAccount(String sql, Account account) throws Exception { Object[] params = {account.getAccount_name(), account.getAccount_balance()}; return update(sql,params); } /** * 向tb_account表新修改一条数据 * * @param sql SQL语句 * @param account 要添加的数据 * @return 受影响行数 * @throws Exception */ @Override public int updateAccount(String sql, Account account) throws Exception { Object[] params = {account.getAccount_name(), account.getAccount_balance(),account.getId()}; return update(sql,params); } /** * 根据id从tb_account表删除一行数据 * * @param sql SQL语句 * @param id 要删除的id * @return 受影响行数 * @throws Exception */ @Override public int deleteAccount(String sql, Integer id) throws Exception { Object[] params = {id}; return update(sql,params); } /** * 获取tb_account表中的所有数据 * * @param sql SQL语句 * @param params 外界传入的分页参数 * @return List结果集 * @throws Exception */ @Override public List
listAccount(String sql, Object... params) throws Exception { return getAll(sql,params); } /** * 根据id获取tb_account表中的一行数据 * * @param sql SQL * @param params 外界传入的ID * @return 实体对象 * @throws Exception */ @Override public T getAccountById(String sql, Object... params) throws Exception { return getOne(sql,params); } /** * 获取tb_account表的总行数 * * @param sql SQL语句 * @return 总行数 * @throws Exception */ @Override public long getAccountCount(String sql) throws Exception { return getCount(sql); }}

测试类

package com.atguigu.jdbc;import com.atguigu.dao.AccountDAO;import com.atguigu.dao.AccountDAOImpl;import com.atguigu.entity.Account;import org.junit.Test;import java.util.List;/** * 测试BaseDAO */public class BaseDAOTest {       private AccountDAO
dao = new AccountDAOImpl<>(Account.class); @Test public void saveAccountTest(){ Account account = new Account(-1,"王老五",50001); String sql = "insert into tb_account(account_name,account_balance)values(?,?)"; try { int rows = dao.saveAccount(sql, account); String result = rows > 0 ? "添加成功":"添加失败"; System.out.println(result+"受影响行数:"+rows); } catch (Exception e) { e.printStackTrace(); } } @Test public void deleteAccountTest() { String sql = "delete from tb_account where id = ?"; try { int rows = dao.deleteAccount(sql, 14); String result = rows > 0 ? "删除成功":"删除失败"; System.out.println(result+"受影响行数:"+rows); } catch (Exception e) { e.printStackTrace(); } } @Test public void updateAccountTest(){ String sql = "update tb_account set account_name= ? ,account_balance = ? where id = ?"; Account account = new Account(11,"老马",505050); try { int rows = dao.updateAccount(sql,account); String result = rows > 0 ? "删除成功":"删除失败"; System.out.println(result+"受影响行数:"+rows); } catch (Exception e) { e.printStackTrace(); } } @Test public void getAllAccountTest(){ String sql = "select id,account_name,account_balance from tb_account"; try { List
list = dao.listAccount(sql); list.forEach(System.out::println); } catch (Exception e) { e.printStackTrace(); } } @Test public void getAccountByIdTest(){ String sql = "select id,account_name,account_balance from tb_account where id= ?"; Object[] params = { 10}; try { Account account = dao.getAccountById(sql,params); System.out.println(account); } catch (Exception e) { e.printStackTrace(); } } @Test public void getCountTest(){ String sql = "select count(*) from tb_account"; try { long count = dao.getAccountCount(sql); System.out.println("Count==="+count); } catch (Exception e) { e.printStackTrace(); } }}

转载地址:http://upni.baihongyu.com/

你可能感兴趣的文章
MSSQL数据库迁移到Oracle(二)
查看>>
MSSQL日期格式转换函数(使用CONVERT)
查看>>
MSTP多生成树协议(第二课)
查看>>
MSTP是什么?有哪些专有名词?
查看>>
Mstsc 远程桌面链接 And 网络映射
查看>>
Myeclipse常用快捷键
查看>>
MyEclipse更改项目名web发布名字不改问题
查看>>
MyEclipse用(JDBC)连接SQL出现的问题~
查看>>
mt-datetime-picker type="date" 时间格式 bug
查看>>
myeclipse的新建severlet不见解决方法
查看>>
MyEclipse设置当前行背景颜色、选中单词前景色、背景色
查看>>
Mtab书签导航程序 LinkStore/getIcon SQL注入漏洞复现
查看>>
myeclipse配置springmvc教程
查看>>
MyEclipse配置SVN
查看>>
MTCNN 人脸检测
查看>>
MyEcplise中SpringBoot怎样定制启动banner?
查看>>
MyPython
查看>>
MTD技术介绍
查看>>
MySQL
查看>>
MySQL
查看>>