本文共 36014 字,大约阅读时间需要 120 分钟。
什么是JDBC? Java DataBase Connectivity
JDBC是Java连接数据库的技术,它提供一套操作所有关系型数据库的规则(接口)可以让所有的关系型数据库使用一套代码和Java程序进行交互。
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最核心的接口,也是内存资源消耗最大的一个接口。
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(); } }
上面几个示例有如下缺陷:
我们如何解决上面的问题呢?
场景:使用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); }}
使用工具类好处:
之前我们编写的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(); } }}
ResultSet接口用来存储从数据库中查询的结果,它的本质是一个集合
场景:需要编写一个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服务器返回的查询结果
池:是一个容器,里面容纳了数据库连接对象。通常项目启动会创建数据库连接池对象,当我们需要使用Connection连接对象的时候,从数据库连接池中获取连接对象Connection,不用自己创建。使用完毕之后不是销毁对象,而是将连接对象归还到数据库连接池中。
任何生产数据库连接池的厂商必须实现javax.sql.DataSource接口。它是数据库连接池的核心,也是数据库连接池的标准。
频繁创建Connection对象很消耗内存资源,我们需要将消耗资源的对象重用。工作中不允许直接创建Connection连接对象必须从数据库连接池中获取连接对象Connection。
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); } }
之前编写的程序创建一个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语句。杜绝部分成功部分失败。
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"; BeanListHandlerlistHandler = 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; BeanHandlerhandler = 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"; ScalarHandlerhandler = 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单行单列结果集,例如:统计表的总行数
上一章我们使用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 AccountDAOImplextends 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 AccountDAOdao = 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/