JDBC基础学习之事务控制(四)
程序员文章站
2022-07-12 16:10:17
...
一、现象
写道
上图中机构表与人员信息表是1:n关系,人员信息表与用户登录信息是1:1关系
场景,删除人员信息的时候要把对应的用户登录信息删除,在这个过程中可能会出现以下几种情况,1)人员信息删除了但对应的用户登录信息没有删除 2)人员信息没删除但对应的用户登录信息删除了 3)人员信息与对应的用户登录信息都删除了,在这个过程中应该要不全部删除,要不都不删除,这两个过程中是同一个过程不能被分割。
场景,删除人员信息的时候要把对应的用户登录信息删除,在这个过程中可能会出现以下几种情况,1)人员信息删除了但对应的用户登录信息没有删除 2)人员信息没删除但对应的用户登录信息删除了 3)人员信息与对应的用户登录信息都删除了,在这个过程中应该要不全部删除,要不都不删除,这两个过程中是同一个过程不能被分割。
二、事务的概念
写道
数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。 事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据的资源。通过将一组相关操作组合为一个要么全部成功要么全部失败的单元,可以简化错误恢复并使应用程序更加可靠。一个逻辑工作单元要成为事务,必须满足所谓的ACID(原子性、一致性、隔离性和持久性)属性。事务是数据库运行中的逻辑工作单位,由DBMS中的事务管理子系统负责事务的处理。
三、事务特征
写道
事务是恢复和并发控制的基本单位。
事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。
原子性(atomicity)。一个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做。
一致性(consistency)。事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
隔离性(isolation)。一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
持久性(durability)。持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响
事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。
原子性(atomicity)。一个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做。
一致性(consistency)。事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
隔离性(isolation)。一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
持久性(durability)。持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响
四、事务的类型
写道
(1)手动事务
手动事务允许显式处理若干过程,这些过程包括:开始事务、控制事务边界内的每个连接和资源登记、确定事务结果(提交或中止)以及结束事务。尽管此模型提供了对事务的标准控制,但它缺少一些内置于自动事务模型的简化操作。例如,在手动事务中数据存储区之间没有自动登记和协调。此外,与自动事务不同,手动事务中事务不在对象间流动。
如果选择手动控制分布式事务,则必须管理恢复、并发、安全性和完整性。也就是说,必须应用维护与事务处理关联的 ACID 属性所需的所有编程方法。
(2)自动事务
.NET 页、XML Web services方法或 .NET Framework 类一旦被标记为参与事务,它们将自动在事务范围内执行。您可以通过在页、XML Web services 方法或类中设置一个事务属性值来控制对象的事务行为。特性值反过来确定实例化对象的事务性行为。因此,根据声明特性值的不同,对象将自动参与现有事务或正在进行的事务,成为新事务的根或者根本不参与事务。声明事务属性的语法在 .NET Framework 类、.NET 页和 XML Web services 方法中稍有不同。
声明性事务特性指定对象如何参与事务,如何以编程方式被配置。尽管此声明性级别表示事务的逻辑,但它是一个已从物理事务中移除的步骤。物理事务在事务性对象访问数据库或消息队列这样的数据资源时发生。与对象关联的事务自动流向合适的资源管理器,诸如 OLE DB、开放式数据库连接 (ODBC) 或 ActiveX 数据对象 (ADO) 的关联驱动程序在对象的上下文中查找事务,并通过分布式事务处理协调器 (DTC) 在此事务中登记。整个物理事务自动发生。
手动事务允许显式处理若干过程,这些过程包括:开始事务、控制事务边界内的每个连接和资源登记、确定事务结果(提交或中止)以及结束事务。尽管此模型提供了对事务的标准控制,但它缺少一些内置于自动事务模型的简化操作。例如,在手动事务中数据存储区之间没有自动登记和协调。此外,与自动事务不同,手动事务中事务不在对象间流动。
如果选择手动控制分布式事务,则必须管理恢复、并发、安全性和完整性。也就是说,必须应用维护与事务处理关联的 ACID 属性所需的所有编程方法。
(2)自动事务
.NET 页、XML Web services方法或 .NET Framework 类一旦被标记为参与事务,它们将自动在事务范围内执行。您可以通过在页、XML Web services 方法或类中设置一个事务属性值来控制对象的事务行为。特性值反过来确定实例化对象的事务性行为。因此,根据声明特性值的不同,对象将自动参与现有事务或正在进行的事务,成为新事务的根或者根本不参与事务。声明事务属性的语法在 .NET Framework 类、.NET 页和 XML Web services 方法中稍有不同。
声明性事务特性指定对象如何参与事务,如何以编程方式被配置。尽管此声明性级别表示事务的逻辑,但它是一个已从物理事务中移除的步骤。物理事务在事务性对象访问数据库或消息队列这样的数据资源时发生。与对象关联的事务自动流向合适的资源管理器,诸如 OLE DB、开放式数据库连接 (ODBC) 或 ActiveX 数据对象 (ADO) 的关联驱动程序在对象的上下文中查找事务,并通过分布式事务处理协调器 (DTC) 在此事务中登记。整个物理事务自动发生。
五、示例
5.1情况1:正常删除人员信息,删除用户信息是有异常,虽然将事务设置为手动提交,但仍然不能控制一致性,原因是他们进行操作时是两个Connection.
package com.zlt.jdbc; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.List; import com.zlt.jdbc.util.DBUtil; public class Test5 { public static void main(String[] args) { deletePersonById(1); deleteUserByPid(1); } /** * 根据人员ID删除人员信息 * @param id * @return reuslt :-1,删除失败;非-1,删除成功 */ public static int deletePersonById(int id){ int result = 0; Connection conn = null; PreparedStatement pstmt = null; try { conn = DBUtil.getConnection(); conn.setAutoCommit(false);//将事务设为手动提交 String sql = "delete from t_person where id = ?" ; pstmt = conn.prepareStatement(sql); pstmt.setInt(1, id); result = pstmt.executeUpdate(); conn.commit();//提交事务 } catch (SQLException e) { e.printStackTrace(); result = -1; try { conn.rollback(); } catch (SQLException e1) { e1.printStackTrace(); }//事务回滚 }finally{ DBUtil.close(pstmt); DBUtil.close(conn); } return result; } /** * 根据人员ID删除用户信息 * @param pid 人员ID * @return */ public static int deleteUserByPid(int pid){ Connection conn = null; PreparedStatement pstmt = null; int result = 0; try { conn = DBUtil.getConnection(); conn.setAutoCommit(false); String sql = "delete from user where pid = ?"; pstmt = conn.prepareStatement(sql); pstmt.setInt(1, pid); Integer.parseInt("123a"); result = pstmt.executeUpdate(); conn.commit(); } catch (SQLException e) { e.printStackTrace(); result = -1; try { conn.rollback(); } catch (SQLException e1) { e1.printStackTrace(); } }finally{ DBUtil.close(pstmt); DBUtil.close(conn); } return result; } }
5.2改进事务控制,将两个方法的Connection控制在一个Connection
package com.zlt.jdbc; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.List; import com.zlt.jdbc.util.DBUtil; public class Test5 { public static void main(String[] args) { Connection conn = null; try{ conn = DBUtil.getConnection(); conn.setAutoCommit(false);//将事务设为手动提交 deletePersonById(conn,1); deleteUserByPid(conn,1); conn.commit();//提交事务 }catch(Exception e){ try { conn.rollback();//事务回滚 } catch (SQLException e1) { e1.printStackTrace(); } }finally{ DBUtil.close(conn); } } /** * 根据人员ID删除人员信息 * @param id * @return reuslt :-1,删除失败;非-1,删除成功 */ public static int deletePersonById(Connection conn,int id){ int result = 0; PreparedStatement pstmt = null; try { String sql = "delete from t_person where id = ?" ; pstmt = conn.prepareStatement(sql); pstmt.setInt(1, id); result = pstmt.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); result = -1; } return result; } /** * 根据人员ID删除用户信息 * @param pid 人员ID * @return */ public static int deleteUserByPid(Connection conn,int pid){ PreparedStatement pstmt = null; int result = 0; try { String sql = "delete from user where pid = ?"; pstmt = conn.prepareStatement(sql); pstmt.setInt(1, pid); Integer.parseInt("123a"); result = pstmt.executeUpdate(); } catch (Exception e) { e.printStackTrace(); result = -1; } return result; } }
以上示例代码虽然进行的改进但是事务还是没有控制住,只有一张表中的数据被删除了,由于在deleteUserByPid方法中出现了异常没有往上抛,被底下把异常Catch了,当然一般持久化异常都是SQLException,此处我们为了做实验才catch Exception异常
5.3小结
写道
进行事务控制的两个条件:
1)同一个Connection 2)在被调用的方法中不能把异常给处理掉要往上面抛
1)同一个Connection 2)在被调用的方法中不能把异常给处理掉要往上面抛
最后的代码:方法deleteUserByPid虽然有异常,但方法deletePersonById中也未删除数据,说明进行了事务回滚
package com.zlt.jdbc; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.List; import com.zlt.jdbc.util.DBUtil; public class Test5 { public static void main(String[] args) { Connection conn = null; try{ conn = DBUtil.getConnection(); conn.setAutoCommit(false);//将事务设为手动提交 deletePersonById(conn,1); deleteUserByPid(conn,1); conn.commit();//提交事务 }catch(Exception e){ try { conn.rollback();//事务回滚 } catch (SQLException e1) { e1.printStackTrace(); } }finally{ DBUtil.close(conn); } } /** * 根据人员ID删除人员信息 * @param id * @return reuslt :-1,删除失败;非-1,删除成功 */ public static int deletePersonById(Connection conn,int id) throws SQLException{ int result = 0; PreparedStatement pstmt = null; try { String sql = "delete from t_person where id = ?" ; pstmt = conn.prepareStatement(sql); pstmt.setInt(1, id); result = pstmt.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); result = -1; throw new SQLException(); } return result; } /** * 根据人员ID删除用户信息 * @param pid 人员ID * @return */ public static int deleteUserByPid(Connection conn,int pid) throws SQLException{ PreparedStatement pstmt = null; int result = 0; try { String sql = "delete from user where pid = ?"; pstmt = conn.prepareStatement(sql); pstmt.setInt(1, pid); Integer.parseInt("123a"); result = pstmt.executeUpdate(); } catch (Exception e) { e.printStackTrace(); result = -1; throw new SQLException(); } return result; } }
5.4 遗留的问题
1)JDBC有大量重复的地方
2)随着功能模块的增加维护越来越不方面,因此将数据库操作的那层抽象出来,DAO层
3)流行的持久层开源框架:Mybatis与Hibernate
上一篇: 存储过程简单案例
下一篇: JDBC基础学习之增删改查(三)