JDBC(一)

Java数据库连接,(Java Database Connectivity,简称JDBC)是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数据库中数据的方法。JDBC是面向关系型数据库的。

JDBC本质:官方(sun公司)定义的一套操作所有关系型数据库的规则,即接口。各个数据库厂商去实现这套接口,提供数据库驱动jar包。我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类

(理解:Heart接口,Person实现类。Heart h = new Person()多态),执行h.beat,其实执行的是Person实现类中重写的beat方法)

JDBC入门

步骤

1.导入jar包

mysql-connector包已经提前下载好了

在项目中创建一个libs文件夹,将jar包复制到其中。

文件目录结构

注意要将复制的jar包导入驱动:

注意要将复制的jar包导入驱动

2.注册驱动

3.获取数据库连接对象

4.定义sql语句

5.执行sql的对象Statement

6.执行sql

7.处理结果

8.释放资源

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;

public class Demo01 {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
// 1.导入驱动jar包
// 2.注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 3.获取数据库连接对象
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc_study", "root", "1234");
// 4.定义sql语句
String sql = "update employees set age = 30 where name = 'jay';";
// 5.获取执行sql的对象Statement
Statement statement = connection.createStatement();
// 6.执行sql
int count = statement.executeUpdate(sql);
// 7.处理结果
System.out.println("一共执行了"+count+"条数据");
// 8.释放资源
statement.close();
connection.close();
}
}

JDBC各个类分析

  • DriverManager:驱动管理对象
  • Connection:数据库连接对象
  • Statement:执行sql的对象
  • ResultSet:结果集对象,封装查询结果
  • PreparedStatement:执行sql的对象

DriverManager:驱动管理对象

功能:

1.注册驱动:告诉程序该使用哪一个数据库驱动(jar包)

static void registerDriver(Dirver driver):注册与给定的驱动DriverManager

使用时:Class.forName("com.mysql.jdbc.Driver");

源码:在com.mysql.jdbc.Driver类中的静态代码块如下:

1
2
3
4
5
6
7
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}

注意:mysql5之后的驱动jar包可以省略注册驱动步骤

原因:在META-INF文件夹下有以下文件:

2.获取数据库连接

方法:static Connection getConnection(String url,String user,String password)

三个参数

  • url:指定连接的路径
    • 语法:jdbc:mysql://ip地址(域名):端口号/数据库名称
    • 例子:jdbc:mysql://localhost:3306/jdbc_study
    • 细节:如果连接的是本地的mysql服务并且服务器默认端口是3306,则url可以简写为:jdbc:mysql:///数据库名称
  • user:用户名
  • password:密码

Connection:数据库连接对象

功能:

1.执行sql的对象

  • Statement createStatement()
  • PreparedStatement prepareStatement(String sql)

2.管理事务

  • 开启事务:setAutoCommit(boolean autoCommit)调用该方法设置参数为false,即开启事务
  • 提交事务:commit()
  • 回滚事务:rollback()

Statement:执行sql的对象

功能:

1.执行sql

  • boolean execute(String sql)可以执行任意的sql(了解)
  • int executeUpdate(String sql):执行DMLDDL语句。
    • 返回值:影响的行数,即操作的记录数。可以通过这个影响行数判断DML语句是否成功。返回值>0则表示执行成功,反之则失败。
  • ResultSet executeQuery(String sql):执行DQL语句

ResultSet:结果集对象,封装查询结果

两个方法:

  • boolean next():游标向下移动一行,并判断当前行是否是最后一行,如果是返回false,如果不是则返回true
  • getXxx(参数):获取数据
    • Xxx表示数据类型,如:int getInt()String getString()
    • 参数:
      • 参数为int:表示列的编号,从1开始,如:getString(1)
      • 参数为string:表示列的名称。如:getInt("age")

例子:使用jdbc查询employees表中的id=1和id=4的数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package day01;

import java.sql.*;

public class Demo03 {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
// 1.导入驱动jar包
// 2.注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 3.获取数据库连接对象
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc_study", "root", "1234");
// 4.定义sql语句
String sql = "select * from employees where id = 1 or id = 4;";
// 5.执行sql的对象Statement
Statement statement = connection.createStatement();
// 6.执行sql
ResultSet resultSet = statement.executeQuery(sql);
// 7.处理结果
System.out.println("查询结果为:");
while (resultSet.next()){ // 游标从第0行开始向下移动,到最后一行结束
int id = resultSet.getInt(1); // 参数为1表示返回第一列数据(游标所指向的行的)
String name = resultSet.getString("name"); // 参数为"name"表示返回列名为name的数据(游标所指向的行的)
int age = resultSet.getInt("age"); // 参数为"age"表示返回列名为age的数据(游标所指向的行的)
System.out.println(id+" "+name+" "+age);
}
// 8.释放资源
statement.close();
connection.close();
resultSet.close();
}
}

