【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.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种具有代表性的查询操作。

//	范例 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 ~》

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Grandle安装配置(8.2.1)-windows环境

一、官网地址 https://gradle.org/releases/ 下载链接&#xff1a; https://downloads.gradle.org/distributions/gradle-8.2.1-bin.zip 下载后解压到指定文件夹,实例安装目录为&#xff1a; D:\ProgramFiles\gradle-8.2.1 二、配置环境变量 示例中配置的目录为&#xff1a…

服务器出入口IP通俗理解

一、出口IP 这是机房的网络设备&#xff08;如防火墙&#xff09;给内网主机生成的一个外网IP&#xff0c;用来保证内网主机能和其他公网主机进行来回通信&#xff0c;通常作为其他接入方的IP白名单使用&#xff0c;一般有几台内网主机就有几个出口IP&#xff0c;出口IP通常和入…

基于SSM家电补贴一站式服务平台-计算机毕设 附源码12305

ssm家电补贴一站式服务平台 摘 要 近年来&#xff0c;随着移动互联网的快速发展&#xff0c;电子商务越来越受到网民们的欢迎&#xff0c;电子商务对国家经济的发展也起着越来越重要的作用。简单的流程、便捷可靠的支付方式、快捷畅通的物流快递、安全的信息保护都使得电子商务…

无涯教程-Lua - 条件判断

if结构要求程序员确定一个或多个要由程序判断或测试的条件&#xff0c;以及要确定的条件为真的情况下要执行的一条或多条语句&#xff0c;如果条件为真&#xff0c;则执行指定语句&#xff0c;如果条件为假&#xff0c;则执行其他语句。 Lua编程语言假定布尔值 true 和 non-nil…

【硬件设计】模拟电子基础一--元器件介绍

模拟电子基础一--元器件介绍 一、半导体&#xff08;了解&#xff09;1.1 基础知识1.2 PN结 二、二级管2.1 定义与特性2.2 二极管的分类 三、三级管四、MOS管三、其他元器件管3.1 电容3.2 光耦3.3 发声器件3.4 继电器3.5 瞬态电压抑制器 前言&#xff1a;本章为知识的简单复习&…

蓝桥云课ROS机器人旧版实验报告-01入门

项目名称 实验一 ROS[Kinetic/Melodic/Noetic]入门 成绩 设计要求&#xff1a; 机器人操作系统安装、虚拟机、Docker、嵌入式系统 实验记录&#xff08;70分&#xff09; 1.以 $ 开头的行是终端命令。 - 要打开一个新终端 → 使用快捷键 ctrlaltt。 - 要在现有终端内…

C语言结构体讲解

目录 结构体的声明 结构的基础知识 结构的声明 为什么要出现结构体&#xff1f; 结构成员的类型 结构体变量的定义和初始化 定义&#xff1a;&#xff08;全局变量//局部变量&#xff09; 初始化&#xff1a; 结构体成员的访问 结构体传参 结构体的声明 结构的基础知识…

你真的会自动化吗?Web自动化测试-PO模式实战,一文通透...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 PO模式 Page Obj…

C# Socket实际应用案例与属性详解

引言 Socket是一个在网络编程中非常常见和重要的概念&#xff0c;它提供了一种通信机制&#xff0c;使不同的计算机之间可以进行数据传输。本文将介绍C#中Socket的实际应用案例&#xff0c;并对Socket的常用属性进行详细解析。 文章目录 1. Socket的实际应用案例2. Socket的属…

【Lua学习笔记】Lua进阶——协程

文章目录 协程协程的定义和调度wrap StatusRunning补充 协程与主程的数据交互——return...yield 协程 协程是一种并发操作&#xff0c;相比于线程&#xff0c;线程在执行时往往是并行的&#xff0c;并且线程在创建销毁执行时极其消耗资源&#xff0c;并且过长的执行时间会造成…

