八、JDBC

本文详细介绍了JDBC的概念、依赖包及核心API,包括如何加载和注册驱动、获取数据库连接以及执行SQL语句。通过实例演示了JDBCUtils工具类的创建,用户登录案例以及PreparedStatement接口的使用,强调了SQL注入问题和事务处理的重要性。

主要内容:

  • 能够理解JDBC的概念
  • 能够使用DriverManager类
  • 能够使用Connection接口
  • 能够使用Statement接口
  • 能够使用ResultSet接口
  • 能够说出SQL注入原因和解决方案
  • 能够通过PreparedStatement完成增、删、改、查
  • 能够完成PreparedStatement改造登录案例

1 JDBC入门

1.1 客户端操作MySQL数据库的方式

  1. 使用第三方客户端来访问MySQL:SQLyog、Navicat、SQLWave、MyDB Studio、EMS SQL Manager for MySQL;
    在这里插入图片描述
  2. 使用MySQL自带的命令行方式;
  3. 通过Java来访问MySQL数据库,今天要学习的内容。

1.2 JDBC的基本概念

  1. 概念:Java DataBase Connectivity(Java 数据库连接),Java语言操作数据库

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

  3. 优点:

    1. 程序员如果要开发访问数据库的程序,只需要会调用JDBC接口中的方法即可,不用关注类是如何实现的;
    2. 使用同一套Java代码,进行少量的修改就可以访问其他JDBC支持的数据库。

    在这里插入图片描述

1.3 JDBC依赖包与核心API

  1. JDBC开发使用到的包:
    使用到的包说明
    java.sql所有与JDBC访问数据库相关的接口和类
    javax.sql数据库扩展包,提供数据库额外的功能。如:连接池
    数据库驱动由各大数据库厂商提供,需要额外去下载,是对JDBC接口实现的类
  2. JDBC的核心API:
    接口或类作用
    DriverManager类1. 管理和注册数据库驱动
    2. 得到数据库连接对象
    Connection接口一个连接对象,可用于创建Statement和PreparedStatement对象
    Statement接口一个SQL语句对象,用于将SQL语句发送给数据库服务器
    PreparedStatement接口一个SQL语句对象,是Statement的子接口
    ResultSet接口用于封装数据库查询的结果集,返回给客户端Java程序

2 JDBC操作步骤

在这里插入图片描述

2.1 导入驱动Jar包

  1. 复制 mysql-connector-java-5.1.37-bin.jar 到项目的 lib 目录下:
    在这里插入图片描述
  2. 右键Add As Library
    在这里插入图片描述

2.2 加载和注册驱动

  1. 语法:Class.forName(数据库驱动实现类)
    数据库驱动由数据库厂商提供:

    • MySQL驱动类名:com.mysql.jdbc.Driver
    • Oracle驱动类名:oracle.jdbc.driver.OracleDriver
    • SQL server驱动类名:com.microsoft.jdbc.sqlserver.SQLServerDriver
    public class Demo1 {
        public static void main(String[] args) throws ClassNotFoundException {
    		//抛出类找不到的异常,注册数据库驱动
            Class.forName("com.mysql.jdbc.Driver");
        }
    }
    
  2. 为什么这样可以注册驱动?
    查看 Class.forName(String className) 源码:

    @CallerSensitive
    public static Class<?> forName(String className) throws ClassNotFoundException {
        Class<?> caller = Reflection.getCallerClass();
        return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
    }
    

    再看 forName0 方法:

    private static native Class<?> forName0(String name, boolean initialize,
                                     ClassLoader loader,
                                     Class<?> caller)
    	throws ClassNotFoundException;
    

    forName0 是一个 native 方法(由非java语言实现),返回一个给定类或者接口的一个 Class 对象,如果没有给定 ClassLoader, 那么会使用根类加载器。如果 initalize 这个参数传了 true,那么给定的类如果之前没有被初始化过,则会被初始化。JDBC 传入的参数是 com.mysql.jdbc.Driver。也就是说这个类会被初始化,查看这个类的源码:

    public class Driver implements java.sql.Driver {
        static {
            try {
                DriverManager.registerDriver(new Driver()); //注册数据库驱动
            } catch (SQLException var1) {
                throw new RuntimeException("Can't register driver!");
            }
        }
    
        public Driver() throws SQLException {
        }
    }
    

    类在初始化的时候,静态代码块的内容会被执行的。也就是说我们 Class.forName 和直接写 DriverManager.registerDriver(new Driver) 两者功能是等同的。

    注:从JDBC3开始,目前已经普遍使用的版本。可以不用注册驱动而直接使用。Class.forName这句话可以省略。

