增删改都会使得数据库中的数据发生变化,这种变化称为数据更新,因此在执行insert
、update
、delete
语句时,Statement
对象调用executeUpdate()
方法。
但对数据进行查询时,数据库中的数据没有发生任何变化,因此查询数据时,Statement
对象提供了executeQuery()
方法用于执行查询,该方法返回查询结果集对象ResultSet
。
在对数据进行查询时,可以将ResultSet
理解为一个指针,该指针同一时刻只能执行查询结果集中的一条记录,默认该指针指向查询结果集的表头,调用next()
将该指针下移一行,如果下移后的行有数据,返回true
,否则返回false
。
一、ResultSetMetaData接口
在使用JDBC查询数据时,使用ResultSetMetaData
接口可以获取表的元数据。
方法 | 作用 |
---|---|
int getColumnCount(int column) | 返回表中列的数量 |
String getColumnTypeName(int column) | 获取表中列的数据类型名称 |
String getColumnName(int column) | 获取表中列的名称 |
String getColumnLabel(int column) | 获取表中列的别名(标签) |
boolean isAutoIncrement(int column) | 获取表中的列是否自动增长 |
int isNullable(int column) | 获取表中的列是否可空(非0即真) |
import java.sql.*;
/**
* 使用JDBC获取查询数据的元数据
*
* @author DingYi
* @date 2020/4/19 15:12
*/
public class GetMetaDataDemo {
public static void main(String[] args) {
getMetaData();
}
private static void getMetaData () {
Connection conn = null;
Statement stat = null;
ResultSet rs = null;
try {
// 1. 注册数据库驱动
Class.forName("com.mysql.jdbc.Driver");
// 2. 建立和数据库之间的连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc_demo?useSSL=false&characterEncoding=utf8", "root", "root");
// 3. 拼写sql语句
String sql = "select id as 编号, username as 用户名, userpass as 密码 from users;";
// 4. 向数据库发送并执行sql语句
stat = conn.createStatement();
rs = stat.executeQuery(sql);
// 5. 处理执行结果(元数据)
ResultSetMetaData rsm = rs.getMetaData();
for (int i = 0; i < rsm.getColumnCount(); i ++) {
System.out.println(rsm.getColumnTypeName(i + 1) + " " +
rsm.getColumnName(i + 1) + " " +
rsm.getColumnLabel(i + 1) + " " +
rsm.isAutoIncrement(i + 1) + " " +
rsm.isNullable(i + 1));
}
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
} finally {
// 6. 关闭资源
if(rs != null){
try {
rs.close();
rs = null;
} catch (SQLException e) {
e.printStackTrace();
}
}
if (stat != null) {
try {
stat.close();
stat = null;
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null) {
try {
conn.close();
conn = null;
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
二、PreparedStatement接口
JDBC将SQL语句发送到数据库后,数据库要做3件事:
- 分析SQL语句是否合法
- 编译该SQL语句
- 执行SQL语句
1、PreparedStatement接口
PreparedStatement
接口是Statement
接口的子接口,也是用来将SQL语句发送到数据库中执行并获取返回结果。
PreparedStatement
具有的优点:
- 高效率:如果用一条SQL语句每次都分析语法、编译、执行,显然效率低下。使用
PreparedStatement
接口可以将编译过的 SQL语句缓存,当再次执行缓存过的SQL语句时,会忽略分析语法和编译的过程,提高了运行效率。在开发中建议使用PreparedStatement
接口执行SQL语句。 - 安全:
PreparedStatement
可以防止SQL注入。SQL注入是利用SQL语句的漏洞对数据库发送有潜在威胁的SQL语句。
Statement
接口在执行SQL语句时,会将用户输入的数据作为SQL语句的一部分,导致被SQL注入,带来了安全隐患。PreparedStatement
接口在执行SQL语句时,会将用户输入的数据作为SQL语句的参数值,而不是作为SQL语句的一部分,因此可以防止SQL注入。
import java.sql.*;
/**
* @author DingYi
* @date 2020/4/19 15:47
*/
public class LoginDemo2 {
public static void main(String[] args) {
login("1' or '1' = '1", "1' or '2' = '2");
}
/**
* 登录的方法
* @param username 用户名
* @param userpass 用户密码
*/
private static void login (String username, String userpass) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
// 1. 注册数据库驱动
Class.forName("com.mysql.jdbc.Driver");
// 2. 建立和数据库之间的连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc_demo?useSSL=false&characterEncoding=utf8", "root", "root");
// 3. 拼写sql语句
String sql = "select * from users where username = ? and userpass = ?;";
// 4. 向数据库发送并执行sql语句
ps = conn.prepareStatement(sql);
ps.setObject(1, username);
ps.setObject(2, userpass);
rs = ps.executeQuery();
// 5. 处理执行结果
System.out.println("编号\t用户名\t密码");
while (rs.next()) {
System.out.print(rs.getInt("id"));
System.out.print("\t\t");
System.out.print(rs.getString("username"));
System.out.print("\t");
System.out.print(rs.getString("userpass"));
System.out.println();
}
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
} finally {
// 6. 关闭资源
if(rs != null){
try {
rs.close();
rs = null;
} catch (SQLException e) {
e.printStackTrace();
}
}
if (ps != null) {
try {
ps.close();
ps = null;
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null) {
try {
conn.close();
conn = null;
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
2、获取新增记录的自增长值
添加记录时,如果记录的主键是自增长列,那么自增长列的值是由数据库自动维护的,在记录添加成功后,可以获取到新增记录的主键值。
Statement.RETURN_GENERATED_KEYS
表示获取生成的主键值。PreparedStatement. getGeneratedKeys();
表示获取生成主键值的结果集。
三、JDBC 事务
事务(Transaction)是访问并可能更新数据库中各种数据项的一个程序执行单元(unit
)。在关系数据库中,一个事务可以是一条SQL语句,或者多条SQL语句。事务的执行是由高级编程语言编写的程序来执行的。在Java中使用JDBC操作事务。
1、事务的特性
事务具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。
- 原子性(
Atomicity
):一个事务是一个不可分割的工作单位,事务中包括的所有SQL语句,要么都执行,要么都不执行。原子性是事务最根本的特性。 - 一致性(
Consistency
):事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。 - 隔离性(
Isolation
):一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。 - 持久性(
Durability
):持久性是指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* @author DingYi
* @date 2020/4/21 11:38
*/
public class TransationDemo1 {
public static void main(String[] args) {
transfer();
}
/**
* 转账的方法
*/
private static void transfer() {
Connection conn = null;
PreparedStatement ps = null;
try {
// 1. 注册数据库驱动
Class.forName("com.mysql.jdbc.Driver");
// 2. 建立和数据库之间的连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bank?useSSL=false&characterEncoding=utf8","root","root");
// 老兵转出100元
// 3. 拼写SQL语句
String sql1 = "update customer set balance = balance - ? where customerName = ?;";
// 4. 向数据库发送并执行SQL语句
ps = conn.prepareStatement(sql1);
ps.setObject(1, 100);
ps.setObject(2, "老兵");
int rows1 = ps.executeUpdate();
// 5. 处理执行结果
System.out.println("数据库中有" + rows1 + "条数据被执行...,老兵转出成功!");
if(true){
throw new RuntimeException("转账期间发生异常!");
}
// 新兵转入100元
// 3. 拼写SQL语句
String sql2 = "update customer set balance = balance + ? where customerName = ?;";
// 4. 向数据库发送并执行SQL语句
ps = conn.prepareStatement(sql2);
ps.setObject(1, 100);
ps.setObject(2, "新兵");
int rows2 = ps.executeUpdate();
// 5. 处理执行结果
System.out.println("数据库中有" + rows2 + "条数据被执行...,新兵转入成功!");
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
} finally {
// 6. 关闭资源
if (ps != null) {
try {
ps.close();
ps = null;
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
conn = null;
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
2、事务的类型
事务的执行分为三个步骤:
- 开始事务:
Connection
对象调用setAutoCommit(false)
实现手动开始事务,Connection
对象调用setAutoCommit(true)
实现自动开始事务,默认是自动开始事务。 - 执行事务:
Statement
接口和PreparedStatement
接口调用executeUpdate()
方法时开始执行事务。 - 结束事务
而结束事务有两种情况:
- 提交事务:
Connection
对象调用commit()
方法提交事务。 - 回滚事务:
Connection
对象调用rollback()
方法提回滚务。
注意,同一个事务中的所有SQL语句,必须使用同一个Connection
对象!
如果事务中的所有SQL语句都正确执行了,那么事务会提交,提交后事务结束。如果事务中有任意一条语句执行失败,就会回滚事务,回滚后事务结束。
事务的类型分为两种,分别是自动事务和手动事务。自动事务是JDBC自动开始事务、执行事务、结束事务。JDBC默认使用自动事务,自动事务执行每一条语句时都是一个独立的事务。手动事务是开发人员编写程序来控制事务的开始、事务的执行、事务的结束。如果一个事务中包含两条或以上的SQL语句时,需要用手动事务。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* @author DingYi
* @date 2020/4/21 11:40
*/
public class TransationDemo2 {
public static void main(String[] args) {
transfer();
}
/**
* 转账的方法
*/
private static void transfer() {
Connection conn = null;
PreparedStatement ps = null;
try {
// 1. 注册数据库驱动
Class.forName("com.mysql.jdbc.Driver");
// 2. 建立和数据库之间的连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bank?useSSL=false&characterEncoding=utf8","root","root");
// 开始事务
conn.setAutoCommit(false);
// 老兵转出100元
// 3. 拼写SQL语句
String sql1 = "update customer set balance = balance - ? where customerName = ?;";
// 4. 向数据库发送并执行SQL语句
ps = conn.prepareStatement(sql1);
ps.setObject(1, 100);
ps.setObject(2, "老兵");
int rows1 = ps.executeUpdate();
// 5. 处理执行结果
System.out.println("数据库中有" + rows1 + "条数据被执行...,老兵转出成功!");
if(true){
throw new RuntimeException("转账期间发生异常!");
}
// 新兵转入100元
// 3. 拼写SQL语句
String sql2 = "update customer set balance = balance + ? where customerName = ?;";
// 4. 向数据库发送并执行SQL语句
ps = conn.prepareStatement(sql2);
ps.setObject(1, 100);
ps.setObject(2, "新兵");
int rows2 = ps.executeUpdate();
// 5. 处理执行结果
System.out.println("数据库中有" + rows2 + "条数据被执行...,新兵转入成功!");
// 提交事务
conn.commit();
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
// 回滚事务
try {
conn.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
} finally {
// 6. 关闭资源
if (ps != null) {
try {
ps.close();
ps = null;
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
conn = null;
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}