jdbc中为什么要使用事务?事务的工作流程介绍、事务示例、事务保存点示例

为什么使用事务

在数据库系统中,事务被设计为通过将要执行的多个语句分组作为一个单元来保持数据的完整性。
在事务中,要么执行所有语句,要么不执行任何语句。
如果任何语句执行失败,整个事务将中止,数据库将回滚到以前的状态。这确保了数据在网络问题、软件错误等事件中保持一致。

让我们看一个例子。

想象一下,在一个销售应用程序中,经理保存了一个新订单,并更新了当月的总销售额。订单明细和总销售额应同时更新,否则数据将不一致。在这里,应用程序应该对saveorderdetails语句进行分组,并更新事务中的totalsales语句。这两个语句都必须执行。如果有一个语句执行失败,所有更改都将被放弃。

事务还通过使用锁定机制阻止其他用户访问当前事务正在访问的数据,从而防止多个用户同时访问同一数据时可能出现的冲突。

JDBC中的典型事务处理工作流

try {
 
    // begin the transaction:
    connection.setAutoCommit(false);
 
    // execute statement #1
 
    // execute statement #2
 
    // execute statement #3
 
    // ...
 
    // commit the transaction
    connection.commit();
 
} catch (SQLException ex) {
    // abort the transaction
    connection.rollback();
 
} finally {
 
    // close statements
 
    connection.setAutoCommit(true);
}

1. 禁用自动提交模式
默认情况下,新连接处于自动提交模式。 这意味着每个SQL语句都被视为事务,并在执行后立即自动提交。 因此,我们必须禁用自动提交模式,才能将两个或多个语句分组到一个事务中:

2. Committing the transaction(保存交易)
After the auto commit mode is disabled, all subsequent SQL statements are included in the current transaction, and they are committed as a single unit until we call the method commit():

So a transaction begins right after the auto commit is disabled and ends right after the connection is committed. Remember to execute SQL statements between these calls to ensure they are in the same transaction.(因此,事务在禁用自动提交后立即开始,并在数据库连接提交后立即结束。 请记住在这些调用之间执行SQL语句,以确保它们在同一事务中。)

3. 回滚交易
如果任何语句执行失败,将引发SQLException,并且在catch块中,我们调用方法rollback()来中止事务
成功语句所做的任何更改都将被丢弃,并且数据库将回滚到事务处理之前的先前状态。

4. 启用自动提交模式
Finally最后,我们启用自动提交模式以将连接恢复为默认状态
在默认状态下(启用了自动提交),每个SQL都被视为事务,并且我们不需要手动调用commit()方法。

JDBC事务示例

Now, let’s look at a real code example. Suppose that we are working on a database called sales with the following tables:
在这里插入图片描述

新建数据库、表:

订单表:订单编号,产品编号,数量,订单日期
产品表:产品编号,产品名称,产品价格
月销售表:销售月份,产品编号,总销售量

mysql> create database sales;
Query OK, 1 row affected (0.01 sec)

mysql> use sales;
Database changed
mysql> create table orders(
    -> order_id int(11),
    -> product_id int(11),
    -> amount float,
    -> order_date datetime,
    -> primary key(order_id));
Query OK, 0 rows affected, 2 warnings (0.07 sec)

mysql> create table products(
    -> product_id int(11),
    -> product_name varchar(45),
    -> price float,
    -> primary key(product_id));
Query OK, 0 rows affected, 1 warning (0.04 sec)

mysql> create table monthly_sales(report_month int(11),product_id int(11),total_amount float);
Query OK, 0 rows affected, 2 warnings (0.03 sec)

mysql> show tables;
+-----------------+
| Tables_in_sales |
+-----------------+
| monthly_sales   |
| orders          |
| products        |
+-----------------+
3 rows in set (0.01 sec)

mysql>

为orders表和monthly_sales表添加外键约束