2.3 获取数据库连接

  1. DriverManager类:驱动管理对象。
    1. Connection getConnection (String url, String user, String password) :通过连接字符串,用户名,密码来得到数据库的连接对象;

      • url :数据库连接路径,用于标识数据库的位置,告知JDBC程序连接哪个数据库。
        • 格式:协议名:子协议://服务器名或IP地址:端口号/数据库名?参数=参数值
        • MySQL格式:jdbc:mysql://ip地址(域名):端口号/数据库名称?参数=参数值
          在这里插入图片描述

          注:如果连接的是本地服务器,并且端口号是3306,可简写为:jdbc:mysql:///数据库名称

      • user :登录的用户名。
      • password :登录的密码。
      • 乱码处理:指定参数 ?characterEncoding=utf8,表示让数据库以UTF-8编码来处理数据。
        jdbc:mysql://localhost:3306/数据库?characterEncoding=utf8
      import java.sql.Connection;
      import java.sql.DriverManager;
      import java.sql.SQLException;
      
      public class Demo2 {
          public static void main(String[] args) throws SQLException {
              //使用用户名、密码、URL得到连接对象
              String url = "jdbc:mysql://localhost:3306/day24";
              Connection connection = DriverManager.getConnection(url, "root", "root");
              System.out.println(connection); //com.mysql.jdbc.JDBC4Connection@68de145
          }
      }
      
    2. Connection getConnection (String url, Properties info) :通过连接字符串,属性对象来得到连接对象。

      import java.sql.Connection;
      import java.sql.DriverManager;
      import java.sql.SQLException;
      import java.util.Properties;
      
      public class Demo3 {
          public static void main(String[] args) throws SQLException {
      		//url连接字符串
              String url = "jdbc:mysql://localhost:3306/day24";
      		//属性对象
              Properties info = new Properties();
      		//把用户名和密码放在info对象中
              info.setProperty("user", "root");
              info.setProperty("password", "root");
              Connection connection = DriverManager.getConnection(url, info);
              System.out.println(connection); //com.mysql.jdbc.JDBC4Connection@68de145
          }
      }
      

