【Java基础教程】(五十)JDBC篇:JDBC概念及操作步骤、主要类与接口解析、批处理与事务处理~

Java基础教程之JDBC

  • 🔹本章学习目标
  • 1️⃣ JDBC概念
  • 2️⃣ 连接数据库
  • 3️⃣ Statement 接口
      • 3.1 数据更新操作
      • 3.2 数据查询
  • 4️⃣ PreparedStatement 接口
      • 4.1 Statement 接口问题
      • 4.2 PreparedStatement操作
  • 5️⃣ 批处理与事务处理
  • 🌾 总结

在这里插入图片描述

🔹本章学习目标

  • 了解 JDBC 的概念以及几种常用驱动分类;
  • 可以使用JDBC 进行 MYSQL、Oracle 等数据库的开发;
  • 可以使用 DriverManagerConnectionPreparedStatementResultSet 对数据库进行增删改查操作;
  • 掌握事务的概念以及JDBC 对事务的支持;

1️⃣ JDBC概念

Java 数据库连接技术 (Java Database Connective, JDBC)是由 Java 提供的一组与平台无关的数据库的操作标准,其本身由一组类与接口组成,并且在操作中将按照严格的顺序执行。由于数据库属于资源操作,所以所有的数据库操作的最后必须要关闭数据库连接。

图1 JDBC 4种操作形式

在JDBC 技术范畴规定了以下4种Java数据库操作的形式。

  1. JDBC-ODBC 桥接技术
    Windows 中的开放数据库连接 (Open Database Connectivity, ODBC) 是由微软提供的数据库编程接口。JDBC-ODBC 桥接技术是先利用 ODBC 技术作为数据库的连接方式,再利用 JDBC 进行 ODBC 的连接,以实现数据库的操作。此类操作由于中间会使用 ODBC, 所以性能较差,但是此种方式不需要进行任何第三方开发包配置,所以使用较为方便。
  2. JDBC 本地驱动
    JDBC 本地驱动是由不同的数据库生产商根据 JDBC 定义的操作标准实现各自的驱动程序,程序可以直接通过 JDBC 进行数据库的连接操作。该操作性能较高,但是需要针对不同的数据库配置与之匹配的驱动程序。
  3. JDBC 网络驱动
    JDBC 网络驱动将利用特定的数据库连接协议进行数据库的网络连接,这样可以连接任何一个指定服务器的数据库,使用起来较为灵活,在实际开发中被广泛使用。
    这种形式通过网络协议(如TCP/IP)与远程数据库服务器进行通信。网络驱动程序使得Java应用程序能够通过网络传输数据并与远程数据库进行交互,无需直接部署数据库客户端。
  4. JDBC 协议驱动
    JDBC 协议驱动是利用 JDBC 提供的协议标准,将数据库的操作以特定的网络协议的方式进行处理。

2️⃣ 连接数据库

如果要进行数据库的连接操作,那么要使用 java.sql 包中提供的程序类,此包提供了以下核心类与接口。

  • java.sql.DriverManagers 类:提供数据库的驱动管理,主要负责数据库的连接对象取得;
  • java.sql.Connection 接口:用于描述数据库的连接,并且可以通过此接口关闭连接;
  • java.sql.Statement 接口:数据库的操作接口,通过连接对象打开;
  • java.sql.PreparedStatement 接口:数据库预处理操作接口,通过连接对象打开;
  • java.sql.ResultSet 接口:数据查询结果集描述,通过此接口取得查询结果。

在实际的操作中 JDBC 的操作步骤具体分为如下四步。
(1)向容器中加载数据库驱动程序;
所有的 JDBC 都是由各个不同的数据库生产商提供的数据库驱动程序,这些驱动程序都是以 *.jar 文件的方式给出的,所以如果要使用 JDBC 就要先为其配置 CLASSPATH, 再设置驱动程序的类名称 (包类)。
需要注意,如果是通过 IDE工具进行开发,则需要在 “Java Build Path” 中添加此 jar文件的路径。
(2)通过 DriverManager 类根据指定的数据库连接地址、用户名、密码取得数据库连接。 注意取得数据库连接的前提是数据库服务已经启动,而此时进行连接需要提供以下的3个信息:

  • 数据库的连接地址:jdbc:oracle:连接方式:@主机名称:端口名称:数据库的SID;
    例如要连接本机的 test 数据库: jdbc:oracle:thin:@localhost:1521:test;
  • 数据库的用户名: username;
  • 数据库的密码: password

要连接数据库必须依靠 DriverManager 类完成,在此类定义的方法: public static Connection getConnection(String url, String user, String password) throws SQLException;
在 JDBC 里面,每一个数据库连接都要求使用一个 Connection 接口对象进行封装,所以只要有一个新的 Connection 对象就表示要连接一次数据库。
(3)利用 StatementPreparedStatementResultSet 实现数据的 CRUD 操作。
利用 Connection 接口中的 createStatement() 方法可以创建 Statement 接口对象,利用 prepareStatement() 方法可以创建 PreparedStatement 接口对象,利用这两个接口对象可以与 SQL 语句结合实现数据库的数据操作。
(4)释放占用的资源。
ConnectionStatementPreparedStatementResultSet 4个接口都是 AutoCloseable 的子接口,在这4个接口中都提供了 close() 方法,在数据库操作完毕可以使用 close() 方法 (public void close() throws SQLException)关闭所有的数据库操作。
虽然4个JDBC操作的核心接口中都提供了close()方法,但是只要连接关闭,所有的操作就自然进行资源释放,也就是说在编写代码的最后,只需要调用 Connection 接口的 close()方法就可以释放全部资源。

下面用一个代码案例演示上述步骤过程。