mysql> alter table orders add constraint FK_product_id foreign key(product_id) references products(product_id);
Query OK, 0 rows affected (0.09 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> alter table monthly_sales add constraint FK_product_id_monthly_sales foreign key(product_id) references products(product_id);
Query OK, 0 rows affected (0.06 sec)
Records: 0  Duplicates: 0  Warnings: 0

向products表中插入一条数据

mysql> insert into products(product_id,product_name,price) value(1,"T-Shirt",53.2);
Query OK, 1 row affected (0.01 sec)

向orders表中插入一条数据

mysql> insert into orders(order_id,product_id,amount,order_date) value(1,1,2,now());
Query OK, 1 row affected (0.00 sec)

修改orders表,将字段order_id设置为自增列

mysql> alter table orders modify order_id int not null  auto_increment;
Query OK, 1 row affected (0.09 sec)

保存新订单时(在订单表中插入了新行数据),还必须更新月销售额(在表month_sales中更新相应的行)。 因此,这两个语句(保存新订单和更新销售额)应分组为一个事务。

下面的方法展示了如何使用JDBC在事务中执行这两个语句:

package Bean;

import java.sql.*;


public class SalesDao {
    String url = "jdbc:mysql://localhost:3306/sales?serverTimezone=UTC";
    String name = "root";
    String password = "admin";

    /*
    产品编号
    订单日期
    销售数量
    销售月份
     */
    public void saveOrderk(int productId, Date orderDate, float amount, int reportMonth) {


        Connection connection = null;
        PreparedStatement orders_statement = null;
        PreparedStatement monthly_sales_statement = null;


        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            System.err.println(e.getCause());
        }

        System.out.println("JDBC_Driver loaded successfully!");

        try {
            connection = DriverManager.getConnection(url, name, password);
            if (connection != null) {
                System.out.println("connection connected!");
            }

//            禁用自动提交
            connection.setAutoCommit(false);

//            新增订单,订单信息:产品编号,订单日期,购买数量
            String sqlSaveOrder = "insert into orders (product_id,order_date,amount) values(?,?,?)";

            String sqlUpdate_monthly_sales = "update monthly_sales set total_amount=total_amount+? where product_id =? and report_month=?";

            orders_statement = connection.prepareStatement(sqlSaveOrder);
            monthly_sales_statement = connection.prepareStatement(sqlUpdate_monthly_sales);


            orders_statement.setInt(1, productId);
            orders_statement.setDate(2, orderDate);
            orders_statement.setFloat(3, amount);

            monthly_sales_statement.setFloat(1, amount);
            monthly_sales_statement.setInt(2, productId);
            monthly_sales_statement.setInt(3, reportMonth);

            orders_statement.executeUpdate();
            monthly_sales_statement.executeUpdate();
//           提交事务
            connection.commit();

            System.out.println("当前订单已经保存,月度销售数据已更新");


        } catch (SQLException e) {

            System.err.println(e.getCause());
            try {
                connection.rollback();
                System.out.println("rolled back.");
            } catch (SQLException e1) {

                System.err.println(e1.getCause());
            }


        } finally {
            try {
                if (orders_statement != null) {
                    orders_statement.close();
                }
                if (monthly_sales_statement != null) {
                    monthly_sales_statement.close();
                }
                if (connection != null) {
                    connection.close();
                }
            } catch (SQLException e) {
                System.err.println(e.getCause());
            }

        }


    }
}

测试jdbc事务

package Bean;

import java.sql.Date;

public class TransactionDemo {
    public static void main(String[] args) {


        SalesDao salesDao = new SalesDao();
        salesDao.saveOrderk(1, Date.valueOf("2020-11-12"), 100, 11);

    }
}

事务回滚点介绍与示例

保存点标记了当前事务可以回滚的点。 与其回滚所有更改,它可以选择仅回滚其中一些更改。 例如,假设:

  • start a transaction,
  • insert 10 rows into a table,
  • set a savepoint,
  • insert another 5 rows,
  • rollback to the savepoint,
  • commit the transaction.

完成此操作后,该表将包含您插入的前10行。 其他5行将被回滚删除。

事务保存的回滚点方法

package Bean;

import com.mysql.cj.protocol.Resultset;

import java.sql.*;


public class SalesDao {
    String url = "jdbc:mysql://localhost:3306/sales?serverTimezone=UTC";
    String name = "root";
    String password = "admin";