2.4 执行sql语句

  1. Connection接口:数据库连接对象。
    1. Statement createStatement() :创建一条SQL语句对象。
  2. ResultSet接口:结果集对象,用于封装查询结果。
    在这里插入图片描述
    • boolean next() :游标向下移动1行,判断当前指向的记录是否还有下一条记录,如果还有下一条返回true;否则返回false。

    • 参数数据类型 getXxx(参数)

      • 通过列名:
        getInt(“id”),getString(“name”),getBoolean("gender "),getDate(“birthday”)
      • 通过列号:
        getInt(1), getString(2),getBoolean(3),getDate(4)

      在这里插入图片描述
      常用数据类型转换表:

      SQL类型JDBC对应方法返回类型
      BIT(1) bit(n)getBoolean()boolean
      TINYINTgetByte()byte
      SMALLINTgetShort()short
      INTgetInt()int
      BIGINTgetLong()long
      CHAR,VARCHARgetString()String
      Text(Clob) BlobgetClob getBlob()Clob Blob
      DATEgetDate()java.sql.Date 只代表日期
      TIMEgetTime()java.sql.Time 只表示时间
      TIMESTAMPgetTimestamp()java.sql.Timestamp 同时有日期和时间

      java.sql.Date、Time、Timestamp(时间戳),三个共同父类是:java.util.Date

  3. Statement接口:代表一条语句对象,用于发送SQL语句给服务器,用于执行静态 SQL 语句并返回它所生成结果的对象。
    1. 释放资源:

      1. 需要释放的对象:ResultSet结果集,Statement语句,Connection连接;
      2. 释放原则:先开的后关,后开的先关。ResultSet → Statement → Connection;
      3. 放在哪个代码块中:finally块。
    2. Statement中的方法:

      1. int executeUpdate(String sql) :执行DML(insert、update、delete)语句、DDL(create,alter、drop)语句,返回对数据库影响的行数。

        1. 执行DDL操作:使用JDBC在MySQL的数据库中创建一张学生表。

          import java.sql.Connection;
          import java.sql.DriverManager;
          import java.sql.SQLException;
          import java.sql.Statement;
          
          public class Demo4DDL {
              public static void main(String[] args) {
          		//1. 创建连接
                  Connection conn = null;
                  Statement statement = null;
                  try {
                      conn = DriverManager.getConnection("jdbc:mysql:///day24", "root", "root");
          			//2. 通过连接对象得到语句对象
                      statement = conn.createStatement();
          			//3. 通过语句对象发送SQL语句给服务器
          			//4. 执行SQL
                      statement.executeUpdate("create table student (id int PRIMARY key auto_increment, " +
                              "name varchar(20) not null, gender boolean, birthday date)");
          			//5. 返回影响行数(DDL没有返回值)
                      System.out.println("创建表成功");
                  } catch (SQLException e) {
                      e.printStackTrace();
                  }
          		//6. 释放资源
                  finally {
          			//关闭之前要先判断
                      if (statement != null) {
                          try {
                              statement.close();
                          } catch (SQLException e) {
                              e.printStackTrace();
                          }
          
                      }
                      if (conn != null) {
                          try {
                              conn.close();
                          } catch (SQLException e) {
                              e.printStackTrace();
                          }
                      }
                  }
              }
          }
          

          在这里插入图片描述

        2. 执行DML操作:使用JDBC向学生表中添加4条记录,主键是自动增长。

          import java.sql.Connection;
          import java.sql.DriverManager;
          import java.sql.SQLException;
          import java.sql.Statement;
          
          public class Demo5DML {
              public static void main(String[] args) throws SQLException {
          		// 1) 创建连接对象
                  Connection connection = DriverManager.getConnection("jdbc:mysql:///day24", "root", "root");
          		// 2) 创建Statement语句对象
                  Statement statement = connection.createStatement();
          		// 3) 执行SQL语句:executeUpdate(sql)
                  int count = 0;
          		// 4) 返回影响的行数
                  count += statement.executeUpdate("insert into student values(null, '孙悟空', 1, '1993-03-24')");
                  count += statement.executeUpdate("insert into student values(null, '白骨精', 0, '1995-03-24')");
                  count += statement.executeUpdate("insert into student values(null, '猪八戒', 1, '1903-03-24 ')");
                  count += statement.executeUpdate("insert into student values(null, '嫦娥', 0, '1993-03-11')");
                  System.out.println("插入了" + count + "条记录");
          		// 5) 释放资源
                  statement.close();
                  connection.close();
              }
          }
          

          在这里插入图片描述

      2. ResultSet executeQuery(String sql) :执行DQL(select)语句,返回结果集对象。

        • 执行DQL操作:确保数据库中有3条以上的记录,查询所有的学员信息。

          import java.sql.*;
          
          public class Demo6DQL {
              public static void main(String[] args) throws SQLException {
          		//1) 得到连接对象
                  Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/day24", "root", "root");
          		//2) 得到语句对象
                  Statement statement = connection.createStatement();
          		//3) 执行SQL语句得到结果集ResultSet对象
                  ResultSet rs = statement.executeQuery("select * from student");
          		//4) 循环遍历取出每一条记录
                  while (rs.next()) {
                      int id = rs.getInt("id");
                      String name = rs.getString("name");
                      boolean gender = rs.getBoolean("gender");
                      Date birthday = rs.getDate("birthday");
          			//5) 输出的控制台上
                      System.out.println("编号:" + id + ", 姓名:" + name + ", 性别:" + gender + ", 生日:" + birthday);
                  }
          		//6) 释放资源
                  rs.close();
                  statement.close();
                  connection.close();
              }
          }
          

          结果:
          在这里插入图片描述

          注意:
          1)如果光标在第一行之前,使用rs.getXX()获取列值,报错:Before start of result set;
          2)如果光标在最后一行之后,使用rs.getXX()获取列值,报错:After end of result set;
          3)使用完毕以后要关闭结果集ResultSet,再关闭Statement,再关闭Connection。

3 数据库工具类JDBCUtils

