1. JDBC 概述

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

1) 方式1: 使用第三方客户端来访问 MySQL:SQLyog
image.png
2) 方式2: 使用命令行访问MySQL
image.png
3) 我们今天要学习的是通过 Java程序 来访问 MySQL 数据库

1.2 什么是JDBC

JDBC(Java Data Base Connectivity) 是Java访问数据库的标准规范,是一种用于执行SQL语句的Java API,可以为多种关系型数据库提供统一访问,它由一组用Java语言编写成的类和接口组成。是Java访问数据库的标准规范。

1.3 JDBC 原理

JDBC是接口,驱动是接口的实现,没有驱动将无法完成数据库连接,从而不能操作数据库。每个数据库厂商都需
要提供自己的驱动,用来连接自己公司的数据库,也就是说驱动一般都由数据库生成厂商提供。

JDBC概述.png
总结:
JDBC就是由sun公司定义的一套操作所有关系型数据库的规则**(接口),而数据库厂商需要实现这套接口,提供数据库驱动jar, 我们可以使用这套接口编程,**真正执行的代码是对应驱动包中的实现类。

2. JDBC 开发

2.1 数据准备

  1. -- 创建 jdbc_user
  2. CREATE TABLE jdbc_user (
  3. id INT PRIMARY KEY AUTO_INCREMENT ,
  4. username VARCHAR(50),
  5. PASSWORD VARCHAR(50),
  6. birthday DATE
  7. );
  8. -- 添加数据
  9. INSERT INTO jdbc_user (username, PASSWORD,birthday)
  10. VALUES('admin1', '123','1991/12/24'),
  11. ('admin2','123','1995/12/24'),
  12. ('test1', '123','1998/12/24'),
  13. ('test2', '123','2000/12/24');

2.2 MySql驱动包

  1. 将MySQL驱动包添加到jar包库文件夹中,Myjar文件夹,用于存放当前项目需要的所有jar包

image.png

  1. 在 idea中 配置jar包库的位置

image.png
image.png

  1. 创建一个新的项目jdbc_task01, 配置jar包库

image.png

2.3 API使用: 1.注册驱动

  • JDBC规范定义驱动接口java.sql.Driver
  • MySql驱动包提供了实现类com.mysql.jdbc.Driver | 加载注册驱动的方式 | 描述 | | —- | —- | | Class.forName(数据库驱动实现类) | 加载和注册数据库驱动,数据库驱动由数据库厂商MySql提供”com.mysql.jdbc.Driver” |

1) 代码示例

package com.lagou.demo01;

/**
 * @author 西风月
 * @date 2020/9/17
 * @description
 */
public class JDBCDemo01 {
    public static void main(String[] args) throws Exception {
        //1. 注册驱动(可以省略)
        Class.forName("com.mysql.jdbc.Driver");
    }
}

2) 为什么这样可以注册驱动?
我们知道 Class类的forName方法 ,可以将一个类初始化, 现在我们一起Driver类的 看一下源码:

// Driver类是MySql提供的数据库驱动类, 实现了JDBC的Driver接口 java.sql.Driver
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    // 空参构造
    public Driver() throws SQLException {
    }

    //静态代码块,Class类的 forName()方法将Driver类 加载到内存, static代码块会自动执行
    static {
        try {
            /*
                DriverManager 驱动管理类
                registerDriver(new Driver) 注册驱动的方法
                注册数据库驱动
            */
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}

注:

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

2.4 API使用: 2.获得连接

  • Connection 接口,代表一个连接对象 ,具体的实现类由数据库的厂商实现
  • 使用 DriverManager类的静态方法,getConnection可以获取数据库的连接

image.png

1) getConnection方法 3个 连接参数说明

image.png

2) 对URL的详细说明

jdbc:mysql://localhost:3306/db4?characterEncoding=UTF-8
  • JDBC规定url的格式由三部分组成,每个部分中间使用冒号分隔。
    • 第一部分是协议 jdbc,这是固定的;
    • 第二部分是子协议,就是数据库名称,连接mysql数据库,第二部分当然是mysql了;
    • 第三部分是由数据库厂商规定的,我们需要了解每个数据库厂商的要求,mysql的第三部分分别由数据库服务器的IP地址(localhost)、端口号(3306),以及要使用的 数据库名称 组成。

