JDBC的使用(详细)

1. JDBC是什么

Java DataBase Connectivity(java语言连接数据库)

2. JDBC本质是什么

JDBC是SUN公司制定的一套接口

接口都有调用者和实现者

面向接口调用、面向接口写实现类,这都属于面向接口编程

为什么要面向接口编程?

解耦合:降低程序的耦合度,提高程序的扩展力

多态机制就是非常典型的:面向抽象编程(不需要面向具体编程)

建议:

Animal a = new Cat();

Animal b = new Dog();

// 喂养的方法

public void feed(Animal a){

}

// 不建议:

Dog d = new Dog()

Cat c = new Cat()

3. JDBC开发前的准备工作,先从官网下载对应的驱动jar包,然后将其配置到环境变量classpath当中

classpath=.;D:\MYSQL_CONNECTION\mysql-connector-java-5.1.47\mysql-connector-java-5.1.47\mysql-connector-java-5.1.47-bin.jar;

以上的配置是针对于文本编辑器的方式开发,使用IDEA工具的时候,不需要配置以上的环境变量

IDEA有自己的配置方式

4. JDBC编程六步(需要背会)

第一步:注册驱动(告诉java程序,即将连接的是哪一个品牌的数据库)

第二步:获取连接(表示JVM的进程和数据库进程之间的通道打开了,这属于进程之间的通信,重量级的,使用完一定要关闭)

第三步:获取数据库操作对象(专门执行sql语句的对象)

第四步:执行SQL语句

第五步:处理查询结果(只有当第四步执行的时select语句时,才有这第五步处理查询的结果集)

第六步:释放资源(使用完资源之后就一定要关闭资源。Java和数据库属于进程间的通信,开启后一定要关闭)

// JDBC编程六步

import java.sql.Driver;

import java.sql.DriverManager;

import java.sql.SQLException;

import java.sql.Connection;

public class JDBCTest01 {

public static void main(String[] args) {

Connection conn = null;

Statement stmt = null;

try {

// 1. 注册驱动

Driver driver = new com.mysql.jdbc.Driver(); // 多态,父类型指向子类型对象

DriverManager.registerDriver(driver);

// 2. 获取连接

/**

* url: 统一资源定位符(网络中某个资源的绝对路径)

* url 包括哪几个部分?

* 协议 IP PORT 资源名

* jdbc:mysql://127.0.0.1:3306/bjpowernode

* jdbc:mysql:// 协议

*127.0.0.1 IP地址

*3306 mysql数据库端口号

* bjpowernode 具体的数据库实例名

* 说明:localhost和127.0.0.1都是本机IP地址

* 什么是通信协议,有什么用?

* 通信协议是通信之前就提前定好的数据传送格式

* 数据包具体怎么传数据,格式是提前定好的

*/

String url = "jdbc:mysql://127.0.0.1:3306/bjpowernode";

String user = "root";

String password = "123456";

conn = DriverManager.getConnection(url, user, password);

System.out.println("数据库连接对象 = " + conn);

// 3. 获取数据库操作对象(Statement专门执行sql语句的)

stmt = conn.createStatement();

// 4. 执行sql

String sql = insert into dept(deptno,dname,loc) values('50','人事部','北京');

// 执行DML语句(insert delete update)

// 返回值是“影响数据库中的记录条数”

int count = stmt.executeUpdate(sql);

System.out.println(count == 1 ? "保存成功" : "保存失败");

// 5. 处理查询结果集

} catch(SQLException e) {

e.printStackTrace();

} finally {

// 6. 释放资源

// 为了保证资源一定释放,在finally语句块中关闭资源

// 并且要遵循从小到大依次关闭

// 分别对其try...catch...

try {

if(stmt != null) {

stmt.close();

}

} catch(SQLException e) {

e.printStackTrace();

}

try {

if(conn != null) {

conn.close();

}

} catch(SQLException e) {

e.printStackTrace();

}

}

}

}

5. 注册驱动的另一种方式(常用)

import java.sql.*;

