博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
2021-04-01
阅读量:194 次
发布时间:2019-02-28

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

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/

你可能感兴趣的文章