image.png

3) 代码示例

package com.lagou.demo01;

import java.sql.Connection;
import java.sql.DriverManager;

/**
 * @author 西风月
 * @date 2020/9/17
 * @description
 */
public class JDBCDemo01 {
    public static void main(String[] args) throws Exception {
        //1. 注册驱动(可以省略)
        Class.forName("com.mysql.jdbc.Driver");

        //2. 获取连接connection连接对象

        //协议:子协议(数据库类型)://主机:端口/数据库
        String url = "jdbc:mysql://localhost:3306/db4";
        Connection con = DriverManager.getConnection(url,"root","em1tu0f");   //java.sql.SQLException
        //com.mysql.jdbc.JDBC4Connection@2e3fc542
        System.out.println(con);
    }
}

2.5 API 使用: 3.获取语句执行平台

  • 通过Connection 的 createStatement方法 获取sql语句执行对象

image.png

  • Statement :代表一条语句对象,用于发送 SQL 语句给服务器,用于执行静态 SQL 语句并返回它所生成结果的对象。

image.png

package com.lagou.demo01;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;

/**
 * @author 西风月
 * @date 2020/9/17
 * @description
 */
public class JDBCDemo01 {
    public static void main(String[] args) throws Exception {
        //1. 注册驱动(可以省略)
        Class.forName("com.mysql.jdbc.Driver");

        //2. 获取连接connection连接对象

        //协议:子协议(数据库类型)://主机:端口/数据库
        String url = "jdbc:mysql://localhost:3306/db4";
        Connection con = DriverManager.getConnection(url,"root","em1tu0f");   //java.sql.SQLException

        //3. 获取Statement对象
        Statement statement = con.createStatement();

        //4. 执行创建表操作
        String sql = "create table test01(id int, name varchar(20), age int)";

        //5. 增删改操作,使用executeUpdate,增加一张表
        int i = statement.executeUpdate(sql);

        //7. 返回值是受影响的行数
        System.out.println(i);

        //7. 关闭流
        statement.close();
        con.close();
    }
}

2.6 API 使用: 4.处理结果集

  • 只有在进行查询操作的时候, 才会处理结果集

    2.6.1 ResultSet接口

  • 作用:封装数据库查询的结果集,对结果集进行遍历,取出每一条记录。

image.png
image.png

package com.lagou.demo01;

import java.sql.*;

/**
 * @author 西风月
 * @date 2020/9/17
 * @description
 */
public class JDBCDemo02 {
    public static void main(String[] args) throws Exception {
        //1. 注册驱动(可以省略)
        Class.forName("com.mysql.jdbc.Driver");

        //2. 获取连接connection连接对象

        //协议:子协议(数据库类型)://主机:端口/数据库
        String url = "jdbc:mysql://localhost:3306/db4";
        Connection con = DriverManager.getConnection(url,"root","em1tu0f");   //java.sql.SQLException

        //3. 获取Statement对象
        Statement statement = con.createStatement();

        String sql = "select * from jdbc_usr";
        //4. 执行查询操作,返回的是一个ResultSet结果对象
        ResultSet resultSet = statement.executeQuery(sql);

        //4. 处理结果集
        while(resultSet.next()) {
            //获取id
            int id = resultSet.getInt("id");
            //获取姓名
            String username = resultSet.getString("username");
            //获取生日
            Date date = resultSet.getDate("birthday");    //java.sql.Date

            System.out.println(id + " = " + username + " : " + date);
        }

        //关闭连接
        resultSet.close();
        statement.close();
        con.close();
    }
}

2.7 API 使用: 5.释放资源

  1. 需要释放的对象:ResultSet 结果集,Statement 语句,Connection 连接
  2. 释放原则:先开的后关,后开的先关。ResultSet ==> Statement ==> Connection
  3. 放在哪个代码块中:finally 块
  • 与IO流一样,使用后的东西都需要关闭!关闭的顺序是先开后关, 先得到的后关闭,后得到的先关闭