3.1 建立JDBCUtils

  1. 什么时候自己创建工具类?
    如果一个功能经常要用到,我们建议把这个功能做成一个工具类,可以在不同的地方重用。

  2. 创建类JDBCUtils包含3个方法:

    • 可以把几个字符串定义成常量:
      用户名密码URL驱动类
    • 得到数据库的连接:
      getConnection()
    • 关闭所有打开的资源:
      close(Connection conn, Statement stmt)
      close(Connection conn, Statement stmt, ResultSet rs)
  3. 代码:

    import java.sql.*;
    
    /**
     * 访问数据库的工具类
     */
    public class JDBCUtils {
        //可以把几个字符串定义成常量:用户名,密码,URL,驱动类
        private static final String USER = "root";
        private static final String PWD = "root";
        private static final String URL = "jdbc:mysql://localhost:3306/day24";
        private static final String DRIVER = "com.mysql.jdbc.Driver";
    
        /**
         * 注册驱动
         */
        static {
            try {
                Class.forName(DRIVER);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 得到数据库的连接
         */
        public static Connection getConnection() throws SQLException {
            return DriverManager.getConnection(URL, USER, PWD);
        }
    
        /**
         * 关闭所有打开的资源
         */
        public static void close(Connection conn, Statement stmt) {
            if (stmt != null) {
                try {
                    stmt.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    
        /**
         * 关闭所有打开的资源
         */
        public static void close(Connection conn, Statement stmt, ResultSet rs) {
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            close(conn, stmt);
        }
    }
    

3.2 案例:用户登录

  1. 有一张用户表,添加几条用户记录:

    CREATE TABLE USER (
    	id INT PRIMARY KEY auto_increment,
    	NAME VARCHAR ( 20 ),
    	PASSWORD VARCHAR ( 20 ) 
    ) 
    INSERT INTO USER VALUES (NULL,'jack','123'),(NULL,'rose','456');
    -- 登录, SQL中大小写不敏感
    SELECT * FROM USER WHERE NAME='JACK' AND PASSWORD='123';
    -- 登录失败
    SELECT * FROM USER WHERE NAME='JACK' AND PASSWORD='333';
    
  2. 根据用户输入的用户名和密码,使用JDBC来查询数据库:

    import com.itheima.utils.JDBCUtils;
    
    import javax.xml.transform.Result;
    import java.sql.Connection;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.sql.Statement;
    import java.util.Scanner;
    
    public class Demo7Login {
        //从控制台上输入的用户名和密码
        public static void main(String[] args) {
            Scanner sc = new Scanner(System.in);
            System.out.println("请输入用户名:");
            String name = sc.nextLine();
            System.out.println("请输入密码:");
            String password = sc.nextLine();
            login(name, password);
        }
    
        /**
         * 登录的方法
         */
        public static void login(String name, String password) {
    		//a) 通过工具类得到连接
            Connection connection = null;
            Statement statement = null;
            ResultSet rs = null;
            try {
                connection = JDBCUtils.getConnection();
    			//b) 创建语句对象,使用拼接字符串的方式生成SQL语句
                statement = connection.createStatement();
    			//c) 查询数据库,如果有记录则表示登录成功,否则登录失败
                String sql = "select * from user where name='" + name + "' and password='" + password + "'";
                System.out.println(sql);
                rs = statement.executeQuery(sql);
                if (rs.next()) {
                    System.out.println("登录成功,欢迎您:" + name);
                } else {
                    System.out.println("登录失败");
                }
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
    			//d) 释放资源
                JDBCUtils.close(connection, statement, rs);
            }
        }
    }
    

3.3 SQL注入问题

当我们输入以下密码,我们发现我们账号和密码都不对竟然登录成功了:

请输入用户名:
newboy
请输入密码:
a' or '1'='1
select * from user where name='newboy' and password='a' or '1'='1'
登录成功,欢迎您:newboy

分析:

select * from user where name='newboy' and password='a' or '1'='1'

name='newboy' and password='a' 为假;
'1'='1' 为真;
相当于 select * from user where true; 查询了所有记录。

我们让用户输入的密码和SQL语句进行字符串拼接。用户输入的内容作为了SQL语句语法的一部分,改变了原有SQL真正的意义,以上问题称为SQL注入。要解决SQL注入就不能让用户输入的密码和我们的SQL语句进行简单的字符串拼接。

3.4 PreparedStatement接口

  1. 概念:是Statement接口的子接口,继承于父接口中所有的方法,可预编译SQL语句。有效防止SQL注入的问题,安全性更高。

  2. 执行原理:
    在这里插入图片描述

  3. Connection创建PreparedStatement对象:

    1. PreparedStatement prepareStatement(String sql) :指定预编译的SQL语句,SQL语句中使用占位符 ? 创建一个语句对象。
  4. PreparedStatement接口中的方法:

    1. int executeUpdate() :执行DML,增删改的操作,返回影响的行数。
    2. ResultSet executeQuery() :执行DQL,查询的操作,返回结果集。
  5. PreparedSatement的好处:

    1. prepareStatement(String)会先将SQL语句发送给数据库预编译。PreparedStatement会引用着预编译后的结果。可以多次传入不同的参数给PreparedStatement对象并执行。减少SQL编译次数,提高效率;
    2. 安全性更高,没有SQL注入的隐患;
    3. 提高了程序的可读性。
  6. 使用PreparedStatement的步骤:

    1. 编写SQL语句,未知内容使用 ? 占位:

      SELECT * FROM user WHERE name=? AND password=?;
      
    2. 获得PreparedStatement对象;

    3. 设置实际参数 setXxx(占位符的位置, 真实的值)

      PreparedStatement中设置参数的方法描述
      void setDouble(int parameterIndex, double x)将指定参数设置为给定 Java double 值
      void setFloat(int parameterIndex, float x)将指定参数设置为给定 Java REAL 值
      void setInt(int parameterIndex, int x)将指定参数设置为给定 Java int 值
      void setLong(int parameterIndex, long x)将指定参数设置为给定 Java long 值
      void setObject(int parameterIndex, Object x)使用给定对象设置指定参数的值
      void setString(int parameterIndex, String x)将指定参数设置为给定 Java String 值
    4. 执行参数化SQL语句;

    5. 关闭资源。

  7. 使用PreparedStatement改写上面的登录程序,看有没有SQL注入的情况:

    import com.itheima.utils.JDBCUtils;
    
    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.util.Scanner;
    
    /**
     * 使用PreparedStatement
     */
    public class Demo8Login {
        //从控制台上输入的用户名和密码
        public static void main(String[] args) throws SQLException {
            Scanner sc = new Scanner(System.in);
            System.out.println("请输入用户名:");
            String name = sc.nextLine();
            System.out.println("请输入密码:");
            String password = sc.nextLine();
            login(name, password);
        }
    
        /**
         * 登录的方法
         *
         * @param name
         * @param password
         */
        private static void login(String name, String password) throws SQLException {
            Connection connection = JDBCUtils.getConnection();
            //写成登录SQL语句,没有单引号
            String sql = "select * from user where name=? and password=?";
            //得到语句对象
            PreparedStatement ps = connection.prepareStatement(sql);
            //设置参数
            ps.setString(1, name);
            ps.setString(2, password);
            ResultSet resultSet = ps.executeQuery();
            if (resultSet.next()) {
                System.out.println("登录成功:" + name);
            } else {
                System.out.println("登录失败");
            }
            //释放资源,子接口直接给父接口
            JDBCUtils.close(connection, ps, resultSet);
        }
    }
    

3.5 表与类的关系

在这里插入图片描述

  1. 使用PreparedStatement查询一条数据,封装成一个学生Student对象:

    import com.itheima.entity.Student;
    import com.itheima.utils.JDBCUtils;
    
    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    
    public class Demo9Student {
        public static void main(String[] args) throws SQLException {
    		//创建学生对象
            Student student = new Student();
            Connection connection = JDBCUtils.getConnection();
            PreparedStatement ps = connection.prepareStatement("select * from student where id=?");
    		//设置参数
            ps.setInt(1, 2);
            ResultSet resultSet = ps.executeQuery();
            if (resultSet.next()) {
    			//封装成一个学生对象
                student.setId(resultSet.getInt("id"));
                student.setName(resultSet.getString("name"));
                student.setGender(resultSet.getBoolean("gender"));
                student.setBirthday(resultSet.getDate("birthday"));
            }
    		//释放资源
            JDBCUtils.close(connection, ps, resultSet);
    		//可以数据
            System.out.println(student);
        }
    }
    
  2. 将多条记录封装成集合List,集合中每个元素是一个JavaBean实体类:

    import com.itheima.entity.Student;
    import com.itheima.utils.JDBCUtils;
    
    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.util.ArrayList;
    import java.util.List;
    
    public class Demo10List {
        public static void main(String[] args) throws SQLException {
    		//创建一个集合
            List<Student> students = new ArrayList<>();
            Connection connection = JDBCUtils.getConnection();
            PreparedStatement ps = connection.prepareStatement("select * from student");
    		//没有参数替换
            ResultSet resultSet = ps.executeQuery();
            while (resultSet.next()) {
    			//每次循环是一个学生对象
                Student student = new Student();
    			//封装成一个学生对象
                student.setId(resultSet.getInt("id"));
                student.setName(resultSet.getString("name"));
                student.setGender(resultSet.getBoolean("gender"));
                student.setBirthday(resultSet.getDate("birthday"));
    			//把数据放到集合中
                students.add(student);
            }
    		//关闭连接
            JDBCUtils.close(connection, ps, resultSet);
    		//使用数据
            for (Student stu : students) {
                System.out.println(stu);
            }
        }
    }
    
  3. PreparedStatement执行DML操作:

    import com.itheima.utils.JDBCUtils;
    
    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.SQLException;
    
    public class Demo11DML {
        public static void main(String[] args) throws SQLException {
            //insert();
            //update();
            delete();
        }
    
        //插入记录
        private static void insert() throws SQLException {
            Connection connection = JDBCUtils.getConnection();
            PreparedStatement ps = connection.prepareStatement("insert into student values(null,?,?,?)");
            ps.setString(1, "小白龙");
            ps.setBoolean(2, true);
            ps.setDate(3, java.sql.Date.valueOf("1999-11-11"));
            int row = ps.executeUpdate();
            System.out.println("插入了" + row + "条记录");
            JDBCUtils.close(connection, ps);
        }
    
        //更新记录: 换名字和生日
        private static void update() throws SQLException {
            Connection connection = JDBCUtils.getConnection();
            PreparedStatement ps = connection.prepareStatement("update student set name=?, birthday=? where id=?");
            ps.setString(1, "黑熊怪");
            ps.setDate(2, java.sql.Date.valueOf("1999-03-23"));
            ps.setInt(3, 5);
            int row = ps.executeUpdate();
            System.out.println("更新" + row + "条记录");
            JDBCUtils.close(connection, ps);
        }
    
        //删除记录: 删除第5条记录
        private static void delete() throws SQLException {
            Connection connection = JDBCUtils.getConnection();
            PreparedStatement ps = connection.prepareStatement("delete from student where id=?");
            ps.setInt(1, 5);
            int row = ps.executeUpdate();
            System.out.println("删除了" + row + "条记录");
            JDBCUtils.close(connection, ps);
        }
    }
    

3.6 JDBC事务的处理

之前我们是使用MySQL的命令来操作事务。接下来我们使用JDBC来操作银行转账的事务。

  1. 准备数据:

    CREATE TABLE account (
    	id INT PRIMARY KEY AUTO_INCREMENT,
    	NAME VARCHAR ( 10 ),
    	balance DOUBLE 
    );
    -- 添加数据
    INSERT INTO account (NAME,balance) VALUES ('Jack',1000),('Rose',1000);
    
  2. Connection接口中与事务有关的方法:

    1. void setAutoCommit(boolean autoCommit) :参数是true或false。如果设置为false,表示关闭自动提交,相当于开启事务
    2. void commit() :提交事务;
    3. void rollback() :回滚事务。
  3. 具体步骤:

    1. 获取连接;
    2. 开启事务;
    3. 获取到PreparedStatement;
    4. 使用PreparedStatement执行两次更新操作;
    5. 正常情况下提交事务;
    6. 出现异常回滚事务;
    7. 最后关闭资源。
    import com.itheima.utils.JDBCUtils;
    
    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.SQLException;
    
    public class Demo12Transaction {
        //没有异常,提交事务,出现异常回滚事务
        public static void main(String[] args) {
    		//1) 注册驱动
            Connection connection = null;
            PreparedStatement ps = null;
            try {
    			//2) 获取连接
                connection = JDBCUtils.getConnection();
    			//3) 开启事务
                connection.setAutoCommit(false);
    			//4) 获取到PreparedStatement
    			//从Jack扣钱
                ps = connection.prepareStatement("update account set balance = balance - ? where name=?");
                ps.setInt(1, 500);
                ps.setString(2, "Jack");
                ps.executeUpdate();
    			//出现异常
                System.out.println(100 / 0);
    			//给Rose加钱
                ps = connection.prepareStatement("update account set balance = balance + ? where name=?");
                ps.setInt(1, 500);
                ps.setString(2, "Rose");
                ps.executeUpdate();
    			//提交事务
                connection.commit();
                System.out.println("转账成功");
            } catch (Exception e) {
                e.printStackTrace();
                try {
    				//事务的回滚
                    connection.rollback();
                } catch (SQLException e1) {
                    e1.printStackTrace();
                }
                System.out.println("转账失败");
            } finally {
    			//7) 关闭资源
                JDBCUtils.close(connection, ps);
            }
        }
    }
    
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值