从上一篇 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 = “jdbc:mysql://localhost:3306”; //连接到本机数据库服务器//或:”jdbc:mysql://localhost:3306/ProductDB”;//连接到本机服务器中的数据库
以远程方式通过网络连接MySQL服务器的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指令的例子。
//完整程序见本文压缩附件中名为JDBCMySqlTest3.java源代码…String createTable = “CREATETABLEBooks (ISBN CHAR(13),Title VARCHAR(50),Price DECIMAL(” “6, 2),Inventory INT, Publisher VARCHAR(30))”;String insertRecord1 = “INSERTINTOBooks (ISBN, Title, Price, Inventory, Publisher)VALUES(” “‘9781890774555’, ‘Java Lover’, 66, 10, ‘ABC Press’) “;String updateRecord1 = “UPDATEBooks SETPrice = 69.15WHEREPrice= 66”;//String deleteRecord1 = “DELETEFROMBooks WHEREISBN = ‘9781890774555’”;stmt.executeUpdate(createTable); //调用发送SQL指令方法;stmt.executeUpdate(insertRecord1);stmt.executeUpdate(updateRecord1);//stmt.executeUpdate(deleteRecord1); //可在理解这个编程实例后再执行删除指令 … //其他操作指令Stmt.close(); //关闭… //异常处理这个例子向数据库发送了4个常用指令:CREATEINSERTUPDATEDELETE
建议你在理解了从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对象StringsqlString = “SELECT * FROM Products”; //定义SQL指令ResultSet rs = stmt.executeQuery(sqlString); //执行SQL指令并得到回传结果while(rs.next()) { //如果还有记录则继续循环Stringcode = rs.getString();//得到当前记录中的第一个 字段的值StringTitle = 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中记录指示器(也称光标)的操作,如移动或证实当前记录器位置。
java.sql包中的ResultSet提供静态字段来设置对记录的提取方式。表2列出了ResultSet常用字段和移动/证实记录指示器的常用方法。
表2 ResultSet的常用字段和移动/证实记录指示器常用方法
字段/方法
解释
CONCUR_READ_ONLY
只允许并行读取记录(系统预设)
CONCUR_UPDATABLE
允许变更ResultSet对象
FETCH_FORWARD
指定按先后次序得到一个记录中的各字段值
FETCH_REVERSE
指定按相反的次序得到一个记录中的各字段值
TYPE_FORWARD_ONLY
指定记录指示器只能朝前移动(系统预设)
boolean absolute(int row)
按指定记录行移动记录指示器。记录行可以是负值。如果移动成功,返回真,否则返回假
close()
关闭数据库和JDBC的连接
boolean first()
将记录指示器移至第一个记录的开始。如果移动成功,返回真,否则返回假
boolean isFirst()
如果记录指示器在第一个记录的开始位置,返回真,否则返回假
boolean isLast())
如果记录指示器在最后一个记录的开始位置,返回真,否则返回假
boolean last()
将记录指示器移至最后一个记录的开始。如果移动成功,返回真,否则返回假
boolean next()
将记录指示器移至下一个记录的开始。如果移动成功,返回真,否则返回假
boolean previous()
将记录指示器移至上一个记录的开始。如果移动成功,返回真,否则返回假
注意 ResultSet预设的纪录指示器位置为0。首次调用next()时,记录指示器位置为第一个记录的开始。如果记录指示器指向一个不存在的记录时,ResultSet对象则为null。
例子之一:利用ResultSet的静态字段设置按记录中的相反次序提取数据,并允许变更。提取方式的设定通过调用Statement重载的方法createStatement()实现,如:Statementstmt = 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为nullif(isFirst())rs.next();//记录指示器在第二个记录的开始…
04
提取和更新传回的记录
表3列出了ResultSet用来提取记录数据以及变更和删除记录的常用方法。
表3 esultSet提取记录数据和更新记录的常用方法
方法
解释
deleteRow()
从ResultSet对象和数据库删除当前记录
BigDecimal getBigDecimal(int column)
BigDecimal getBigDecimal(String columnName)
提取指定字段的数据并以BigDecimal对象返回
boolean getBoolean(int column)
boolean getBoolean(String columnName)
按指定字段提取布尔代数值
Date getDate(int column)
Date getDate(String columnName)
按指定字段提取日期对象
double getDouble(int column)
double getDouble(String columnName)
按指定字段提取双精度数值
int getInt(int column)
int getInt(String columnName)
按指定字段提取整数值
long getLong(int column)
long getLong(String columnName)
按指定字段提取长整数值
int getRow()
返回当前的记录行数
updateDataType(int column, dataType volue) updateDataType(String columnName, dataType volue)
按指定字段更新指定数据。其中dataType可以是任何Java数据类型
值得一提的是,数据库操作中所有记录位置指示器,包括字段位置(列),都从1算起。
例子之一:提取ResultSet中的结果。假设执行了如下SQL指令:
例子之二:调用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(“UPDATEBooks SETCode = ‘1109123466666’, Price = 125.89” “WHERECode = ‘9781890774555’”);
这种操作指涉及数据库,而不影响当前在ResultSet中的该记录。
例子之四:继续上例,删除指定的记录。rs.delelteRow(); //删除在ResultSet中以及数据库Books表中当前记录
同上,也可利用SQL指令DELETE删除数据库中的指定记录,如:
stmt.executeUpdate(“DELETEFROMBooks WHERECode = ‘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()完成指令的执行。如:
例子之一:一个典型预备指令。
//完整程序见本文压缩附件中名为PreparedStatementTest1.java源代码…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,代码中必须提供处理这个异常的机制。
例子之二:利用预备指令在数据表中加入记录。
//完整程序见本文压缩文件中名为PreparedStatementTest2.java源代码…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();}
例子之三:利用预备指令选择指定数据表中的记录。
//完整程序见本文压缩附件中名为PreparedStatementTest3.java源代码…Stringchoice = “y”;ResultSet rs = null;Connection con = DriverManager.getConnection(dbURL, username, password);StringdeleteSql = “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(); //指向这个记录Stringrecord = 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();…
例子之四:利用预备指令在数据表中删除记录。
//完整程序见本文压缩附件中名为PreparedStatementTest4.java源代码…try{doubleprice = 0;boolean quit = false;Scanner sc = newScanner(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;elsesc.nextLine();}ps.close();}catch(ClassNotFoundException e){System.out.println(“Database driver not found.”);}catch(SQLException e) {e.printStackTrace();}
06
源代码下载
07
参考书籍
《Java高手是怎样练成的:原理、方法与实践》
ISBN:978-7-302-56384-6
高永强 卢晨 编著
定价:118元
08
精彩文章回顾