释放资源.jpg

package com.lagou.demo01;

import java.sql.*;

/**
 * @author 西风月
 * @date 2020/9/17
 * @description
 */
public class JDBCDemo03 {
    public static void main(String[] args) {
        Connection con = null;
        Statement statement = null;
        ResultSet resultSet = null;
        try {
            //1. 注册驱动(可以省略)
            Class.forName("com.mysql.jdbc.Driver");

            //2. 获取连接connection连接对象

            //协议:子协议(数据库类型)://主机:端口/数据库
            String url = "jdbc:mysql://localhost:3306/db4";
            con = DriverManager.getConnection(url, "root", "em1tu0f");   //java.sql.SQLException

            //3. 获取Statement对象
            statement = con.createStatement();

            String sql = "select * from jdbc_usr";
            //4. 执行查询操作,返回的是一个ResultSet结果对象
            resultSet = statement.executeQuery(sql);

            //4. 处理结果集
            while (resultSet.next()) {
                //获取id
                int id = resultSet.getInt("id");
                //获取姓名
                String username = resultSet.getString("username");
                //获取生日
                Date date = resultSet.getDate("birthday");    //java.sql.Date

                System.out.println(id + " = " + username + " : " + date);
            }
        } catch (SQLException | ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            //关闭连接
            try {
                resultSet.close();
                statement.close();
                con.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

2.8 步骤总结

  1. 获取驱动(可以省略)
  2. 获取连接
  3. 获取Statement对象
  4. 处理结果集(只在查询时处理)
  5. 释放资源

3. JDBC实现增删改查

3.1 JDBC工具类

  • 什么时候自己创建工具类?
    • 如果一个功能经常要用到,我们建议把这个功能做成一个工具类,可以在不同的地方重用。
    • “获得数据库连接”操作,将在以后的增删改查所有功能中都存在,可以封装工具类JDBCUtils。提供获取连接对象的方法,从而达到代码的重复利用。
  • 工具类包含的内容
    • 1) 可以把几个字符串定义成常量:用户名,密码,URL,驱动类
    • 2) 得到数据库的连接:getConnection()
    • 3) 关闭所有打开的资源
package com.lagou.demo01;

import java.sql.*;

/**
 * @author 西风月
 * @date 2020/9/17
 * @description
 */
public class JDBCUtils {
    // 1.定义字符串常量,记录获取连接所需要的信息
    public static final String DRIVERNAME = "com.mysql.jdbc.Driver";
    public static final String URL = "jdbc:mysql://localhost:3306/db4?characterEncoding=UTF-8";
    public static final String USER = "root";
    public static final String PASSWORD = "em1tu0f";

