当前位置: 代码迷 >> 综合 >> JAVAWEB学习笔记--Day4
  详细解决方案

JAVAWEB学习笔记--Day4

热度:12   发布时间:2023-12-02 03:50:22.0

目录

事务

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;
  1. 多个连接开启各自事务操作数据库中数据时,数据库系统要负责隔离操作,以保证各个连接再获取数据时的准确性
  2. 如果不考虑隔离性,可能会引发如下问题:
  • 脏读:当一个事务读取另一个事务尚未提交的修改时,产生脏读
  • 不可重复读:同一查询在同一事物中多次进行,由于其他提交事务所做的修改或删除,每次返回不同的结果集,此时发成不可重复读
  • 幻读:同一查询在同一事物中多次进行,由于其他提交事务所做的插入操作,每次返回不同的结果集,此时发生幻读

MySQL事务隔离级别:

概念:MySQL隔离级别定义了食物与食物之间的隔离程度

MySQL隔离级别         脏读         不可重复读 幻读 加锁读
读未提交(Read uncommitted) 会出现 会出现 会出现 不加锁
读已提交(Read committed) 不会出现 会出现 会出现 不加锁
可重复读(Repeatable read) 不会出现 不会出现 不会出现 不加锁
可串行化(Serializable) 不会出现 不会出现 不会出现 加锁
  1. 查看当前会话隔离级别
    SELECT @@TRANSACTION_ISOLATION;

  2. 查看系统当前隔离级别
    SELECT @@GLOBAL_ISOLATION;

  3. 设置当前会话隔离级别
    SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;

  4. 设置系统当前隔离级别
    SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;

  5. 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(驱动管理类)作用:

  1. 注册驱动
    Class.forName("com.mysql.cj.jdbc.Driver");

    查看Driver类源码

     static {try {DriverManager.registerDriver(new Driver());} catch (SQLException var1) {throw new RuntimeException("Can't register driver!");}}

  2. 获取数据库连接
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:列的名称

使用步骤:

  1. 游标向下一行,并判断该行是否有数据:next()
  2. 获取数据: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好处:

  1. 预编译SQL,性能更高
  2. 防止SQL注入:将敏感字符进行转义

PreparedStatement预编译功能开启:

useSeverPrepStmts=true

PreparedStatement 原理:

  1. 在获取PreparedStatement对象时,将sql语句发送给mysql服务器进行检查,编译(这些步骤很耗时)
  2. 执行时就不用再进行这些步骤了,速度更快
  3. 如果sql模板一样,则只需要进行一次检查