JDBC实战(三)JDBC中的事务、保存点

JDBC中的事务与批处理

1、Statements, PreparedStatement和CallableStatement

当获得了与数据库的连接后,就可以与数据库进行交互了。 JDBC Statement,CallableStatement和PreparedStatement接口定义了可用于发送SQL或PL/SQL命令,并从数据库接收数据的方法和属性。
它们还定义了有助于在Java和SQL数据类型的数据类型差异转换的方法。

接口使用场景
Statement用于对数据库进行通用访问,在运行时使用静态SQL语句时很有用。如果用于运行时接受输入参数可能导致sql注入问题。
PreparedStatement适合PreparedStatement接口在运行时接受输入参数,消除sql注入问题。使用作占位符
CallableStatement当想要访问数据库存储过程时使用。CallableStatement接口也可以接受运行时输入参数。

1.1、Statement对象

创建Statement对象
在使用Statement对象执行SQL语句之前,需要使用Connection对象的createStatement()方法创建一个Statement对象,如以下示例所示:

Statement st = null;
try {
   st = conn.createStatement( );
   . . .
}
catch (SQLException e) {
   . . .
}
finally {
   . . .
}

在创建Statement对象后,可以使用它来执行一个SQL语句,它有三个执行方法可以执行。它们分别是

  • boolean execute (String SQL) : 如果可以检索到ResultSet对象,则返回一个布尔值true; 否则返回false。使用此方法执行SQLDDL语句或需要使用真正的动态SQL,可使用于执行创建数据库,创建表的SQL语句等等。
  • int executeUpdate (String SQL): 返回受SQL语句执行影响的行数。使用此方法执行预期会影响多行的SQL语句,例如:INSERT,UPDATE或DELETE语句。
  • ResultSet executeQuery(String SQL):返回一个ResultSet对象。 当您希望获得结果集时,请使用此方法,就像使用SELECT语句一样。

关闭Statement对象
就像关闭一个Connection对象一样,以保存数据库资源一样,由于同样的原因,还应该关闭Statement对象。
一个简单的调用close()方法将执行该作业(工作)。 如果先关闭Connection对象,它也会关闭Statement对象。 但是,应该始终显式关闭Statement对象,以确保正确的清理顺序。

1.2、PreparedStatement对象

PreparedStatement接口扩展了Statement接口,它添加了比Statement对象更好一些优点的功能。
此语句可以动态地提供/接受参数。

创建PreparedStatement对象

PreparedStatement ps = null;
try {
   String SQL = "select * from temp  WHERE id = ?";
   ps = conn.prepareStatement(SQL);
   ps.setInt(1,id);//将占位符替换为id
   . . .
}
catch (SQLException e) {
   . . .
}

JDBC中的所有参数都由 ? 符号作为占位符,这被称为参数标记。 在执行SQL语句之前,必须为每个参数(占位符)提供值。

  • setXXX()方法将值绑定到参数,其中XXX表示要绑定到输入参数的值的Java数据类型。 如果忘记提供绑定值,则将会抛出一个SQLException。
    每个参数标记是它其顺序位置引用。第一个标记表示位置1,下一个位置2等等。 该方法与Java数组索引不同(它不从0开始)。
  • 所有Statement对象与数据库交互的方法如execute(),executeQuery()和executeUpdate()也可以用于PreparedStatement对象。 但是,这些方法被修改为可以使用输入参数的SQL语句。注意使用于PreparedStatement时不要再添加sql参数,如executeQuery(sql)。

关闭PreparedStatement对象
就像关闭Statement对象一样,由于同样的原因(节省数据库系统资源),也应该关闭PreparedStatement对象。
简单的调用close()方法将执行关闭。 如果先关闭Connection对象,它也会关闭PreparedStatement对象。 但是,应该始终显式关闭PreparedStatement对象,以确保以正确顺序清理资源。

1.3、CallableStatement对象