执行结果为:

PreparedStatement:执行sql的对象

sql注入基础原理(超详细)

史上最完整的MySQL注入

sql注入问题:在拼接sql时,有一些sql的特殊关键字符参与字符串的拼接。会造成安全性问题

解决sql注入问题:使用PreparedStatement对象。

预编译的sql:sql的参数使用?作为占位符,如:

1
select * from user where username = ? and password = ?;

使用步骤:

  1. 导入jar包
  2. 注册驱动
  3. 获取数据库连接Connection对象
  4. 定义sql(注意sql参数使用?作为占位符。如select * from user where username = ? and password = ?;)
  5. 获取执行sql的PreparedStatement对象:connection.preparedStatement(String sql)
  6. ?赋值
    • 方法:setXxx(参数1,参数2) (Xxx为数据类型,如getInt)
      • 参数1:?的位置编号,从1开始
      • 参数2:?的值
  7. 执行sql,接受返回结果,不需要传递sql语句
  8. 处理结果
  9. 释放资源

注意:后面我们都会使用PreparedStatement来完成增删改查

  • 可以防止sql注入
  • 效率更高

JDBC增删改练习

针对我们现有的employees表,我们通过JDBC进行以下操作(DML):

添加一条记录

符合规范的jdbc代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package day01;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;