    //2. 静态代码块,随着类的加载而加载
    static {
        try {
            Class.forName(DRIVERNAME);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    // 3. 获取连接的静态方法
    public static Connection getConnection() {
        try {
            Connection connection = DriverManager.getConnection(URL,USER,PASSWORD);
            return connection;
        } catch (SQLException e) {
            e.printStackTrace();
            return null;
        }
    }

    // 4.关闭资源的方法
    //更新类sql
    public static void close(Connection con, Statement st) {
        if(null != con && null != st) {
            try {
                st.close();
                con.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
    //查询类的sql
    public static void close(Connection con, Statement st, ResultSet rs) {
        if(null != rs) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            close(con, st);
        }
    }
}

3.2 DML操作

3.2.1 插入记录

  • 解决插入中文乱码问题:

    jdbc:mysql://localhost:3306/db4?characterEncoding=UTF-8
    characterEncoding=UTF-8 指定字符的编码、解码格式。
    
    /**
       * 插入数据
       * */
      @Test
      public void testInsert() throws SQLException {
          //1. 通过工具类获取数据库连接
          Connection con = JDBCUtils.getConnection();
    
          //2. 获取Statement对象
          Statement statement = con.createStatement();
    
          //2.1 编写sql
          String sql = "insert into jdbc_usr values(null,'张百万','123','2020/1/1')";
    
          //2.2 执行Sql
          int i = statement.executeUpdate(sql);
          System.out.println(i);
    
          //3. 关闭流
          JDBCUtils.close(con, statement);
      }
    

    3.2.2 更新记录

  • 根据ID来更新用户名称

    /**
       *   修改 id 为1 的用户名为 广坤
       */
      @Test
      public void testUpdate() throws SQLException {
          Connection con = JDBCUtils.getConnection();
          Statement statement = con.createStatement();
          String sql = "update jdbc_usr set username = '广坤' where id = 1";
          int i = statement.executeUpdate(sql);
          System.out.println(i);
          JDBCUtils.close(con, statement);
      }
    

    3.2.3 删除记录

  • 删除id为 3 和 4 的记录 ```java /**

    • 删除id 为 3 和 4的记录 *
    • */ @Test public void testDelete() throws SQLException { Connection connection = JDBCUtils.getConnection(); Statement statement = connection.createStatement(); String sql = “delete from jdbc_usr where id in(3,4)”; int i = statement.executeUpdate(sql); System.out.println(i); JDBCUtils.close(connection, statement); } }
<a name="F5SwW"></a>
## 3.3 DQL操作
<a name="Ob0UX"></a>
### 3.3.1 查询姓名为张百万的一条记录
```java
package com.lagou.jdbc02;

import com.lagou.jdbc01.JDBCUtils;

import javax.naming.BinaryRefAddr;
import java.sql.*;

/**
 * @author 西风月
 * @date 2020/9/17
 * @description
 */
public class TestDQL {
    public static void main(String[] args) throws SQLException {
        Connection con = JDBCUtils.getConnection();
        Statement statement = con.createStatement();
        String sql = "SELECT * FROM jdbc_usr WHERE username = '张百万';";
        ResultSet resultSet = statement.executeQuery(sql);

        while(resultSet.next()) {
            int id = resultSet.getInt("id");
            String username = resultSet.getString("username");
            String password = resultSet.getString("password");
            Date birthday = resultSet.getDate("birthday");
            System.out.println(id + " : " + username + " : " + password + " : "+ birthday);
        }

        JDBCUtils.close(con, statement, resultSet);
    }
}

4. SQL注入问题

4.1 Sql注入演示

1) 向jdbc_user表中 插入两条数据

#插入2条数据
INSERT INTO jdbc_user VALUES(NULL,'jack','123456','2020/2/24');
INSERT INTO jdbc_user VALUES(NULL,'tom','123456','2020/2/24');
  • ) SQL注入演示
    # SQL注入演示
    -- 填写一个错误的密码
    SELECT * FROM jdbc_user WHERE username = 'tom' AND PASSWORD = '123' OR '1' = '1';
    
    如果这是一个登陆操作,那么用户就登陆成功了.显然这不是我们想要看到的结果~

4.2 sql注入案例:用户登陆

image.png

package com.lagou.jdbc02;

import com.lagou.jdbc01.JDBCUtils;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Scanner;

/**
 * @author 西风月
 * @date 2020/9/19
 * @description
 */
public class TestLogin01 {
    /**
     * 用户登录案例
     * @param args
     */
    public static void main(String[] args) throws SQLException {
        //1. 获取连接
        Connection connection = JDBCUtils.getConnection();

        //2. 获取Statement对象
        Statement statement = connection.createStatement();

        //3. 获取用户输入的用户名与密码
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入用户名:");
        String username = sc.nextLine();
        System.out.println("请输入密码:");
        String password = sc.nextLine();

        //4. 拼接查询SQL
        String sql = "select * from jdbc_usr where username = '" + username + "' " + "and password = '" + password + "'";
        System.out.println(sql);
        //5. 执行查询,获取结果集对象
        ResultSet resultSet = statement.executeQuery(sql);

        //6. 处理结果集
        if (resultSet.next()) {
            System.out.println("登录成功!欢迎您:" + username);
        } else
            System.out.println("登录失败!");

        //7. 关闭流
        JDBCUtils.close(connection,statement,resultSet);
    }
}

4.3 问题分析

1) 什么是SQL注入?

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

2) 如何实现的注入

  • 根据用户输入的数据,拼接处的字符串

image.png

3) 如何解决