类似Connection对象创建Statement和PreparedStatement对象一样,它还可以使用同样的方式创建CallableStatement对象,该对象将用于执行对数据库存储过程的调用
创建CallableStatement对象
假设需要执行以下MySQL存储过程 如下:

DELIMITER $$#设置sql语句结束符

DROP PROCEDURE IF EXISTS `EMP`.`getEmpName` $$
CREATE PROCEDURE `EMP`.`getEmpName` #创建存储过程
   (IN EMP_ID INT, OUT EMP_FIRST VARCHAR(255))
BEGIN
   SELECT first INTO EMP_FIRST
   FROM Employees
   WHERE ID = EMP_ID;
END $$

DELIMITER ;

SQL存在三种类型的参数:IN,OUT和INOUT。 PreparedStatement对象只使用IN参数。CallableStatement对象可以使用上面三个参数类型。以下是上面三种类型参数的定义

参数描述
IN创建SQL语句时其参数值是未知的。 使用setXXX()方法将值绑定到IN参数。
OUT由SQL语句返回的参数值。可以使用getXXX()方法从OUT参数中检索值。
INOUT提供输入和输出值的参数。使用setXXX()方法绑定变量并使用getXXX()方法检索值。

以下代码片段显示了如何使用Connection.prepareCall()方法根据上述存储过程来实例化一个CallableStatement对象

CallableStatement cs = null;
try {
   String sql= "{call getEmpName (?, ?)}";
   cs = conn.prepareCall (sql);
   . . .
}
catch (SQLException e) {
   . . .
}
finally {
   . . .
}

String变量sql表示存储过程的调用,带有两个参数占位符。使用CallableStatement对象就像使用PreparedStatement对象一样。 在执行语句之前,必须将值绑定到所有参数,否则将抛出一个SQLException异常。注意:

  • 如果有IN参数,只需遵循适用于PreparedStatement对象的相同规则和技术; 使用与绑定的Java数据类型相对应的setXXX()方法。
  • 使用OUT和INOUT参数时,必须使用一个额外的CallableStatement对象方法registerOutParameter()。 registerOutParameter()方法将JDBC数据类型绑定到存储过程并返回预期数据类型。
  • 当调用存储过程,可以使用适当的getXXX()方法从OUT参数中检索该值。 此方法将检索到的SQL类型的值转换为对应的Java数据类型。

关闭CallableStatement对象
就像关闭其他Statement对象一样,由于同样的原因(节省数据库系统资源),还应该关闭CallableStatement对象。
简单的调用close()方法将执行关闭CallableStatement对象。 如果先关闭Connection对象,它也会关闭CallableStatement对象。 但是,应该始终显式关闭CallableStatement对象,以确保按正确顺序的清理资源。

2、JAVA事务和保存点

2.1、事务

回顾事务

MySQL 事务主要用于处理操作量大,复杂度高的数据。比如说,在人员管理系统中,你删除一个人员,你既需要删除人员的基本资料,也要删除和该人员相关的信息,如信箱,文章等等,这样,这些数据库操作语句就构成一个事务!

  • 在 MySQL 中只有使用了 Innodb 数据库引擎的数据库或表才支持事务或者其他的事务型数据库引擎。
  • 事务处理可以用来维护数据库的完整性,保证成批的 SQL 语句要么全部执行,要么全部不执行。
  • 事务用来管理 insert,update,delete 语句
    在这里插入图片描述
    当我们需要执行多条SQL语句时,我们需要事务来保证数据的安全和一致性、完整性。

实战例子:

package EvensAndSavepoint;

import driver.JDBCUtils;

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

/**
 * 练习使用JDBC连接数据库,运行事务
 */

