从上一篇 Java高手速成│编写你第一个数据库程序 的例子中可以看出,Java和数据库的连接和对话离不开JDK库类,如java.sql包中支持数据库编程的各种API类、数据库软件DBMS、JDBC驱动软件或Java Connector以及你编写的数据库编程代码。
并且,在访问数据库的程序中,除调用Class类的forName()来装载JDBC驱动软件外,所有数据库操作的API类由库包java.sql提供,各种操作包括:数据库连接、SQL指令的传送、选择记录的回传、提取和相关操作,以及数据表信息metadada的获取。如下内容通过实例详细讨论这些具体的数据库编程技术。
01、连接数据库——高手都会这样做
首先调用Class类的静态方法forName()装载指定的JDBC驱动软件,再调用java.sql包提供的DriverManager的静态方法getConnection(),对指定的数据库进行连接操作。其一般代码格式如下:
-
try {
-
Class.forName(driverName);
-
Connection
con
= DriverManager.getConnection(dbUrl, username, password);
-
...
-
}
-
catch (ClassNotFoundException e) {
-
System.err.println(e);
-
}
-
catch (SQLException ex) {
-
System.err.println(e);
-
}
其中:
driverName——字符串参数。由数据库指定的JDBC驱动软件名。如MySQL的驱动软件名为:
drivername = "com.mysql.cj.jdbc.Driver";
注意,不同的数据库使用各自规定的驱动软件名。使用时必须参考该数据库对驱动软件的命名。
dbUrl——字符串参数。指定的数据库连接方式和地址,可以包括已存在的数据库名。如以本机方式连接MySQL服务器的dbUrl为:
-
dbUrl =
"jdbc:mysql://localhost:3306";
//连接到本机数据库服务器
-
/
/或:"jdbc:mysql:/
/localhost:3306/ProductDB
";//连接到本机服务器中的数据库
以远程方式通过网络连接MySQL服务器的dbUrl为:
dbUrl = "jdbc:mysql://hostServerIP:3306/ProductDB";
其中hostServerIP为MySQL服务器的IP地址或服务器名;3306为MySQL预设的网络端口;ProductDB为已创建的数据库名。
Username——字符串参数。指定的用户名。如"root"。
Password——字符串参数。指定的密码。如果没有密码,则为""。
forName()方法将抛出检查性异常ClassNotFoundException,getConection()将抛出检查性异常SQLException,代码中必须提供处理这两个异常的机制。
同样要注意不同的数据库使用各自规定的dbUrl和路径进行远程数据库连接。使用时必须参考该数据库对通过网络对数据库连接的规定。
02、向数据库发送SQL指令
发送SQL指令的一般代码格式为:
-
try {
-
Connection
con
= DriverManager.getConnection(dbUrl, username, password);
-
Statement
stmt
= con.createStatement();
//返回Statement对象
-
stmt.executeUpdate(sqlString);
//调用发送SQL指令方法;
-
...
//更多调用发送SQL指令的方法
-
stmt.close();
//关闭发送
-
}
-
catch (ClassNotFoundException e) {
-
System.err.println(e);
-
}
-
catch (SQLException ex) {
-
System.err.println(e);
-
}
其中createStatement()将返回一个Statement的对象,然后调用其executeUpdate()方法,将指定的SQL指令发送到数据库,并加以执行。
sqlString为字符串,必须是合法SQL指令,否则将产生检查性异常SQLException。sqlString可以是除SELECT之外的任何SQL指令。
最后调用close()方法,关闭发送SQL指令的操作。
如下是常用发送SQL指令的例子。
-
-
...
-
String createTable =
"CREATE TABLE Books (ISBN CHAR(13),Title VARCHAR
-
(50),Price DECIMAL("+
"6, 2),Inventory INT, Publisher VARCHAR(30))";
-
String insertRecord1 =
"INSERT INTO Books (ISBN, Title, Price, Inventory, Publisher)VALUES ("+
"'9781890774555', 'Java Lover', 66, 10, 'ABC Press') ";
-
String updateRecord1 =
"UPDATE Books SET Price = 69.15 WHERE Price= 66";
-
//String deleteRecord1 = " DELETE FROM Books WHERE ISBN = '9781890774555'";
-
stmt.
executeUpdate(createTable);
//调用发送SQL指令方法;
-
stmt.
executeUpdate(insertRecord1);
-
stmt.
executeUpdate(updateRecord1);
-
//stmt.executeUpdate(deleteRecord1); //可在理解这个编程实例后再执行删除指令
-
...
//其他操作指令
-
Stmt.
close();
//关闭
-
...
//异常处理
这个例子向数据库发送了4个常用指令:
-
CREATE
-
INSERT
-
UPDATE
-
DELETE
建议你在理解了从java程序怎样发送指令到数据库后,再执行删除指令。另外建议你打开数据库服务器:
MySQL 8.0 Command Line Client
直接发送SQL指令,如use(调用数据库)、desc(描述数据表)、select (显示数据表)等帮助你查询和了解在执行了Java语句后数据库内容的变化。
03、接收从数据库传回的记录
调用Statement的方法executeQuery()将返回一个ResultSet对象。executeQuery()将执行指定的获取数据表数据的SQL指令,如SELECT,并将执行结果封装在这个ResultSet对象中。ResultSet提供了一系列方法和静态字段,用来提取回传的结果。
以Products数据表为例,得到回传结果的一般代码格式为:
-
try {
-
Connection
con
= DriverManager.getConnection(dbUrl, username, password);
-
Statement
stmt
= con.createStatement();
//返回Statement对象
-
String
sqlString
=
"SELECT * FROM Products";
//定义SQL指令
-
ResultSet
rs
= stmt.executeQuery(sqlString);
//执行SQL指令并得到回传结果
-
while(rs.next()) {
//如果还有记录则继续循环
-
String
code
= rs.getString();
//得到当前记录中的第一个 字段的值
-
String
Title
= rs.getString();
//得到当前记录中的第二个字段的值
-
Double
price
= rs.getDouble();
//得到当前记录中的第三个字段的值
-
...
//执行利用得到数据的各种操作
-
}
-
rs.close();
//关闭
-
catch (ClassNotFoundException e) {
-
System.err.println(e);
-
}
-
catch (SQLException ex) {
-
System.err.println(e);
-
}
可以看到,利用executeUpdate()方法向数据库发送SQL指令;而利用executeQuery()得到数据库记录的回传结果。值得一提的是,ResultSet本身是一个接口,在执行executeQuery()时,产生一个完善了ResultSet的对象,并作为引用返回这个对象。
在提取封装在ResultSet中的记录时,涉及三类操作:
设置提取方式以及对ResultSet中记录指示器(也称光标)的操作,如移动或证实当前记录器位置。
得到记录中的数据或者删除、更新记录的操作。
得到有关数据表信息metadata,既元数据的操作。
java.sql包中的ResultSet提供静态字段来设置对记录的提取方式。表2列出了ResultSet常用字段和移动/证实记录指示器的常用方法。
表2 ResultSet的常用字段和移动/证实记录指示器常用方法
注意 ResultSet预设的纪录指示器位置为0。首次调用next()时,记录指示器位置为第一个记录的开始。如果记录指示器指向一个不存在的记录时,ResultSet对象则为null。
例子之一:利用ResultSet的静态字段设置按记录中的相反次序提取数据,并允许变更。提取方式的设定通过调用Statement重载的方法createStatement()实现,如:
Statement stmt = con.createStatement(ResultSet.FETCH_REVERSE, CONCUR_UPDATABLE);
例子之二:调用其他移动记录指示器的方法。
-
...
-
rs
.first();
//指示器移到第一个记录的开始
-
rs
.last();
//指示器移到最后一个记录的开始
-
rs
.absolute(
10);
//指示器移到第10个记录的开始
-
rs
.absolute(
1);
//等同于rs.first()
-
rs
.absolute(-
1);
//等同于rs.last()
-
rs
.absolute(-
2);
//倒数第二个记录的开始
-
rs
.relative(-
3);
//指示器从当前位置返回3个记录
-
rs
.relative(
5);
//指示器从当前位置往下移动5个记录。如果移至的位置无记录,ResultSet为null
-
if (isFirst())
-
rs
.next();
//记录指示器在第二个记录的开始
-
...
04、提取和更新传回的记录
表3列出了ResultSet用来提取记录数据以及变更和删除记录的常用方法。
表3 esultSet提取记录数据和更新记录的常用方法
值得一提的是,数据库操作中所有记录位置指示器,包括字段位置(列),都从1算起。
例子之一:提取ResultSet中的结果。假设执行了如下SQL指令:
ResultSet rs = stmt.executeQuery("SELECT * FROM Books");
数据表Books的字段定义见作者的新书22.5.2小节中的详细讨论。
例子之二:调用next()方法并利用循环提取ResultSet中的所有记录数据。
-
//完整程序见本文压缩附件中名为ResultSetTest.java源代码
-
...
-
while (rs.
next()) {
//如果还有记录,则继续循环
-
//执行封装在rs中的各记录数据的操作
-
System
.out
.println(
"ISNB: " + rs.
getString(
1));
//或rs.getString("ISNB"));
-
System
.out
.println(
"Book Title: " + rs.
getString(
2));
//或rs.getString("Title"));
-
System
.out
.println(
"Price: " + rs.
getDouble(
3));
//或rs.getString("Price"));
-
System
.out
.println(
"Inventory: " + rs.
getInt(
4));
//或rs.getString("Inventory"));
-
System
.out
.println(
"Publisher: " + rs.
getString(
5));
//或rs.getString("Publisher"));
-
}
-
...
例子之三:更新当前记录的内容。
rs.updateString(1, "1109123466666"); //修改当前记录指定字段(ISBN)的字符 //串值为新值
rs.updateDouble("Price", 125.89); //修改当前记录指定字段(Price)的值为 125.89
以上对当前记录的修改也可利用SQL指令UPDATE完成,如:
stmt.executeUpdate("UPDATE Books SET Code = '1109123466666', Price = 125.89"+ "WHERE Code = '9781890774555'");
这种操作指涉及数据库,而不影响当前在ResultSet中的该记录。
例子之四:继续上例,删除指定的记录。
rs.delelteRow(); //删除在ResultSet中以及数据库Books表中当前记录
同上,也可利用SQL指令DELETE删除数据库中的指定记录,如:
stmt.executeUpdate("DELETE FROM Books WHERE Code = '1109123466666'");
你可利用ResultSetTest.java这个程序实例,加入这里讨论的4个指令,运行并分析结果,以便加深理解如何应用这些操作。
05、预备指令是怎么回事
如果一个SQL指令需要以不同的数值或参数执行多次,预备指令,又称预备语句,则为首选。预备指令prepared statement,也称问号指令。指在SQL指令中将字段的值以问号?形式,设为变量,在执行中将被具体数据所代替。这种指令也称为参数化指令。
前面讨论的由Statement的executeUpdate()以及executeQuery()发送的SQL指令,都必须经过数据库编译后,方可执行。而预备指令,正如其名,则产生预先编译好的SQL指令,再由其setXxx()方法将具体参数值提供给SQL指令。预备指令实现了抽象指令模式和具体执行指令的分离,减少代码重复,提高编程效率。
预备指令功能包括在由java.sql包提供的PraparedStatement中,通过调用Connection的prepareStatement()方法,由其返回一个PreparedStatement对象而得到。调用其各种setXxx()方法得到参数值,再调用其executeUpdate()或executeQuery()完成指令的执行。如:
例子之一:一个典型预备指令。
-
-
...
-
try {
-
Connection
con
= DriverManager.getConnection(url, username, password);
-
String
selectSql
=
"UPDATE Products SET Price = ? WHERE Code = ?";
-
PreparedStatement
ps
= con.prepareStatement(selectSql);
//编译预备指令
-
ps.setDouble(
1,
1209.88);
//1代表第一个问号
-
ps.setString(
2,
"2200");
//2代表第二个问号
-
ps.executeUpdate();
//执行预备指令
-
ps.close();
//关闭
-
}
-
catch(ClassNotFoundException e){
-
System.out.println(
"Database driver not found.");
-
}
-
catch (SQLException e) {e.printStackTrace();}
以上代码中,两个问号代表指令参数。在调用setXxx()方法指定其值时,首先提供代表问号的序号(从1开始),再提供代表问号的值。如上例中,1代表第一个问号,表示Price的参数;2代表第二个问号,代表Code的参数。
一个预备指令中可以有多个问号。其序号按出现次序确定。PreparedStatement提供了设置所有数据类型值的方法setXx(),调用时必须注意数据类型的匹配。预备指令将抛出检查性异常SQLException,代码中必须提供处理这个异常的机制。
例子之二:利用预备指令在数据表中加入记录。
-
-
...
-
try {
-
Connection
con
= DriverManager.getConnection(url, username, password);
-
String
insertSql
=
"INSERT INTO Products (Code, Title, Price) VALUES (?, ?, ?)";
-
PreparedStatement
ps
= con.prepareStatement(insertSql);
//编译预备指令
-
ps.setString(
1,
"1110");
//1代表第一个问号
-
ps.setString(
2,
"Java EE Programming" );
//2代表第二个问号
-
ps.setDouble(
3,
77.02);
//3代表第三个问号
-
ps.executeUpdate()
//执行预备指令;
-
ps.close();
//关闭
-
}
-
catch(ClassNotFoundException e){
-
System.out.println(
"Database driver not found.");
-
}
-
catch (SQLException e) {e.printStackTrace();}
例子之三:利用预备指令选择指定数据表中的记录。
-
-
...
-
String
choice
=
"y";
-
ResultSet
rs
=
null;
-
Connection
con
= DriverManager.getConnection(dbURL, username, password);
-
String
deleteSql
=
"SELECT * FROM Products WHERE Code = ?";
-
PreparedStatement
ps
= con.prepareStatement(deleteSql);
-
while (
true) {
-
code = JOptionPane.showInputDialog(
"Enter the product code: ");
-
ps.setString(
1, code);
//指定的记录
-
rs = ps.executeQuery();
//执行预备指令
-
rs.next();
//指向这个记录
-
String
record
= rs.getString(
1) +
" " + rs.getString(
2) +
" " + rs.getDouble(
3);
//产生记录格式
-
JOptionPane.showMessageDialog(
null, record);
//显示记录
-
choice = JOptionPane.showInputDialog(
"是否继续?(y/n): "); ;
-
if (choice.equalsIgnoreCase(
"n"))
-
break;
-
}
-
ps.close();
-
...
例子之四:利用预备指令在数据表中删除记录。
-
-
...
-
try {
-
double
price
=
0;
-
boolean
quit
=
false;
-
Scanner
sc
=
new
Scanner(System.in);
-
Connection
con
= DriverManager.getConnection(url, username, password);
-
String
deleteSql
=
"DELETE FROM Products WHERE Price) = ?";
-
PreparedStatement
ps
= con.prepareStatement(deleteSql);
-
while (
true) {
-
System.out.println(
"Please enter the price you want that record to be deleted: ");
-
price = sc.nextDouble();
-
ps.setDouble(
1, price);
//删除由price指定的记录
-
ps.executeUpdate();
//执行预备指令
-
System.out.println(
"Do you want to continue? (y/n): ");
-
choice = sc.next();
-
if (choice.equalsIgnoreCase(
"n"))
-
break;
-
else
-
sc.nextLine();
-
}
-
ps.close();
-
}
-
catch(ClassNotFoundException e){
-
System.out.println(
"Database driver not found.");
-
}
-
catch (SQLException e) {e.printStackTrace();}
核心代码:
JDBCExamples.zip - 坚果云 - 云盘|网盘|企业网盘|同步|备份|无限空间|免费网络硬盘|企业云盘
转载:https://blog.csdn.net/qq_41640218/article/details/128475833