    /*
    产品编号
    订单日期
    销售数量
    销售月份
     */
    public void saveOrderk(String newProductName, float newProductPrice, int productId, Date orderDate, float orderamount, int reportMonth) {


        Connection connection = null;
        PreparedStatement orders_statement = null;
        PreparedStatement monthly_sales_statement = null;
        PreparedStatement productStatement = null;
        PreparedStatement getMonthlySalesStatement = null;


        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            System.err.println(e.getCause());
        }

        System.out.println("JDBC_Driver loaded successfully!");

        try {
            connection = DriverManager.getConnection(url, name, password);
            if (connection != null) {
                System.out.println("connection connected!");
            }

//            禁用自动提交
            connection.setAutoCommit(false);

            /*
            保存产品信息
            产品名称和价格
             */
            String sqlSaveProduct = "insert into products (product_id,product_name,price) values(?,?,?)";
            productStatement = connection.prepareStatement(sqlSaveProduct);
            productStatement.setInt(1, productId);
            productStatement.setString(2, newProductName);
            productStatement.setFloat(3, newProductPrice);
            productStatement.executeUpdate();

            /*
            设置事务保存点
             */
            Savepoint savepoint = connection.setSavepoint();



/*
            插入新订单信息
            新增订单,订单信息:产品编号,订单日期,购买数量

 */
            String sqlSaveOrder = "insert into orders (product_id,order_date,amount) values(?,?,?)";
            orders_statement = connection.prepareStatement(sqlSaveOrder);
            orders_statement.setInt(1, productId);
            orders_statement.setDate(2, orderDate);
            orders_statement.setFloat(3, orderamount);
            orders_statement.executeUpdate();

            /*
            查询当前的月销售额
             */
            String sqlGetMonthlySales = "select total_amount from monthly_sales where product_id=? and report_month=?";
            getMonthlySalesStatement = connection.prepareStatement(sqlGetMonthlySales);

            getMonthlySalesStatement.setInt(1, productId);
            getMonthlySalesStatement.setInt(2, reportMonth);

            ResultSet resultset = getMonthlySalesStatement.executeQuery();

            float totalAmount = 0;
            if (resultset.next()) {
                totalAmount = resultset.getFloat("total_amount");
            }
            resultset.close();

            if (totalAmount + orderamount < 10000) {
                connection.rollback(savepoint);
            }
            /*
            更新月销售信息
            销售月份
            销售产品编号
            销售的数量
             */
            String sqlUpdate_monthly_sales = "update monthly_sales set total_amount=total_amount+? where product_id =? and report_month=?";
            monthly_sales_statement = connection.prepareStatement(sqlUpdate_monthly_sales);
            monthly_sales_statement.setFloat(1, orderamount);
            monthly_sales_statement.setInt(2, productId);
            monthly_sales_statement.setInt(3, reportMonth);
            monthly_sales_statement.executeUpdate();


//           提交事务
            connection.commit();

            System.out.println("事务结束");


        } catch (SQLException e) {

            System.err.println(e);
            System.err.println(e.getStackTrace());

            System.err.println(e.getMessage());
            System.err.println(e.getCause());
            try {
                connection.rollback();
                System.out.println("rolled back.");
            } catch (SQLException e1) {

                System.err.println(e1.getCause());
            }


        } finally {
            try {
                if (orders_statement != null) {
                    orders_statement.close();
                }
                if (monthly_sales_statement != null) {
                    monthly_sales_statement.close();
                }
                if (productStatement != null) {
                    productStatement.close();
                }
                if (connection != null) {
                    connection.close();
                }

            } catch (SQLException e) {
                System.err.println(e.getCause());
            }

        }


    }
}

测试事务回滚点

package Bean;

import java.sql.Date;

public class TransactionDemo {
    public static void main(String[] args) {


        SalesDao salesDao = new SalesDao();

        String newProductName = "ipod";
        float newProductPrice = 339;

        int productId = 2;
        int reportMonth = 7;

        Date date = new Date(System.currentTimeMillis());
        float orderAmount = 580;

        salesDao.saveOrderk(newProductName, newProductPrice, productId, date, orderAmount, reportMonth);


    }
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值