public class Event_test {
    public static void main(String[] args) {
        Connection conn = null;
        Statement st = null;
        ResultSet rs = null;
        try {
            conn = JDBCUtils.getConnect();
            conn.setAutoCommit(false);//将自动提交关闭
            String sql1 = "update bank set money=money-10 where name='王二小'";
            st = conn.createStatement();
            st.executeUpdate(sql1);
            String sql2 = "select money from bank where name='王小'";
            rs = st.executeQuery(sql2);
            float money = 0.0f;
            while (rs.next()) {
                money = rs.getFloat("money");
            }
            if (money > 1000) {
                throw new RuntimeException("数目已经达到1000元,不需要再转入!");
            }
            System.out.println("更新王二小的钱");
            String sql3 = "update bank set money=money+10 where name='王小'";
            st.executeUpdate(sql3);
            conn.commit();
        } catch (SQLException e) {
           if(conn!=null)
                try{
                    conn.rollback();//回滚
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }
        } finally {
            JDBCUtils.free(conn, st, rs);
        }
    }
}

2.2、保存点

在MySQL事务控制语句中:

  • BEGIN 或 START TRANSACTION 显式地开启一个事务;

  • COMMIT 也可以使用 COMMIT WORK,不过二者是等价的。COMMIT 会提交事务,并使已对数据库进行的所有修改成为永久性的;

  • ROLLBACK 也可以使用 ROLLBACK WORK,不过二者是等价的。回滚会结束用户的事务,并撤销正在进行的所有未提交的修改;

  • SAVEPOINT identifier,SAVEPOINT 允许在事务中创建一个保存点,一个事务中可以有多个 SAVEPOINT;

  • RELEASE SAVEPOINT identifier 删除一个事务的保存点,当没有指定的保存点时,执行该语句会抛出一个异常;

  • ROLLBACK TO identifier 把事务回滚到标记点;

  • SET TRANSACTION 用来设置事务的隔离级别。InnoDB 存储引擎提供事务的隔离级别有READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ 和 SERIALIZABLE。

当我们对事务的一些语句操作不需要回滚,而在之后的语句可能需要回滚。我们就可以设置一个事务保存点。当发生异常时,可选择回滚到事务保存点还是事务全都回滚。JDBC也提供了相关函数支持。如:

package EvensAndSavepoint;

import driver.JDBCUtils;

import java.sql.*;

/**
 * 练习使用JDBC连接数据库,运行事务保存点
 */

public class Event_test {
    public static void main(String[] args) {
        Connection conn = null;
        Statement st = null;
        ResultSet rs = null;
        Savepoint s1 = null;
        try {
            conn = JDBCUtils.getConnect();
            conn.setAutoCommit(false);//将自动提交关闭
            String sql1 = "update bank set money=money-10 where name='王二小'";
            st = conn.createStatement();
            st.executeUpdate(sql1);
            String sql2 = "select money from bank where name='王小'";
            rs = st.executeQuery(sql2);
            s1 = conn.setSavepoint();
            System.out.println(s1);
            float money = 0.0f;
            while (rs.next()) {
                money = rs.getFloat("money");

            }
            if (money > 1000) {
                throw new RuntimeException("数目已经达到1000元,不需要再转入!");
            }
            System.out.println("更新王二小的钱");
            String sql3 = "update bank set money=money+10 where name='王小'";
            st.executeUpdate(sql3);
            //conn.commit();
        } catch (RuntimeException e) {
            if (conn != null && s1 != null)
                try {
                    conn.rollback(s1);//发生异常,回滚到指定的事务点
                    conn.commit();
                    //conn.rollback();//回滚
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }
        }catch (SQLException e){
            e.printStackTrace();
        }finally {
            JDBCUtils.free(conn, st, rs);
        }
    }
}

原来的数据:

在这里插入图片描述
回滚,只执行了将王二小的钱减10块。
在这里插入图片描述
结论

在这里插入图片描述

2.3 JAVA事务

数据库操作的事务习惯上就称为Java事务。

2.3.1、JDBC事务

