JDBC 控制事务(MySQL为例)

事务

一个包含多个步骤的业务操作。如果这个业务操作被事务管理,则这多个步骤要么同时成功,要么同时失败。

对事务的操作

  1. 开启事务
  2. 提交事务
  3. 回滚事务

使用Connection对象来管理事务

java.sql.Connection接口是一个数据库连接对象。它与特定数据库的连接(会话)。 执行SQL语句并在连接的上下文中返回结果。

  • 开启事务

    setAutoCommit(boolean autoCommit)
    // 调用该方法设置参数为false,即开启事务
    
  • 提交事务

    commit()
    // 当所有sql都执行完提交事务
    
  • 回滚事务

    rollback()
    // 在catch中回滚事务
    

Java代码举例

有如下一个MySQL数据表,利用Java程序:把id = 1对应的余额减少500,id = 2对应的余额增加500

CREATE TABLE account (
    id INT PRIMARY KEY AUTO_INCREMENT,   -- id
    NAME VARCHAR(10),                    -- 名字
    balance DOUBLE                       -- 余额
);

INSERT INTO account (NAME, balance) VALUES ('LeeHua', 1000), ('Tom', 1000);

自定义一个注解,获取连接数据库的信息:

package my.view.util;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE) // 注解能作用于类上
@Retention(RetentionPolicy.RUNTIME) // 当前被描述的注解,会保留到class字节码文件中,并被JVM读取到
public @interface PropertiesAnnotation {

    /* URL */
    public abstract String url();

    /* 用户 */
    public abstract String user();

    /* 密码 */
    public abstract String password();

    /* 驱动包 */
    public abstract String driver();
}

定义一个工具类,用来注册驱动和获取数据库连接对象:

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

@PropertiesAnnotation(
        url = "jdbc:mysql:///Study", user = "账号", password = "密码", driver = "com.mysql.jdbc.Driver"
)
public class JdbcUtils02 {

    private static String url;
    private static String user;
    private static String password;
    private static String driver;

    /* 文件的读取,只需要读取一次即可拿到这些值。利用反射和注解、使用静态代码块 */
    static{
        // 读取资源文件,获取值。

        try {
            // 1. 解析注解
            // 1.1 获取JdbcUtils02类的字节码文件对象
            Class<JdbcUtils02> jdbcUtils02Class = JdbcUtils02.class;

            // 2. 获取上边的注解对象
            PropertiesAnnotation annotation = jdbcUtils02Class.getAnnotation(PropertiesAnnotation.class);

            // 3. 调用注解中定义的抽象方法,获取返回值,赋值给静态成员变量
            url = annotation.url();
            user = annotation.user();
            password = annotation.password();
            driver = annotation.driver();

            // 4. 注册驱动
            Class.forName(driver);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }


    /**
     * 获取连接
     * @return 连接对象
     */
    public static Connection getConnection() throws SQLException {
        return DriverManager.getConnection(url, user, password);
    }

}

有了以上条件,对数据表进行操作:

  1. 获取数据库连接

    Connection connection = JdbcUtils02.getConnection();
    
  2. 获取到数据库连接对象后,开启事务

    connection.setAutoCommit(false);
    
  3. 开启事务实际上是创建一个日志文件,将定义的SQL语句的执行结果暂时放到日记中,如果没有错,提交事务,如果出错,那么就回滚事务。接下来定义动态SQL语句

    String sql1 = "update account set balance = balance - ? where id = ?";
    
    String sql2 = "update account set balance = balance + ? where id = ?";
    
  4. 定义好了SQL语句,接下来就要获取执行动态SQL语句的对象

    PreparedStatement preparedStatement1 = connection.prepareStatement(sql1);
    
    PreparedStatement preparedStatement2 = connection.prepareStatement(sql2);
    
  5. 给动态SQL语句传入参数

    // LeeHua 的账户余额减少500元
    preparedStatement1.setDouble(1,500);
    preparedStatement1.setInt(2,1);
    
    // Tom 的账户余额增加500元
    preparedStatement2.setDouble(1,500);
    preparedStatement2.setInt(2,2);
    
  6. 一切准备就绪,这时候就可以执行SQL语句了

    preparedStatement1.executeUpdate();
    
    preparedStatement2.executeUpdate();
    
  7. 执行SQL语句后,如果没有错误,那么就提交事务,这时候的表记录就会更改

    connection.commit();
    
  8. 执行SQL语句后,如果有错误,那么就回滚事务,这个时候,会把日记中的记录删除,不会提交到表中,表的记录不会更改

    connection.rollback();
    
  9. 无论执行SQL语句,是否存在错误,最后都需要释放资源,调用自定义releaseResources()方法,释放资源

    releaseResources(preparedStatement2);
    releaseResources(preparedStatement1);
    releaseResources(connection);
    
  10. releaseResources()方法的定义如下:

    /**
     * 释放资源
     * @param t 要被释放的资源
     * @param <T> 要被释放的资源对象的类型
     */
    public static <T> void releaseResources (T t){
        if(t != null){
            try {
                // 利用反射,获取class对象
                Class<?> aClass = t.getClass();
                // 获取class对象中的方法对象
                Method close = aClass.getMethod("close");
                // 执行方法
                close.invoke(t);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

对数据表进行操作的实现代码如下:

package my.view.jdbc;

import my.view.util.JdbcUtils02;

import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class JdbcDemo09 {

    public static void main(String[] args) {
        Connection connection = null;
        PreparedStatement preparedStatement1 = null;
        PreparedStatement preparedStatement2 = null;

        try {
            // 1. 获取连接
            connection = JdbcUtils02.getConnection();

            // 开启事务
            connection.setAutoCommit(false);

            // 2. 定义sql
            // 2.1 定义减少账户余额的SQL语句
            String sql1 = "update account set balance = balance - ? where id = ?";
            // 2.2 定义增加账户余额的SQL语句
            String sql2 = "update account set balance = balance + ? where id = ?";

            // 3.获取执行SQL语句的对象
            preparedStatement1 = connection.prepareStatement(sql1);
            preparedStatement2 = connection.prepareStatement(sql2);

            // 4. 设置参数
            // 4.1 LeeHua 的账户余额减少500元
            preparedStatement1.setDouble(1,500);
            preparedStatement1.setInt(2,1);
            // 4.2 Tom 的账户余额增加500元
            preparedStatement2.setDouble(1,500);
            preparedStatement2.setInt(2,2);

            // 5. 执行SQL语句
            preparedStatement1.executeUpdate();
            preparedStatement2.executeUpdate();

            // 6. 提交事务
            connection.commit();
        } catch (Exception e) {
            // 7. 事务回滚
            try {
                if(connection != null) {
                    connection.rollback();
                }
            } catch (SQLException e1) {
                e1.printStackTrace();
            }
        } finally {
            // 8. 释放资源
            releaseResources(preparedStatement2);
            releaseResources(preparedStatement1);
            releaseResources(connection);
        }

    }

    /**
     * 释放资源
     * @param t 要被释放的资源
     * @param <T> 要被释放的资源对象的类型
     */
    public static <T> void releaseResources (T t){
        if(t != null){
            try {
                // 利用反射,获取class对象
                Class<?> aClass = t.getClass();
                // 获取class对象中的方法对象
                Method close = aClass.getMethod("close");
                // 执行方法
                close.invoke(t);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

}

运行程序,查看表中的记录,发现LeeHua的账号余额减少了500,Tom的账户余额增加了500。

posted @ 2020-02-18 02:06  LeeHua  阅读(523)  评论(0编辑  收藏  举报