//	范例 1: 连接数据库。
package com.xiaoshan.demo;
import java.sql.Connection;
import java.sql.DriverManager;public class TestDemo {private static final String DBDRIVER = "oracle.jdbc.driver.OracleDriver";private static final String DBURL = "jdbc:oracle:thin:@localhost:1521:test";private static final String USER = "xiaoshan";private static final String PASSWORD = "xiaoshan";public static void main(String[] args) throws Exception {//第一步:加载数据库驱动程序,此时不需要实例化,因为会由容器自己负责管理Class.forName(DBDRIVER);//第二步:根据连接协议、用户名、密码连接数据库Connection conn = DriverManager.getConnection(DBURL, USER, PASSWORD);System.out.println(conn);   // 输出数据库连接conn.close();	//第四步:关闭数据库}
}

程序执行结果:

oracle.jdbc.driver.T4CConnection@2d38eb89

程序按照给定的操作步骤实现了数据库的连接操作,连接时首先会利用反射机制进行驱动程序的加载,然后利用 DriverManager 类中的 getConnection() 方法就可以取得 Connection 接口对象,而在程序运行的最后一定要使用 close() 方法关闭连接,从而释放数据库资源。

通过范例1 程序的分析可以发现, DriverManager 类主要功能是取得数据库连接,这一操作实质上属于工厂设计模式,而 DriverManager 就属于工厂类,如下所示。

图2 通过DriverManager 类获取数据库连接

3️⃣ Statement 接口

当取得了数据库连接对象后,就意味着可以进行数据库操作了,而数据库中的数据操作可以使用 Statement 接口完成。
如果要取得 Statement 接口的实例化对象则需要依靠 Connection 接口提供的方法完成。

  • 取得 Statement 接口对象:public Statement createStatement() throws SQLException;

当取得了Statement 接口对象后可以使用以下两个方法实现数据库操作:

  • 数据更新 :public int executeUpdate(String sql) throws SQLException,返回更新行数;
  • 数据查询:public ResultSet executeQuery(String sql) throws SQLException

为了便于理解,下面编写一个数据库创建脚本,同时在此脚本中将包含各常用的数据类型: NUMBERVARCHAR2DATECLOB,而对于主键将采用 Oracle 序列的方式进行处理。

//	编写数据库创建脚本。
DROP TABLE member PURGE;
DROP SEQUENCE myseq;
CREATE SEQUENCE myseq;
CREATE TABLE member(mid NUMBER,name VARCHAR2(20),birthday DATE DEFAULT SYSDATE,age NUMBER(3),note CLOB,CONSTRAINT pk_mid PRIMARY KEY(mid)
);

这个脚本分别创建了一个序列和一个数据表对象,对于数据表中的 mid 字段内容,将在执行 INSERT 语句时利用"myseq.nextval" 伪列的值进行设置。

3.1 数据更新操作

数据更新操作主要分为增加、修改、删除3种,在 Statement 接口中这3 种操作都统一使用 executeUpdate()方法执行,并且执行后会返回更新的数据行数,如果没有数据更新则更新行数返回为 0。这样在实际开发中,就可以根据返回的更新行数来判断此更新操作是否成功。
增加数据SQL 语法如下。

//	范例 2: 数据增加: INSERT INTO 表名称(列,列.…) VALUES  (值,值.…)
package com.xiaoshan.demo;
import java.sql.Connection;
import java.sqL.DriverManager;
import java.sql.Statement;public class TestDemo  {private static final String DBDRIVER = "oracle.jdbc.driver.OracleDriver";private static final String DBURL = "jdbc:oracle:thin:@localhost:1521:test";private static final String USER = "xiaoshan";private static final String PASSWORD = "xiaoshan";public static void main(String[] args) throws Exception{//第一步:加载数据库驱动程序,此时不需要实例化,因为会由容器自己负责管理Class.forName(DBDRIVER);//第二步:根据连接协议、用户名、密码连接数据库Connection conn = DriverManager.getConnection(DBURL, USER, PASSWORD);//第三步:进行数据库的数据操作Statement stmt = conn.createStatement();//在编写SQL 的过程里面,如果太长需要增加换行, 一定要在前后加上空格String sql = "INSERT INTO member(mid,name,birthday,age,note) VALUES "+ "(myseq.nextval, '小山', TO_DATE(1997-09-15','yyyy-mm-dd), 27, '备注')";int len = stmt.executeUpdate(sql);	//执行SQL返回更新的数据行System.out.println("影响的数据行:"+len);//第四步:关闭数据库stmt.close();                 //本操作是可选的,在数据库连接已关闭时自动关闭conn.close();}
}

程序执行结果:

影响的数据行:1

程序首先利用 Connection 接口对象创建了 Statement 接口对象,然后利用 Statement 接口对象执行了 INSERT 语句,同时输出本次更新操作影响的数据行数,由于增加数据只更新了一条数据,所以更新行数为1。数据执行后可以进行数据表的查询,查询结果如下所示。

在这里插入图片描述

修改数据 SQL 执行操作案例如下。

//	范例 3: 数据修改:UPDATE 表名称 SET 字段=值,… WHERE 更新条件(s)
package com.xiaoshan.demo;
import java.sql.Connection;
import java.sql  DriverManager;
import java.sql.Statement;public class TestDemo  {private static final String DBDRIVER = "oracle.jdbc.driver.OracleDriver";private static final String DBURL = "idbc:oracle:thin:@localhost:1521:test";private static final String USER = "xiaoshan";private static final String PASSWORD = "xiaoshan";public  static void main(String[] args) throws Exception  {//第一步:加载数据库驱动程序,此时不需要实例化,因为会由容器自己负责管理Class.forName(DBDRIVER);//第二步:根据连接协议、用户名、密码连接数据库Connection conn = DriverManager.getConnection(DBURL, USER, PASSWORD);//第三步:进行数据库的数据操作Statement stmt = conn.createStatement();//在编写SQL 的过程里面,如果太长需要增加换行, 一定要在前后加上空格String sql = "UPDATE member SET name='小山山', birthday = SYSDATE, age=30"+ " WHERE mid IN (1)";int len = stmt.executeUpdate(sql);    // 执行SQL返回更新的数据行System.out.println("影响的数据行:"+len);//第四步:关闭数据库stmt.close();	//本操作是可选的,在数据库连接已关闭时自动关闭conn.close();}
}

程序执行结果:

影响的数据行:1

此程序首先将 INSERT 语句更换为 UPDATE 语句,然后继续使用 Statement 接口中的 executeUpdate() 语句执行该 SQL 语句,由于有1条数据满足此次更新要求,所以最后返回更新行数为1。更新完成后相应的数据内容如下图所示。
在这里插入图片描述

删除数据SQL 执行操作案例如下。

//	范例 4: 删除数据:DELETE FROM 表名称 WHERE 删除条件(s)
package com.xiaoshan.demo;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;public class TestDemo  {
private static final String DBDRIVER = "oracle.jdbc.driver.OracleDriver";
private static final String DBURL = "jdbc:oracle:thin:@localhost:1521:test";
private static final String USER = "xiaoshan";
private static final String PASSWORD = "xiaoshan";public static void main(String[] args) throws Exception{//第一步:加载数据库驱动程序,此时不需要实例化,因为会由容器自己负责管理Class.forName(DBDRIVER);//第二步:根据连接协议、用户名、密码连接数据库Connection conn = DriverManager.getConnection(DBURL, USER,PASSWORD);//第三步:进行数据库的数据操作Statement stmt = conn.createStatement();//在编写SQL 的过程里面,如果太长需要增加换行, 一定要在前后加上空格String sql = "DELETE FROM member WHERE mid IN(1)";int len = stmt.executeUpdate(sql);    // 执行SQL返回更新的数据行System.out.println("影响的数据行:"+len);//第四步:关闭数据库stmt.close();                         //本操作是可选的,在数据库连接已关闭时自动关闭conn.close();}
}

程序执行结果:

影响的数据行:1

此程序在删除数据时使用了 IN 限定符,这样将删除mid为1的记录,所以 executeUpdate() 方法返回的影响的数据行数为1。

3.2 数据查询

每当使用 SELECT 进行查询时会将所有的查询结果返回给用户显示,而显示的基本结构就是表的形式,可是如果要进行查询,这些查询的结果应该返回给程序,并由用户来进行处理,那么就必须有一种 类型可以接收所有的返回结果。在数据库里面虽然可能有几百张数据表,但是整个数据表的组成数据类型都是固定的,所以在 ResultSet 设计的过程中按照数据类型的方式来保存返回数据。 ResultSet 的工作流程如下图所示。

图3 ResultSet 的工作流程

java.sql.ResultSet 接口里面定义了以下两种方法。

  • 向下移动指针并判断是否有数据行: public boolean next() throws SQLException;
    移动后可以直接取得当前数据行中所有数据列的内容;
  • 取出数据列的内容: getInt()getDouble()getString()getDate()
//	范例 5: 实现数据的查询
package com.xiaoshan.demo;
import java.sql.Connection;
import java.sqL.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Date;public class TestDemo  {private static final String DBDRIVER = "oracle.jdbc.driver.OracleDriver";private static final String DBURL = "jdbc:oracle:thin:@localhost:1521:test";private static final String USER = "xiaoshan";private static final String PASSWORD = "xiaoshan";public static void main(String[] args) throws Exception{//第一步:加载数据库驱动程序,此时不需要实例化,因为会由容器自己负责管理Class.forName(DBDRIVER);//第二步:根据连接协议、用户名、密码连接数据库Connection conn = DriverManager.getConnection(DBURL, USER, PASSWORD);//第三步:进行数据库的数据操作Statement stmt = conn.createStatement();//在编写SQL 的过程里面,如果太长需要增加换行, 一定要在前后加上空格String sql = "SELECT mid,name,age,birthday,note FROM member";ResultSet rs = stmt.executeQuery(sql);  // 实现数据查询while (rs.next()){                 //循环取出返回的每一行数据int mid = rs.getInt("mid");                     // 取出mid字段内容String name = rs.getString("name");				// 取出name 字段内容int age = rs.getInt("age");                  	// 取出age字段内容Date birthday = rs.getDate("birthday");			// 取出birthday字段内容String note = rs.getString("note");    			// 取出note字段内容System.out.println(mid + ", " + name + ", " + age + ", " + birthday + ", " + note);}rs.close();	//第四步:关闭数据库stmt.close();conn.close();}
}

程序执行结果:

1, 小山山, 30, 2023-07-31, 备注

此程序由于要执行查询语句,所以直接使用了 Statement 接口中的 executeQuery() 方法,而后查询结果将以 ResultSet 对象形式返回。在 ResultSet 中首先利用迭代方式取出每一行数据,然后利用 getXxx() 形式的方法根据指定的列名称取得相应的数据。

利用范例5的方式已经可以取出查询结果中对应的数据,但是在进行查询时也会发现另外一个 问题:在程序中已经明确地在 SELECT 子句中出现了查询字段:“SELECT mid,name,age,birthday, note”, 但是在取得数据列内容时还重复设置了要取得的列名称(例如:“rs.getInt("mid")” 或 “rs.getString("'name")”), 这样的做法会有些重复。所以在 ResultSet 接口中,当利用 getXxx() 形式取出列数据时,可以根据 SELECT 子句出现列的顺序编号取出,例如:mid 列是第1个取出来的, name 列是第2个取出来的,依次类推。

//	范例 6: 修改 ResultSet 读取数据的方法(代码片段)
...ResultSet rs = stmt.executeQuery(sql);	//实现数据查询while (rs.next()){		//循环取出返回的每一行数据int mid = rs.getInt(1);String name = rs.getString(2);int age = rs.getInt(3);Date birthday = rs.getDate(4);String note = rs.getString(5);System.out.println(mid+", "+name+", "+age+", "+birthday+", "+note);}
...

此程序在取出数据时并没有使用列名称,而是根据查询列的顺序取出所要的数据,这样的做法较为方便。

4️⃣ PreparedStatement 接口

虽然 java.sql.Statement 接口可以实现数据库中数据的操作,但是其本身却存在一个致命的问题:如果传入数据要采用拼凑 SQL 的形式完成,这样会为程序带来严重的安全隐患。为了解决这样的问题,在 java.sql 包中定义了一个
Statement 的子接口—— PreparedStatement 接口。

4.1 Statement 接口问题

为了帮助大家更好地理解 Statement 数据的操作问题,下面将利用 Statement 接口实现数据的增加操作,但是此时增加的数据并不是直接定义在字符串中,而是利用变量进行设置(模拟数据输入)。

//	范例 7: 以数据增加操作为例观察 Statement 接口的问题
package com.xiaoshan.demo;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;public class TestDemo  {private static final String DBDRIVER = "oracle.jdbc.driver.OracleDriver";private static final String DBURL = "jdbc:oracle:thin:@localhost:1521:test"; private static final String USER = "xiaoshan";private static final String PASSWORD = "xiaoshan";public static void main(String[] args) throws Exception{String name = "Mr'SMITH";		//增加的name 数据String birthday = "1998-10-10";int age = 18;String note = "~备注~";Class.forName( DBDRIVER) ;	//加载驱动程序Connection conn =DriverManager.getConnection(DBURL, USER, PASSWORD);   // 连接数据库Statement stmt = conn.createStatement();              //创建Statement接口对象String sql= "INSERT INTO member(mid,name,birthday,age,note) VALUES"+" (myseq.nextval, "+ name +", TO_DATE("+ birthday+",'yyyy-mm-dd),"	+ age +","+ note +")";System.out.println(sql);	//采用拼凑SQI语句形式,代码混乱int len = stmt.executeUpdate(sql);	//执行SQL返回更新的数据行System.out.println("影响的数据行:"+len);conn.close();	//关闭数据库连接}
}

程序执行结果:

INSERT INTO member(mid,name,birthday,age,note)  VALUES
(myseq.nextval, 'Mr'SMITH', TO_DATE(1998-10-10','yyyy-mm-dd), 18, '~备注~')
Exception in thread"main"java.sqL.SQLSyntaxErrorException:ORA-00917: 缺失逗号

本程序执行完成后出现"SQLSyntaxErrorException", 此时表示 SQL 语句出现了问题,而造成此类问题的原因也很简单,就是 name 保存的数据中存在"'“, 而”'" 在数据库中用于定义字符串,所以属于标记错乱。也就是说 Statement 执行时都需要拼凑SQL 语句,所以对于一些敏感的字符操作并不方便。

4.2 PreparedStatement操作

Statement 执行的关键性的问题在于它需要一个完整的字符串来定义要使用的 SQL 语句,所以这就导致在使用中需要大量地进行 SQL 的拼凑。而 PreparedStatementStatement 不同的地方在于,它执行的是一个完整的具备特殊占位标记的 SQL 语句,并且可以动态地设置所需要的数据。

PreparedStatement 属于 Statement 的子接口,但是如果要取得该子接口的实例化对象,依然需要使用 Connection 接口所提供的方法: public PreparedStatement prepareStatement(String sql) throws SQLException

在此方法中需要传入一个 SQL 语句,这个SQL 是一个具备特殊标记的完整SQL, 但是此时没有内容,所有的内容都会以占位符 “?” 的形式出现,而当取得了PreparedStatement 接口对象后需要使用一系列 setXxx()方法为指定顺序编号(根据“?”从1开始排序) 的占位符设置具体内容,如图所示。

图4 PreparedStatement 增加数据

由于在实例化 PreparedStatement 接口对象时已经设置好了要执行的 SQL 语句,所以对于PreparedStatement 的数据更新或查询操作就可以通过如下两个方法完成。

  • 更新操作:public int executeUpdate() throws SQLException;
  • 查询操作:public ResultSet executeQuery() throws SQLException
//	范例 8: 改进数据增加
package com.xiaoshan.demo;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.util.Date;public class TestDemo  {private static final String DBDRIVER = "oracle.jdbc.driver.OracleDriver";private static final String DBURL = "idbc:oracle:thin:@localhost:1521:test";private static final String USER = "xiaoshan";private static final String PASSWORD = "xiaoshan";public static void main(String[] args) throws Exception {String name ="Mr'SMITH";	//增加的name 数据Date birthday = new Date();	//增加的birthday数据,使用java.util.Dateint age =18;String note = "~备注~";Class.forName( DBDRIVER);	//加载驱动程序Connection conn = DriverManager.getConnection(DBURL,USER, PASSWORD);	// 连接数据库String sql = "INSERT INTO member(mid,name,birthday,age,note) VALUES "	+"(myseq.nextval, ?, ?, ?, ?) ";        	//使用占位符设置预处理数据	PreparedStatement pstmt = conn.prepareStatement(sql);	pstmt.setString(1,name);       	//设置第1个占位符"?"	pstmt.setDate(2, new Date(birthday.getTime()));		//设置第2个占位符"?"pstmt.setInt(3, age);	//设置第3个占位符"?"pstmt.setString(4, note);	//设置第4个占位符"?"int len = pstmt.executeUpdate();	//执行SQL返回更新的数据行System.out.println("影响的数据行:"+len);conn.close();	//关闭数据库连接}
}

程序执行结果:

影响的数据行:1

此程序利用 PreparedStatement 接口实现了包含敏感字符的数据更新操作,在程序中首先使用占位符实例化要更新的数据库操作对象,然后利用 setXxx()方法根据索引顺序设置每一个占位符的数据,最后利用 executeUpdate() 使数据保存到数据库中,程序执行完毕数据库中的数据如图所示。

在这里插入图片描述

按照同样的方式大家也可以自行实现数据的修改与删除操作。但是从实际的开发来讲,数据的更新操作是较为简单的,而且操作步骤也较为固定,最麻烦的就属于数据的查询操作。考虑到实际开发中 JDBC 技术使用较为广泛,下面将为大家讲解4种具有代表性的查询操作。

//	范例 9: 查询全部数据
package com.xiaoshan.demo;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Date;public class TestDemo  {private static final String DBDRIVER="oracle.jdbc.driver.OracleDriver";private static final String DBURL ="jdbc:oracle:thin:@localhost:1521:test";private static final String USER ="xiaoshan";private static final String PASSWORD="xiaoshan";public static void main(String[] args) throws Exception{Class.forName(DBDRIVER);               //加载驱动程序Connection conn = DriverManager.getConnection(DBURL, USER, PASSWORD);	// 连接数据库String sql = "SELECT mid,name,birthday,age,note FROM member ORDER BY mid"; PreparedStatement pstmt = conn.prepareStatement(sql);ResultSet rs = pstmt.executeQuery(); 	//数据查询,不设置占位符while(rs.next()){int mid = rs.getInt(1);				//取出第1个数据列内容String name  = rs.getString(2); Date birthday  = rs.getDate(3); int age = rs.getInt(4);String note = rs.getString(5);		//取出第5个数据列内容System.out.println(mid+", "+name+", "+birthday+", "+age+", "+note);}conn.close();	//关闭数据库连接}
}

程序执行结果:

1, 小山山, 2023-07-31, 30, 备注
2, Mr'SMITH, 2023-07-31, 18, ~备注~

此程序利用 PreparedStatement 接口实现了数据查询操作,由于在定义 SQL 语句时并没有设置占位符的信息,所以也就不需要使用 setXxx() 设置数据,实例化 PreparedStatement 接口后直接调用 executeQuery() 方法将查询结果返回给 ResultSet 输出即可。

//	范例 10: 模糊查询
package com.xiaoshan.demo;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Date;public class TestDemo{private static final String DBDRIVER = "oracle.jdbc.driver.OracleDriver";private static final String DBURL = "jdbc:oracle;thin:@localhost:1521:test";private static final String USER = "xiaoshan";private static final String PASSWORD = "xiaoshan";public static void main(String[] args) throws Exception {String keyWord = "山";           	//模糊查询关键字private static final String DBDRIVER="oracle.jdbc.driver.OracleDriver";private static final String DBURL ="jdbc:oracle:thin:@localhost:1521:test";private static final String USER ="xiaoshan";private static final String PASSWORD="xiaoshan";public static void main(String[] args) throws Exception{Class.forName( DBDRIVER);               //加载驱动程序Connection conn = DriverManager.getConnection(DBURL,USER, PASSWORD);	//连接数据库 String sql ="SELECT mid,name,birthday,age,note FROM member"+" WHERE name LIKE ? ORDER BY mid"; 	//此时设置了限定查询与占位符PreparedStatement pstmt = conn.prepareStatement(sql); pstmt.setString(1,"%"+keyWord+"%");ResultSet rs = pstmt.executeQuery(); 	//数据查询,不设置占位符while (rs.next()){int mid = rs.getInt(1);String name = rs.getString(2); Date birthday = rs.getDate(3); int age = rs.getInt(4);String note = rs.getString(5);System.out.println(mid+", "+name+", "+birthday+", "+age+", "+note);}conn.close();	//关闭数据库连接}}
}

程序执行结果:

1, 小山山, 2023-07-31, 30, 备注

本程序在 WHERE 子句利用 LIKE 子句实现了数据的模糊查询,由于需要进行模糊匹配,所以设置数据时在关键字的左右加上了“%”。
在实际开发中,并不能直接查询数据表中的全部记录,所有的查询操作往往都需要结合分页语句一起使用,下面将利用 ROWNUM 伪列实现数据库的分页查询操作。

//	范例 11: 数据分页显示
package com.xiaoshan.demo;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sq1.PreparedStatement;
import java.sql.ResultSet;
import java.util.Date;public class TestDemo{private static final String DBDRIVER ="oracle.jdbc.driver.OracleDriver";private static final String DBURL="idbc:oracle:thin:@localhost:1521:test";private static final String USER="xiaoshan";private static final String PASSWORD="xiaoshan";public static void main(String[] args) throws Exception{String keyWord="";	//不设置关键字表示查询全部int currentPage=1;	//当前所在页int lineSize=2;		//每页显示行数Class.forName( DBDRIVER);	//加载驱动程序Connection conn=DriverManager.gelConnection(DBURL, USER, PASSWORD);  // 连接数据库String sql = "SELECT * FROM ("+ "SELECT mid,name,birthday,age,note,ROWNUM rn FROM member"+ "WHERE name LIKE ? AND ROWNUM<= ?) temp"+ "WHERE temp.rn > ? ORDER BY mid";PreparedStatement pstmt = conn.prepareStatement(sql);pstmt.setString(1,"%"+keyWord+"%");			//设置查询关键字pstmt.setInt(2, currentPage * lineSize);	//分页参数pstmt.setInt(3,(currentPage -1) * lineSize);	//分页参数ResultSet rs = pstmt.executeQuery();		//数据查询,不设置占位符while (rs.next()){int mid = rs.getInt(1);			//取出第1个数据列内容String name = rs.getString(2);	//取出第2个数据列内容Date birthday = rs.getDate(3);	//取出第3个数据列内容int age = rs.getInt(4);			//取出第4个数据列内容String note = rs.getString(5);	//取出第5个数据列内容System.out.println(mid+", "+name+", "+birthday+", "+age+", "+note);}conn.close();	//关闭数据库连接}
}

程序执行结果:

1, 小山山, 2023-07-31, 30, 备注
2, Mr'SMITH, 2023-07-31, 18, ~备注~

程序实现了基于 Oracle 数据库的数据分页显示,利用 ROWNUM 数据伪列实现了分页查询操作,并且结合了模糊查询(此处没有设置模糊查询关键字,属于查询全部) 操作。所以最终显示的结果是第1页开始的 1~2 条记录(每页显示2条)。

//	范例 12: 统计数据量,使用 COUNT()函数
package com.xiaoshan.demo;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;public class TestDemo {private static final String DBDRIVER="oracle.jdbc.driver.OracleDriver";private static final String  DBURL="jdbc:oracle:thin:@localhost:1521:test";private static final String USER="xiaoshan";private static final String PASSWORD="xiaoshan";public static void main(String[] args) throws Exception{String keyWord = "";       	//不设置关键字表示查询全部Class.forName( DBDRIVER) ;                     //加载驱动程序Connection conn = DriverManager.getConnection(DBURL,USER,PASSWORD);  // 连接数据库String sql="SELECT COUNT(mid) FROM member WHERE name LIKE ?";PreparedStatement pstmt = conn.prepareStatement(sql);pstmt.setString(1,"%"+keyWord+"%");   	//设置查询关键字ResultSet rs = pstmt.executeQuery(); 	//数据查询,不设置占位符if (rs.next()){int count = rs.getInt(1);	System.out.println("数据记录个数为:"+count);}conn.close();             	//关闭数据库连接}
}

程序执行结果:

数据记录个数为:2

此程序使用 COUNT() 函数并且结合模糊查询实现了数据表中数据记录的统计操作。需要提醒大家的是 ,COUNT() 函数在数据库统计操作中使用时即使表中没有记录,也会有一个统计的数据 0 作为结果,也就是说此时 ResultSet 接口中的 next()方法一定会返回 true

5️⃣ 批处理与事务处理

在之前使用的全部的数据库操作,严格来讲都属于 JDBC 1.0 中规定的操作模式,而在较新的4.0版本,由于实体层开发框架的普及(比如Mybatis),大部分开发人员并不会选择使用此版本的开发支持。而从 JDBC 2.0版本开始也增加了一些新的功能:可滚动的结果集,可以利用结果集执行增加、更新、删除、批处理操作。其中以批处理的操作最为实用。

所谓批处理指的是一次性向数据库中发出多条操作命令,而后所有的SQL语句将一起执行。在 Statement 接口与 PreparedStatement 接口中有关于批处理操作的定义如下。

Statement 接口里的方法:

  • 增加批处理: public void addBatch(String sql) throws SQLException;
  • 执行批处理: public int[] executeBatch() throws SQLException; 返回的数组是包含执行每条SQL 语句后所影响的数据行数;

PreparedStatement 接口里的方法:

  • 增加批处理:public void addBatch() throws SQLException
//	范例 13: 执行批处理(以Statement 接口操作为例)
package  com.xiaoshan.demo;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
import java.util.Arrays;public class TestDemo  {private static final String DBDRIVER= "oracle.jdbc.driver.OracleDriver";private static final String DBURL= "idbc:oracle:thin:@localhost:1521:test"private static final String USER= "xiaoshan";private static final String PASSWORD="xiaoshan";public static void main(String[] args) throws Exception {Class.forName( DBDRIVER);                   //加载驱动程序Connection conn = DriverManager.getConnection(DBURL,USER,PASSWORD);   // 连接数据库Statement stmt = conn.createStatement();      // 创建数据库操作对象stmt.addBatch("INSERT INTO member(mid,name) VALUES (myseq.nextval,'小山A')"); stmt.addBatch("INSERT INTO member(mid,name) VALUES (myseq.nextval,'小山B')"); stmt.addBatch("INSERT INTO member(mid,name) VALUES (myseq.nextval,'小山C')"); stmt.addBatch("INSERT INTO member(mid,name) VALUES (myseq.nextval,'小山D')"); stmt.addBatch("INSERT INTO member(mid,name) VALUES (myseq.nextval,'小山E')");int result[] = stmt.executeBatch(); 	//执行批处理System.out.println(Arrays.toString(result));conn.close();	//关闭数据库连接}
}

程序执行结果:

[1,1,1,1,1]

此程序实现了数据的批量增加操作,首先使用 addBatch() 方法添加每一条要执行的 SQL 语句,然后利用executeBatch() 方法一次性将所有的更新语句提交到服务器上,此时会返回一个数组,数组中的每一项内容都是该SQL 语句影响的数据行数。

范例13的代码实现了批处理的数据操作,但是在该程序中会存在一个问题:在正常情况下批处理描述的一定是一组关联的 SQL 操作,而如果执行多条更新语句中有一条语句出现了错误,那么理论上所有的语句都不应该被更新。不过默认情况下在错误语句之前的 SQL 更新都会正常执行,而出错之后的信息并不会执行。为了实现对批量处理操作的支持,可以使用事务来进行控制。

JDBC 提供事务处理操作来进行手工的事务控制,所有的操作方法都在 Connection 接口里定义。

  • 事务提交:public void commit() throws SQLException;
  • 事务回滚:public void rollback() throws SQLException;
  • 设置是否为自动提交:public void setAutoCommit(boolean autoCommit) throws SQLException
//	范例 14: 利用事务处理
package com.xiaoshan.demo;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
import java.util.Arrays;public class TestDemo  {private static final String DBDRIVER="oracle.jdbc.driver.OracleDriver";private static final String DBURL= "idbc:oracle:thin:@localhost:1521:test";private static final String USER= "xiaoshan";private static final String PASSWORD="xiaoshan";public static void main(String[] args) throws Exception  {Class.forName( DBDRIVER);                   //加载驱动程序Connection conn = DriverManager.getConnection(DBURL,USER,PASSWORD);	//连接数据库Statement stmt = conn.createStatement();        //创建数据库操作对象conn.setAutoCommit(false);              		//取消自动提交try {stmt.addBatch("INSERT INTO member(mid,name) VALUES (myseq.nextval,'小山A')");stmt.addBatch("INSERT INTO member(mid,name) VALUES (myseq.nextval,'小山B')");//此时以下语句出现了错误,由于使用了事务控制,这样所有批处理的更新语句将都不会执行 stmt.addBatch("INSERT INTO member(mid,name) VALUES (myseq.nextval,'小山'C')");stmt.addBatch("INSERT INTO member(mid,name) VALUES (myseq.nextval,'小山D')");stmt.addBatch("INSERT INTO member(mid,name) VALUES (myseq.nextval,'小山E')");int result[] = stmt.executeBatch();    // 执行批处理System.out.println(Arrays.toString(result));conn.commit();		//如果没有错误,进行提交}catch (Exception e){e.printStackTrace();conn.rollback();	//如果出现异常,则进行回滚}conn.close();}
}

程序执行结果:

java.sql.BatchUpdateException:批处理中出现错误: ORA-00917:  缺失逗号

程序使用批处理进行了数据更新操作,但是很明显第3条 SQL 语句出现了错误,所以整体更新操作都将不会提交,这样就保证了数据的完整性。

🌾 总结

通过本文的介绍,我们了解了JDBC的基本概念和使用方法。我们学习了如何连接数据库,并使用Statement接口进行数据更新和查询操作。同时,我们也了解到了Statement接口的一些问题,并介绍了PreparedStatement接口的作用和优势。最后,我们还讨论了批处理和事务处理的重要性和使用方法。

总之,JDBC是Java连接数据库的标准接口,它提供了方便、灵活和高效的方式来操作数据库。通过掌握JDBC的相关知识,我们可以更好地与数据库进行交互,并实现数据的更新、查询等操作。同时,使用PreparedStatement接口可以提高程序的性能和安全性。批处理和事务处理则是在处理大量数据和保证数据的一致性方面非常有用。


温习回顾上一篇(点击跳转)
《【Java基础教程】(四十九)集合体系篇 · 下:双列集合解析——HashMap、Hashtable、LinkedHashMap、TreeMap、Properties ~》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/75174.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

高性能网络框架笔记

目录 TCP粘包、分包惊群断开连接&#xff0c;TCP怎么检测的&#xff1f;大量的close wait&#xff0c;如何解 ?双方同时调用close水平触发和边沿触发的区别 TCP粘包、分包 解决&#xff1a;1.应用层协议头前面pktlen&#xff1b;2.为每一个包加上分隔符&#xff1b;(\r\n&…

Java 版 spring cloud + spring boot 工程系统管理 工程项目管理系统源码 工程项目各模块及其功能点清单

工程项目各模块及其功能点清单 一、系统管理 1、数据字典&#xff1a;实现对数据字典标签的增删改查操作 2、编码管理&#xff1a;实现对系统编码的增删改查操作 3、用户管理&#xff1a;管理和查看用户角色 4、菜单管理&#xff1a;实现对系统菜单的增删改查操…

经典CNN(三):DenseNet算法实战与解析

&#x1f368; 本文为&#x1f517;365天深度学习训练营中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊|接辅导、项目定制 1 前言 在计算机视觉领域&#xff0c;卷积神经网络&#xff08;CNN&#xff09;已经成为最主流的方法&#xff0c;比如GoogleNet&#xff0c;…

【多模态】ALBEF-融合前对齐

目录 &#x1f341;&#x1f341;背景 &#x1f337;&#x1f337;网络结构 &#x1f385;&#x1f385;损失函数 &#x1f33c;&#x1f33c;动量蒸馏 &#x1f33a;&#x1f33a;下游任务结果 &#x1f4d2;&#x1f4d2;Grad-CAM 特征可视化 &#x1f6a6;&#x1f6a…

欧拉函数与筛法求欧拉函数

目录 欧拉函数欧拉函数的定义欧拉函数的公式欧拉函数的公式推导欧拉定理典型例题代码实现 筛法求欧拉函数思路分析经典例题代码实现 欧拉函数 欧拉函数的定义 对于任意正整数 n n n,欧拉函数 φ ( n ) φ(n) φ(n) 表示小于或等于 n n n 的正整数中&#xff0c;与 n n n …

【视觉SLAM入门】5.1. 特征提取和匹配--FAST,ORB(关键点描述子),2D-2D对极几何,本质矩阵,单应矩阵,三角测量,三角化矛盾

"不言而善应" 0. 基础知识1. 特征提取和匹配1.1 FAST关键点1.2 ORB的关键点--改进FAST1.3 ORB的描述子--BRIEF1.4 总结 2. 对极几何&#xff0c;对极约束2.1 本质矩阵(对极约束)2.1.1 求解本质矩阵2.1.2 恢复相机运动 R &#xff0c; t R&#xff0c;t R&#xff0c;…

修改状态栏The application could not be installed: INSTALL_FAILED_ABORTEDList

打开theme修改状态栏为可见。 <resources xmlns:tools"http://schemas.android.com/tools"><!-- Base application theme. --><style name"Base.Theme.MyApplication" parent"Theme.AppCompat.DayNight"><!-- Customize yo…

[JavaScript游戏开发] 绘制冰宫宝藏地图、人物鼠标点击移动、障碍检测

系列文章目录 第一章 2D二维地图绘制、人物移动、障碍检测 第二章 跟随人物二维动态地图绘制、自动寻径、小地图显示(人物红点显示) 第三章 绘制冰宫宝藏地图、人物鼠标点击移动、障碍检测 第四章 绘制Q版地图、键盘上下左右地图场景切换 文章目录 系列文章目录前言一、本章节…

呼吸灯——FPGA

文章目录 前言一、呼吸灯是什么&#xff1f;1、介绍2、占空比调节示意图 二、系统设计1、系统框图2、RTL视图 三、源码四、效果五、总结六、参考资料 前言 环境&#xff1a; 1、Quartus18.0 2、vscode 3、板子型号&#xff1a;EP4CE6F17C8 要求&#xff1a; 将四个LED灯实现循环…

电缆故障综合测试仪

一、电缆故障查找仪产品简介 本产品用于地埋电缆故障点的快速、企业产品免费信息发布平台定位、电缆埋设路径及埋设深度的电子商务测&#xff08;在故障点处获取深度&#xff09;。 主要特点 1、用特殊结构的声波振动传感器及低噪声专用器件作前置放大&#xff0c;提高了仪器定…

VLT:Vision-Language Transformer用于引用的视觉语言转换和查询生成分割

摘要 在这项工作中&#xff0c;我们解决了引用分割的挑战性任务。引用分割中的查询表达式通常通过描述目标对象与其他对象的关系来表示目标对象。因此&#xff0c;为了在图像中的所有实例中找到目标实例&#xff0c;模型必须对整个图像有一个整体的理解。为了实现这一点&#…

鸿蒙4.0发布会说了啥?关注个性与效率,小艺智能程度令人惊艳

鸿蒙4.0系统的发布会已经结束&#xff0c;整个发布会看下来&#xff0c;给我最深刻的印象就是——鸿蒙4.0是一个让手机更接近个人终端的系统。但选择系统难免掺杂个人喜好和偏见&#xff0c;因此本文我只会从鸿蒙4.0那些让我感到惊喜的功能入手介绍&#xff0c;不对系统进行评价…

【Golang 接口自动化01】使用标准库net/http发送Get请求

目录 发送Get请求 响应信息 拓展 资料获取方法 发送Get请求 使用Golang发送get请求很容易&#xff0c;我们还是使用http://httpbin.org作为服务端来进行演示。 package mainimport ("bytes""fmt""log""net/http""net/url&qu…

Vue 自定义事件绑定与解绑

绑定自定义事件 说到 Vue 自定义事件&#xff0c;那就需要搞清楚一个问题&#xff0c;为啥有这个玩意。 说到自定义事件之前&#xff0c;需要理解 组件基础的概念。理解了基础概念之后&#xff0c;我们就知道 Vue 的父子之间的通信&#xff0c; 一是 父组件通过 Prop 向子组件…

8.3day04git+数据结构

文章目录 git版本控制学习高性能的单机管理主机的心跳服务算法题 git版本控制学习 一个免费开源&#xff0c;分布式的代码版本控制系统&#xff0c;帮助开发团队维护代码 作用&#xff1a;记录代码内容&#xff0c;切换代码版本&#xff0c;多人开发时高效合并代码内容 安装g…

抽象工厂模式(Abstract Factory)

抽象工厂模式提供一个创建一组相关或相互依赖的对象的接口&#xff0c;而无须指定它们具体的类&#xff0c;每个子类可以生产一系列相关的产品。 The Abstract Factory Pattern is to provide an interface for creating families of related or dependent objects without s…

谷歌: 安卓补丁漏洞让 N-days 与 0-days 同样危险

近日&#xff0c;谷歌发布了年度零日漏洞报告&#xff0c;展示了 2022 年的野外漏洞统计数据&#xff0c;并强调了 Android 平台中长期存在的问题&#xff0c;该问题在很长一段时间内提高了已披露漏洞的价值和使用。 更具体地说&#xff0c;谷歌的报告强调了安卓系统中的 &quo…

Matlab对TMS320F28335编程--SVPWM配置互补PWM输出

前言 F28335中断 目的&#xff1a;FOC的核心算法及SVPWM输出&#xff0c;SVPWM的载波频率10kHz&#xff0c;SVPWM的每个周期都会触发ADC中断采集相电流&#xff0c;SVPWM为芯片ePWM4、5、6通道&#xff0c;配置死区 1、配置中断SVPWM进ADC中断&#xff0c;查上表知CPU1,PIE1 …

回归分析书籍推荐

回归分析在线免费书籍&#xff1a;I 1-ntroduction to Regression Methods for Public Health using R Introduction to Regression Methods for Public Health Using R 2-An Introduction to Statistical Learning https://hastie.su.domains/ISLR2/ISLRv2_website.pdf 可以…

【Jmeter】压测mysql数据库中间件mycat

目录 背景 环境准备 1、下载Jmeter 2、下载mysql数据库的驱动包 3、要进行测试的数据库 Jmeter配置 1、启动Jmeter图形界面 2、加载mysql驱动包 3、新建一个线程组&#xff0c;然后如下图所示添加 JDBC Connection Configuration 4、配置JDBC Connection Configurati…