统一观测|借助 Prometheus 监控 ClickHouse 数据库

引言 ClickHouse 作为用于联机分析(OLAP)的列式数据库管理系统(DBMS), 最核心的特点是极致压缩率和极速查询性能。同时&#xff0c;ClickHouse 支持 SQL 查询&#xff0c;在基于大宽表的聚合分析查询场景下展现出优异的性能。因此&#xff0c;获得了广泛的应用。本文旨在分享阿…

CentOS7.3 安装 docker

亲测、截图 阿里云服务器 文章目录 更新源2345 启动开机自启 更新源 sudo yum update -y2 sudo yum install -y yum-utils device-mapper-persistent-data lvm23 sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo4 sudo yum …

计数排序算法

计数排序 计数排序说明&#xff1a; 计数排序&#xff08;Counting Sort&#xff09;是一种非比较性的排序算法&#xff0c;它通过统计元素出现的次数&#xff0c;然后根据元素出现的次数将元素排列在正确的位置上&#xff0c;从而实现排序。计数排序适用于非负整数或者具有确…

算法综合篇专题一:双指针问题

"就算没有看清那株灿烂的花蕊&#xff0c;也应该放声歌颂赞美鲜红的玫瑰" 1、移动零 (1) 题目解析 (2) 算法原理 class Solution { public:void moveZeroes(vector<int>& nums) {for(int cur0,dest-1;cur<nums.size();cur){if(nums[cu…

保姆级秋招教程之简历篇

大家好&#xff0c;我是千寻哥&#xff0c;个人简历在程序员求职过程中扮演着至关重要的角色。 今天我将详细给大家介绍一下写简历的必备要素和布局&#xff0c;同时强调应避免的“坑”&#xff01; 希望能通过这些技巧&#xff0c;能帮助程序员打造一份出色的简历&#xff0c;…

WEB:mfw

背景知识 Git泄露 Githack使用 命令执行漏洞 题目 这里页面里有Git&#xff0c;猜测是Git泄露 先用dirsearch扫一下 确实存在.git目录&#xff0c;可以尝试访问一下 使用Githack来下载并恢复.git文件 这里记得使用的时候关闭杀毒软件 结果会自动保存 点进去先看一下flag这个…

【前端知识】React 基础巩固(四十二)——React Hooks的介绍

React 基础巩固(四十二)——React Hooks的介绍 一、为什么需要Hook? Hook 是 React 16.8 的新增特性&#xff0c;它可以让我们在不编写class的情况下使用state以及其他的React特性&#xff08;比如生命周期&#xff09;。 class组件 VS 函数式组件&#xff1a; class的优势…

【机器学习】Feature scaling and Learning Rate (Multi-variable)

Feature scaling and Learning Rate 1、数据集2、学习率2.1 α \alpha α 9.9e-72.2 α \alpha α 9e-72.3 α \alpha α 1e-7 3、特征缩放3.1 特征缩放的原因3.2 Z-score 归一化3.3 预测3.4 损失等值线 导入所需的库 import numpy as np np.set_printoptions(precision…

为Win12做准备?微软Win11 23H2将集成AI助手:GPT4免费用

微软日前确认今年4季度推出Win11 23H2&#xff0c;这是Win11第二个年度更新。 Win11 23H2具体有哪些功能升级&#xff0c;现在还不好说&#xff0c;但它会集成微软的Copilot&#xff0c;它很容易让人想到多年前的“曲别针”助手&#xff0c;但这次是AI技术加持的&#xff0c;Co…

在k8s集群内搭建Prometheus监控平台

基本架构 Prometheus由SoundCloud发布&#xff0c;是一套由go语言开发的开源的监控&报警&时间序列数据库的组合。 Prometheus的基本原理是通过HTTP协议周期性抓取被监控组件的状态&#xff0c;任意组件只要提供对应的HTTP接口就可以接入监控。不需要任何SDK或者其他的…