💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
# 在 Derby 中使用 JDBC 进行编程 > 原文: [http://zetcode.com/db/apachederbytutorial/jdbc/](http://zetcode.com/db/apachederbytutorial/jdbc/) 在本章中,我们将创建将与 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`依赖关系用于客户端/服务器应用。 ```java <dependency> <groupId>org.apache.derby</groupId> <artifactId>derby</artifactId> <version>10.13.1.1</version> </dependency> ``` 这是包含`derby`驱动程序的 Maven 依赖项。 ```java <dependency> <groupId>org.apache.derby</groupId> <artifactId>derbyclient</artifactId> <version>10.13.1.1</version> </dependency> ``` 这是包含`derbyclient`驱动程序的 Maven 依赖项。 ### 连接字符串 客户端/服务器和嵌入式应用的连接字符串不同。 ```java jdbc:derby://localhost:1527/dbname ``` 这是客户端/服务器应用的连接 URL。 ```java jdbc:derby:dbname ``` 这是嵌入式应用的连接 URL。 ## 创建`CARS`表 在我们的示例中,我们使用嵌入式 Derby 数据库。 在第一个示例中,我们将创建一个`CARS`表并在其中插入八行。 ```java $ $DERBY_HOME/bin/ij ij version 10.11 ij> CONNECT 'jdbc:derby:testdb'; ij> DROP TABLE USER12.CARS; 0 rows inserted/updated/deleted ``` 如果在运行示例之前已经创建了`CARS`表,则应该从数据库中删除该表。 CreateCars.java ```java 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。 ```java String url = "jdbc:derby:testdb;user=USER12"; ``` 这是用于以嵌入式模式和`USER12`模式连接到`testdb`数据库的 URL。 ```java System.setProperty("derby.system.home", "/home/janbodnar/.derby"); ``` 我们为 Derby 系统目录设置了系统属性。 ```java con = DriverManager.getConnection(url); ``` 创建与 Derby 数据库的连接。 创建连接后,将启动 Derby 数据库。 ```java 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`语句以及类似`CREATE TABLE`的 DDL 语句,我们使用`executeUpdate()`方法。 ```java DriverManager.getConnection("jdbc:derby:;shutdown=true"); ``` Derby 数据库引擎已关闭。 ```java } catch (SQLException ex) { Logger lgr = Logger.getLogger(CreateCars.class.getName()); ``` 我们抓到`SQLException`。 `Logger`类用于记录错误消息。 ```java if (((ex.getErrorCode() == 50000) && ("XJ015".equals(ex.getSQLState())))) { lgr.log(Level.INFO, "Derby shut down normally", ex); } ``` 当 Derby 引擎关闭时,将抛出`SQLException`。 我们捕获此异常并记录一条信息消息。 ```java } finally { try { if (st != null) { st.close(); } if (con != null) { con.close(); } ``` 在`finally`子句中,我们释放资源。 ```java 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`表中获取所有数据。 `SelectAllCars.java` ```java 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`表中获得所有汽车,并将它们打印到控制台。 ```java st = con.createStatement(); rs = st.executeQuery("SELECT * FROM USER12.CARS"); ``` 我们执行一个查询,该查询从`CARS`表中选择所有列。 我们使用`executeQuery()`方法。 该方法执行给定的 SQL 语句,该语句返回单个`ResultSet`对象。 `ResultSet`是 SQL 查询返回的数据表。 还要注意,由于我们尚未在 URL 中指定用户名,因此必须在 SQL 语句中显式提及架构名称。 ```java 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`。 ```java 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`表中的所有汽车的列表。 ## 属性 通常的做法是将配置数据放在程序外部的单独文件中。 我们可以更改用户,密码或连接字符串,而无需重新编译程序。 它在需要大量测试,调试,保护数据等的动态环境中特别有用。 在 Java 中,`Properties`是经常用于存储基本配置数据的类。 该类用于轻松读取和保存键/值属性。 db.properties ```java db.url=jdbc:derby:testdb;user=USER12 db.user=USER12 db.passwd=34klq* db.syshome=/home/janbodnar/.derby ``` 我们有一个`db.roperties`文件,其中有四个键/值对。 这些是在程序执行期间动态加载的。 该文件位于`src/main/resources`目录中。 PropertiesExample.java ```java 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`文件中读取的。 ```java Properties props = new Properties(); FileInputStream in = null; try { in = new FileInputStream("src/main/resources/db.properties"); props.load(in); ``` 创建`Properties`类。 数据是从名为`db.properties`的文件中加载的,其中包含我们的配置数据。 ```java String url = props.getProperty("db.url"); String user = props.getProperty("db.user"); String passwd = props.getProperty("db.passwd"); ``` 使用`getProperty()`方法检索这些值。 ```java con = DriverManager.getConnection(url, user, passwd); ``` 请注意,在默认的 Derby 配置中,密码将被忽略。 ## 预备语句 现在,我们将以预备语句来关注自己。 在编写预备语句时,我们使用占位符,而不是直接将值写入语句中。 预准备的语句可提高安全性和性能。 在 Java 中,`PreparedStatement`是代表预编译的 SQL 语句的对象。 Prepared.java ```java 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 的汽车的价格。 ```java int price = 58000; int id = 2; ``` 这些是将要预备语句的值。 这些值可能来自用户,并且来自用户的所有内容都应被视为潜在危险。 ```java pst = con.prepareStatement("UPDATE CARS SET PRICE = ? WHERE ID = ?"); ``` 在这里,我们创建一个预备语句。 在编写预备语句时,我们使用占位符,而不是直接将值写入语句中。 预备语句更快,并且可以防止 SQL 注入攻击。 `?`是一个占位符,稍后将填充。 ```java pst.setInt(1, price); pst.setInt(2, id); ``` 值绑定到占位符。 ```java pst.executeUpdate(); ``` 执行预备语句。 当我们不希望返回任何数据时,我们使用语句对象的`executeUpdate()`方法。 这是当我们创建数据库或执行`INSERT`,`UPDATE`和`DELETE`语句时。 ```java ij> SELECT * FROM CARS WHERE ID=2; ID |NAME |PRICE ------------------------------------------------------ 2 |Mercedes |58000 1 row selected ``` 运行示例后,我们使用`ij`工具检查结果。 ## 列标题 接下来,我们将展示如何使用数据库表中的数据打印列标题。 我们将列名称称为元数据。 元数据是有关数据库中核心数据的数据。 ColumnHeaders.java ```java 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 文件位于本教程的第一章中。 ```java String query = "SELECT NAME, TITLE From AUTHORS, " + "Books WHERE AUTHORS.ID=BOOKS.AUTHOR_ID"; ``` 这是将作者与他们的书联系在一起的 SQL 语句。 ```java ResultSetMetaData meta = rs.getMetaData(); ``` 要获取列名,我们需要获取`ResultSetMetaData`。 它是一个对象,可用于获取有关`ResultSet`对象中列的类型和属性的信息。 `ResultSetMetaData`是通过`getMetaData()`方法从`ResultSet`获得的。 ```java String colname1 = meta.getColumnName(1); String colname2 = meta.getColumnName(2); ``` 从获得的元数据中,我们使用`getColumnName()`方法获得列名。 ```java Formatter fmt1 = new Formatter(); fmt1.format("%-21s%s", colname1, colname2); System.out.println(fmt1); ``` 我们将列名称打印到控制台。 `Formatter`对象格式化数据。 ```java 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 个字符,并在左侧对齐。 ```java 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`的新表。 ```java ij> CREATE TABLE IMAGES(ID INT PRIMARY KEY, DATA BLOB); 0 rows inserted/updated/deleted ``` `DATA`列具有`BLOB`类型。 在那里,我们将插入编码的二进制数据。 WriteImage.java ```java 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`表中。 ```java File imgFile = new File("woman.jpg"); try (FileInputStream fin = new FileInputStream(imgFile)) { ``` 我们为图像文件创建一个`File`对象。 要从该文件读取字节,我们创建一个`FileInputStream`对象。 ```java pst = con.prepareStatement("INSERT INTO IMAGES(ID, DATA) VALUES(1, ?)"); ``` 该 SQL 语句将图像插入`Images`表。 ```java pst.setBinaryStream(1, fin, (int) img.length()); ``` 二进制流设置为预备语句。 `setBinaryStream()`方法的参数是要绑定的参数索引,输入流和流中的字节数。 ```java pst.executeUpdate(); ``` 我们使用`executeUpdate()`方法执行该语句。 ## 读取图像 在前面的示例中,我们已将图像插入数据库表中。 现在,我们将从表中读取图像。 ReadImage.java ```java 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`表中读取了一张图像。 ```java String query = "SELECT DATA FROM IMAGES WHERE ID = 1"; ``` 选择一条记录。 ```java try (FileOutputStream fos = new FileOutputStream(fileName)) { ``` 创建`FileOutputStream`对象以写入文件。 它旨在写入原始字节流,例如图像数据。 ```java Blob blob = result.getBlob("DATA"); ``` 我们通过调用`getBlob()`方法从`DATA`列中获取图像数据。 ```java int len = (int) blob.length(); ``` 我们找出斑点数据的长度。 换句话说,我们得到字节数。 ```java byte[] buf = blob.getBytes(1, len); ``` `getBytes()`方法以字节数组的形式检索`BLOB`对象的所有字节。 ```java fos.write(buf, 0, len); ``` 字节被写入输出流。 该映像是在文件系统上创建的。 ## 事务支持 事务是针对一个或多个数据库中数据的数据库操作的基本单位。 事务中所有 SQL 语句的影响可以全部提交给数据库,也可以全部回滚。 创建连接后,它处于自动提交模式。 这意味着每个单独的 SQL 语句都被视为事务,并在执行后立即自动提交。 对于所有 JDBC 驱动程序(包括 Derby 的驱动程序)都是如此。 要开始新的事务,我们关闭自动提交。 在直接 SQL 中,事务以`BEGIN TRANSACTION`语句开始,并以`END TRANSACTION` / `COMMIT`语句结束。 在 Derby 中,这些语句是`BEGIN`和`COMMIT`。 但是,在使用驱动程序时,将省略这些语句。 它们由驱动处理。 确切的细节是特定于驱动程序的。 例如,`psycopg2` Python 驱动程序在第一个 SQL 语句之后启动事务。 如果要使用自动提交模式,则必须将`autocommit`属性设置为 True。 相反,默认情况下,JDBC 驱动程序处于自动提交模式。 并且要开始新事务,必须关闭自动提交。 Transaction.java ```java 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`表的第一行中更改作者的姓名。 我们还必须更改与该作者相关的书籍。 如果我们更改作者但不更改作者的书,则数据已损坏。 ```java con.setAutoCommit(false); ``` 要处理事务,必须将`autocommit`设置为`false`。 默认情况下,数据库连接处于自动提交模式。 在这种模式下,每条语句在执行后都会立即提交给数据库。 声明无法撤消。 当自动提交关闭时,我们通过调用`commit()`提交更改,或通过调用`rollback()`方法将其回滚。 ```java st.executeUpdate("UPDATE BOOKS SET TITL = 'Anna Karenina' " + "WHERE Id = 2"); ``` 第三个 SQL 语句有一个错误。 `BOOKS`表中没有`TITL`列。 ```java con.commit(); ``` 如果没有异常,则提交事务。 如果自动提交关闭,则必须显式调用`commit()`方法。 ```java if (con != null) { try { con.rollback(); } catch (SQLException ex1) { lgr.log(Level.WARNING, ex1.getMessage(), ex1); } } ``` 如果发生 Derby 系统关闭以外的异常,则事务将回滚。 没有更改提交到数据库。 ```java 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' is not a column in table`消息。 引发异常。 事务已回滚,并且未进行任何更改。 ```java 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 ``` 数据未损坏。 但是,如果没有事务,数据是不安全的。 NonTransaction.java ```java 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); } } } } ``` 我们有同样的例子。 这次,没有事务支持。 ```java 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 ``` 再次引发异常。 列夫·托尔斯泰(Leo Tolstoy)没有写马丁·伊登(Martin Eden):数据已损坏。 ## 批量更新 当我们需要使用多个语句更新数据时,可以使用批处理更新。 批量更新可用于`INSERT`,`UPDATE`,`DELETE`语句以及`CREATE TABLE`和`DROP TABLE`语句。 BatchUpdates.java ```java 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`表中删除所有行,并在其中插入 9 行。 ```java con.setAutoCommit(false); ``` 进行批处理更新时,应始终关闭自动提交。 ```java 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()`方法向该语句添加新命令。 ```java int counts[] = st.executeBatch(); ``` 添加所有命令后,我们调用`executeBatch()`进行批量更新。 该方法返回已提交更改的数组。 ```java con.commit(); ``` 批处理更新在事务中提交。 ```java 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 编程。