文章目录
JDBC操作数据库
通过使用 JDBC Statement
, CallableStatement
和PreparedStatement
接口定义的方法和属性,使可以使用 SQL 或 PL/SQL 命令和从数据库接收数据。它们还定义了许多方法,帮助消除 Java 和数据库之间数据类型的差异。
接口 | 应用场景 |
---|---|
Statement | 当在运行时使用静态 SQL 语句时(Statement 接口不能接收参数) |
CallableStatement | 当要访问数据库中的存储过程时(CallableStatement 对象的接口还可以接收运行时输入参数) |
PreparedStatement | 当计划多次使用 SQL 语句时(PreparedStatement 接口接收在运行时输入参数) |
Statement
我们要使用Statement
接口,第一步肯定是创建一个Statement
对象了。我们需要使用Connection
对象的createStatement()
方法进行创建。
Statement stmt = null;
try {
stmt = conn.createStatement( );
. . .
}
catch (SQLException e) {
. . .
}
finally {
. . .
}
一旦创建了一个 Statement 对象,我们就可以用它来执行 SQL 语句了
方法 | 说明 |
---|---|
boolean execute(String SQL) | 如果 ResultSet 对象可以被检索返回布尔值 true,否则返回 false。使用这个方法来执行 SQL DDL 语句,或当需要使用真正的动态 SQL |
int executeUpdate(String SQL) | 用于执行 INSERT、UPDATE 或 DELETE 语句以及 SQLDDL(数据定义语言)语句。返回值是一个整数,指示受影响的行数(即更新计数) |
ResultSet executeQuery(String SQL) | 返回 ResultSet 对象。用于产生单个结果集的语句,例如 SELECT 语句 |
正如关闭一个 Connection 对象来释放数据库连接资源,出于同样的原因,也应该关闭 Statement 对象。
Statement stmt = null;
try {
stmt = conn.createStatement( );
. . .
}
catch (SQLException e) {
. . .
}
finally {
stmt.close();
}
注:如果关闭了 Connection 对象首先它会关闭 Statement 对象,然而应该始终明确关闭 Statement 对象,以确保正确的清除。
PreparedStatement
PreparedStatement
接口扩展了Statement
接口,有利于高效地执行多次使用的 SQL 语句。我们先来创建一个PreparedStatement
对象。 Statement
为一条 SQL 语句生成执行计划。如果要执行两条 SQL 语句,会生成两个执行计划。一万个查询就生成一万个执行计划!
select colume from table where colume=1;
select colume from table where colume=2;
PreparedStatement 用于使用绑定变量重用执行计划。
select colume from table where colume=:x;
通过 set 不同数据,只需要生成一次执行计划,并且可以重用。
PreparedStatement pstmt = null;
try {
/*
在JDBC中所有的参数都被代表?符号,这是已知的参数标记。在执行SQL语句之前,必须提供值的每一个参数。
*/
String SQL = "Update Students SET age = ? WHERE id = ?";
pstmt = conn.prepareStatement(SQL);
. . .
}
/*
setXXX()方法将值绑定到参数,其中XXX表示希望绑定到输入参数值的 Java 数据类型。如果忘了提供值,将收到一个 SQLException。
*/
catch (SQLException e) {
. . .
}
finally {
//同理,我们需要关闭 PreparedStatement 对象
pstmt.close();
}
示例
- 先创建数据库和相应的内容:
create database EXAMPLE;
use EXAMPLE
create table Students
(
id int not null,
age int not null,
name varchar(255),
primary key(id)
);
insert into Students values(1,18,'Tom'),
(2,20,'Aby'),(4,20,'Tomson');
- Java代码
import java.sql.*;
public class JdbcTest {
// JDBC 驱动器的名称和数据库地址
static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
static final String DB_URL = "jdbc:mysql://localhost/EXAMPLE";
static final String USER = "root";
static final String PASS = "";
public static void main(String[] args) {
Connection conn = null;
PreparedStatement stmt = null;
try{
//注册 JDBC 驱动器
Class.forName("com.mysql.jdbc.Driver");
//打开连接
System.out.println("Connecting to database...");
conn = DriverManager.getConnection(DB_URL,USER,PASS);
//执行查询
System.out.println("Creating statement...");
//这里我们要更改一个同学的年龄,参数待定
String sql = "UPDATE Students set age=? WHERE id=?";
stmt = conn.prepareStatement(sql);
//将值绑定到参数,参数从左至右序号为1,2...
stmt.setInt(1, 22); // 绑定 age 的值(序号为1)
stmt.setInt(2, 1); // 绑定 ID 的值
// 更新 ID 为1的同学的年龄
int rows = stmt.executeUpdate();
System.out.println("被影响的行数 : " + rows );
// 查询所有记录,并显示.
sql = "SELECT id, name, age FROM Students";
ResultSet rs = stmt.executeQuery(sql);
//处理结果集
while(rs.next()){
//检索
int id = rs.getInt("id");
int age = rs.getInt("age");
String name = rs.getString("name");
//显示
System.out.print("ID: " + id);
System.out.print(", Age: " + age);
System.out.print(", Name: " + name);
System.out.println();
}
//清理
rs.close();
stmt.close();
conn.close();
}catch(SQLException se){
se.printStackTrace();
}catch(Exception e){
e.printStackTrace();
}finally{
try{
if(stmt!=null)
stmt.close();
}catch(SQLException se2){
}
try{
if(conn!=null)
conn.close();
}catch(SQLException se){
se.printStackTrace();
}
}
System.out.println("Goodbye!");
}
}
CallableStatement
CallableStatement
对象为所有的 DBMS 提供了一种以标准形式调用存储过程的方法。存储过程储存在数据库中。对储存过程的调用是 CallableStatement
对象所含的内容。三种类型的参数有:IN
,OUT
和INOUT
。
PreparedStatement
对象只使用 IN 参数。CallableStatement
对象可以使用所有三个
参数 | 描述 |
---|---|
IN | 它的值是在创建 SQL 语句时未知的参数,将 IN 参数传给 CallableStatement 对象是通过 setXXX() 方法完成的 |
OUT | 其值由它返回的 SQL 语句提供的参数。从 OUT 参数的 getXXX() 方法检索值 |
INOUT | 同时提供输入和输出值的参数,绑定的 setXXX() 方法的变量,并使用 getXXX() 方法检索值 |
在 JDBC 中调用存储过程的语法如下所示。注意,方括号表示其间的内容是可选项;方括号本身并不是语法的组成部份。
{call 存储过程名[(?, ?, ...)]}
返回结果参数的过程的语法为:
{? = call 存储过程名[(?, ?, ...)]}
不带参数的存储过程的语法类似:
{call 存储过程名}
CallableStatement 对象是用 Connection 方法 prepareCall 创建的。
CallableStatement cstmt = null;
try {
String SQL = "{call getEXAMPLEName (?, ?)}";
cstmt = conn.prepareCall (SQL);
. . .
}
catch (SQLException e) {
. . .
}
finally {
cstmt.close();
}
JDBC 结果集
结果集通常是通过执行查询数据库的语句生成,表示数据库查询结果的数据表。
ResultSet 介绍
ResultSet
对象具有指向其当前数据行的光标。最初,光标被置于第一行之前。next
方法将光标移动到下一行;因为该方法在ResultSet
对象没有下一行时返回false
,所以可以在while
循环中使用它来迭代结果集。光标可以方便我们对结果集进行遍历。默认的ResultSet
对象不可更新,仅有一个向前移动的光标。因此,只能迭代它一次,并且只能按从第一行到最后一行的顺序进行。
ResultSet
接口的方法可分为三类:
- 导航方法:用于移动光标
- 获取方法:用于查看当前行的光标所指向的列中的数据
- 更新方法:用于更新当前行的列中的数据
JDBC 提供下列连接方法来创建所需的ResultSet
语句:
createStatement(int RSType, int RSConcurrency);
prepareStatement(String SQL, int RSType, int RSConcurrency);
prepareCall(String sql, int RSType, int RSConcurrency);
RSType
表示 ResultSet 对象的类型
RSConcurrency
是 ResultSet 常量,用于指定一个结果集是否为只读或可更新。
ResultSet 的类型
,如果不指定 ResultSet 类型,将自动获得一个是 TYPE_FORWARD_ONLY:
类型 | 描述 |
---|---|
ResultSet.TYPE_FORWARD_ONLY | 游标只能向前移动的结果集 |
ResultSet.TYPE_SCROLL_INSENSITIVE | 游标可以向前和向后滚动,但不及时更新,就是如果数据库里的数据修改过,并不在 ResultSet 中反应出来 |
ResultSet.TYPE_SCROLL_SENSITIVE | 游标可以向前和向后滚动,并及时跟踪数据库的更新,以便更改 ResultSet 中的数据 |
并发性的 ResultSet,如果不指定任何并发类型,将自动获得一个为 CONCUR_READ_ONLY
并发 | 描述 |
---|---|
ResultSet.CONCUR_READ_ONLY | 创建结果集只读。这是默认的 |
ResultSet.CONCUR_UPDATABLE | 创建一个可更新的结果集 |
如初始化一个 Statement 对象来创建一个双向、可更新的 ResultSet 对象:
try {
Statement stmt = conn.createStatement(
ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_UPDATABLE);
}
catch(Exception ex) {
....
}
finally {
....
}
导航
方法 | 说明 |
---|---|
public void beforeFirst() throws SQLException | 将光标移动到正好位于第一行之前 |
public void afterLast() throws SQLException | 将光标移动到刚刚结束的最后一行 |
public boolean first() throws SQLException | 将光标移动到第一行 |
public void last() throws SQLException | 将光标移动到最后一行 |
public boolean absolute(int row) throws SQLException | 将光标移动到指定的行 |
public boolean relative(int row) throws SQLException | 从它目前所指向向前或向后移动光标行的给定数量 |
public boolean previous() throws SQLException | 将光标移动到上一行。上一行关闭的结果集此方法返回 false |
public boolean next() throws SQLException | 将光标移动到下一行。如果没有更多的行结果集中的此方法返回 false |
public int getRow() throws SQLException | 返回的行号,该光标指向的行 |
public void moveToInsertRow() throws SQLException | 将光标移动到一个特殊的行,可以用来插入新行插入到数据库中的结果集。当前光标位置被记住 |
public void moveToCurrentRow() throws SQLException | 移动光标返回到当前行,如果光标在当前插入行,否则,这个方法不执行任何操作 |
示例
- 先创建数据库和相应的内容:
create database EXAMPLE;
use EXAMPLE
create table Students
(
id int not null,
age int not null,
name varchar(255),
primary key(id)
);
insert into Students values(1,18,'Tom'),
(2,20,'Aby'),(4,20,'Tomson');
- Java代码
import java.sql.*;
public class JdbcTest {
// JDBC 驱动器名称 和数据库地址
static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
//数据库的名称为 EXAMPLE
static final String DB_URL = "jdbc:mysql://localhost/EXAMPLE";
// 数据库用户和密码
static final String USER = "root";
static final String PASS = "";
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
try{
//注册JDBC 驱动程序
Class.forName("com.mysql.jdbc.Driver");
//打开连接
System.out.println("Connecting to database...");
conn = DriverManager.getConnection(DB_URL,USER,PASS);
System.out.println("Creating statement...");
//创建所需的ResultSet,双向,只读
stmt = conn.createStatement(
ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_READ_ONLY);
String sql;
sql = "SELECT id, name, age FROM Students";
ResultSet rs = stmt.executeQuery(sql);
// 将光标移到最后一行
System.out.println("Moving cursor to the last...");
rs.last();
//处理结果集
System.out.println("Displaying record...");
int id = rs.getInt("id");
int age = rs.getInt("age");
String name = rs.getString("name");
//显示
System.out.print("ID: " + id);
System.out.print(", Age: " + age);
System.out.print(", Name: " + name);
System.out.println();
// 将光标移到第一行
System.out.println("Moving cursor to the first row...");
rs.first();
System.out.println("Displaying record...");
id = rs.getInt("id");
age = rs.getInt("age");
name = rs.getString("name");
//显示
System.out.print("ID: " + id);
System.out.print(", Age: " + age);
System.out.print(", Name: " + name);
//将光标移至下一行
System.out.println("Moving cursor to the next row...");
rs.next();
System.out.println("Displaying record...");
id = rs.getInt("id");
age = rs.getInt("age");
name = rs.getString("name");
// 显示
System.out.print("ID: " + id);
System.out.print(", Age: " + age);
System.out.print(", Name: " + name);
rs.close();
stmt.close();
conn.close();
}catch(SQLException se){
se.printStackTrace();
}catch(Exception e){
e.printStackTrace();
}finally{
try{
if(stmt!=null)
stmt.close();
}catch(SQLException se2){
}
try{
if(conn!=null)
conn.close();
}catch(SQLException se){
se.printStackTrace();
}
}
System.out.println("Goodbye!");
}
}
获取
ResultSet
接口中我们经常使用 get
方法来查看结果集。
方法 | 说明 |
---|---|
public int getInt(String columnName) throws SQLException | 当前行中名为 ColumnName 列的值 |
public int getInt(int columnIndex) throws SQLException | 当前行中指定列的索引的值。列索引从 1 开始,意味着一个行的第一列是 1,行的第二列是 2,依此类推 |
当然还有getString()
等等。
更新
方法 | 说明 |
---|---|
public void updateString(int columnIndex, String s) throws SQLException | 指定列中的字符串更改为 s 的值 |
public void updateString(String columnName, String s) throws SQLException | 类似于前面的方法,不同之处在于由它的名称,而不是它的索引指定的列 |
类似的还有updateDouble()
等等。
我们在更新了结果集中的内容,当然需要更新一下数据库了。我们可以调用下面的方法更新数据库。
方法 | 说明 |
---|---|
public void updateRow() | 通过更新数据库中相应的行更新当前行 |
public void deleteRow() | 从数据库中删除当前行 |
public void refreshRow() | 刷新在结果集的数据,以反映最新变化在数据库中 |
public void cancelRowUpdates() | 取消所做的当前行的任何更新 |
public void insertRow() | 插入一行到数据库中。当光标指向插入行此方法只能被调用 |
Statement stmt = conn.createStatement(
ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_UPDATABLE);
String sql = "SELECT id, name, age FROM Students";
ResultSet rs = stmt.executeQuery(sql);
//结果集中插入新行
rs.moveToInsertRow();
rs.updateInt("id",5);
rs.updateString("name","John");
rs.updateInt("age",21);
//更新数据库
rs.insertRow();
JDBC 事务
我们在编写 java 程序的时候,在默认情况下,JDBC 连接是在自动提交模式下,即每个 SQL 语句都是在其完成时提交到数据库。但有时候我们为了提高程序运行的性能或者保持业务流程的完整性,以及使用了分布式事务管理方式,这个时候我们可能想关闭自动提交而自己管理和控制自己的事务。
让多条 SQL 在一个事务中执行,并且保证这些语句是在同一时间共同执行的时候,我们就应该为这多条语句定义一个事务。一个事务是把单个 SQL 语句或一组 SQL 语句作为一个逻辑单元,并且如果事务中任何语句失败,则整个事务失败。
如果我们要启动一个事务,而不是让 JDBC 驱动程序默认使用 auto-commit 模式支持。这个时候我们就要使用 Connection 对象的setAutoCommit()
方法。我们传递一个布尔值 false 到setAutoCommit()
中,就可以关闭自动提交。反之我们传入一个 true 便将其重新打开。
Connection conn = null;
conn = DriverManager.getConnection(URL);
//关闭自动提交
conn.setAutoCommit(false);
我们关闭了自动提交后,**如果我们要提交数据库更改怎么办呢?**这时候就要用到我们的提交和回滚了。我们要提交更改,可以调用commit()
方法:
conn.commit();
尤其不要忘记,在 catch 块内添加回滚事务,表示操作出现异常,撤销事务:
conn.rollback();
插入数据
JDBC 插入数据使用的频率非常高,接下来说明如何使用 JDBC 插入数据。
示例
import java.sql.*;
public class JdbcInsertTest {
public static Connection connection = null;
public static void main(String[] args) {
try {
Class.forName("com.mysql.jdbc.Driver");
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/EXAMPLE", "root", "");
Statement statement = connection.createStatement();
//单条插入
boolean execute = statement.execute("insert into Students values (0,1,'shiyanlou')");
if (execute) {
System.out.println("插入失败");
}else {
System.out.println("单条插入成功");
}
// 批量插入 需要关闭自动提交
connection.setAutoCommit(false);
String sql = "insert into Students values (?,?,?)";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//设置插入的值
for (int i = 1; i <= 10; i++) {
preparedStatement.setInt(1, i);
preparedStatement.setInt(2, i + 10);
preparedStatement.setString(3, "shiyanlou");
preparedStatement.addBatch();
}
//执行批量插入,使用executeBatch 方法
preparedStatement.executeBatch();
//提交到数据库
connection.commit();
System.out.println("提交批量插入完成");
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}
}
}