JDBC 事务是用 Connection 对象控制的。JDBC Connection 接口( java.sql.Connection )提供了两种事务模式:自动提交和手工提交。

java.sql.Connection 提供了以下控制事务的方法:

public void setAutoCommit(boolean)  
public boolean getAutoCommit()  
public void commit()  
public void rollback()  

使用 JDBC 事务界定时,您可以将多个 SQL 语句结合到一个事务中。JDBC 事务的一个缺点是事务的范围局限于一个数据库连接。一个 JDBC 事务不能跨越多个数据库。

2.3.2、JTA(Java Transaction API)事务

JTA是一种高层的,与实现无关的,与协议无关的API,应用程序和应用服务器可以使用JTA来访问事务

JTA允许应用程序执行分布式事务处理——在两个或多个网络计算机资源上访问并且更新数据,这些数据可以分布在多个数据库上。JDBC驱动程序的JTA支持极大地增强了数据访问能力。

如果计划用 JTA 界定事务,那么就需要有一个实现 javax.sql.XADataSource 、 javax.sql.XAConnection 和 javax.sql.XAResource 接口的 JDBC 驱动程序。一个实现了这些接口的驱动程序将可以参与 JTA 事务。一个 XADataSource 对象就是一个 XAConnection 对象的工厂。 XAConnections 是参与 JTA 事务的 JDBC 连接。

您将需要用应用服务器的管理工具设置 XADataSource。(从应用服务器和 JDBC 驱动程序的文档中可以了解到相关的指导)

J2EE应用程序用 JNDI 查询数据源。一旦应用程序找到了数据源对象,它就调用 javax.sql.DataSource.getConnection() 以获得到数据库的连接。

XA 连接与非 XA 连接不同。一定要记住 XA 连接参与了 JTA 事务。这意味着 XA 连接不支持 JDBC 的自动提交功能。同时,应用程序一定不要对 XA 连接调用 java.sql.Connection.commit() 或者 java.sql.Connection.rollback() .

相反,应用程序应该使用 UserTransaction.begin()、 UserTransaction.commit() 和 UserTransaction.rollback()

事务(JTA)针对不同数据库数据交互

当你准备转账给别人时,你的银行卡账号与别人的不一样,即跨银行转账。这个时候的数据库处理需要用能跨越多个数据源的事务处理,我们用的是JTA容器。

JTA容器实现分布式事务处理
在这里插入图片描述

小结:
Java事务API(baiJTA;Java Transaction API)和它的同胞Java事务服du务(JTS;Java Transaction Service),为J2EE平台提供了分布式事务服务。一个分布式事务(distributed transaction)包括一个事务管理器(transaction manager)和一个或多个资源管理器(resource manager)。一个资源管理器(resource manager)是任意类型的持久化数据存储。事务管理器(transaction manager)承担着所有事务参与单元者的相互通讯的责任。
JTA事务比JDBC事务更强大。一个JTA事务可以有多个参与者,而一个JDBC事务则被限定在一个单一的数据库连接。下列任一个Java平台的组件都可以参与到一个JTA事务中:

  • JDBC连接
  • JDO PersistenceManager 对象
  • JMS 队列
  • JMS 主题
  • 企业JavaBeans(EJB)

2.3.3、容器事务

容器事务主要是J2EE应用服务器提供的,容器事务大多是基于JTA完成,这是一个基于JNDI的,相当复杂的API实现。相对编码实现JTA事务管理,我们可以通过EJB容器提供的容器事务管理机制(CMT)完成同一个功能,这项功能由J2EE应用服务器提供。这使得我们可以简单的指定将哪个方法加入事务,一旦指定,容器将负责事务管理任务。这是我们土建的解决方式,因为通过这种方式我们可以将事务代码排除在逻辑编码之外,同时将所有困难交给J2EE容器去解决。使用EJB CMT的另外一个好处就是程序员无需关心JTA API的编码,不过,理论上我们必须使用EJB.

©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页