Java JDBC 数据库编程全解析
在 Java 编程中,使用 JDBC(Java Database Connectivity)进行数据库操作是一项重要的技能。下面将详细介绍如何让 JDBC 示例程序正常工作,以及一些相关的注意事项和更复杂的示例。
让示例程序正常工作
要让 JDBC 示例程序正常工作,需要完成以下几个步骤:
1.
找到 JDBC 驱动
:程序中包含
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
语句,但在某些 JDK 1.1 安装中,可能找不到
JdbcOdbcDriver.class
文件。如果加载语句不起作用,可能是 Java 版本更改导致名称改变,需要重新查阅文档。可以通过注释掉加载语句之后到
catch
子句之前的代码来测试驱动加载是否正常,如果程序没有抛出异常,则说明驱动加载正常。
2.
配置数据库
:以 32 位 Windows 系统为例,具体步骤如下:
- 打开控制面板,选择“32bit ODBC”图标。
- 在打开的选项卡对话框中,对于 JDBC - ODBC 桥,重要的是在“System DSN”中设置数据库,同时为了测试配置和创建查询,还需要在“File DSN”中设置数据库。
- 选择一个已有的数据库,例如将多年维护的“people”数据库导出为逗号分隔的 ASCII 文件(通常扩展名为
.csv
)。在“System DSN”部分选择“Add”,选择文本驱动来处理该文件,并取消勾选“use current directory”,指定导出数据文件的目录。
- 数据库通常表示为单个目录下的一组文件,每个文件通常包含一个表。只包含单个表的数据库称为平面文件数据库,而需要多个表通过连接来产生所需结果的数据库称为关系数据库。
3.
测试配置
:可以通过运行 JDBC 程序示例,直到
Connection c = DriverManager.getConnection(dbUrl, user, password);
语句。如果抛出异常,则说明配置不正确。也可以使用查询生成工具,如 Microsoft Query(随 Microsoft Office 提供)。该工具需要知道数据库的位置,需要在 ODBC 管理员的“File DSN”选项卡中添加新条目,指定文本驱动和数据库所在目录。
4.
生成 SQL 查询
:使用查询工具(如 Microsoft Query)创建查询。以搜索姓氏为“Eckel”且有电子邮件地址的记录为例,步骤如下:
- 启动新查询并使用查询向导,选择“people”数据库。
- 选择“people”表,选择
FIRST
、
LAST
和
EMAIL
列。
- 在“Filter Data”中,选择
LAST
并选择“equals”,参数为“Eckel”,点击“AND”单选按钮。
- 选择
EMAIL
并选择“Is not Null”。
- 在“Sort By”中选择
FIRST
。
- 点击 SQL 按钮,将自动生成正确的 SQL 代码:
SELECT people.FIRST, people.LAST, people.EMAIL
FROM people.csv people
WHERE (people.LAST='Eckel') AND
(people.EMAIL Is Not Null)
ORDER BY people.FIRST
- 修改并粘贴查询 :由于查询工具使用了全限定名称,对于只涉及一个表的查询,可以选择移除大部分名称的“people”限定符:
SELECT FIRST, LAST, EMAIL
FROM people.csv people
WHERE (LAST='Eckel') AND
(EMAIL Is Not Null)
ORDER BY FIRST
为了使程序能够根据命令行参数搜索不同的姓氏,将 SQL 语句改为动态创建的字符串:
"SELECT FIRST, LAST, EMAIL " +
"FROM people.csv people " +
"WHERE " +
"(LAST='" + args[0] + "') " +
" AND (EMAIL Is Not Null) " +
"ORDER BY FIRST";
GUI 版本的查找程序
为了更方便地使用查找程序,可以创建一个 GUI 版本。以下是示例代码:
//: c15:jdbc:VLookup.java
// GUI version of Lookup.java.
// <applet code=VLookup
// width=500 height=200></applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.event.*;
import java.sql.*;
import com.bruceeckel.swing.*;
public class VLookup extends JApplet {
String dbUrl = "jdbc:odbc:people";
String user = "";
String password = "";
Statement s;
JTextField searchFor = new JTextField(20);
JLabel completion =
new JLabel(" ");
JTextArea results = new JTextArea(40, 20);
public void init() {
searchFor.getDocument().addDocumentListener(
new SearchL());
JPanel p = new JPanel();
p.add(new Label("Last name to search for:"));
p.add(searchFor);
p.add(completion);
Container cp = getContentPane();
cp.add(p, BorderLayout.NORTH);
cp.add(results, BorderLayout.CENTER);
try {
// Load the driver (registers itself)
Class.forName(
"sun.jdbc.odbc.JdbcOdbcDriver");
Connection c = DriverManager.getConnection(
dbUrl, user, password);
s = c.createStatement();
} catch(Exception e) {
results.setText(e.toString());
}
}
class SearchL implements DocumentListener {
public void changedUpdate(DocumentEvent e){}
public void insertUpdate(DocumentEvent e){
textValueChanged();
}
public void removeUpdate(DocumentEvent e){
textValueChanged();
}
}
public void textValueChanged() {
ResultSet r;
if(searchFor.getText().length() == 0) {
completion.setText("");
results.setText("");
return;
}
try {
// Name completion:
r = s.executeQuery(
"SELECT LAST FROM people.csv people " +
"WHERE (LAST Like '" +
searchFor.getText() +
"%') ORDER BY LAST");
if(r.next())
completion.setText(
r.getString("last"));
r = s.executeQuery(
"SELECT FIRST, LAST, EMAIL " +
"FROM people.csv people " +
"WHERE (LAST='" +
completion.getText() +
"') AND (EMAIL Is Not Null) " +
"ORDER BY FIRST");
} catch(Exception e) {
results.setText(
searchFor.getText() + "\n");
results.append(e.toString());
return;
}
results.setText("");
try {
while(r.next()) {
results.append(
r.getString("Last") + ", "
+ r.getString("fIRST") +
": " + r.getString("EMAIL") + "\n");
}
} catch(Exception e) {
results.setText(e.toString());
}
}
public static void main(String[] args) {
Console.run(new VLookup(), 500, 200);
}
} ///:~
这个程序添加了名称补全功能,当在文本框中输入姓氏时,会自动查找以该输入开头的姓氏,并显示第一个匹配的姓氏。然后根据该姓氏进行查询并显示结果。
为什么 JDBC API 看起来如此复杂
浏览 JDBC 的在线文档时,可能会觉得它很复杂。特别是
DatabaseMetaData
接口,包含了很多方法,如
dataDefinitionCausesTransactionCommit()
、
getMaxColumnNameLength()
等。这是因为数据库从一开始就处于不断变化的状态,虽然有 SQL 标准,但存在很多变体。JDBC 提供了庞大的
DatabaseMetaData
接口,以便代码能够发现当前连接的特定“标准”SQL 数据库的功能。可以编写简单、可移植的 SQL,但如果要优化速度,就需要深入研究特定数据库的功能,代码量会大幅增加。
更复杂的示例:社区兴趣数据库(CID)
一个更复杂的示例是社区兴趣数据库(Community Interests Database,CID),它是一个位于服务器上的多表数据库,用于提供社区活动的存储库,并允许人们报名参加这些活动。以下是相关的代码和说明:
1.
数据库连接信息
:将数据库驱动、URL、用户名和密码放在一个单独的类中:
//: c15:jdbc:CIDConnect.java
// Database connection information for
// the community interests database (CID).
public class CIDConnect {
// All the information specific to CloudScape:
public static String dbDriver =
"COM.cloudscape.core.JDBCDriver";
public static String dbURL =
"jdbc:cloudscape:d:/docs/_work/JSapienDB";
public static String user = "";
public static String password = "";
} ///:~
- 数据库表结构 :数据库由多个表组成,包含社区成员信息、活动信息、活动地点信息以及成员与活动的关联信息。以下是创建这些表的 SQL 字符串:
//: c15:jdbc:CIDSQL.java
// SQL strings to create the tables for the CID.
public class CIDSQL {
public static String[] sql = {
// Create the MEMBERS table:
"drop table MEMBERS",
"create table MEMBERS " +
"(MEM_ID INTEGER primary key, " +
"MEM_UNAME VARCHAR(12) not null unique, "+
"MEM_LNAME VARCHAR(40), " +
"MEM_FNAME VARCHAR(20), " +
"ADDRESS VARCHAR(40), " +
"CITY VARCHAR(20), " +
"STATE CHAR(4), " +
"ZIP CHAR(5), " +
"PHONE CHAR(12), " +
"EMAIL VARCHAR(30))",
"create unique index " +
"LNAME_IDX on MEMBERS(MEM_LNAME)",
// Create the EVENTS table
"drop table EVENTS",
"create table EVENTS " +
"(EVT_ID INTEGER primary key, " +
"EVT_TITLE VARCHAR(30) not null, " +
"EVT_TYPE VARCHAR(20), " +
"LOC_ID INTEGER, " +
"PRICE DECIMAL, " +
"DATETIME TIMESTAMP)",
"create unique index " +
"TITLE_IDX on EVENTS(EVT_TITLE)",
// Create the EVTMEMS table
"drop table EVTMEMS",
"create table EVTMEMS " +
"(MEM_ID INTEGER not null, " +
"EVT_ID INTEGER not null, " +
"MEM_ORD INTEGER)",
"create unique index " +
"EVTMEM_IDX on EVTMEMS(MEM_ID, EVT_ID)",
// Create the LOCATIONS table
"drop table LOCATIONS",
"create table LOCATIONS " +
"(LOC_ID INTEGER primary key, " +
"LOC_NAME VARCHAR(30) not null, " +
"CONTACT VARCHAR(50), " +
"ADDRESS VARCHAR(40), " +
"CITY VARCHAR(20), " +
"STATE VARCHAR(4), " +
"ZIP VARCHAR(5), " +
"PHONE CHAR(12), " +
"DIRECTIONS VARCHAR(4096))",
"create unique index " +
"NAME_IDX on LOCATIONS(LOC_NAME)",
};
} ///:~
-
创建数据库表
:以下程序使用
CIDConnect和CIDSQL信息加载 JDBC 驱动,连接到数据库,并创建上述表结构:
//: c15:jdbc:CIDCreateTables.java
// Creates database tables for the
// community interests database.
import java.sql.*;
public class CIDCreateTables {
public static void main(String[] args)
throws SQLException, ClassNotFoundException,
IllegalAccessException {
// Load the driver (registers itself)
Class.forName(CIDConnect.dbDriver);
Connection c = DriverManager.getConnection(
CIDConnect.dbURL, CIDConnect.user,
CIDConnect.password);
Statement s = c.createStatement();
for(int i = 0; i < CIDSQL.sql.length; i++) {
System.out.println(CIDSQL.sql[i]);
try {
s.executeUpdate(CIDSQL.sql[i]);
} catch(SQLException sqlEx) {
System.err.println(
"Probably a 'drop table' failed");
}
}
s.close();
c.close();
}
} ///:~
第一次运行该程序时,“drop table”命令可能会失败,会捕获并报告异常,但忽略该异常。这样做是为了方便实验,可以修改定义表的 SQL 并重新运行程序,用新表替换旧表。
4.
加载和测试数据库
:以下程序将一些示例数据加载到数据库中,并进行查询测试:
//: c15:jdbc:LoadDB.java
// Loads and tests the database.
import java.sql.*;
class TestSet {
Object[][] data = {
{ "MEMBERS", new Integer(1),
"dbartlett", "Bartlett", "David",
"123 Mockingbird Lane",
"Gettysburg", "PA", "19312",
"123.456.7890", "bart@you.net" },
{ "MEMBERS", new Integer(2),
"beckel", "Eckel", "Bruce",
"123 Over Rainbow Lane",
"Crested Butte", "CO", "81224",
"123.456.7890", "beckel@you.net" },
{ "MEMBERS", new Integer(3),
"rcastaneda", "Castaneda", "Robert",
"123 Downunder Lane",
"Sydney", "NSW", "12345",
"123.456.7890", "rcastaneda@you.net" },
{ "LOCATIONS", new Integer(1),
"Center for Arts",
"Betty Wright", "123 Elk Ave.",
"Crested Butte", "CO", "81224",
"123.456.7890",
"Go this way then that." },
{ "LOCATIONS", new Integer(2),
"Witts End Conference Center",
"John Wittig", "123 Music Drive",
"Zoneville", "PA", "19123",
"123.456.7890",
"Go that way then this." },
{ "EVENTS", new Integer(1),
"Project Management Myths",
"Software Development",
new Integer(1), new Float(2.50),
"2000-07-17 19:30:00" },
{ "EVENTS", new Integer(2),
"Life of the Crested Dog",
"Archeology",
new Integer(2), new Float(0.00),
"2000-07-19 19:00:00" },
// Match some people with events
{ "EVTMEMS",
new Integer(1), // Dave is going to
new Integer(1), // the Software event.
new Integer(0) },
{ "EVTMEMS",
new Integer(2), // Bruce is going to
new Integer(2), // the Archeology event.
new Integer(0) },
{ "EVTMEMS",
new Integer(3), // Robert is going to
new Integer(1), // the Software event.
new Integer(1) },
{ "EVTMEMS",
new Integer(3), // ... and
new Integer(2), // the Archeology event.
new Integer(1) },
};
// Use the default data set:
public TestSet() {}
// Use a different data set:
public TestSet(Object[][] dat) { data = dat; }
}
public class LoadDB {
Statement statement;
Connection connection;
TestSet tset;
public LoadDB(TestSet t) throws SQLException {
tset = t;
try {
// Load the driver (registers itself)
Class.forName(CIDConnect.dbDriver);
} catch(java.lang.ClassNotFoundException e) {
e.printStackTrace(System.err);
}
connection = DriverManager.getConnection(
CIDConnect.dbURL, CIDConnect.user,
CIDConnect.password);
statement = connection.createStatement();
}
public void cleanup() throws SQLException {
statement.close();
connection.close();
}
public void executeInsert(Object[] data) {
String sql = "insert into "
+ data[0] + " values(";
for(int i = 1; i < data.length; i++) {
if(data[i] instanceof String)
sql += "'" + data[i] + "'";
else
sql += data[i];
if(i < data.length - 1)
sql += ", ";
}
sql += ')';
System.out.println(sql);
try {
statement.executeUpdate(sql);
} catch(SQLException sqlEx) {
System.err.println("Insert failed.");
while (sqlEx != null) {
System.err.println(sqlEx.toString());
sqlEx = sqlEx.getNextException();
}
}
}
public void load() {
for(int i = 0; i< tset.data.length; i++)
executeInsert(tset.data[i]);
}
// Throw exceptions out to console:
public static void main(String[] args)
throws SQLException {
LoadDB db = new LoadDB(new TestSet());
db.load();
try {
// Get a ResultSet from the loaded database:
ResultSet rs = db.statement.executeQuery(
"select " +
"e.EVT_TITLE, m.MEM_LNAME, m.MEM_FNAME "+
"from EVENTS e, MEMBERS m, EVTMEMS em " +
"where em.EVT_ID = 2 " +
"and e.EVT_ID = em.EVT_ID " +
"and m.MEM_ID = em.MEM_ID");
while (rs.next())
System.out.println(
rs.getString(1) + " " +
rs.getString(2) + ", " +
rs.getString(3));
} finally {
db.cleanup();
}
}
} ///:~
TestSet
类包含一个默认的数据集,可以使用不同的数据集创建
TestSet
对象。
executeInsert()
方法使用运行时类型识别(RTTI)来区分字符串数据和非字符串数据,构建 SQL 命令并发送到数据库。
LoadDB
类的构造函数建立数据库连接,
load()
方法遍历数据集并调用
executeInsert()
插入每条记录,
cleanup()
方法关闭语句和连接。数据库加载完成后,使用
executeQuery()
语句生成一个示例结果集,该查询是一个连接多个表的示例。
通过以上步骤和示例,你可以掌握使用 JDBC 进行数据库操作的基本方法,包括简单的查询和复杂的多表操作。同时,了解了 JDBC API 复杂的原因以及如何处理不同数据库的差异。在实际开发中,可以根据具体需求选择合适的数据库和查询方式,以提高程序的性能和可维护性。
Java JDBC 数据库编程全解析
不同场景下的 JDBC 应用分析
简单查询与复杂查询的区别
在前面的示例中,我们看到了简单查询和复杂查询的不同实现。简单查询通常只涉及单个表,如在“people”数据库中搜索特定姓氏的记录:
SELECT FIRST, LAST, EMAIL
FROM people.csv people
WHERE (LAST='Eckel') AND
(EMAIL Is Not Null)
ORDER BY FIRST
而复杂查询可能涉及多个表的连接,如在社区兴趣数据库(CID)中查询参加特定活动的成员信息:
select
e.EVT_TITLE, m.MEM_LNAME, m.MEM_FNAME
from EVENTS e, MEMBERS m, EVTMEMS em
where em.EVT_ID = 2
and e.EVT_ID = em.EVT_ID
and m.MEM_ID = em.MEM_ID
简单查询的优点是代码简单、易于理解和维护,适用于对单个表进行基本的数据检索。复杂查询则可以处理更复杂的业务逻辑,通过连接多个表来获取更全面的数据,但代码复杂度和性能开销相对较高。
性能优化考虑
当使用 JDBC 进行数据库操作时,性能优化是一个重要的考虑因素。以下是一些性能优化的建议:
1.
合理使用索引
:在数据库表中创建适当的索引可以加快查询速度。例如,在 CID 数据库中,为
MEMBERS
表的
MEM_LNAME
列创建了唯一索引:
create unique index
LNAME_IDX on MEMBERS(MEM_LNAME)
这样在根据姓氏进行查询时,数据库可以更快地定位到相关记录。
2.
批量操作
:如果需要插入或更新大量数据,使用批量操作可以减少与数据库的交互次数,提高性能。例如,可以使用
PreparedStatement
的
addBatch()
和
executeBatch()
方法。
3.
避免不必要的查询
:在编写代码时,要确保只查询需要的数据,避免查询过多的列或记录。可以使用
SELECT
语句指定需要的列,而不是使用
SELECT *
。
JDBC 操作流程总结
为了更清晰地展示 JDBC 操作的流程,我们可以用以下 mermaid 流程图表示:
graph TD
A[加载 JDBC 驱动] --> B[建立数据库连接]
B --> C[创建 Statement 对象]
C --> D{执行 SQL 语句}
D -->|查询| E[获取 ResultSet 结果集]
D -->|更新| F[获取受影响的行数]
E --> G[处理结果集]
F --> H[处理更新结果]
G --> I[关闭 ResultSet]
H --> I
I --> J[关闭 Statement]
J --> K[关闭数据库连接]
常见问题及解决方法
在使用 JDBC 进行数据库操作时,可能会遇到一些常见的问题,以下是一些问题及解决方法:
|问题|原因|解决方法|
|----|----|----|
|驱动加载失败|驱动类名错误、驱动文件未找到|检查驱动类名是否正确,确保驱动文件存在于类路径中|
|数据库连接失败|数据库 URL、用户名或密码错误,数据库服务未启动|检查数据库连接信息是否正确,确保数据库服务已启动|
|SQL 语句执行异常|SQL 语法错误、表名或列名错误|检查 SQL 语句的语法,确保表名和列名正确|
总结与展望
通过本文的介绍,我们详细了解了使用 JDBC 进行数据库操作的方法,包括让示例程序正常工作的步骤、GUI 版本的查找程序、JDBC API 复杂的原因以及更复杂的多表数据库示例。在实际开发中,我们可以根据具体需求选择合适的数据库和查询方式,同时注意性能优化和常见问题的处理。
随着技术的不断发展,JDBC 也在不断演进,未来可能会有更多的优化和改进。同时,也会有更多的数据库连接框架和工具出现,帮助开发者更高效地进行数据库操作。但无论如何,掌握 JDBC 的基本原理和操作方法仍然是非常重要的,它是进行 Java 数据库编程的基础。希望本文能对大家在使用 JDBC 进行数据库开发时有所帮助。
超级会员免费看
1556

被折叠的 条评论
为什么被折叠?



