在 Derby 中使用 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
依赖关系用于客户端/服务器应用。
<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://localhost: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
表,则应该从数据库中删除该表。
CreateCars.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。
String url = "jdbc:derby:testdb;user=USER12";
这是用于以嵌入式模式和USER12
模式连接到testdb
数据库的 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
语句以及类似CREATE TABLE
的 DDL 语句,我们使用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
表中获取所有数据。
SelectAllCars.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
表中获得所有汽车,并将它们打印到控制台。
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
表中的所有汽车的列表。
属性
通常的做法是将配置数据放在程序外部的单独文件中。 我们可以更改用户,密码或连接字符串,而无需重新编译程序。 它在需要大量测试,调试,保护数据等的动态环境中特别有用。
在 Java 中,Properties
是经常用于存储基本配置数据的类。 该类用于轻松读取和保存键/值属性。
db.properties
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
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 语句的对象。
Prepared.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 的汽车的价格。
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
工具检查结果。
列标题
接下来,我们将展示如何使用数据库表中的数据打印列标题。 我们将列名称称为元数据。 元数据是有关数据库中核心数据的数据。
ColumnHeaders.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 文件位于本教程的第一章中。
String query = "SELECT NAME, TITLE From AUTHORS, "
+ "Books WHERE AUTHORS.ID=BOOKS.AUTHOR_ID";
这是将作者与他们的书联系在一起的 SQL 语句。
ResultSetMetaData meta = rs.getMetaData();
要获取列名,我们需要获取ResultSetMetaData
。 它是一个对象,可用于获取有关ResultSet
对象中列的类型和属性的信息。 ResultSetMetaData
是通过getMetaData()
方法从ResultSet
获得的。
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
类型。 在那里,我们将插入编码的二进制数据。
WriteImage.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
表中。
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()
方法执行该语句。
读取图像
在前面的示例中,我们已将图像插入数据库表中。 现在,我们将从表中读取图像。
ReadImage.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
表中读取了一张图像。
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();
我们找出斑点数据的长度。 换句话说,我们得到字节数。
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 语句之后启动事务。 如果要使用自动提交模式,则必须将autocommit
属性设置为 True。 相反,默认情况下,JDBC 驱动程序处于自动提交模式。 并且要开始新事务,必须关闭自动提交。
Transaction.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
表的第一行中更改作者的姓名。 我们还必须更改与该作者相关的书籍。 如果我们更改作者但不更改作者的书,则数据已损坏。
con.setAutoCommit(false);
要处理事务,必须将autocommit
设置为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' is not a column in table
消息。 引发异常。 事务已回滚,并且未进行任何更改。
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
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
再次引发异常。 列夫·托尔斯泰(Leo Tolstoy)没有写马丁·伊登(Martin Eden):数据已损坏。
批量更新
当我们需要使用多个语句更新数据时,可以使用批处理更新。 批量更新可用于INSERT
,UPDATE
,DELETE
语句以及CREATE TABLE
和DROP TABLE
语句。
BatchUpdates.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 行。
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 编程。