要解决SQL 注入就不能让用户输入的密码和我们的 SQL 语句进行简单的字符串拼接。
**

5. 预处理对象

5.1 PreparedStatement 接口介绍

  • PreparedStatement 是 Statement 接口的子接口,继承于父接口中所有的方法。它是一个预编译的 SQL 语句对象。
  • 预编译: 是指SQL 语句被预编译,并存储在 PreparedStatement 对象中。然后可以使用此对象多次高效地执行该语句。

    5.2 PreparedStatement 特点

  • 因为有预先编译的功能,提高 SQL 的执行效率

  • 可以有效的防止 SQL 注入的问题,安全性更高

    5.3 获取PreparedStatement对象

  • 通过Connection创建PreparedStatement对象 | Connection 接口中的方法 | 说明 | | —- | —- | | PreparedStatement prepareStatement(String sql) | 指定预编译的 SQL 语句,
    SQL 语句中使用占位符 ? 创建一个语句对象 |

5.4 PreparedStatement接口常用方法

常用方法 说明
int executeUpdate(); 执行insert update delete语句
ResultSet executeQuery(); 执行select语句. 返回结果集对象 Resulet

5.5 使用PreparedStatement的步骤

1) 编写 SQL 语句,未知内容使用?占位
2) 获得 PreparedStatement 对象
3) 设置实际参数:setXxx( 占位符的位置, 真实的值)
4) 执行参数化SQL语句
5) 关闭资源
image.png

5.6 使用PreparedStatement完成登录案例

image.png

package com.lagou.jdbc03;

import com.lagou.jdbc01.JDBCUtils;

import java.sql.*;
import java.util.Scanner;

/**
 * @author 西风月
 * @date 2020/9/19
 * @description
 */
public class TestLogin02 {
    /**
     * SQL注入
     * 用户输入的用户名和密码与我们编写的SQL进行拼接
     * 这导致用户输入的内容称为SQL的一部分
     * 用户会利用这里漏洞,输入一些其他字符串,改变了SQL原有的意思
     *
     * 解决方案:
     *     要解决SQL注入 就不能让用户输入的数据直接参与SQL的拼接
     *
     * 预处理对象 PrepareStatement 它是Statement接口的子接口
     *     使用预处理对象可以预编译SQL语句,提高了SQL的执行效率
     *     使用预处理对象 通过占位符的方式来设置参数可以有效的防止SQL注入
     *
     * @param args
     */
    public static void main(String[] args) throws SQLException {
        //1. 获取连接
        Connection connection = JDBCUtils.getConnection();

        //2. 获取PrepareStatement 预处理对象
        // 使用 ? 占位符 的方式来设置参数
        String sql = "select * from jdbc_usr where username = ?  and  password = ?";
        PreparedStatement ps = connection.prepareStatement(sql);
        //3. 获取用户输入的用户名与密码
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入用户名:");
        String username = sc.nextLine();
        System.out.println("请输入密码:");
        String password = sc.nextLine();

        //4. 设置参数 使用setXXX(占位符的位置, 要设置的值)的方法设置占位符的参数
        ps.setString(1, username);
        ps.setString(2, password);

        //5. 执行查询
        ResultSet resultSet = ps.executeQuery();

        //6. 处理结果集
        if (resultSet.next()) {
            System.out.println("登录成功!欢迎您:" + username);
        } else
            System.out.println("登录失败!");

        //7. 关闭流
        JDBCUtils.close(connection,ps,resultSet);
    }
}