public class Demo02 {
public static void main(String[] args) {
Connection connection = null;
Statement statement = null;
// 2.注册驱动
try {
Class.forName("com.mysql.jdbc.Driver");
// 3.获取数据库连接对象
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc_study", "root", "1234");
// 4.定义sql语句
String sql = " insert into employees values (4,'tom',18);";
// 5.执行sql的对象Statement
statement = connection.createStatement();
// 6.执行sql
int count = statement.executeUpdate(sql);
// 7.处理结果
System.out.println("一共执行了"+count+"条记录");
if (count>0){
System.out.println("sql执行成功!");
}else {
System.out.println("sql执行失败!");
}
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
} finally { // 将释放资源放到finally代码块,保证了资源释放操作一定会执行
// 8.释放资源
// 这里的try-catch是为了避免空指针异常
if (statement!=null){
try {
statement.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (connection!=null){
try {
connection.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
}

修改记录

在上述代码的基础上,将String sql = " insert into employees values (4,'tom',18);";修改为:

1
String sql = " update employees set age = 20 where name = 'tom';";

删除一条记录

在上述代码的基础上,将String sql = " update employees set age = 20 where name = 'tom';";修改为:

1
String sql = " delete from employees where id = 2;";

JDBC查询数据(JDBC select语句)

查询emp表中所有数据并将其封装为对象,然后装载集合。将查询结果打印在控制台上。

其中emp表的数据和结构如下:

emp数据

emp表结构

  1. 定义emp类
  2. 定义方法public List<Emp> findAll(){}
  3. 实现功能select * from emp;

我们在domain包下创建Emp.java类,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package day01.domain;

import java.math.BigDecimal;
import java.util.Date;

/**
* 封装emp表数据的JavaBean
*/
public class Emp {
private Integer id; // 对应表中id字段
private String ename; // 对应表中ename字段
private Integer job_id; // 对应表中job_id字段
private Integer mgr; // 对应表中mgr字段
private Date joindate; // 对应表中joindate字段
private BigDecimal salary; // 对应表中salary字段
private BigDecimal bonus; // 对应表中bonus字段
private Integer dept_id; // 对应表中dept_id字段

// 此处省略Getter和Setter方法
@Override
public String toString() {
return "Emp{" +
"id=" + id +
", ename='" + ename + '\'' +
", job_id=" + job_id +
", mgr=" + mgr +
", joindate=" + joindate +
", salary=" + salary +
", bonus=" + bonus +
", dept_id=" + dept_id +
'}';
}
}

(省略Alt+Insert快速生成的Getter和Setter方法)

然后创建主启动类Demo05.java(含findAll方法),代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
package day01;

import day01.domain.Emp;

import java.math.BigDecimal;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

public class Demo05 {
public static void main(String[] args) {
List<Emp> list = findAll();
for (Emp emp : list) {
System.out.println(emp);
}
}

// 查询所有emp对象的方法
public static List<Emp> findAll(){
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
List<Emp> empList = null;
try {
// 1.注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2.获取数据库连接对象
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc_study", "root", "1234");
// 3.定义sql语句
String sql = "select * from emp;";
// 4.获取执行sql的对象Statement
statement = connection.createStatement();
// 5.执行sql
resultSet = statement.executeQuery(sql);
// 6.遍历结果集,封装对象,装载集合
empList = new ArrayList<>(); // 在循环前创建一个集合用来存储获取的emp对象
Emp emp = null; // 放在外面,减少内存
while (resultSet.next()){
// 获取数据
int id = resultSet.getInt("id");
String ename = resultSet.getString("ename");
int job_id = resultSet.getInt("job_id");
int mgr = resultSet.getInt("mgr");
Date joindate = resultSet.getDate("joindate");
BigDecimal salary = resultSet.getBigDecimal("salary");
BigDecimal bonus = resultSet.getBigDecimal("bonus");
int dept_id = resultSet.getInt("dept_id");
// 创建emp对象,并赋值给其成员变量
emp = new Emp();
emp.setId(id);
emp.setEname(ename);
emp.setJob_id(job_id);
emp.setMgr(mgr);
emp.setJoindate(joindate);
emp.setSalary(salary);
emp.setBonus(bonus);
emp.setDept_id(dept_id);
// 装载集合
empList.add(emp);
}

} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
// 释放资源
// 这里的try-catch是为了避免空指针异常
if (statement!=null){
try {
statement.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (connection!=null){
try {
connection.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (resultSet!=null){
try {
resultSet.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
return empList;
}
}

执行结果如下:

执行结果

这里给出数据库类型和Java数据类型对应如下:

类型名称 显示长度 数据库类型 JAVA类型 JDBC类型索引(int)
VARCHAR L+N VARCHAR java.lang.String 12
CHAR N CHAR java.lang.String 1
BLOB L+N BLOB java.lang.byte[] -4
TEXT 65535 VARCHAR java.lang.String -1
INTEGER 4 INTEGER UNSIGNED java.lang.Long 4
TINYINT 3 TINYINT UNSIGNED java.lang.Integer -6
SMALLINT 5 SMALLINT UNSIGNED java.lang.Integer 5
MEDIUMINT 8 MEDIUMINT UNSIGNED java.lang.Integer 4
BIT 1 BIT java.lang.Boolean -7
BIGINT 20 BIGINT UNSIGNED java.math.BigInteger -5
FLOAT 4+8 FLOAT java.lang.Float 7
DOUBLE 22 DOUBLE java.lang.Double 8
DECIMAL 11 DECIMAL java.math.BigDecimal 3
BOOLEAN 1 同TINYINT
ID 11 PK (INTEGER UNSIGNED) java.lang.Long 4
DATE 10 DATE java.sql.Date 91
TIME 8 TIME java.sql.Time 92
DATETIME 19 DATETIME java.sql.Timestamp 93
TIMESTAMP 19 TIMESTAMP java.sql.Timestamp 93
YEAR 4 YEAR java.sql.Date 91

JDBC工具类

JDBC工具类(可以体会到配置文件的优点)

从上述的[查询数据](#JDBC查询数据(JDBC select语句))例子中可以发现,有很多重复的代码。

所以我们试着抽取JDBC工具类:

目的:简化代码

分析:

  1. 抽取注册驱动

  2. 抽取一个方法来获取数据库连接对象

    • 需求:在getConnection()方法中不想传递参数,同时还得保证工具类的通用性

    • 解决:使用配置文件

      在src文件夹下创建配置文件jdbc.properties文件,代码如下:

      1
      2
      3
      4
      url=jdbc:mysql://localhost:3306/jdbc_study
      user=root
      password=1234
      driver=com.mysql.jdbc.Driver
  3. 抽取一个方法来释放资源

例子:我们在创建util包下创建JDBCUtils.java类,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
package day01.util;

import java.io.FileReader;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;

// JDBC工具类
public class JDBCUtils {
private static String url;
private static String user;
private static String password;
private static String driver;

// 文件的读取,只需要读取一次即可拿到这些值。使用静态代码块
static {
// 读取资源文件,获取值
// 以下步骤具体可看反射笔记(https://www.qingbo1011.top/2021/01/22/%E5%8F%8D%E5%B0%84/#%E6%A1%88%E4%BE%8B)
try {
// 1.创建Properties集合类
Properties properties = new Properties();

// 2.加载文件
properties.load(new FileReader("src/jdbc.properties"));

// 3.获取数据,赋值
url = properties.getProperty("url");
user = properties.getProperty("user");
password = properties.getProperty("password");
driver = properties.getProperty("driver");

// 4.注册驱动
Class.forName(driver);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}


// 获取连接对象
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(url,user,password);
}


// 释放资源(有两种情况,需要重载方法)
public static void close(Statement statement, Connection connection){
if (statement != null){
try {
statement.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (connection != null){
try {
connection.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
public static void close(Statement statement, Connection connection, ResultSet resultSet){
if (statement != null){
try {
statement.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (connection != null){
try {
connection.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (resultSet != null){
try {
resultSet.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}

在src文件夹下创建配置文件jdbc.properties文件,代码如下:

1
2
3
4
url=jdbc:mysql://localhost:3306/jdbc_study
user=root
password=1234
driver=com.mysql.jdbc.Driver

然后我们创建主启动类Demo04.java,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package day01;

import day01.domain.Emp;
import day01.util.JDBCUtils;

import java.math.BigDecimal;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

public class Demo04 {
public static void main(String[] args) {
List<Emp> list = findAll();
for (Emp emp : list) {
System.out.println(emp);
}
}

// 查询所有emp对象的方法
public static List<Emp> findAll(){
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
List<Emp> empList = null;
try {
// 1.注册驱动,获取数据库连接对象
connection = JDBCUtils.getConnection();
// 2.定义sql语句
String sql = "select * from emp;";
// 3.获取执行sql的对象Statement
statement = connection.createStatement();
// 4.执行sql
resultSet = statement.executeQuery(sql);
// 5.遍历结果集,封装对象,装载集合
empList = new ArrayList<>(); // 在循环前创建一个集合用来存储获取的emp对象
Emp emp = null; // 放在外面,减少内存
while (resultSet.next()){
// 获取数据
int id = resultSet.getInt("id");
String ename = resultSet.getString("ename");
int job_id = resultSet.getInt("job_id");
int mgr = resultSet.getInt("mgr");
Date joindate = resultSet.getDate("joindate");
BigDecimal salary = resultSet.getBigDecimal("salary");
BigDecimal bonus = resultSet.getBigDecimal("bonus");
int dept_id = resultSet.getInt("dept_id");
// 创建emp对象,并赋值给其成员变量
emp = new Emp();
emp.setId(id);
emp.setEname(ename);
emp.setJob_id(job_id);
emp.setMgr(mgr);
emp.setJoindate(joindate);
emp.setSalary(salary);
emp.setBonus(bonus);
emp.setDept_id(dept_id);
// 装载集合
empList.add(emp);
}
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
// 释放资源
JDBCUtils.close(statement,connection,resultSet);
}
return empList;
}
}

执行结果和上述的[查询数据](#JDBC查询数据(JDBC select语句))例子的结果是一样的

JDBC登录案例

需求:

  • 通过键盘录入用户名和密码
  • 判断用户是否登录成功(用户名和密码存储在user表中)

现有user表如下:

user数据

JDBC工具类JDBCUtils.java和jdbc.properties配置文件同上JDBC工具类

使用createStatement()来执行sql

创建主启动类Demo06.java代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package day01.util;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Scanner;

public class Demo06 {
public static void main(String[] args) {
// 1.键盘录入接受用户名和密码
Scanner scanner = new Scanner(System.in);
System.out.print("请输入用户名:");
String username = scanner.nextLine();
System.out.print("请输入密码:");
String password = scanner.nextLine();
// 2.调用方法
boolean flag = login(username, password);
if (flag){
System.out.println("登陆成功!");
}else {
System.out.println("登录失败!用户名或密码错误");
}
}

public static boolean login(String username, String password){
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
if (username==null || password==null){
System.out.println("用户名或密码不能为空!");
return false;
}else {
try {
// 1.获取连接
connection = JDBCUtils.getConnection();
// 2.定义sql语句
String sql = "select * from user where username = '"+username+"' and password = '"+password+"';";
// 3.获取执行sql的对象Statement
statement = connection.createStatement();
// 4.执行sql
resultSet = statement.executeQuery(sql);
// 5.判断
return resultSet.next(); // 如果有下一行则返回true
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
JDBCUtils.close(statement,connection,resultSet);
}
}
return false; // 如果出现异常则返回false
}
}

这里会出现一个问题,那就是sql注入。关于sql注入在上文已经给出说明了。直接来看一个例子:

sql注入导致错误的用户名和密码却可以登录成功

为了解决sql注入的问题,我们使用prepareStatement(sql)来执行sql

使用prepareStatement(sql)来执行sql

创建主启动类Demo07.java代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package day01;

import day01.util.JDBCUtils;

import java.sql.*;
import java.util.Scanner;

public class Demo07 {
public static void main(String[] args) {
// 1.键盘录入接受用户名和密码
Scanner scanner = new Scanner(System.in);
System.out.print("请输入用户名:");
String username = scanner.nextLine();
System.out.print("请输入密码:");
String password = scanner.nextLine();
// 2.调用方法
boolean flag = login(username, password);
if (flag){
System.out.println("登陆成功!");
}else {
System.out.println("登录失败!用户名或密码错误");
}
}

public static boolean login(String username, String password){
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
if (username==null || password==null){
System.out.println("用户名或密码不能为空!");
return false;
}else {
try {
// 1.获取连接
connection = JDBCUtils.getConnection();
// 2.定义sql语句
String sql = "select * from user where username = ? and password = ?;";
// 3.获取执行sql的对象Statement
preparedStatement = connection.prepareStatement(sql); // preparedStatement生成时需要传递sql
// 给占位符?赋值
preparedStatement.setString(1,username);
preparedStatement.setString(2,password);
// 4.执行sql
System.out.println("执行的sql为:"+sql);
resultSet = preparedStatement.executeQuery();
// 5.判断
return resultSet.next(); // 如果有下一行则返回true
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
JDBCUtils.close(preparedStatement,connection,resultSet);
}
}
return false; // 如果出现异常则返回false
}
}

执行效果为:

正确的用户名额密码可以登录成功

成功防止sql注入

在后续开发中我们都会使用PreparedStatement来完成增删改查


JDBC管理事务

关于事务的讲解,之前的笔记有讲到过。这里再简单说一下:

事务:一个包含多个步骤的业务操作。如果这个业务操作被事务管理,则这多个步骤要么同时成功,要么同时失败。

相关操作:

  • 开启事务
  • 提交事务
  • 回滚事务

在JDBC中,我们使用Connection对象来管理事务:

  • 开启事务:setAutoCommit(boolean autoCommit):要调用该方法则设置参数为false,即开启事务
    • 在执行sql之前开启事务
  • 提交事务:commit()
    • 当所有sql都执行完提交事务
  • 回滚事务:rollback()
    • try-catch中在catch块里回滚事务

例子:

我们有account表如下:

account表数据

account表结构

JDBC工具类中添加close的重载方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static void close(Statement statement1, Statement statement2, Connection connection) {
if (statement1 != null) {
try {
statement1.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (statement2 != null) {
try {
statement2.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}

配置文件同上

创建主启动类Demo09.java,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package day01;

import day01.util.JDBCUtils;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class Demo08 {
public static void main(String[] args) {
Connection connection = null;
PreparedStatement preparedStatement1 = null;
PreparedStatement preparedStatement2 = null;
ResultSet resultSet = null;
try {
// 1.获取连接
connection = JDBCUtils.getConnection();
// a.开启事务
connection.setAutoCommit(false); // 参数设置为false即为开启事务
// 2.定义sql
// 2-1 转账方扣钱
String sql1 = "update account set balance = balance - ? where NAME = ?";
// 2-2 收账方扣钱
String sql2 = "update account set balance = balance + ? where NAME = ?";
// 3.获取执行sql对象
preparedStatement1 = connection.prepareStatement(sql1);
preparedStatement2 = connection.prepareStatement(sql2);
// 4.设置参数
preparedStatement1.setDouble(1,500);
preparedStatement1.setString(2,"zhangsan");

preparedStatement2.setDouble(1,500);
preparedStatement2.setString(2,"lisi");
// 5.执行sql
preparedStatement1.executeUpdate();
// 人为制造异常
int i = 1/0;
preparedStatement2.executeUpdate();
// b.提交事务
connection.commit();
} catch (Exception exception) { // 这里的异常要改为所有异常的父类Exception,因为不管发生什么异常,都需要事务回滚
// c.事务回滚
try {
if (connection!=null){ // 回滚前要先判断connection是否为空
connection.rollback();
}
} catch (SQLException e) {
e.printStackTrace();
}
exception.printStackTrace();
} finally {
// 释放资源
JDBCUtils.close(preparedStatement1,preparedStatement2,connection); // 注意要在在上述JDBC工具类中添加相应的close的重载方法
}
}
}