目录
事务
JDBC
JDBC快速入门
JDBC API详解
DriverManager
Connection
Statement
ResultSet
PreparedStatement
事务
- 数据库的事务是一种机制、一个操作序列,包含了一组数据库操作命令
- 事务把所有的命令作为一个整体一起向系统提交或撤销操作请求,即这一组数据库命令要么同时成功,要么同时失败
- 事务是一个不可分割的工作逻辑单元
-- 开启事务
START TRANSACTION;
或者BEGIN;
-- 保存点名(设置保存点)
SAVEPOINT
-- 回退事务
ROLLBACK TO
-- 回滚全部事务
ROLLBACK;
-- 提交事务,所有操作生效,不能回退
COMMIT;
设置保存点就相当于游戏存档,回滚就相当于读档
如果不开始事务,默认情况下,dml操作是自动提交的,不能回滚
事务的四大特征:
-
原子性(Atomicity):事务是不可分割的最小操作单位,要么同时成功,要么同时失败
-
一致性(Consistency):事务完成时,必须使所有的数据都保持一致状态
-
隔离性(Isolation):多个事务之间,操作的可见性
-
持久性(Durability):事务一旦提交或回滚,他对数据库中的数据的改变就是永久的
事务的隔离级别:
-- 查看当前会话的隔离级别
SELECT @@transaction_isotion;
- 多个连接开启各自事务操作数据库中数据时,数据库系统要负责隔离操作,以保证各个连接再获取数据时的准确性
- 如果不考虑隔离性,可能会引发如下问题:
- 脏读:当一个事务读取另一个事务尚未提交的修改时,产生脏读
- 不可重复读:同一查询在同一事物中多次进行,由于其他提交事务所做的修改或删除,每次返回不同的结果集,此时发成不可重复读
- 幻读:同一查询在同一事物中多次进行,由于其他提交事务所做的插入操作,每次返回不同的结果集,此时发生幻读
MySQL事务隔离级别:
概念:MySQL隔离级别定义了食物与食物之间的隔离程度
MySQL隔离级别 | 脏读 | 不可重复读 | 幻读 | 加锁读 |
读未提交(Read uncommitted) | 会出现 | 会出现 | 会出现 | 不加锁 |
读已提交(Read committed) | 不会出现 | 会出现 | 会出现 | 不加锁 |
可重复读(Repeatable read) | 不会出现 | 不会出现 | 不会出现 | 不加锁 |
可串行化(Serializable) | 不会出现 | 不会出现 | 不会出现 | 加锁 |
- 查看当前会话隔离级别
SELECT @@TRANSACTION_ISOLATION;
- 查看系统当前隔离级别
SELECT @@GLOBAL_ISOLATION;
- 设置当前会话隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
- 设置系统当前隔离级别
SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;
- MySQL默认的事务隔离级别时repeatable read,一般情况下,没有特殊要求,没必要修改(因为该级别可以满足绝大部分项目需求)
JDBC
JDBC简介:
- JDBC就是使用Java语言操作关系型数据库的一套API
- 全称:Java DataBase Connectivity:Java数据库连接
JDBC本质:
- 官方(sun公司)定义的一套所有关系型数据库的规则,即接口
- 各个数据库厂商去实现这套接口,提供数据库驱动jar包中的实现类
JDBC好处:
- 个数据库厂商使用相同的接口,Java代码不需要针对不同数据库分别开发
- 可随时替换底层数据库,访问数据库的Java代码基本不变
JDBC快速入门
步骤:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;/*
*JDBC 快速入门
* */
public class JDBCdemo{public static void main(String[] args) throws SQLException {//1.注册驱动try {Class.forName("com.mysql.cj.jdbc.Driver");} catch (ClassNotFoundException e) {e.printStackTrace();}//2.获取连接String url = "jdbc:mysql://127.0.0.1:3306/db1";String username = "root";String password = "jbw051201";Connection conn = DriverManager.getConnection(url, username, password);//3.定义sql语句String sql = "update account set money = 2000 where id = 2";//4.获取执行sql的对象Statement stmt = conn.createStatement();//5.执行sqlint count = stmt.executeUpdate(sql);//受影响的行数//6.处理结果System.out.println(count);//7.释放资源stmt.close();conn.close();}
}
JDBC API详解
DriverManager
DriverManager(驱动管理类)作用:
- 注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
查看Driver类源码
static {try {DriverManager.registerDriver(new Driver());} catch (SQLException var1) {throw new RuntimeException("Can't register driver!");}}
- 获取数据库连接
public static Connection getConnection(String url,String user, String password);
参数:
1.url:连接路径
语法:jdbc:mysql://ip地址(域名):端口号/数据库名称?参数键值对1&参数键值对2...
示例:jdbc:mysql//127.0.0.1:3306/db1
细节:
- 如果连接的是本机mysql服务器,并且mysql服务默认端口是3306,则url可以简写为:jdbc:mysql:///数据库名称?参数键值对
- 配置useSSL=false参数,禁用安全连接方式,解决警告提示
2.user:用户名
3.password:密码
Connection
Connection(数据库连接对象)作用:
1.获取执行SQL的对象
- 普通执行SQL对象
Statement createStatement()
-
预编译SQL的执行SQL对象:防止SQL注入
PreparedStatement preparedStatement(sql)
-
执行存储过程的对象
CallableStatement prepareCall(sql)
2.事务管理
开启事务:setAutoCommit(boolean autoCommit):true为自动提交事务,false为手动提交事务,即为开启事务
提交事务:commit()
回滚事务:rollback()
try {//开启事务conn.setAutoCommit(false);//5.执行sqlint count1 = stmt.executeUpdate(sql1);//受影响的行数//6.处理结果System.out.println(count1);//5.执行sqlint count2 = stmt.executeUpdate(sql2);//受影响的行数//6.处理结果System.out.println(count2);//提交事务conn.commit();} catch (Exception throwables) {//回滚事务conn.rollback();throwables.printStackTrace();}
Statement
Statement作用:执行SQL语句
int executeUpdate(sql); //执行DML,DDL语句
//返回值:1.DML语句影响的行数
// 2.DDL语句执行后,执行成功也肯能返回0
Result executeQuery(sql);//执行DQL语句
//返回值:ResultSet结果集对象
//执行sqlint count = stmt.executeUpdate(sql);//受影响的行数//6.处理结果//System.out.println(count);if(count > 0){System.out.println("修改成功");}else {System.out.println("修改失败");}
ResultSet
ResultSet(结果集对象)作用
1.封装了DQL查询语句的结果
ResultSet stmt.executeQuery(sql);//执行DQL语句,返回ResultSet对象
获取查询结果
boolean next();//将光标从当前位置向下移动一行;判断当前行是否为有效行
返回值:true:有效行,当前行有数据false:无效行,当前行无数据
xxx getXxx(参数);//获取数据
//xxx:数据类型;如:int getInt(参数);String getString(参数)
//参数:int:列的编号,从1开始String:列的名称
使用步骤:
- 游标向下一行,并判断该行是否有数据:next()
- 获取数据:getXxx(参数)
//循环判断游标是否是最后一行末尾 while(rs.next()){//获取数据rs.getXxx(参数); }
//执行sqlResultSet rs = stmt.executeQuery(sql);//处理结果:遍历rs中的所有数据//1.游标向下一行,并且判断当前行是否有数据while (rs.next()) {//2.获取数据int id = rs.getInt(1);String name = rs.getString(2);double money = rs.getDouble(3);System.out.println(id);System.out.println(name);System.out.println(money);System.out.println("--------------------------");}
//执行sqlResultSet rs = stmt.executeQuery(sql);while (rs.next()) {//获取数据int id = rs.getInt("id");String name = rs.getString("name");double money = rs.getDouble("money");System.out.println(id);System.out.println(name);System.out.println(money);System.out.println("--------------------------");}
ResultSet案例:
需求:查询account账户表数据,封装为Account对象,并且储存到ArrayList集合中
@Testpublic void testResultSet2() throws Exception {//注册驱动try {Class.forName("com.mysql.cj.jdbc.Driver");} catch (ClassNotFoundException e) {e.printStackTrace();}//获取连接String url = "jdbc:mysql://127.0.0.1:3306/db1";String username = "root";String password = "jbw051201";Connection conn = DriverManager.getConnection(url, username, password);//定义sqlString sql = "select * from account";//获取statement对象Statement stmt = conn.createStatement();//执行sqlResultSet rs = stmt.executeQuery(sql);//创建集合List<Account> list = new ArrayList<>();while (rs.next()) {Account account = new Account();//获取数据int id = rs.getInt("id");String name = rs.getString("name");double money = rs.getDouble("money");//赋值account.setId(id);account.setName(name);account.setMoney(money);//存入集合list.add(account);}System.out.println(list);//释放资源rs.close();stmt.close();conn.close();}
PreparedStatement
作用:预编译SQL语句并执行:预防SQL注入的问题
SQL注入:SQL注入时通过操作输入来修改事先定义好的SQL语句,用以达到执行代码对服务器进行攻击的方法
案例:
1.创建一个tb_user表:
2.通过JDBC模拟用户登录
//接收用户输入,用户名和密码String name = "Tom";String pwd1 = "123";String sql = "select * from tb_user where username = '" + name + "'and password = '" + pwd + "'";//获取stmt对象Statement stmt = conn.createStatement();//执行sqlResultSet rs = stmt.executeQuery(sql);//判断是否成功if (rs.next()) {System.out.println("登陆成功");} else {System.out.println("登陆失败");}
输出结果:
3.输入一个不存在的name和一个特定的pwd2:
//接收用户输入,用户名和密码String name = "hksjlafiujawjsd";String pwd2 = "' or ' 1 '=' 1 ";String sql = "select * from tb_user where username = '" + name + "'and password = '" + pwd2 + "'";//获取stmt对象Statement stmt = conn.createStatement();//执行sqlResultSet rs = stmt.executeQuery(sql);//判断是否成功if (rs.next()) {System.out.println("登陆成功");} else {System.out.println("登陆失败");}
输出结果:
按理说,当输入一个不存在的用户名时,应该登录失败才对,但之所以登陆成功,是因为输入的密码产生了SQL注入 ,时SQL语句判断为真并执行
如果我们来输出pwd2对应的sql语句:
会发现不管or前面是真是假,or后面始终是一个恒等式,所以这条语句一定会被执行,这也就产生了sql注入的问题,而这种情况是我们不希望看到的,这就需要我们利用PreparedStatement对象来解决
PreparedStatement作用:
预编译SQL并执行SQL语句
1.获取PreparedStatement对象
//SQL语句中的参数值,使用?占位符替代
String sql = "select * from tb_user where username = ? and password = ?";//通过Connection对象获取,并传入对应地sql语句
PreparedStatement pstmt = conn.preparedStatement(sql);
2.设置参数值
PreparedStatement对象:setXxx(参数值1,参数值2):给?赋值
Xxx:数据类型;如setInt(参数1,参数2)
参数:参数1:?的位置编号,从1开始
参数2:?的值
3.执行SQL
executeUpgrade();/executeQuery();//不需要在传递sql
//接收用户输入,用户名和密码String name = "jfaljsfjasjfj";String pwd = "' or ' 1 '=' 1";//定义sqlString sql = "select * from tb_user where username = ? and password = ?";//获取pstmt对象PreparedStatement pstmt = conn.prepareStatement(sql);//设置?的值pstmt.setString(1,name);pstmt.setString(2,pwd);//执行sqlResultSet rs = pstmt.executeQuery();//判断是否成功if (rs.next()) {System.out.println("登陆成功");} else {System.out.println("登陆失败");}
这次就登陆失败了
PreparedStatement好处:
- 预编译SQL,性能更高
- 防止SQL注入:将敏感字符进行转义
PreparedStatement预编译功能开启:
useSeverPrepStmts=true
PreparedStatement 原理:
- 在获取PreparedStatement对象时,将sql语句发送给mysql服务器进行检查,编译(这些步骤很耗时)
- 执行时就不用再进行这些步骤了,速度更快
- 如果sql模板一样,则只需要进行一次检查