在 Derby 中使用 JDBC 编程
最后修改于 2020 年 7 月 6 日
在本章中,我们将创建与 Derby 数据库配合使用的 Java 程序。
JDBC
JDBC 是 Java 编程语言的 API,它定义了客户端如何访问数据库。它提供了在数据库中查询和更新数据的方法。JDBC 面向关系数据库。从技术角度来看,API 是 java.sql 包中的一组类。要将 JDBC 与特定数据库一起使用,我们需要该数据库的 JDBC 驱动程序。
客户端/服务器和嵌入式 Derby 应用程序
Derby 可以通过两种基本方式在 Java 应用程序中使用:客户端/服务器和嵌入式。对于客户端/服务器应用程序,我们使用 org.apache.derby.jdbc.ClientDriver,对于 Derby 嵌入式应用程序,我们使用 org.apache.derby.jdbc.EmbeddedDriver。
Maven 依赖
有两个 Derby 驱动程序的 Maven 依赖项:derby 和 derbynet。derby 依赖项用于嵌入式应用程序,而 derbynet 用于客户端/服务器应用程序。
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derby</artifactId>
<version>10.13.1.1</version>
</dependency>
这是包含 derby 驱动程序的 Maven 依赖项。
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derbyclient</artifactId>
<version>10.13.1.1</version>
</dependency>
这是包含 derbyclient 驱动程序的 Maven 依赖项。
连接字符串
客户端/服务器和嵌入式应用程序的连接字符串不同。
jdbc:derby://:1527/dbname
这是客户端/服务器应用程序的连接 URL。
jdbc:derby:dbname
这是嵌入式应用程序的连接 URL。
创建 CARS 表
在我们的示例中,我们使用嵌入式 Derby 数据库。在第一个示例中,我们将创建一个 CARS 表并向其中插入八行。
$ $DERBY_HOME/bin/ij ij version 10.11 ij> CONNECT 'jdbc:derby:testdb'; ij> DROP TABLE USER12.CARS; 0 rows inserted/updated/deleted
在运行示例之前,如果已经创建了 CARS 表,我们应该从数据库中删除它。
package com.zetcode;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.logging.Level;
import java.util.logging.Logger;
public class CreateCars {
public static void main(String[] args) {
Connection con = null;
Statement st = null;
String url = "jdbc:derby:testdb;user=USER12";
try {
System.setProperty("derby.system.home", "/home/janbodnar/.derby");
con = DriverManager.getConnection(url);
st = con.createStatement();
st.executeUpdate("CREATE TABLE CARS(ID INT PRIMARY KEY,"
+ "NAME VARCHAR(30), PRICE INT)");
st.executeUpdate("INSERT INTO CARS VALUES(1, 'Audi', 52642)");
st.executeUpdate("INSERT INTO CARS VALUES(2, 'Mercedes', 57127)");
st.executeUpdate("INSERT INTO CARS VALUES(3, 'Skoda', 9000)");
st.executeUpdate("INSERT INTO CARS VALUES(4, 'Volvo', 29000)");
st.executeUpdate("INSERT INTO CARS VALUES(5, 'Bentley', 350000)");
st.executeUpdate("INSERT INTO CARS VALUES(6, 'Citroen', 21000)");
st.executeUpdate("INSERT INTO CARS VALUES(7, 'Hummer', 41400)");
st.executeUpdate("INSERT INTO CARS VALUES(8, 'Volkswagen', 21600)");
DriverManager.getConnection("jdbc:derby:;shutdown=true");
} catch (SQLException ex) {
Logger lgr = Logger.getLogger(CreateCars.class.getName());
if (((ex.getErrorCode() == 50000)
&& ("XJ015".equals(ex.getSQLState())))) {
lgr.log(Level.INFO, "Derby shut down normally", ex);
} else {
lgr.log(Level.SEVERE, ex.getMessage(), ex);
}
} finally {
try {
if (st != null) {
st.close();
}
if (con != null) {
con.close();
}
} catch (SQLException ex) {
Logger lgr = Logger.getLogger(CreateCars.class.getName());
lgr.log(Level.WARNING, ex.getMessage(), ex);
}
}
}
}
该示例以嵌入模式连接到 Derby。它创建一个 CARS 表并向其中添加 8 行。最后,它关闭 Derby。
String url = "jdbc:derby:testdb;user=USER12";
这是在嵌入模式下连接到 testdb 数据库以及 USER12 模式的 URL。
System.setProperty("derby.system.home", "/home/janbodnar/.derby");
我们设置 Derby 系统目录的系统属性。
con = DriverManager.getConnection(url);
创建到 Derby 数据库的连接。创建连接后,将启动 Derby 数据库。
st.executeUpdate("CREATE TABLE CARS(ID INT PRIMARY KEY,"
+ "NAME VARCHAR(30), PRICE INT)");
st.executeUpdate("INSERT INTO CARS VALUES(1, 'Audi', 52642)");
...
我们执行创建数据库并用一些数据填充数据库的 SQL 语句。对于 INSERT、UPDATE 和 DELETE 语句以及 DDL 语句,例如 CREATE TABLE,我们使用 executeUpdate 方法。
DriverManager.getConnection("jdbc:derby:;shutdown=true");
Derby 数据库引擎已关闭。
} catch (SQLException ex) {
Logger lgr = Logger.getLogger(CreateCars.class.getName());
我们捕获 SQLException。Logger 类用于记录错误消息。
if (((ex.getErrorCode() == 50000)
&& ("XJ015".equals(ex.getSQLState())))) {
lgr.log(Level.INFO, "Derby shut down normally", ex);
}
当 Derby 引擎关闭时,会抛出 SQLException。我们捕获此异常并记录一条信息消息。
} finally {
try {
if (st != null) {
st.close();
}
if (con != null) {
con.close();
}
在 finally 子句中,我们释放资源。
Mar 22, 2017 12:22:15 PM com.zetcode.CreateCars main INFO: Derby shut down normally java.sql.SQLException: Derby system shutdown. ...
我们编译并运行该示例。Derby 的关闭将以 SQLException 结束。这是 Derby 数据库的一个特性。
检索数据
接下来,我们将展示如何从数据库表中检索数据。我们从 CARS 表中获取所有数据。
package com.zetcode;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.logging.Level;
import java.util.logging.Logger;
public class SelectAllCars {
public static void main(String[] args) {
Connection con = null;
Statement st = null;
ResultSet rs = null;
String url = "jdbc:derby:testdb";
try {
System.setProperty("derby.system.home", "/home/janbodnar/.derby");
con = DriverManager.getConnection(url);
st = con.createStatement();
rs = st.executeQuery("SELECT * FROM USER12.CARS");
while (rs.next()) {
System.out.print(rs.getInt(1));
System.out.print(" ");
System.out.print(rs.getString(2));
System.out.print(" ");
System.out.println(rs.getString(3));
}
DriverManager.getConnection("jdbc:derby:;shutdown=true");
} catch (SQLException ex) {
Logger lgr = Logger.getLogger(SelectAllCars.class.getName());
if (((ex.getErrorCode() == 50000)
&& ("XJ015".equals(ex.getSQLState())))) {
lgr.log(Level.INFO, "Derby shut down normally", ex);
} else {
lgr.log(Level.SEVERE, ex.getMessage(), ex);
}
} finally {
try {
if (rs != null) {
rs.close();
}
if (st != null) {
st.close();
}
if (con != null) {
con.close();
}
} catch (SQLException ex) {
Logger lgr = Logger.getLogger(SelectAllCars.class.getName());
lgr.log(Level.WARNING, ex.getMessage(), ex);
}
}
}
}
我们从 CARS 表中获取所有汽车,并将它们打印到控制台。
st = con.createStatement();
rs = st.executeQuery("SELECT * FROM USER12.CARS");
我们执行一个查询,该查询从 CARS 表中选择所有列。我们使用 executeQuery 方法。该方法执行给定的 SQL 语句,该语句返回单个 ResultSet 对象。ResultSet 是 SQL 查询返回的数据表。另请注意,由于我们未在 URL 中指定用户名,因此我们必须在 SQL 语句中显式提及模式名称。
while (rs.next()) {
System.out.print(rs.getInt(1));
System.out.print(" ");
System.out.print(rs.getString(2));
System.out.print(" ");
System.out.println(rs.getString(3));
}
next 方法将光标移动到结果集的下一条记录。当结果集中没有更多行时,它返回 false。getInt 和 getString 方法检索此 ResultSet 对象当前行中指定列的值; Java 编程语言中的 int 和 String。
1 Audi 52642 2 Mercedes 57127 3 Skoda 9000 4 Volvo 29000 5 Bentley 350000 6 Citroen 21000 7 Hummer 41400 8 Volkswagen 21600 Mar 22, 2017 12:28:36 PM com.zetcode.SelectAllCars main INFO: Derby shut down normally java.sql.SQLException: Derby system shutdown. ...
我们编译并运行该示例。我们有一个来自 testdb 数据库的 CARS 表的所有汽车的列表。
属性(Properties)
将配置数据放在程序外部的单独文件中是一种常见做法。我们可以更改用户、密码或连接字符串,而无需重新编译程序。这在动态环境中特别有用,在动态环境中需要大量的测试、调试、保护数据等。
在 Java 中,Properties 是一个类,通常用于存储基本配置数据。该类用于轻松读取和保存键/值属性。
db.url=jdbc:derby:testdb;user=USER12 db.user=USER12 db.passwd=34klq* db.syshome=/home/janbodnar/.derby
我们有一个 db.roperties 文件,其中我们有四个键/值对。这些是在程序执行期间动态加载的。该文件位于 src/main/resources 目录中。
package com.zetcode;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
public class PropertiesExample {
public static void main(String[] args) {
Connection con = null;
PreparedStatement pst = null;
ResultSet rs = null;
Properties props = new Properties();
FileInputStream in = null;
try {
in = new FileInputStream("src/main/resources/db.properties");
props.load(in);
} catch (FileNotFoundException ex) {
Logger lgr = Logger.getLogger(PropertiesExample.class.getName());
lgr.log(Level.SEVERE, ex.getMessage(), ex);
} catch (IOException ex) {
Logger lgr = Logger.getLogger(PropertiesExample.class.getName());
lgr.log(Level.SEVERE, ex.getMessage(), ex);
} finally {
try {
if (in != null) {
in.close();
}
} catch (IOException ex) {
Logger lgr = Logger.getLogger(PropertiesExample.class.getName());
lgr.log(Level.SEVERE, ex.getMessage(), ex);
}
}
String url = props.getProperty("db.url");
String user = props.getProperty("db.user");
String passwd = props.getProperty("db.passwd");
try {
System.setProperty("derby.system.home",
props.getProperty("db.syshome"));
con = DriverManager.getConnection(url, user, passwd);
pst = con.prepareStatement("SELECT * FROM CARS");
rs = pst.executeQuery();
while (rs.next()) {
System.out.print(rs.getInt(1));
System.out.print(": ");
System.out.println(rs.getString(2));
}
DriverManager.getConnection("jdbc:derby:;shutdown=true");
} catch (SQLException ex) {
Logger lgr = Logger.getLogger(PropertiesExample.class.getName());
if (((ex.getErrorCode() == 50000)
&& ("XJ015".equals(ex.getSQLState())))) {
lgr.log(Level.INFO, "Derby shut down normally", ex);
} else {
lgr.log(Level.SEVERE, ex.getMessage(), ex);
}
} finally {
try {
if (rs != null) {
rs.close();
}
if (pst != null) {
pst.close();
}
if (con != null) {
con.close();
}
} catch (SQLException ex) {
Logger lgr = Logger.getLogger(PropertiesExample.class.getName());
lgr.log(Level.WARNING, ex.getMessage(), ex);
}
}
}
}
我们连接到 testdb 并从 CARS 表中选择所有汽车。该示例的配置数据是从 db.properties 文件中读取的。
Properties props = new Properties();
FileInputStream in = null;
try {
in = new FileInputStream("src/main/resources/db.properties");
props.load(in);
创建 Properties 类。数据从名为 db.properties 的文件中加载,在该文件中我们有配置数据。
String url = props.getProperty("db.url");
String user = props.getProperty("db.user");
String passwd = props.getProperty("db.passwd");
值通过 getProperty 方法检索。
con = DriverManager.getConnection(url, user, passwd);
请注意,在默认的 Derby 配置中,密码被忽略。
预处理语句
现在我们将关注预处理语句。当我们编写预处理语句时,我们使用占位符而不是直接将值写入语句中。预处理语句可以提高安全性和性能。
在 Java 中,PreparedStatement 是一个表示预编译 SQL 语句的对象。
package com.zetcode;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.logging.Level;
import java.util.logging.Logger;
public class Prepared {
public static void main(String[] args) {
Connection con = null;
PreparedStatement pst = null;
ResultSet rs = null;
String url = "jdbc:derby:testdb;user=USER12";
int price = 58000;
int id = 2;
try {
System.setProperty("derby.system.home", "/home/janbodnar/.derby");
con = DriverManager.getConnection(url);
pst = con.prepareStatement("UPDATE CARS SET PRICE = ? WHERE ID = ?");
pst.setInt(1, price);
pst.setInt(2, id);
pst.executeUpdate();
DriverManager.getConnection("jdbc:derby:;shutdown=true");
} catch (SQLException ex) {
Logger lgr = Logger.getLogger(Prepared.class.getName());
if (((ex.getErrorCode() == 50000)
&& ("XJ015".equals(ex.getSQLState())))) {
lgr.log(Level.INFO, "Derby shut down normally", ex);
} else {
lgr.log(Level.SEVERE, ex.getMessage(), ex);
}
} finally {
try {
if (rs != null) {
rs.close();
}
if (pst != null) {
pst.close();
}
if (con != null) {
con.close();
}
} catch (SQLException ex) {
Logger lgr = Logger.getLogger(Prepared.class.getName());
lgr.log(Level.WARNING, ex.getMessage(), ex);
}
}
}
}
我们更改了 id 等于 2 的汽车的价格。
int price = 58000; int id = 2;
这些是要设置为预处理语句的值。这些值可能来自用户,并且来自用户的所有内容都应被视为潜在的危险。
pst = con.prepareStatement("UPDATE CARS SET PRICE = ? WHERE ID = ?");
在这里,我们创建一个预处理语句。当我们编写预处理语句时,我们使用占位符,而不是直接将值写入语句中。预处理语句更快,并且可以防止 SQL 注入攻击。? 是一个占位符,稍后将填充该占位符。
pst.setInt(1, price); pst.setInt(2, id);
值被绑定到占位符上。
pst.executeUpdate();
执行预处理语句。当我们不期望返回任何数据时,我们使用语句对象的 executeUpdate 方法。当创建数据库或执行 INSERT、UPDATE 和 DELETE 语句时,就是这种情况。
ij> SELECT * FROM CARS WHERE ID=2; ID |NAME |PRICE ------------------------------------------------------ 2 |Mercedes |58000 1 row selected
运行该示例后,我们使用 ij 工具检查结果。
列标题
接下来,我们将展示如何使用数据库表中的数据打印列标题。我们将列名称为 MetaData。MetaData 是关于数据库中核心数据的数据。
package com.zetcode;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.Formatter;
import java.util.logging.Level;
import java.util.logging.Logger;
public class ColumnHeaders {
public static void main(String[] args) {
Connection con = null;
PreparedStatement pst = null;
ResultSet rs = null;
String url = "jdbc:derby:testdb;user=USER12";
try {
System.setProperty("derby.system.home", "/home/janbodnar/.derby");
con = DriverManager.getConnection(url);
String query = "SELECT NAME, TITLE From AUTHORS, "
+ "Books WHERE AUTHORS.ID=BOOKS.AUTHOR_ID";
pst = con.prepareStatement(query);
rs = pst.executeQuery();
ResultSetMetaData meta = rs.getMetaData();
String colname1 = meta.getColumnName(1);
String colname2 = meta.getColumnName(2);
Formatter fmt1 = new Formatter();
fmt1.format("%-21s%s", colname1, colname2);
System.out.println(fmt1);
while (rs.next()) {
Formatter fmt2 = new Formatter();
fmt2.format("%-21s", rs.getString(1));
System.out.print(fmt2);
System.out.println(rs.getString(2));
}
DriverManager.getConnection("jdbc:derby:;shutdown=true");
} catch (SQLException ex) {
Logger lgr = Logger.getLogger(ColumnHeaders.class.getName());
if (((ex.getErrorCode() == 50000)
&& ("XJ015".equals(ex.getSQLState())))) {
lgr.log(Level.INFO, "Derby shut down normally", ex);
} else {
lgr.log(Level.SEVERE, ex.getMessage(), ex);
}
} finally {
try {
if (rs != null) {
rs.close();
}
if (pst != null) {
pst.close();
}
if (con != null) {
con.close();
}
} catch (SQLException ex) {
Logger lgr = Logger.getLogger(ColumnHeaders.class.getName());
lgr.log(Level.WARNING, ex.getMessage(), ex);
}
}
}
}
在此程序中,我们从 AUTHORS 表中选择作者,并从 BOOKS 表中选择他们的书籍。我们打印结果集中返回的列的名称。我们格式化输出。用于创建表的 SQL 文件位于本教程的第一章中。
String query = "SELECT NAME, TITLE From AUTHORS, "
+ "Books WHERE AUTHORS.ID=BOOKS.AUTHOR_ID";
这是将作者与其书籍连接起来的 SQL 语句。
ResultSetMetaData meta = rs.getMetaData();
要获取列名,我们需要获取 ResultSetMetaData。它是一个可用于获取有关 ResultSet 对象中列的类型和属性的信息的对象。ResultSetMetaData 是从 ResultSet 使用 getMetaData 方法获取的。
String colname1 = meta.getColumnName(1); String colname2 = meta.getColumnName(2);
从获取的元数据中,我们使用 getColumnName 方法获取列名。
Formatter fmt1 = new Formatter();
fmt1.format("%-21s%s", colname1, colname2);
System.out.println(fmt1);
我们将列名打印到控制台。Formatter 对象格式化数据。
while (rs.next()) {
Formatter fmt2 = new Formatter();
fmt2.format("%-21s", rs.getString(1));
System.out.print(fmt2);
System.out.println(rs.getString(2));
}
我们将数据打印到控制台。我们再次使用 Formatter 对象来格式化数据。第一列是 21 个字符宽,并向左对齐。
NAME TITLE Jack London Call of the Wild Jack London Martin Eden Honore de Balzac Old Goriot Honore de Balzac Cousin Bette Lion Feuchtwanger Jew Suess Emile Zola Nana Emile Zola The Belly of Paris Truman Capote In Cold blood Truman Capote Breakfast at Tiffany Mar 22, 2017 12:52:56 PM com.zetcode.ColumnHeaders main INFO: Derby shut down normally java.sql.SQLException: Derby system shutdown. ...
这是示例的输出。
写入图像
有些人喜欢将他们的图像放入数据库,有些人喜欢将它们保存在应用程序的文件系统中。当我们处理大量图像时,就会出现技术难题。图像是二进制数据。Derby 有一个特殊的用于存储二进制数据的数据类型,称为 BLOB (二进制大对象)。
我们为此和以下示例创建一个名为 IMAGES 的新表。
ij> CREATE TABLE IMAGES(ID INT PRIMARY KEY, DATA BLOB); 0 rows inserted/updated/deleted
DATA 列具有 BLOB 类型。我们将在其中插入编码的二进制数据。
package com.zetcode;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.logging.Level;
import java.util.logging.Logger;
public class WriteImage {
public static void main(String[] args) {
Connection con = null;
PreparedStatement pst = null;
String url = "jdbc:derby:testdb;user=USER12";
try {
System.setProperty("derby.system.home", "/home/janbodnar/.derby");
con = DriverManager.getConnection(url);
File imgFile = new File("woman.jpg");
try (FileInputStream fin = new FileInputStream(imgFile)) {
con = DriverManager.getConnection(url);
pst = con.prepareStatement("INSERT INTO IMAGES(ID, DATA) VALUES(1, ?)");
pst.setBinaryStream(1, fin, (int) imgFile.length());
pst.executeUpdate();
}
DriverManager.getConnection("jdbc:derby:;shutdown=true");
} catch (FileNotFoundException ex) {
Logger lgr = Logger.getLogger(WriteImage.class.getName());
lgr.log(Level.SEVERE, ex.getMessage(), ex);
} catch (SQLException ex) {
Logger lgr = Logger.getLogger(WriteImage.class.getName());
if (((ex.getErrorCode() == 50000)
&& ("XJ015".equals(ex.getSQLState())))) {
lgr.log(Level.INFO, "Derby shut down normally", ex);
} else {
lgr.log(Level.SEVERE, ex.getMessage(), ex);
}
} catch (IOException ex) {
Logger.getLogger(WriteImage.class.getName()).log(Level.SEVERE, null, ex);
} finally {
try {
if (pst != null) {
pst.close();
}
if (con != null) {
con.close();
}
} catch (SQLException ex) {
Logger lgr = Logger.getLogger(WriteImage.class.getName());
lgr.log(Level.WARNING, ex.getMessage(), ex);
}
}
}
}
在此示例中,我们从当前工作目录中读取一个 JPG 图像,并将其插入到 IMAGES 表中。
File imgFile = new File("woman.jpg");
try (FileInputStream fin = new FileInputStream(imgFile)) {
我们为图片文件创建一个 File 对象。为了从这个文件中读取字节,我们创建一个 FileInputStream 对象。
pst = con.prepareStatement("INSERT INTO IMAGES(ID, DATA) VALUES(1, ?)");
此 SQL 语句将图像插入到 Images 表中。
pst.setBinaryStream(1, fin, (int) img.length());
二进制流被设置为预处理语句。setBinaryStream 方法的参数是要绑定的参数索引、输入流和流中的字节数。
pst.executeUpdate();
我们使用 executeUpdate 方法执行该语句。
读取图像
在前面的例子中,我们已经将一张图片插入到数据库表中。现在我们将从表中把图片读回来。
package com.zetcode;
import java.io.FileOutputStream;
import java.io.IOException;
import java.sql.Blob;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.logging.Level;
import java.util.logging.Logger;
public class ReadImage {
public static void main(String[] args) {
Connection con = null;
PreparedStatement pst = null;
ResultSet rs = null;
String url = "jdbc:derby:testdb;user=USER12";
try {
System.setProperty("derby.system.home", "/home/janbodnar/.derby");
System.out.println(System.getProperty("user.dir"));
con = DriverManager.getConnection(url);
String query = "SELECT DATA FROM IMAGES WHERE ID = 1";
pst = con.prepareStatement(query);
rs = pst.executeQuery();
rs.next();
String fileName = "src/main/resources/woman.jpg";
try (FileOutputStream fos = new FileOutputStream(fileName)) {
Blob blob = rs.getBlob("DATA");
int len = (int) blob.length();
byte[] buf = blob.getBytes(1, len);
fos.write(buf, 0, len);
}
DriverManager.getConnection("jdbc:derby:;shutdown=true");
} catch (IOException ex) {
Logger lgr = Logger.getLogger(ReadImage.class.getName());
lgr.log(Level.SEVERE, ex.getMessage(), ex);
} catch (SQLException ex) {
Logger lgr = Logger.getLogger(ReadImage.class.getName());
if (((ex.getErrorCode() == 50000)
&& ("XJ015".equals(ex.getSQLState())))) {
lgr.log(Level.INFO, "Derby shut down normally", ex);
} else {
lgr.log(Level.SEVERE, ex.getMessage(), ex);
}
} finally {
try {
if (rs != null) {
rs.close();
}
if (pst != null) {
pst.close();
}
if (con != null) {
con.close();
}
} catch (SQLException ex) {
Logger lgr = Logger.getLogger(ReadImage.class.getName());
lgr.log(Level.WARNING, ex.getMessage(), ex);
}
}
}
}
我们从 IMAGES 表中读取一个图像。
String query = "SELECT DATA FROM IMAGES WHERE ID = 1";
选择一条记录。
try (FileOutputStream fos = new FileOutputStream(fileName)) {
创建 FileOutputStream 对象以写入文件。它用于写入原始字节流,例如图像数据。
Blob blob = result.getBlob("DATA");
我们通过调用 getBlob 方法从 DATA 列中获取图像数据。
int len = (int) blob.length();
我们找出 blob 数据的长度。换句话说,我们获取字节数。
byte[] buf = blob.getBytes(1, len);
getBytes 方法检索 BLOB 对象的所有字节,作为字节数组。
fos.write(buf, 0, len);
字节被写入输出流。图片在文件系统上被创建。
事务支持
事务是针对一个或多个数据库中的数据执行的数据库操作的原子单位。事务中所有 SQL 语句的效果要么全部提交到数据库,要么全部回滚。
创建连接时,它处于自动提交模式。这意味着每个单独的 SQL 语句都被视为一个事务,并在执行后立即自动提交。对于所有 JDBC 驱动程序(包括 Derby 的驱动程序)来说都是如此。要启动一个新事务,我们关闭自动提交。
在直接 SQL 中,事务以 BEGIN TRANSACTION 语句开始,以 END TRANSACTION/COMMIT 语句结束。在 Derby 中,这些语句是 BEGIN 和 COMMIT。但是,在使用驱动程序时,会省略这些语句。它们由驱动程序处理。确切的细节特定于驱动程序。例如,psycopg2 Python 驱动程序在第一个 SQL 语句之后启动一个事务。如果我们想要自动提交模式,我们必须将自动提交属性设置为 True。相比之下,JDBC 驱动程序默认处于自动提交模式。要启动一个新的事务,必须关闭自动提交。
package com.zetcode;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.logging.Level;
import java.util.logging.Logger;
public class Transaction {
public static void main(String[] args) {
Connection con = null;
Statement st = null;
String url = "jdbc:derby:testdb;user=USER12";
try {
System.setProperty("derby.system.home", "/home/janbodnar/.derby");
con = DriverManager.getConnection(url);
st = con.createStatement();
con.setAutoCommit(false);
st.executeUpdate("UPDATE AUTHORS SET NAME = 'Leo Tolstoy' "
+ "WHERE Id = 1");
st.executeUpdate("UPDATE BOOKS SET TITLE = 'War and Peace' "
+ "WHERE Id = 1");
st.executeUpdate("UPDATE BOOKS SET TITL = 'Anna Karenina' "
+ "WHERE Id = 2");
con.commit();
DriverManager.getConnection("jdbc:derby:;shutdown=true");
} catch (SQLException ex) {
Logger lgr = Logger.getLogger(Transaction.class.getName());
if (((ex.getErrorCode() == 50000)
&& ("XJ015".equals(ex.getSQLState())))) {
lgr.log(Level.INFO, "Derby shut down normally", ex);
} else {
if (con != null) {
try {
con.rollback();
} catch (SQLException ex1) {
lgr.log(Level.WARNING, ex1.getMessage(), ex1);
}
}
lgr.log(Level.SEVERE, ex.getMessage(), ex);
}
} finally {
try {
if (st != null) {
st.close();
}
if (con != null) {
con.close();
}
} catch (SQLException ex) {
Logger lgr = Logger.getLogger(Transaction.class.getName());
lgr.log(Level.WARNING, ex.getMessage(), ex);
}
}
}
}
在此程序中,我们希望更改 AUTHORS 表第一行中作者的姓名。我们还必须更改与该作者相关的书籍。如果我们更改作者,而不更改作者的书籍,则数据会损坏。
con.setAutoCommit(false);
要处理事务,我们必须将自动提交设置为 false。默认情况下,数据库连接处于自动提交模式。在这种模式下,每个语句在执行后都会提交到数据库。一个语句无法撤消。当关闭自动提交时,我们通过调用 commit 提交更改或通过调用 rollback 回滚更改。
st.executeUpdate("UPDATE BOOKS SET TITL = 'Anna Karenina' "
+ "WHERE Id = 2");
第三个 SQL 语句有一个错误。BOOKS 表中没有 TITL 列。
con.commit();
如果没有异常,则提交事务。如果关闭了自动提交,我们必须显式调用 commit 方法。
if (con != null) {
try {
con.rollback();
} catch (SQLException ex1) {
lgr.log(Level.WARNING, ex1.getMessage(), ex1);
}
}
如果发生 Derby 系统关闭以外的异常,则回滚事务。不会对数据库进行任何更改。
Mar 22, 2017 2:00:40 PM com.zetcode.Transaction main SEVERE: 'TITL' is not a column in table or VTI 'USER12.BOOKS'. java.sql.SQLSyntaxErrorException: 'TITL' is not a column in table or VTI 'USER12.BOOKS'.
执行失败,并显示“'TITL'不是表中的一列”消息。抛出了一个异常。事务已回滚,并且未进行任何更改。
ij> CONNECT 'jdbc:derby:testdb'; ij> SET CURRENT SCHEMA = USER12; ij> SELECT NAME, TITLE FROM AUTHORS, BOOKS WHERE AUTHORS.ID = BOOKS.AUTHOR_ID; NAME |TITLE ------------------------------------------------------------ Jack London |Call of the Wild Jack London |Martin Eden Honore de Balzac |Old Goriot Honore de Balzac |Cousin Bette Lion Feuchtwanger |Jew Suess Emile Zola |Nana Emile Zola |The Belly of Paris Truman Capote |In Cold blood Truman Capote |Breakfast at Tiffany 9 rows selected
数据未损坏。
然而,没有事务,数据是不安全的。
package com.zetcode;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.logging.Level;
import java.util.logging.Logger;
public class NonTransaction {
public static void main(String[] args) {
Connection con = null;
Statement st = null;
String url = "jdbc:derby:testdb;user=USER12";
try {
System.setProperty("derby.system.home", "/home/janbodnar/.derby");
con = DriverManager.getConnection(url);
st = con.createStatement();
st.executeUpdate("UPDATE AUTHORS SET NAME = 'Leo Tolstoy' "
+ "WHERE Id = 1");
st.executeUpdate("UPDATE BOOKS SET TITLE = 'War and Peace' "
+ "WHERE Id = 1");
st.executeUpdate("UPDATE BOOKS SET TITL = 'Anna Karenina' "
+ "WHERE Id = 2");
DriverManager.getConnection("jdbc:derby:;shutdown=true");
} catch (SQLException ex) {
Logger lgr = Logger.getLogger(NonTransaction.class.getName());
if (((ex.getErrorCode() == 50000)
&& ("XJ015".equals(ex.getSQLState())))) {
lgr.log(Level.INFO, "Derby shut down normally", ex);
} else {
lgr.log(Level.SEVERE, ex.getMessage(), ex);
}
} finally {
try {
if (st != null) {
st.close();
}
if (con != null) {
con.close();
}
} catch (SQLException ex) {
Logger lgr = Logger.getLogger(NonTransaction.class.getName());
lgr.log(Level.WARNING, ex.getMessage(), ex);
}
}
}
}
我们有相同的示例;这次,没有事务支持。
Mar 22, 2017 2:08:40 PM com.zetcode.NonTransaction main SEVERE: 'TITL' is not a column in table or VTI 'USER12.BOOKS'. java.sql.SQLSyntaxErrorException: 'TITL' is not a column in table or VTI 'USER12.BOOKS'. ... ij> CONNECT 'jdbc:derby:testdb'; ij> SET CURRENT SCHEMA = USER12; ij> SELECT NAME, TITLE FROM AUTHORS, BOOKS WHERE AUTHORS.ID = BOOKS.AUTHOR_ID; NAME |TITLE ---------------------------------------------------------------- Leo Tolstoy |War and Peace Leo Tolstoy |Martin Eden Honore de Balzac |Old Goriot Honore de Balzac |Cousin Bette Lion Feuchtwanger |Jew Suess Emile Zola |Nana Emile Zola |The Belly of Paris Truman Capote |In Cold blood Truman Capote |Breakfast at Tiffany 9 rows selected
再次抛出异常。列夫·托尔斯泰没有写《马丁·伊登》:数据已损坏。
批量更新
当我们需要使用多个语句更新数据时,可以使用批量更新。批量更新可用于 INSERT、UPDATE、DELETE 语句以及 CREATE TABLE 和 DROP TABLE 语句。
package com.zetcode;
import java.sql.*;
import java.util.logging.Level;
import java.util.logging.Logger;
public class BatchUpdates {
public static void main(String[] args) {
Connection con = null;
Statement st = null;
ResultSet rs = null;
String url = "jdbc:derby:testdb;user=USER12";
try {
System.setProperty("derby.system.home", "/home/janbodnar/.derby");
con = DriverManager.getConnection(url);
con.setAutoCommit(false);
st = con.createStatement();
st.addBatch("DELETE FROM CARS");
st.addBatch("INSERT INTO CARS VALUES(1, 'Audi', 52642)");
st.addBatch("INSERT INTO CARS VALUES(2, 'Mercedes', 57127)");
st.addBatch("INSERT INTO CARS VALUES(3, 'Skoda', 9000)");
st.addBatch("INSERT INTO CARS VALUES(4, 'Volvo', 29000)");
st.addBatch("INSERT INTO CARS VALUES(5, 'Bentley', 350000)");
st.addBatch("INSERT INTO CARS VALUES(6, 'Citroen', 21000)");
st.addBatch("INSERT INTO CARS VALUES(7, 'Hummer', 41400)");
st.addBatch("INSERT INTO CARS VALUES(8, 'Volkswagen', 21600)");
st.addBatch("INSERT INTO CARS VALUES(9, 'Jaguar', 95000)");
int counts[] = st.executeBatch();
con.commit();
System.out.println("Committed " + counts.length + " updates");
DriverManager.getConnection("jdbc:derby:;shutdown=true");
} catch (SQLException ex) {
Logger lgr = Logger.getLogger(BatchUpdates.class.getName());
if (((ex.getErrorCode() == 50000)
&& ("XJ015".equals(ex.getSQLState())))) {
lgr.log(Level.INFO, "Derby shut down normally", ex);
} else {
if (con != null) {
try {
con.rollback();
} catch (SQLException ex1) {
lgr.log(Level.WARNING, ex1.getMessage(), ex1);
}
}
lgr.log(Level.SEVERE, ex.getMessage(), ex);
}
} finally {
try {
if (rs != null) {
rs.close();
}
if (st != null) {
st.close();
}
if (con != null) {
con.close();
}
} catch (SQLException ex) {
Logger lgr = Logger.getLogger(BatchUpdates.class.getName());
lgr.log(Level.WARNING, ex.getMessage(), ex);
}
}
}
}
这是一个批量更新的示例程序。我们从 CARS 表中删除所有行,并向其中插入九行。
con.setAutoCommit(false);
在进行批量更新时,应始终关闭自动提交。
st.addBatch("DELETE FROM CARS");
st.addBatch("INSERT INTO CARS VALUES(1, 'Audi', 52642)");
st.addBatch("INSERT INTO CARS VALUES(2, 'Mercedes', 57127)");
st.addBatch("INSERT INTO CARS VALUES(3, 'Skoda', 9000)");
...
我们使用 addBatch 方法向语句中添加一个新命令。
int counts[] = st.executeBatch();
添加所有命令后,我们调用 executeBatch 来执行批量更新。该方法返回一个已提交更改的数组。
con.commit();
批量更新在事务中提交。
ij> SELECT * FROM CARS; ID |NAME |PRICE ------------------------------------------------------ 1 |Audi |52642 2 |Mercedes |57127 3 |Skoda |9000 4 |Volvo |29000 5 |Bentley |350000 6 |Citroen |21000 7 |Hummer |41400 8 |Volkswagen |21600 9 |Jaguar |95000
我们已成功地重新创建了 CARS 表。
在本章中,我们使用 Java 和 Derby 进行了一些 JDBC 编程。