Java DataBase Connectivity(Java 数据库连接)
JDBC是SUN公司制定的一套接口(interface)
每个数据库的底层实现都不一样,都有自己的实现原理。
JDBC通过编写一套接口,然后各个数据库厂家实现这个接口,这样我们就可以通过JDBC来操作不同的数据库了。
JDBC API提供了一个标准接口,用于与任何关系数据库管理系统(RDBMS)进行交互。
JDBC API包含以下主要组件:
1.JDBC Driver(JDBC驱动程序)
2.Connection(连接)
3.Statement(声明)
4.ResultSet(结果集)
目录结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AeKQDmLn-1690358526519)(JDBC.assets/image-20220803154108307.png)]
配置文件cus_jdbc.properties
clazzName=MySQl
driver=com.mysql.cj.jdbc.Driver
主类 main.java
import java.util.ResourceBundle;
public class main {
public static void main(String[] args) {
ResourceBundle bundle=ResourceBundle.getBundle("cus_jdbc");
String clazzName =bundle.getString("clazzName");
String mysqlClazzName =bundle.getString("driver");
System.out.println(clazzName);
System.out.println(mysqlClazzName);
}
}
运行结果:
MySQl
com.mysql.cj.jdbc.Driver
进程已结束,退出代码为 0
配置文件cus_jdbc.properties内容和cus_jdbc2.properties的内容相同
clazzName=MySQl
driver=com.mysql.cj.jdbc.Driver
主类 main.java
import java.util.ResourceBundle;
public class main {
public static void main(String[] args) {
//通过resourceBundle对象读取配置文件
ResourceBundle bundle=ResourceBundle.getBundle("cus_jdbc");
String clazzName =bundle.getString("clazzName");
String mysqlClazzName =bundle.getString("driver");
//通过properties对象读取配置文件
Properties p = new Properties();
try {
p.load(new FileInputStream("src/resource/cus_jdbc2.properties"));
} catch (IOException e) {
e.printStackTrace();
}
String jdbc2=p.getProperty("clazzName");
String jdbc22=p.getProperty("driver");
System.out.println(clazzName);
System.out.println(mysqlClazzName);
System.out.println(jdbc2);
System.out.println(jdbc22);
//通过配置文件获取类名,若需要更换数据库,则只需要在配置文件中将对应字段进行更改即可
//这里可以newInstance()通过反射的方式获取类,然后调用类中的方法
//JDBC操作...
}
}
运行结果:
MySQl
com.mysql.cj.jdbc.Driver
MySQl222
com.mysql.cj.jdbc.Driver222
进程已结束,退出代码为 0
下载对应数据库驱动的jar包,并将其配置刀环境变量classpath中。
classpath = .;D:\JDBCConnetJAR\MYSQL\8.0.19
(IDEA 有自己的配置方式,可加可不加)
1.注册驱动
2.获取连接
3.获取数据库操作对象
4.执行SQL语句
5.处理查询结果集
6.释放资源
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
public class main {
public static void main(String[] args) {
Connection connection=null;
Statement statement=null;
try {
/**
* 1.
*/
//1.注册驱动
Driver driver=new com.mysql.cj.jdbc.Driver();
DriverManager.registerDriver(driver);
//2.获取连接
/**
* 协议:jdbc:mysql://
* 地址:localhost:3306/sqlstudy?
* 参数:useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT
*/
String url="jdbc:mysql://localhost:3306/sqlstudy?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT";
String user="root";
String password="li1473606768";
connection=DriverManager.getConnection(url,user,password);
//打印连接对象:
System.out.println("连接对象"+connection);
//3.获取数据库操作对象
statement=connection.createStatement();
String sql="show tables";
//4.执行SQL
ResultSet resultSet=statement.executeQuery(sql);
//5.处理结果集
//随便使用的vector
Vector<String> vector=new Vector<>();
while(resultSet.next()){
vector.add(resultSet.getString(1));
}
System.out.println(vector.toString());
} catch (SQLException e) {
System.out.println("检测到异常");
e.printStackTrace();
} finally{
//6.释放资源
if(statement!=null){
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(connection!=null){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
打开mysql的Driver.java中可以看到源码:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oGWInezH-1690358526524)(JDBC.assets/image-20220803185504924.png)]
可以看到Driver中这个静态代码块已经实现了我们刚开始写的registerDriver()
如何执行这个静态代码块呢?
加载这个类。
如何加载这个类?
使用反射机制。
Class.forName("com.mysql.cj.jdbc.Driver");
//此反射不需要返回值,我们只需要这个类加载动作,也就是static静态代码块。
这种是最常用的JDBC加载方式,因为Class.forName()的方法参数是字符串,我们可以通过读取配置文件将字符串读入其中。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
public class getConnection_reflect {
public static void main(String[] args) {
Connection connection=null;
try {
Class.forName("com.mysql.cj.jdbc.Driver");
connection= DriverManager.getConnection("jdbc:mysql://localhost:3306/sqlstudy?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT","root","li1473606768");
System.out.println("连接对象:"+connection);
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
} finally{
if(connection!=null){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
将以上代码进行更改
jdbc_reflect.properties
driver=com.mysql.cj.jdbc.Driver
url = jdbc:mysql://localhost:3306/sqlstudy?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT
user =root
password = li1473606768
getConnection_reflect.java
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ResourceBundle;
public class getConnection_reflect {
public static void main(String[] args) {
Connection connection=null;
//使用资源绑定器,绑定properties配置文件
ResourceBundle resourceBundle=ResourceBundle.getBundle("jdbc_reflect");
String driver=resourceBundle.getString("driver");
String url=resourceBundle.getString("url");
String user=resourceBundle.getString("user");
String password=resourceBundle.getString("password");
try {
Class.forName(driver);
connection= DriverManager.getConnection(url,user,password);
System.out.println("连接对象:"+connection);
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
} finally{
if(connection!=null){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
此时我们就直接更改.properties文件中的键值对,.java文件都不用重新编译(但是要重新运行)就可以更改数据库的属性。
import java.sql.*;
import java.util.ResourceBundle;
public class getConnection_reflect {
public static void main(String[] args) {
Connection connection=null;
Statement statement=null;
ResultSet resultSet=null;
//使用资源绑定器,绑定properties配置文件
ResourceBundle resourceBundle=ResourceBundle.getBundle("jdbc_reflect");
String driver=resourceBundle.getString("driver");
String url=resourceBundle.getString("url");
String user=resourceBundle.getString("user");
String password=resourceBundle.getString("password");
try {
Class.forName(driver);
connection= DriverManager.getConnection(url,user,password);
System.out.println("连接对象:"+connection);
String sql= "select e.empno,e.ename,e.sal * 12 from emp e";
/**
* emp表中有以下几个字段:
* EMPNO | int
* ENAME | varchar(10)
* JOB | varchar(9)
* MGR | int
* HIREDATE | date
* SAL | double(7,2)
* COMM | double(7,2)
* DEPTNO | int
*/
statement = connection.createStatement();
resultSet=statement.executeQuery(sql);
while(resultSet.next()){
//查询结果集中的列名称
int empno=resultSet.getInt("empno");
String ename=resultSet.getString("ename");
Double sal=resultSet.getDouble("sal");
System.out.println(empno+"--"+ename+"--"+sal);
}
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
} finally{
//注意关闭的顺序
if(connection!=null){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(statement!=null){
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(resultSet!=null){
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
1.新建空项目
2.右键项目名称–>Open module settings–>libraries–>加号–>java
3.选择mysql数据库连接jar包
4.choose modules–>ok
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hAljbr9T-1690358526529)(JDBC.assets/image-20220804090212864.png)]
初始化登录用表
#创建登录用表
drop table if exists t_user;
create table t_user (
id bigint auto_increment,
loginName varchar(20),
loginPwd varchar(65),
realName varchar(20),
primary key(id)
);
#插入登录用户
insert into t_user(loginName,loginPwd,realName)values("maka","123","张三");
insert into t_user(loginName,loginPwd,realName)values("macie","123","李四");
insert into t_user(loginName,loginPwd,realName)values("aaa","123","王五");
insert into t_user(loginName,loginPwd,realName)values("bbb","123","王五");
#查询
select * from t_user;
JDBCLogin.java
package com.mysqlStudy.macie.JDBCTest01Login;
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Scanner;
/**
* @Author: yeyulemon
* @Date: 2022-08-04 09:23
**/
public class JDBCLogin {
public static void main(String[] args) {
//初始化界面
Map<String,String> userLoginInfo=initUI();
//验证用户名和密码
boolean loginResult = Login(userLoginInfo);
System.out.println(loginResult?"登录成功":"登陆失败");
}
/**
* @Description: 用户登录
* @Param: [userLoginInfo] 用户名和密码的map
* @return: boolean true成功,false失败
* @Author: MacieSerenity
* @Date: 2022/8/4 9:29
*/
private static boolean Login(Map<String, String> userLoginInfo) {
//JDBC代码
Connection connection=null;
Statement statement=null;
ResultSet resultSet=null;
ResourceBundle resourceBundle=ResourceBundle.getBundle("jdbcConfig");
String url=resourceBundle.getString("url");
String driver=resourceBundle.getString("driver");
String user=resourceBundle.getString("user");
String password=resourceBundle.getString("password");
String userName=userLoginInfo.get("userName");
String userPwd=userLoginInfo.get("userPwd");
try {
Class.forName(driver);
connection=DriverManager.getConnection(url,user,password);
System.out.println(connection);
statement=connection.createStatement();
String sql="select * from t_user where loginName='" + userName + "' and loginPwd='"+ userPwd+"'";
resultSet =statement.executeQuery(sql);
return resultSet.next()? true:false;
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
} finally {
if(resultSet !=null){
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(statement!=null){
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(connection!=null){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
return false;
}
/**
* @Description: 打印界面,并收集登录账号和密码
* @Param: []
* @return: java.util.Map
* @Author: MacieSerenity
* @Date: 2022/8/4 9:26
*/
private static Map<String, String> initUI() {
Scanner scanner=new Scanner(System.in);
System.out.println("请输入用户名");
String userName=scanner.nextLine();
System.out.println("请输入密码");
String userPwd=scanner.nextLine();
Map<String,String> loginInfo=new HashMap<>();
loginInfo.put("userName",userName);
loginInfo.put("userPwd",userPwd);
return loginInfo;
}
}
在以上代码过程中,我们有用户aaa,其密码为123
但是如果我们将密码输入为:
xxx ' or '1' = '1
依然能成功访问
//请输入用户名
aaa
//请输入密码
asdfasdf' or '1' = '1 <==SQL语句注入
//connect 对象:
com.mysql.cj.jdbc.ConnectionImpl@769a1df5
登录成功 <==显示登录成功
这种现象被称为SQL注入,是一种安全隐患,是黑客经常使用的攻击手段。
这种现象是怎么产生的呢?
在该语句处打个断点,且使用调试模式运行:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rd6jy9zU-1690358526533)(JDBC.assets/image-20220804102957109.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xV8iO2Gf-1690358526538)(JDBC.assets/image-20220804103057678.png)]
可以看到我们的select语句变成了:
select * from t_user where loginName='asdfas' and loginPwd='xxx' or '1' = '1'
我们可以将这个语句放到mysql里执行一下看下结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OfWT4Gul-1690358526543)(JDBC.assets/image-20220804103302141.png)]
查询出了所有结果,所以resultSet.next()不为空,所以就能正常登录。
是因为用户输入的数据中有sql语句的关键字
且这些关键字参与了SQL语句的执行,导致SQL语句的原意被扭曲
从而达成SQL注入
只要用户提供的信息不参与SQL语句的编译过程,就可以解决SQL注入。
即使用户提供的信息中含有SQL语句的关键字,但是只要不参与编译,就可以不起作用
要想用户的信息不参与SQL语句的编译,则我们需要使用java.sql.PreparedStatement
PrepareStatement接口继承了java.sql.Statement
PrepareStatement属于预编译的数据库操作对象
PrepareStatement原理是预先对SQL语句的框架进行编译,然后给SQL语句传值
java
package com.mysqlStudy.macie.JDBCTest02Login;
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Scanner;
/**
* @Author: yeyulemon
* @Date: 2022-08-04 09:23
**/
public class JDBCLogin2 {
public static void main(String[] args) {
//初始化界面
Map<String,String> userLoginInfo=initUI();
//验证用户名和密码
boolean loginResult = Login(userLoginInfo);
System.out.println(loginResult?"登录成功":"登陆失败");
}
/**
* @Description: 用户登录
* @Param: [userLoginInfo] 用户名和密码的map
* @return: boolean true成功,false失败
* @Author: MacieSerenity
* @Date: 2022/8/4 9:29
*/
private static boolean Login(Map<String, String> userLoginInfo) {
//JDBC代码
Connection connection=null;
PreparedStatement preparedStatement=null;
ResultSet resultSet=null;
boolean loginResult=false;
ResourceBundle resourceBundle=ResourceBundle.getBundle("jdbcConfig");
String url=resourceBundle.getString("url");
String driver=resourceBundle.getString("driver");
String user=resourceBundle.getString("user");
String password=resourceBundle.getString("password");
String userName=userLoginInfo.get("userName");
String userPwd=userLoginInfo.get("userPwd");
try {
Class.forName(driver);
connection=DriverManager.getConnection(url,user,password);
System.out.println(connection);
//获取预编译数据库操作对象,一个?表示一个占位符,且不能用单引号
String sql="select * from t_user where loginName=? and loginPwd=?";
//程序执行到这里,会发送SQL的框架,然后DBMS进行SQL语句的预编译
preparedStatement=connection.prepareStatement(sql);
//给占位符传值(JDBC中所有下标从1开始)
preparedStatement.setString(1,userName);
preparedStatement.setString(2,userPwd);
//执行SQL (executeQuery的参数变了)
//若填写了参数,则会重新编译SQL
resultSet =preparedStatement.executeQuery();
// return resultSet.next()? true:false;
loginResult=resultSet.next();
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
} finally {
if(resultSet !=null){
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(preparedStatement!=null){
try {
preparedStatement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(connection!=null){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
return loginResult;
}
/**
* @Description: 打印界面,并收集登录账号和密码
* @Param: []
* @return: java.util.Map
* @Author: MacieSerenity
* @Date: 2022/8/4 9:26
*/
private static Map<String, String> initUI() {
Scanner scanner=new Scanner(System.in);
System.out.println("请输入用户名");
String userName=scanner.nextLine();
System.out.println("请输入密码");
String userPwd=scanner.nextLine();
Map<String,String> loginInfo=new HashMap<>();
loginInfo.put("userName",userName);
loginInfo.put("userPwd",userPwd);
return loginInfo;
}
}
//运行结果:
请输入用户名
adsfa
请输入密码
xxx' or '1' = '1'
com.mysql.cj.jdbc.ConnectionImpl@15d9bc04
登陆失败
重新打断点可以看到:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F3Wo1fZX-1690358526547)(JDBC.assets/image-20220804105319366.png)]
sql 被提前编译了,没有发生注入的现象。
Statement存在SQL注入问题,而PreparedStatement解决了SQL注入问题
Statement的效率没有PreparedStatement高
(原因是数据库执行相同语句时,不会进行二次编译,而Statement每次编译时的用户名和密码都不同,所以每次都需要编译一次,而PreparedStatement每次执行的都是userName=? and userPass=?,所以只需要编译一次)
PreparedStatement在编译阶段会做类型检查。(指定setString则必须是String类型的数据)
综上所述。PreparedStatement使用较多,极少数情况下使用Statement
除开业务要求SQL注入的情况下,其他情况使用PreparedStatement
(如升序降序)
package com.mysqlStudy.macie.JDBCTest03PreparedStatemen;
import java.sql.*;
import java.util.ResourceBundle;
/**
* @Author: yeyulemon
* @Date: 2022-08-04 11:21
**/
public class JDBCLogin3 {
public static void main(String[] args) {
Connection connection=null;
PreparedStatement preparedStatement=null;
ResultSet resultSet=null;
//preparedStatement CURD
ResourceBundle resourceBundle=ResourceBundle.getBundle("jdbcConfig");
String DBdriver=resourceBundle.getString("driver");
String DBurl=resourceBundle.getString("url");
String DBuser=resourceBundle.getString("user");
String DBpass=resourceBundle.getString("password");
try {
Class.forName(DBdriver);
connection= DriverManager.getConnection(DBurl,DBuser,DBpass);
/**
* 插入
*/
// String sql="insert into t_user(loginName,loginPwd,realName)values(?,?,?)";
// preparedStatement=connection.prepareStatement(sql);
// preparedStatement.setString(1,"bbb");
// preparedStatement.setString(2,"bbb");
// preparedStatement.setString(3,"321");
/**
* 更新
*/
// String sql="update t_user set loginName=?,loginPwd=? where id=?";
// preparedStatement=connection.prepareStatement(sql);
//
// preparedStatement.setString(1,"ccc");
// preparedStatement.setString(2,"ccc");
// preparedStatement.setInt(3,5);
/**
* 删除
*/
// String sql="delete from t_user where loginName=?";
// preparedStatement=connection.prepareStatement(sql);
// preparedStatement.setString(1,"bbb");
// int count =preparedStatement.executeUpdate();
// System.out.println("count=="+count);
/**
* 查询使用resultSet
*/
String sql="select * from emp";
preparedStatement=connection.prepareStatement(sql);
resultSet =preparedStatement.executeUpdate();
while(resultSet.next()){
String ename = resulet.getString("ename");
System.out.println("ename:"+ename);
}
}catch (ClassNotFoundException | SQLException e){
e.printStackTrace();
}finally{
if (resultSet!=null){
try {
resultSet.close();
}catch (SQLException e){
e.printStackTrace();
}
}
if(preparedStatement!=null){
try{
preparedStatement.close();
}catch (SQLException e){
e.printStackTrace();
}
}
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
JDBC 中的事务是自动提交的。
但是实际业务中,通常都是N条DML语句联合使用才能完成。
保证DML语句在同一个事务中同时成功或者同时失效。
创建实验表:
drop table if exists t_act;
create table t_act(
actno bigint,
balance double(7,2)
);
insert into t_act(actno,balance)values(1,20000);
insert into t_act(actno,balance)values(2,0);
select * from t_act;
快捷键
选中多行 然后 alt+shift+insert 可以进行批量编辑hhh
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3ZWeR7Sh-1690358526552)(JDBC.assets/image-20220804115653970.png)]
详细的事务使用已用注释标明
package com.mysqlStudy.macie;
import java.sql.*;
import java.util.ResourceBundle;
/**
* @Author: yeyulemon
* @Date: 2022-08-04 11:42
**/
public class JDBCTest04Transaction {
public static void main(String[] args) {
Connection connection=null;
PreparedStatement preparedStatement=null;
ResultSet resultSet=null;
ResourceBundle resourceBundle=ResourceBundle.getBundle("jdbcConfig");
String DBurl=resourceBundle.getString("url");
String DBdriver=resourceBundle.getString("driver");
String DBuser=resourceBundle.getString("user");
String DBpassword=resourceBundle.getString("password");
try {
Class.forName(DBdriver);
connection= DriverManager.getConnection(DBurl,DBuser,DBpassword);
//获取到connect 对象后,将connect对象的自动提交机制关闭
//开启事务
connection.setAutoCommit(false);
String sql="update t_act set balance = ? where actno = ?";
preparedStatement= connection.prepareStatement(sql);
preparedStatement.setDouble(1,10000);
preparedStatement.setInt(2,1);
preparedStatement.executeUpdate();
int count =preparedStatement.executeUpdate();
//模拟程序出现异常
// String s=null;
// s.toString();
preparedStatement.setDouble(1,10000);
preparedStatement.setInt(2,2);
count += preparedStatement.executeUpdate();
System.out.println(count==2?"转账成功":"转账失败");
//程序执行到这里就说明程序执行成功,手动进行事务的提交
//提交事务
connection.commit();
}catch (ClassNotFoundException | SQLException e){
//如果捕获到任何异常,为了保证数据安全,直接进行数据的回滚
//数据回滚
if (connection!=null){
try {
connection.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
e.printStackTrace();
}finally {
if(resultSet!=null){
try {
connection.close();
}catch (SQLException e){
e.printStackTrace();
}
}
if (preparedStatement!=null){
try {
preparedStatement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection!=null){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
//what the fuck
//what the fuck
//what the fuck
//what the fuck
//what the fuck
//what the fuck
//what the fuck
//what the fuck
//what the fuck
//what the fuck
//what the fuck
//what the fuck
//what the fuck
//what the fuck
//what the fuck
//what the fuck
//what the fuck
//what the fuck
//what the fuck
//what the fuck
数据库配置文件jdbcConfig.properties
driver=com.mysql.cj.jdbc.Driver
url = jdbc:mysql://localhost:3306/sqlstudy?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT
user=root
password=li1473606768
package com.mysqlStudy.macie.DBUtils;
import java.sql.*;
import java.util.ResourceBundle;
public class DButils {
/**
* @Description: 工具类中的构造方法都是私有的,工具类中的方法都是静态的,不需要new对象,直接采用类名调用
* @Param: []
* @return:
* @Author: MacieSerenity
* @Date: 2022/8/4 12:16
*/
private DButils(){}
/**
* 静态代码代码块在类加载时就会执行,且只会执行一次
* 防止Class.forName()多次执行
*/
static {
ResourceBundle resourceBundle=ResourceBundle.getBundle("jdbcConfig");
String DBDriver=resourceBundle.getString("driver");
try {
Class.forName(DBDriver);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* @discription 获取连接对象
* @author Macieserenity
* @Params []
* @updateTime 2022/8/4 12:33
* @return java.sql.Connection
* @throws
*/
public static Connection getConnection() throws SQLException{
ResourceBundle resourceBundle=ResourceBundle.getBundle("jdbcConfig");
String DBurl=resourceBundle.getString("url");
String DBuser=resourceBundle.getString("user");
String DBpassword=resourceBundle.getString("password");
return DriverManager.getConnection(DBurl, DBuser, DBpassword);
}
/**
* @discription 关闭数据流
* @author Macieserenity
* @param: connection
* @param: statement
* @param: resultSet
* @updateTime 2022/8/4 12:58
* @return
* @throws
*/
public static void closeConnection(Connection connection, Statement statement, ResultSet resultSet){
if(resultSet!=null){
try {
connection.close();
}catch (SQLException e){
e.printStackTrace();
}
}
if (statement!=null){
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection!=null){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
工具类的使用
package com.mysqlStudy.macie.JDBCTest04Transaction;
import com.mysqlStudy.macie.DBUtils.DButils;
import java.sql.*;
import java.util.ResourceBundle;
/**
* @Author: yeyulemon
* @Date: 2022-08-04 11:42
**/
public class JDBCTest04Transaction {
public static void main(String[] args) {
Connection connection=null;
PreparedStatement preparedStatement=null;
ResultSet resultSet=null;
try {
connection=DButils.getConnection();
String sql="select ename from emp where ename like ?";
preparedStatement=connection.prepareStatement(sql);
preparedStatement.setString(1,"_A%");
resultSet= preparedStatement.executeQuery();
while (resultSet.next()){
System.out.println(resultSet.getString("ename"));
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
DButils.closeConnection(connection,preparedStatement,resultSet);
}
}
}
行级锁,又被称为悲观锁:事务必须排队执行,数据被锁住,无法进行并发
乐观锁:支持并发,事务也不需要排队,只不过需要一个版本号来记录是否被更改
悲观锁
#在我们执行SQL语句的过程中:
select ename,job,sal from emp where job='manager';
#若在后方加一个for update
select ename,job,sal from emp where job='manager' for update;
#就说明job=manager的这几行记录被加上了行级锁,在当前事务没有结束之前,无法被其它语句修改
可以写两个不同的JDBC 的 update 语句,对同一行数据进行修改测试,在第一个程序中的commit()处打上断点。
然后执行第二个程序,会发现第二个程序的返回结果卡住了,然后这时候将第一个程序中的断点继续执行。
第二个程序立刻就返回了结果,这就是悲观锁的基本测试过程。
乐观锁
有两个事务进行并发
事务1:读取到版本号1
事务2:读取到版本号2
#此时,若事务1率先读取到数据,进行修改之后版本号是1.1,然后在提交修改的时候,将版本号设置为1.2
# 事务2页读取到了版本1,修改后准备提交时,发现版本号为1.2,和读取的版本号不相同,则进行回滚。
这就是乐观锁的基本原理。