5.7 PreparedStatement的执行原理

  • 代码示例 ```java package com.lagou.jdbc03;

import com.lagou.jdbc01.JDBCUtils; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Statement;

/**

  • @author 西风月
  • @date 2020/9/20
  • @description */ public class TestPs { public static void main(String[] args) throws SQLException {

     Connection connection = JDBCUtils.getConnection();
    
     //获取Statement
     Statement statement = connection.createStatement();
    
     //向数据库中插入两条数据
     statement.executeUpdate("insert into jdbc_usr values(null, '张三', '123456', '1999/12/31')");
     statement.executeUpdate("insert into jdbc_usr values(null, '李四', '123456', '1993/12/31')");
    
     //获取预处理对象
     PreparedStatement ps = connection.prepareStatement("insert into jdbc_usr values(?,?,?,?)");
    
     //先插入一条数据
     ps.setObject(1,null);
     ps.setString(2,"关羽");
     ps.setString(3, "123321");
     ps.setString(4,"1992/12/31");
     ps.executeUpdate();
    
     //先插入第二条数据
     ps.setObject(1,null);
     ps.setString(2,"刘备");
     ps.setString(3, "123321");
     ps.setString(4,"1992/12/31");
     ps.executeUpdate();
    
     //释放资源
     ps.close();
     statement.close();
     connection.close();
    

    } }

![预处理对象执行原理.jpg](https://cdn.nlark.com/yuque/0/2020/jpeg/1567843/1600608561603-3cb9b335-186b-48e5-93c4-cf4d3c32c53c.jpeg#align=left&display=inline&height=465&margin=%5Bobject%20Object%5D&name=%E9%A2%84%E5%A4%84%E7%90%86%E5%AF%B9%E8%B1%A1%E6%89%A7%E8%A1%8C%E5%8E%9F%E7%90%86.jpg&originHeight=1271&originWidth=2874&size=132996&status=done&style=none&width=1052)
<a name="DcRDy"></a>
## 5.8 Statement 与 PreparedStatement的区别?

1. `Statement`用于执行静态sql语句,在执行时,必须指定一个事先准备好的SQL语句。
1. `PrepareStatement`是预编译的sql语句对象,语句中可以包含动态参数“?”,在执行时可以为**“?”动态设置参数值**。
1. `PrepareStatement`可以减少编译次数提高数据库性能。

<a name="exf1O"></a>
# 6. JDBC 控制事务

- 之前我们是使用 MySQL 的命令来操作事务。接下来我们使用 JDBC 来操作银行转账的事务。
<a name="DPeCo"></a>
## 6.1 数据准备
```sql
-- 创建账户表
CREATE TABLE account(
    -- 主键
    id INT PRIMARY KEY AUTO_INCREMENT,
    -- 姓名
    NAME VARCHAR(10),
    -- 转账金额
    money DOUBLE
);
-- 添加两个用户
INSERT INTO account (NAME, money) VALUES ('tom', 1000), ('jack', 1000);

6.2 事务相关API

image.png

6.3 开发步骤

  1. 获取连接
  2. 开启事务
  3. 获取到 PreparedStatement , 执行两次更新操作
  4. 正常情况下提交事务
  5. 出现异常回滚事务
  6. 最后关闭资源

    6.4 代码示例

    ```java package com.lagou.jdbc04;

import com.lagou.jdbc01.JDBCUtils; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException;

/**

  • @author 西风月
  • @date 2020/9/20
  • @description */ public class TestTransactionOfJDBC { public static void main(String[] args) {

     Connection con = null;
     PreparedStatement ps = null;
     try {
         //1. 获取连接
         con = JDBCUtils.getConnection();
    
         //2. 开启事务
         con.setAutoCommit(false);   //手动提交事务
    
         //3. 获取预处理对象 执行SQL(两次修改操作)
         //3.1 tom账户 - 500
         ps = con.prepareStatement("update account set money = money - ? where name = ?");
         ps.setDouble(1, 500.00);
         ps.setString(2, "tom");
         ps.executeUpdate();
    
         /*
         //模拟tom转账操作以后出现异常
         System.out.println(1/0);
         */
    
         //3.2 Jack账户 + 500
         ps = con.prepareStatement("update account set money = money + ? where name = ?");
         ps.setDouble(1, 500.00);
         ps.setString(2, "jack");
         ps.executeUpdate();
    
         //4. 提交事务
         con.commit();
         System.out.println("转账成功~");
     } catch (SQLException e) {
         e.printStackTrace();
         //5. 出现异常就回滚
         try {
             con.rollback();
         } catch (SQLException ex) {
             ex.printStackTrace();
         }
     } finally {
         //6. 释放资源
         JDBCUtils.close(con, ps);
     }
    

    } }

```