public class JDBCTest01 {

public static void main(String[] args) {

try {

// 1. 注册驱动

// 这是注册驱动的第一种写法

// DriverManager.registerDriver(new com.mysql.jdbc.Driver());

// 注册驱动的第二种方式:常用的

// 为什么这种方式常用,因为参数是一个字符串,字符串可以写到xxx.properties文件中

// 以下方法不需要接收返回值,因为我们只想用它的类加载动作

Class.forName("com.mysql.jdbc.Driver");

// 2. 获取连接

Connection conn = DriverManager.getConncetion("jdbc:mysql://127.0.0.1:3306/bjpowernode", "root", "123456");

System.out.println(conn);

} catch(SQLException e) {

e.printStackTrace();

}

}

6.将连接数据库的所有信息配置到配置文件中

首先需要编辑一个properties文件

driver=com.sql.jdbc.Driver

url=jdbc:mysql://127.0.0.1:3306/bjpowernode

user=root

password=123456

在java文件中,使用资源绑定器绑定属性配置文件

ResourceBundle bundle = ResourceBundle.getBundle("jdbc");

String driver = bundle.getString("driver");

String url = bundle.getString("url");

String user = bundle.getString("user");

String password = bundle.getString("password");

这样在java程序中就不需要将连接数据库的信息写死了

例如:

// 1. 注册驱动

Class.forName(driver);

// 2. 获取连接

Connection conn = DriverManager.getConncetion("url", "user", "password");

7. 处理查询结果

这里省略一二三步,直接到第四步

// 执行sql

String sql = "select empno,ename,sal from emp";

//这里的rs是ResultSet用于存储从数据库获取的结果集合的对象

rs = stmt.executeQuery(sql) // 专门执行DQL的方法

// 处理查询结果集

// 使用while循环遍历

while(re.next()) {

// 注意:getString()方法的特点是:无论数据库中的数据类型是什么,都以String的形式取出

// JDBC中所有下标从1开始,不是从零开始

// String empno = rs.getString(1);

// 以列的名字获取

String empno = rs.getString("empno");

String ename = rs.getString(2);

String sal = rs.getString(3);

System.out.println(empno + "," + ename + "," + sal);

}

8. 用户登录业务介绍

IDEA开发配置驱动

项目或者模块右键

点击Libraries

之后选择自己的驱动包

实现功能:

1、需求:模拟用户登录功能的实现

2、业务描述:

程序运行的时候,提供一个输入的入口,可以让用户输入用户名和密码

用户输入用户名和密码之后,提交信息,java程序收集到用户信息

java程序连接到数据库验证用户名和密码是否合法

合法:显示登录成功

不合法:显示登录失败

3、数据的准备:

在实际的开发中,表的设计会使用专业的建模工具,我们这里安装一个建模工具:PowerDesigner

使用PD工具来进行数据库表的创建(参见userLogin.sql脚本)

1、首先我们创建一个数据库表的脚本

drop table if exists t_user;

/*==============================================================*/

/* Table: t_user */

/*==============================================================*/

create table t_user

(

id bigint auto_increment,

loginName varchar(255),

loginPwd varchar(255),

realName varchar(255),

primary key (id)

);

insert into t_user(loginName,loginPwd,realName) values('zhangsan','123','张三');

insert into t_user(loginName,loginPwd,realName) values('lisi','234','李四');

insert into t_user(loginName,loginPwd,realName) values('wangwu','345','王五');

commit;

select * from t_user;

2、开始进行初始化工作:在控制台上输入账号密码进行登录

我们使用Map的数据结构,采用键值对的方式

/**

* 初始化用户界面

* @return 用户输入的用户名和密码等登录信息

*/

private static Map initUI() {

Scanner s = new Scanner(System.in);

System.out.println("用户名:");

String loginName = s.nextLine();

System.out.println("密码:");

String loginPwd = s.nextLine();

s.close();

Map userLoginInfo = new HashMap<>();

userLoginInfo.put("loginName", loginName);

userLoginInfo.put("loginPwd", loginPwd);

return userLoginInfo;

}

3、开始进行连接数据库的操作

1、初始化工作

boolean loginSuccess = false;

// JDBC代码

Connection conn = null;

Statement stmt = null;

ResultSet rs = null;

2、注册驱动

// 1、注册驱动

Class.forName("com.mysql.jdbc.Driver");

3、获取连接

// 2、获取连接

conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bili?useSSL=false", "root", "123456");

4、获取数据库操作对象

// 3、获取数据库操作对象

stmt = conn.createStatement();

5、执行sql

// 4、执行sql

String sql = "select * from t_user where loginName = '" + userLoginInfo.get("loginName")

+ "' and loginPwd = '" + userLoginInfo.get("loginPwd") + "'";

// 以上正好完成了sql语句的拼接,以下代码的含义是,发送sql语句给DBMS,DBMS进行sql编译

// 正好将用户提供的“非法信息”编译进去,导致原sql语句的含义被扭曲了

rs = stmt.executeQuery(sql);

6、处理结果集

// 5、处理结果集

if (rs.next()) {

loginSuccess = true;

}

7、资源释放

finally {

// 6、释放资源

if (rs != null) {

try {

rs.close();

} catch (SQLException e) {

e.printStackTrace();

}

}

if (stmt != null) {

try {

stmt.close();

} catch (SQLException e) {

e.printStackTrace();

}

}

if (conn != null) {

try {

conn.close();

} catch (SQLException e) {

e.printStackTrace();

}

}

}

8、在主函数中通过布尔值验证登录

// 初始化一个界面

Map userLoginInfo =initUI();

// 验证用户名和密码

boolean loginSuccess = login(userLoginInfo);

// 最后输出结果

System.out.println(loginSuccess ? "登录成功!" : "登录失败!");

4、完整代码

package test;

import java.sql.*;

import java.util.HashMap;

import java.util.Map;

import java.util.Scanner;

/**

* 实现功能:

* 1、需求:模拟用户登录功能的实现

* 2、业务描述:

* 程序运行的时候,提供一个输入的入口,可以让用户输入用户名和密码

* 用户输入用户名和密码之后,提交信息,java程序收集到用户信息

* java程序连接到数据库验证用户名和密码是否合法

* 合法:显示登录成功

* 不合法:显示登录失败

* 3、数据的准备:

* 在实际的开发中,表的设计会使用专业的建模工具,我们这里安装一个建模工具:PowerDesigner

* 使用PD工具来进行数据库表的创建(参见userLogin.sql脚本)

* 4、当前程序存在的问题:

* 用户名:fdsa

* 密码:fdsa' or '1'='1

* 登录成功

* 这种现象称为sql注入(安全隐患)。(黑客经常使用)

* 5、导致sql语句注入的原因是什么?

* 用户输入的信息中含有sql语句的关键字,并且这些关键字参与sql语句的编译过程,

* 导致sql语句的愿意被扭曲,进而达到sql注入

*/

public class JDBCTest01 {

public static void main(String[] args) {

// 初始化一个界面

Map userLoginInfo =initUI();

// 验证用户名和密码

boolean loginSuccess = login(userLoginInfo);

// 最后输出结果

System.out.println(loginSuccess ? "登录成功!" : "登录失败!");

}

/**

* 用户登录

* @param userLoginInfo 用户登录信息

* @return false表示失败,true表示成功

*/

private static boolean login(Map userLoginInfo) {

// 打标记的意识

boolean loginSuccess = false;

// JDBC代码

Connection conn = null;

Statement stmt = null;

ResultSet rs = null;

try {

// 1、注册驱动

Class.forName("com.mysql.jdbc.Driver");

// 2、获取连接

conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bili?useSSL=false", "root", "123456");

// 3、获取数据库操作对象

stmt = conn.createStatement();

// 4、执行sql

String sql = "select * from t_user where loginName = '" + userLoginInfo.get("loginName")

+ "' and loginPwd = '" + userLoginInfo.get("loginPwd") + "'";

// 以上正好完成了sql语句的拼接,以下代码的含义是,发送sql语句给DBMS,DBMS进行sql编译

// 正好将用户提供的“非法信息”编译进去,导致原sql语句的含义被扭曲了

rs = stmt.executeQuery(sql);

// 5、处理结果集

if (rs.next()) {

loginSuccess = true;

}

} catch (Exception e) {

throw new RuntimeException(e);

} finally {

// 6、释放资源

if (rs != null) {

try {

rs.close();

} catch (SQLException e) {

e.printStackTrace();

}

}

if (stmt != null) {

try {

stmt.close();

} catch (SQLException e) {

e.printStackTrace();

}

}

if (conn != null) {

try {

conn.close();

} catch (SQLException e) {

e.printStackTrace();

}

}

}

return loginSuccess;

}

/**

* 初始化用户界面

* @return 用户输入的用户名和密码等登录信息

*/

private static Map initUI() {

Scanner s = new Scanner(System.in);

System.out.println("用户名:");

String loginName = s.nextLine();

System.out.println("密码:");

String loginPwd = s.nextLine();

s.close();

Map userLoginInfo = new HashMap<>();

userLoginInfo.put("loginName", loginName);

userLoginInfo.put("loginPwd", loginPwd);

return userLoginInfo;

}

}

5、存在的问题

当前程序存在的问题:

用户名:fdsa

密码:fdsa' or '1'='1

登录成功

这种现象称为sql注入(安全隐患)。(黑客经常使用)

导致sql语句注入的原因是什么?

用户输入的信息中含有sql语句的关键字,并且这些关键字参与sql语句的编译过程,导致sql语句的愿意被扭曲,进而达到sql注入

9、解决sql注入问题?

解决sql注入问题?

只要用户提供的信息不参与sql语句的编译过程,问题就解决了

即使用户提供的信息中含有sql语句的关键字,但是没有参与编译,不起作用

要想用户信息不参与sql语句的编译,那么必须使用java.sql.PreparedStatement PreparedStatement接口继承了java.sql.Statement

PreparedStatement是属于预编译的数据库操作对象

PreparedStatement原理是预先对sql语句框架进行编译,然后再给sql语句传“值”

在用户登录介绍的基础上进行修改,只需要修改三、四步就能够解决这个问题

1、获取预编译的数据库操作对象

// 3、获取预编译的数据库操作对象

// sql语句框架。其中一个?表示一个占位符,一个?将来接收一个“值”。注意:占位符不能使用单引号括起来

String sql = "select * from t_user where loginName = ? and loginPwd = ?";

// 程序执行到此处,会发送sql语句框子给DBMS,然后DBMS进行sql语句的预先编译

ps = conn.prepareStatement(sql);

// 给占位符?传值(第一个?下标是1,第二个?下标是2,JDBC中所有下标从1开始)

ps.setString(1, loginName);

ps.setString(2, loginPwd);

2、执行sql语句

// 4、执行sql

rs = ps.executeQuery();

3、测试结果

测试结果

用户名:fdas

密码:fdas' or '1'='1

登录失败!

4、完整代码

package test;

import java.sql.*;

import java.util.HashMap;

import java.util.Map;

import java.util.Scanner;

/**

* 1、解决sql注入问题?

* 只要用户提供的信息不参与sql语句的编译过程,问题就解决了

* 即使用户提供的信息中含有sql语句的关键字,但是没有参与编译,不起作用

* 要想用户信息不参与sql语句的编译,那么必须使用java.sql.PreparedStatement

* PreparedStatement接口继承了java.sql.Statement

* PreparedStatement是属于预编译的数据库操作对象

* PreparedStatement原理是预先对sql语句框架进行编译,然后再给sql语句传“值”

* 2、测试结果

* 用户名:fdas

* 密码:fdas' or '1'='1

* 登录失败!

* 3、解决sql注入的关键是什么?

* 用户提供的信息中即使含有sql语句的关键字,但是这些关键字并没有参与编译,不起作用

* 4、对比一下Statement和PreparedStatement

* - Statement存在sql注入问题,PreparedStatement解决了sql注入问题

* - Statement是编译一次执行一次。PreparedStatement是编译一次,可执行N次。PreparedStatement效率较高

* - PreparedStatement会在编译阶段做类型的安全检查

* - 综上所述:PreparedStatement使用较多,只有极少数的情况下需要使用Statement

* 5、什么情况下必须使用Statement?

* 业务方面要求必须支持sql注入的时候

* Statement支持sql注入,凡是业务方面要求是需要进行sql语句拼接的,必须使用Statement

*/

public class JDBCTest02 {

public static void main(String[] args) {

// 初始化一个界面

Map userLoginInfo =initUI();

// 验证用户名和密码

boolean loginSuccess = login(userLoginInfo);

// 最后输出结果

System.out.println(loginSuccess ? "登录成功!" : "登录失败!");

}

/**

* 用户登录

* @param userLoginInfo 用户登录信息

* @return false表示失败,true表示成功

*/

private static boolean login(Map userLoginInfo) {

// 打标记的意识

boolean loginSuccess = false;

// 单独定义变量

String loginName = userLoginInfo.get("loginName");

String loginPwd = userLoginInfo.get("loginPwd");

// JDBC代码

Connection conn = null;

PreparedStatement ps = null; // 这里使用PreparedStatement(预编译的数据库操作对象)

ResultSet rs = null;

try {

// 1、注册驱动

Class.forName("com.mysql.jdbc.Driver");

// 2、获取连接

conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bili?useSSL=false", "root", "123456");

// 3、获取预编译的数据库操作对象

// sql语句框架。其中一个?表示一个占位符,一个?将来接收一个“值”。注意:占位符不能使用单引号括起来

String sql = "select * from t_user where loginName = ? and loginPwd = ?";

// 程序执行到此处,会发送sql语句框子给DBMS,然后DBMS进行sql语句的预先编译

ps = conn.prepareStatement(sql);

// 给占位符?传值(第一个?下标是1,第二个?下标是2,JDBC中所有下标从1开始)

ps.setString(1, loginName);

ps.setString(2, loginPwd);

// 4、执行sql

rs = ps.executeQuery();

// 5、处理结果集

if (rs.next()) {

loginSuccess = true;

}

} catch (Exception e) {

throw new RuntimeException(e);

} finally {

// 6、释放资源

if (rs != null) {

try {

rs.close();

} catch (SQLException e) {

e.printStackTrace();

}

}

if (ps != null) {

try {

ps.close();

} catch (SQLException e) {

e.printStackTrace();

}

}

if (conn != null) {

try {

conn.close();

} catch (SQLException e) {

e.printStackTrace();

}

}

}

return loginSuccess;

}

/**

* 初始化用户界面

* @return 用户输入的用户名和密码等登录信息

*/

private static Map initUI() {

Scanner s = new Scanner(System.in);

System.out.println("用户名:");

String loginName = s.nextLine();

System.out.println("密码:");

String loginPwd = s.nextLine();

Map userLoginInfo = new HashMap<>();

userLoginInfo.put("loginName", loginName);

userLoginInfo.put("loginPwd", loginPwd);

return userLoginInfo;

}

}

5、解决sql注入的关键

用户提供的信息中即使含有sql语句的关键字,但是这些关键字并没有参与编译,不起作用

6、对比一下Statement和PreparedStatement

Statement存在sql注入问题,PreparedStatement解决了sql注入问题

Statement是编译一次执行一次。PreparedStatement是编译一次,可执行N次。PreparedStatement效率较高

PreparedStatement会在编译阶段做类型的安全检查

综上所述:PreparedStatement使用较多,只有极少数的情况下需要使用Statement

7、什么情况下必须使用Statement?

业务方面要求必须支持sql注入的时候

Statement支持sql注入,凡是业务方面要求是需要进行sql语句拼接的,必须使用Statement

10、演示Statement的用法

PreparedStatement和Statement都有各自的用途,大多数情况下使用的是PreparedStatement,但是在一些情况下,就必须使用Statement

例如:在进行mysql语句的升序降序过程,使用PreparedStatement就不行,这时就需要使用到Statement

1、首先使用PreparedStatement来进行升降序的过程

// 获取预编译的数据库操作对象

String sql = "select loginPwd from t_user order by loginPwd ?";

ps = conn.prepareStatement(sql);

ps.setString(1, keyWords);

// 执行sql

rs = ps.executeQuery();

当我们在控制台输入desc或者asc时,会直接出现报错

这次错误信息提示“near ''desc'' at line 1”,这意味着 SQL 查询在尝试理解 'desc' 时遇到了问题,可能是因为 'desc' 被错误地用了单引号包围,这样它就被识别为字符串文字,而不是 SQL 关键字。在 SQL 中 DESC 是用来指定降序排序的关键字,并且在使用时不应该被单引号包围。

因此我们不采用这种方式,使用Statement能够解决这个问题

2、使用Statement来进行升降序的过程

// 获取数据库操作对象

stmt = conn.createStatement();

// 执行sql

String sql = "select loginPwd from t_user order by loginPwd " + keyWords;

rs = stmt.executeQuery(sql);

这样就能够成功解决这个问题

存在就有道理,因此我们不能将Statement忽略掉,不能因为使用的情况少,就抛弃了这种用法

3、完整代码

package test;

import java.sql.*;

import java.util.Scanner;

public class JDBCTest03 {

public static void main(String[] args) {

/*

// 用户在控制台输入desc就是降序,输入asc就是升序

Scanner scanner = new Scanner(System.in);

System.out.println("请输入desc或asc,desc表示降序,asc表示升序");

System.out.println("请输入:");

String keyWords = scanner.nextLine();

// 执行sql

Connection conn = null;

PreparedStatement ps = null;

ResultSet rs = null;

try {

// 注册驱动

Class.forName("com.mysql.jdbc.Driver");

// 获取连接

conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bili?useSSL=false", "root", "123456");

// 获取预编译的数据库操作对象

String sql = "select loginPwd from t_user order by loginPwd ?";

ps = conn.prepareStatement(sql);

ps.setString(1, keyWords);

// 执行sql

rs = ps.executeQuery();

// 遍历结果集

while (rs.next()) {

System.out.println(rs.getString("loginPwd"));

}

} catch (Exception e) {

e.printStackTrace();

} finally {

if (rs != null) {

try {

rs.close();

} catch (SQLException e) {

e.printStackTrace();

}

}

if (ps != null) {

try {

ps.close();

} catch (SQLException e) {

e.printStackTrace();

}

}

if (conn != null) {

try {

conn.close();

} catch (SQLException e) {

e.printStackTrace();

}

}

} */

// 用户在控制台输入desc就是降序,输入asc就是升序

Scanner scanner = new Scanner(System.in);

System.out.println("请输入desc或asc,desc表示降序,asc表示升序");

System.out.println("请输入:");

String keyWords = scanner.nextLine();

// 执行sql

Connection conn = null;

Statement stmt = null;

ResultSet rs = null;

try {

// 注册驱动

Class.forName("com.mysql.jdbc.Driver");

// 获取连接

conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bili?useSSL=false", "root", "123456");

// 获取数据库操作对象

stmt = conn.createStatement();

// 执行sql

String sql = "select loginPwd from t_user order by loginPwd " + keyWords;

rs = stmt.executeQuery(sql);

// 遍历结果集

while (rs.next()) {

System.out.println(rs.getString("loginPwd"));

}

} catch (Exception e) {

e.printStackTrace();

} finally {

if (rs != null) {

try {

rs.close();

} catch (SQLException e) {

e.printStackTrace();

}

}

if (stmt != null) {

try {

stmt.close();

} catch (SQLException e) {

e.printStackTrace();

}

}

if (conn != null) {

try {

conn.close();

} catch (SQLException e) {

e.printStackTrace();

}

}

}

}

}

11、PreparedStatement实现增删改

1、添加数据库

// 添加数据库

String sql = "insert into t_user(loginName,loginPwd,realName) values(?,?,?)";

ps = conn.prepareStatement(sql);

ps.setString(1, "去啊");

ps.setInt(2, 432);

ps.setString(3, "张三丰");

2、更改数据库

// 更改数据库

String sql = "update t_user set loginName = ?, loginPwd = ? where realName = ?";

ps = conn.prepareStatement(sql);

ps.setString(1, "好啊");

ps.setInt(2, 442);

ps.setString(3, "张三丰");

3、删除数据库

// 删除数据库

String sql = "delete from t_user where realName = ?";

ps = conn.prepareStatement(sql);

ps.setString(1, "张三丰");

12、账户转账演示事务

回滚是什么?

在数据库中,"回滚"指的是将数据库状态回退到某个确定点的操作。在SQL中,用于回滚的语句是 ROLLBACK

当在事务中执行了如 INSERT, UPDATE, 或 DELETE 这样的操作后,如果你需要取消这些操作,并恢复到事务开始前的状态,你可以使用 ROLLBACK 命令。这个命令将撤销事务内所有未提交的更改

START TRANSACTION;

INSERT INTO t_act(actno, balance) VALUES (333, 15000);

-- 假设这里发生了错误或者我们出于某种原因需要撤销之前的操作

ROLLBACK;

例如:没有进行操作的数据库表

进行修改后回滚

在IDEA里面,同样需要回滚的操作,保证安全性

重点三行代码 :

conn.setAutoCommit(false); // 开启事务

conn.commit(); // 提交事务

conn.rollback(); // 回滚事务

这里演示了一个账户转账的例子,在第一个账户转账后,会出现异常;如果不进行回滚,那么第一个账户完成了转账,第二个账户却不会收到转账

我们需要定义一个事务的起始点

// 2、获取连接

conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bili?useSSL=false", "root", "123456");

// 将自动提交机制修改为手动提交

conn.setAutoCommit(false); // 开启事务

当发生异常,就捕获异常并回滚事务

catch (Exception e) {

if (conn != null) {

try {

conn.rollback(); // 回滚事务

} catch (SQLException ex) {

e.printStackTrace();

}

}

e.printStackTrace();

}

如果没有发生异常,那么就可以进行事务提交了

conn.commit(); // 提交事务

完整代码

首先是一个数据库表

DROP TABLE IF EXISTS t_act;

CREATE TABLE t_act (

actno INT,

balance DOUBLE(7,2) -- 注意,7,表示有效字数的个数,2表示小数位的个数

);

INSERT INTO t_act(actno, balance) VALUES (111, 20000);

INSERT INTO t_act(actno, balance) VALUES (222, 0);

COMMIT;

SELECT * FROM t_act;

IDEA演示代码

package test;

import java.sql.Connection;

import java.sql.DriverManager;

import java.sql.PreparedStatement;

import java.sql.SQLException;

public class JDBCTest05 {

public static void main(String[] args) {

Connection conn = null;

PreparedStatement ps = null;

try {

// 1、注册驱动

Class.forName("com.mysql.jdbc.Driver");

// 2、获取连接

conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bili?useSSL=false", "root", "123456");

// 将自动提交机制修改为手动提交

conn.setAutoCommit(false); // 开启事务

//3、获取预编译的数据库操作对象

String sql = "update t_act set balance = ? where actno = ?";

ps = conn.prepareStatement(sql);

// 给问号传值

ps.setDouble(1, 10000);

ps.setInt(2, 111);

int count = ps.executeUpdate();

String s = null;

s.toString();

// 再给问号传值

ps.setDouble(1, 10000);

ps.setInt(2, 222);

count += ps.executeUpdate();

System.out.println(count == 2 ? "转账成功" : "转账失败");

// 程序能够走到这里说明以上程序没有异常,事务结束,手动提交数据

conn.commit(); // 提交事务

} catch (Exception e) {

if (conn != null) {

try {

conn.rollback(); // 回滚事务

} catch (SQLException ex) {

e.printStackTrace();

}

}

e.printStackTrace();

} finally {

// 6、释放资源

if (ps != null) {

try {

ps.close();

} catch (SQLException e) {

e.printStackTrace();

}

}

if (conn != null) {

try {

conn.close();

} catch (SQLException e) {

e.printStackTrace();

}

}

}

}

}

13、JDBC工具类的封装

我们一般将工具类放入到一个包里,这个包有一般名为Utils

因为有可能会存在多次获取数据库连接的情况,有不同的数据库;因此我们将注册驱动这一过程放入在静态代码块中,当执行类加载时,就执行,且只执行一次

static {

try {

Class.forName("com.mysql.jdbc.Driver");

} catch (ClassNotFoundException e) {

throw new RuntimeException(e);

}

}

获取数据库连接对象放入到一个静态函数中

/**

* 获取数据库连接对象

* @return 连接对象

* @throws SQLException 抛出异常

*/

public static Connection getConnection () throws SQLException {

return DriverManager.getConnection("jdbc:mysql://localhost:3306/bili?useSSL=false", "root", "123456");

}

释放资源

/**

* 关闭资源

* @param conn 连接对象

* @param ps 数据库操作对象

* @param rs 结果集

*/

public static void close(Connection conn, PreparedStatement ps, ResultSet rs) {

if (rs != null) {

try {

rs.close();

} catch (SQLException e) {

throw new RuntimeException(e);

}

}

if (ps != null) {

try {

ps.close();

} catch (SQLException e) {

throw new RuntimeException(e);

}

}

if (conn != null) {

try {

conn.close();

} catch (SQLException e) {

throw new RuntimeException(e);

}

}

}

完整代码

package utils;

import java.sql.*;

// JDBC工具类,简化JDBC编程

public class DBUtil {

/**

* 工具类中的构造方法都是私有的.

* 因为工具类中的方法都是静态的,不需要new对象,直接采用类名调用

*/

private DBUtil() {}

// 静态代码块在类加载时执行,且只执行一次

static {

try {

Class.forName("com.mysql.jdbc.Driver");

} catch (ClassNotFoundException e) {

throw new RuntimeException(e);

}

}

/**

* 获取数据库连接对象

* @return 连接对象

* @throws SQLException 抛出异常

*/

public static Connection getConnection () throws SQLException {

return DriverManager.getConnection("jdbc:mysql://localhost:3306/bili?useSSL=false", "root", "123456");

}

/**

* 关闭资源

* @param conn 连接对象

* @param ps 数据库操作对象

* @param rs 结果集

*/

public static void close(Connection conn, PreparedStatement ps, ResultSet rs) {

if (rs != null) {

try {

rs.close();

} catch (SQLException e) {

throw new RuntimeException(e);

}

}

if (ps != null) {

try {

ps.close();

} catch (SQLException e) {

throw new RuntimeException(e);

}

}

if (conn != null) {

try {

conn.close();

} catch (SQLException e) {

throw new RuntimeException(e);

}

}

}

}

这样代码就显得代码特别简洁

14、模糊查询

我们先调用刚才写好的工具类

模糊查询执行数据库的过程,这里查询了loginName第二个字符为a的realName,注意,在sql语句中的?前是‘like’而不是‘=’,这样我们就能够查找到想要的结果

// 获取预编译的数据库操作对象

String sql = "select realName from t_user where loginName like ?";

ps = conn.prepareStatement(sql);

ps.setString(1, "_a%");

rs = ps.executeQuery();

while(rs.next()) {

System.out.println(rs.getString("realName"));

}

15、悲观锁和乐观锁的概念

悲观锁:事务必须排队,数据锁住了,不允许并发(行级锁:select后面添加for update)

乐观锁:支持并发,事务也不需要排队,只不过需要一个版本号

演示悲观锁(行级锁)

首先一个事务使用悲观锁锁住相关的记录

package test;

import utils.DBUtil;

import java.sql.Connection;

import java.sql.PreparedStatement;

import java.sql.ResultSet;

import java.sql.SQLException;

// 这个程序开启一个事务,这个事务专门进行查询,并且使用行级锁/悲观锁,锁住相关的记录

public class JDBCTest07 {

public static void main(String[] args) {

Connection conn = null;

PreparedStatement ps = null;

ResultSet rs = null;

try {

conn = DBUtil.getConnection();

// 开启事务

conn.setAutoCommit(false);

String sql = "select * from t_user where loginName = ? for update";

ps = conn.prepareStatement(sql);

ps.setString(1, "lisi");

rs = ps.executeQuery();

while (rs.next()) {

System.out.println(rs.getString("loginName") + "," + rs.getInt("loginPwd") + "," + rs.getString("realName"));

}

conn.commit(); // 提交事务(事务结束)

} catch (SQLException e) {

if (conn != null) {

try {

conn.rollback(); // 回滚事务(事务结束)

} catch (SQLException ex) {

throw new RuntimeException(ex);

}

}

e.printStackTrace();

} finally {

// 释放资源

DBUtil.close(conn, ps, rs);

}

}

}

另一个事务负责修改被锁定的记录

package test;

import utils.DBUtil;

import java.sql.Connection;

import java.sql.PreparedStatement;

import java.sql.SQLException;

// 这个程序负责修改被锁定的记录

public class JDBCTest08 {

public static void main(String[] args) {

Connection conn = null;

PreparedStatement ps = null;

try {

conn = DBUtil.getConnection();

conn.setAutoCommit(false);

String sql = "update t_user set loginPwd = loginPwd * 2 where realName = ?";

ps = conn.prepareStatement(sql);

ps.setString(1, "李四");

int count = ps.executeUpdate();

System.out.println(count);

conn.commit();

} catch (SQLException e) {

if (conn != null) {

try {

conn.rollback(); // 回滚事务(事务结束)

} catch (SQLException ex) {

throw new RuntimeException(ex);

}

}

throw new RuntimeException(e);

} finally {

DBUtil.close(conn, ps, null);

}

}

}

在以上两个程序中,可以实现行级锁机制,第一个程序对相关记录采用了行级锁,如果该事务还未完成,那么在第二个程序就不能对相关数据进行修改,知道第一个事务结束后,第二个数据才能进行修改;

要通过以上两个程序实现这个机制,我们只需要现在第一个程序事务结束处打一个断点,之后debug运行,在运行第二个程序,此时的被锁住的数据未被修改;当第一个事务运行结束后,第二个程序就能够对数据库进行修改了

Top