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

Java基础教程之JDBC

  • 🔹本章学习目标
  • [1️⃣ JDBC概念](#1️⃣ JDBC概念)
  • [2️⃣ 连接数据库](#2️⃣ 连接数据库)
  • [3️⃣ Statement 接口](#3️⃣ Statement 接口)
      • [3.1 数据更新操作](#3.1 数据更新操作)
      • [3.2 数据查询](#3.2 数据查询)
  • [4️⃣ PreparedStatement 接口](#4️⃣ PreparedStatement 接口)
      • [4.1 Statement 接口问题](#4.1 Statement 接口问题)
      • [4.2 PreparedStatement操作](#4.2 PreparedStatement操作)
  • [5️⃣ 批处理与事务处理](#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()方法就可以释放全部资源。

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

java 复制代码
//	范例 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 序列的方式进行处理。

sql 复制代码
//	编写数据库创建脚本。
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 语法如下。

java 复制代码
//	范例 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 执行操作案例如下。

java 复制代码
//	范例 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 执行操作案例如下。

java 复制代码
//	范例 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()
java 复制代码
//	范例 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个取出来的,依次类推。

java 复制代码
//	范例 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 接口实现数据的增加操作,但是此时增加的数据并不是直接定义在字符串中,而是利用变量进行设置(模拟数据输入)。

java 复制代码
//	范例 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
java 复制代码
//	范例 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.Date
		int 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种具有代表性的查询操作。

java 复制代码
//	范例 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 输出即可。

java 复制代码
//	范例 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 伪列实现数据库的分页查询操作。

java 复制代码
//	范例 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条)。

java 复制代码
//	范例 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
java 复制代码
//	范例 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
java 复制代码
//	范例 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 ~》

相关推荐
接着奏乐接着舞。14 分钟前
如何在 Three.js 地球飞线中间生成卡片
开发语言·javascript·ecmascript
阿泽不想掉光头发31 分钟前
C#实现调用DLL 套壳读卡程序(桌面程序开发)
java·开发语言·后端·websocket·http·c#
bglmmz37 分钟前
JPA查询部分字段的最佳实践
java·开发语言
一写代码就开心40 分钟前
DBeaver 咋手动配置sqlite 驱动
java
Marzlam1 小时前
C# IDisposable接口 与析构函数
开发语言·c#
小小药1 小时前
011-spring-整合三方框架的命名空间原理(重要)
java·数据库·spring
forestsea1 小时前
【深度学习】Java DL4J基于 CNN 构建车辆识别与跟踪模型
java·人工智能·深度学习·cnn·deep learning
峰子20121 小时前
Go语言实现守护进程的挑战
开发语言·后端·面试·架构·golang·go
m0_748237151 小时前
PHP实现登录和注册(附源码)
开发语言·php
锦轩韶华1 小时前
【模电刷题复习--选择】
经验分享